fix(management): remove duplicate create_request (management-mode server died at startup) — v1.6.1 #63
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@molecule-ai/mcp-server",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"description": "MCP server for Molecule AI Agent Team \u2014 manage workspaces, agents, and skills from any AI coding tool",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -10,6 +10,14 @@ jest.mock("@modelcontextprotocol/sdk/server/mcp.js", () => ({
|
||||
McpServer: class {
|
||||
registeredToolNames: string[] = [];
|
||||
tool(name: string) {
|
||||
// Mirror the real SDK: duplicate tool names throw at registration.
|
||||
// Without this the composed-server test cannot catch cross-registry
|
||||
// collisions (the management create_request duplicate killed the
|
||||
// management server at startup on 2026-06-11; only the image smoke
|
||||
// gate caught it).
|
||||
if (this.registeredToolNames.includes(name)) {
|
||||
throw new Error(`Tool ${name} is already registered`);
|
||||
}
|
||||
this.registeredToolNames.push(name);
|
||||
}
|
||||
connect() {
|
||||
@@ -53,7 +61,6 @@ import {
|
||||
handleExportBundle,
|
||||
handleListOrgEvents,
|
||||
handleCreateApproval as mgmtCreateApproval,
|
||||
handleCreateRequest as mgmtCreateRequest,
|
||||
} from "../tools/management/index.js";
|
||||
import { handleRecreateWorkspace } from "../tools/management/cp_admin.js";
|
||||
|
||||
@@ -176,21 +183,6 @@ describe("workspace secret tools", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("create_request kind=task POSTs a task-kind request to the user", async () => {
|
||||
const f = mockFetch({ ok: true, id: "req-2" });
|
||||
global.fetch = f as unknown as typeof fetch;
|
||||
await mgmtCreateRequest({ workspace_id: "w1", kind: "task", title: "Review the report", detail: "by EOD" });
|
||||
const { url, init } = lastCall(f);
|
||||
expect(url).toBe(`${HOST}/workspaces/w1/requests`);
|
||||
expect(JSON.parse(init.body as string)).toEqual({
|
||||
kind: "task",
|
||||
recipient_type: "user",
|
||||
recipient_id: "",
|
||||
title: "Review the report",
|
||||
detail: "by EOD",
|
||||
});
|
||||
});
|
||||
|
||||
it("list_workspace_secrets GETs /workspaces/:id/secrets", async () => {
|
||||
const f = mockFetch([{ key: "FOO" }]);
|
||||
global.fetch = f as unknown as typeof fetch;
|
||||
@@ -622,7 +614,7 @@ describe("registration + mode", () => {
|
||||
"mint_org_token", "list_org_tokens", "revoke_org_token", "mint_workspace_token",
|
||||
"get_org_plugin_allowlist", "set_org_plugin_allowlist",
|
||||
"export_bundle", "import_bundle",
|
||||
"list_org_events", "list_pending_approvals", "create_approval", "create_request",
|
||||
"list_org_events", "list_pending_approvals", "create_approval",
|
||||
]) {
|
||||
expect(names).toContain(expected);
|
||||
}
|
||||
@@ -632,11 +624,23 @@ describe("registration + mode", () => {
|
||||
|
||||
it("createServer in management mode registers only the management surface", () => {
|
||||
process.env.MOLECULE_MCP_MODE = "management";
|
||||
// The mock McpServer throws on duplicate names (like the real SDK), so
|
||||
// simply composing the full management-mode server here is the
|
||||
// regression gate against cross-registry tool-name collisions.
|
||||
const srv = createServer() as unknown as { registeredToolNames: string[] };
|
||||
expect(srv.registeredToolNames).toContain("provision_workspace");
|
||||
// The unified request tools come from requests.ts (BOTH modes) — the
|
||||
// management registry must NOT duplicate them.
|
||||
expect(srv.registeredToolNames).toContain("create_request");
|
||||
expect(srv.registeredToolNames).toContain("create_approval");
|
||||
// Legacy-only tools (chat_with_agent) must NOT be present in mgmt mode.
|
||||
expect(srv.registeredToolNames).not.toContain("chat_with_agent");
|
||||
});
|
||||
|
||||
it("createServer in workspace mode composes without tool-name collisions", () => {
|
||||
process.env.MOLECULE_MCP_MODE = "";
|
||||
expect(() => createServer()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("path segment escaping", () => {
|
||||
|
||||
@@ -382,7 +382,11 @@ export async function handleListPendingApprovals() {
|
||||
|
||||
// create_approval (mcp-server#61) — raise an approval-kind request addressed
|
||||
// to the USER via the unified requests system (same shape the workspace-mode
|
||||
// tool uses; see ../approvals.ts handleCreateApproval). Without this tool the
|
||||
// tool uses; see ../approvals.ts handleCreateApproval). The GENERAL form is
|
||||
// create_request from ../requests.ts, registered in BOTH modes by
|
||||
// createServer — do NOT add a management duplicate of it: the MCP SDK throws
|
||||
// on duplicate tool names and the whole management server dies at startup
|
||||
// (caught by the platform-agent image smoke gate, 2026-06-11). Without this
|
||||
// org concierge IMPROVISED approval demos by running gated/destructive ops
|
||||
// (set_workspace_secret on itself → secret-change auto-restart → its own box
|
||||
// terminated mid-turn, twice on 2026-06-11 — core#2573). Deliberately NO
|
||||
@@ -407,30 +411,6 @@ export async function handleCreateApproval(args: unknown) {
|
||||
);
|
||||
}
|
||||
|
||||
// create_request — the unified form (mirrors the workspace-mode tool in
|
||||
// ../requests.ts): kind='task' asks the user to DO something; kind='approval'
|
||||
// asks the user to APPROVE something. create_approval above is the
|
||||
// approval-kind convenience alias (issue #61 names it explicitly).
|
||||
const CreateRequestMgmtSchema = z.object({
|
||||
workspace_id: z.string().describe("Workspace the request is raised for/anchored to"),
|
||||
kind: z.enum(["task", "approval"]).describe("task = please do X; approval = please approve X"),
|
||||
title: z.string().describe("Short title shown in the user's inbox"),
|
||||
detail: z.string().optional().describe("Longer context / why"),
|
||||
});
|
||||
|
||||
export async function handleCreateRequest(args: unknown) {
|
||||
const p = validate(args, CreateRequestMgmtSchema);
|
||||
return toMcpResult(
|
||||
await mgmtCall("POST", `/workspaces/${encodeURIComponent(p.workspace_id)}/requests`, {
|
||||
kind: p.kind,
|
||||
recipient_type: "user",
|
||||
recipient_id: "",
|
||||
title: p.title,
|
||||
detail: p.detail,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Registration
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -680,17 +660,6 @@ export function registerManagementTools(srv: McpServer) {
|
||||
},
|
||||
handleCreateApproval,
|
||||
);
|
||||
srv.tool(
|
||||
"create_request",
|
||||
"Management: raise a request to the user — kind='task' asks them to DO something; kind='approval' asks them to APPROVE something. The safe way to put work or decisions in the user's inbox.",
|
||||
{
|
||||
workspace_id: z.string().describe("Workspace the request is raised for/anchored to"),
|
||||
kind: z.enum(["task", "approval"]).describe("task = please do X; approval = please approve X"),
|
||||
title: z.string().describe("Short title shown in the user's inbox"),
|
||||
detail: z.string().optional().describe("Longer context / why"),
|
||||
},
|
||||
handleCreateRequest,
|
||||
);
|
||||
|
||||
// --- CP-tier tools (separate module — Org API Key cannot reach CP) ---
|
||||
registerCpAdminTools(srv);
|
||||
|
||||
Reference in New Issue
Block a user