diff --git a/package.json b/package.json index d482f8b..0f69e6f 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,11 @@ { "name": "@molecule-ai/mcp-server", - "version": "1.1.1", + "version": "1.2.0", "description": "MCP server for Molecule AI Agent Team \u2014 manage workspaces, agents, and skills from any AI coding tool", "type": "module", "exports": { ".": "./dist/index.js", + "./external-workspace-tools": "./dist/external_workspace_tools.js", "./targets": "./dist/targets.js" }, "types": "./dist/index.d.ts", diff --git a/src/__tests__/external_workspace_tools.test.ts b/src/__tests__/external_workspace_tools.test.ts new file mode 100644 index 0000000..27d76a8 --- /dev/null +++ b/src/__tests__/external_workspace_tools.test.ts @@ -0,0 +1,29 @@ +import { + EXTERNAL_WORKSPACE_MCP_TOOLS, + EXTERNAL_WORKSPACE_TOOL_NAMES, + externalWorkspaceToolByName, +} from "../external_workspace_tools.js"; + +describe("EXTERNAL_WORKSPACE_MCP_TOOLS", () => { + it("pins the universal external-workspace MCP tool names", () => { + expect(EXTERNAL_WORKSPACE_TOOL_NAMES).toEqual([ + "delegate_task", + "delegate_task_async", + "check_task_status", + "list_peers", + "get_workspace_info", + "send_message_to_user", + "commit_memory", + "recall_memory", + ]); + }); + + it("keeps schemas JSON-schema shaped and required fields explicit", () => { + for (const tool of EXTERNAL_WORKSPACE_MCP_TOOLS) { + expect(tool.inputSchema.type).toBe("object"); + expect(tool.inputSchema.properties).toBeTruthy(); + } + expect(externalWorkspaceToolByName("delegate_task")?.inputSchema.required).toEqual(["workspace_id", "task"]); + expect(externalWorkspaceToolByName("send_message_to_user")?.inputSchema.required).toEqual(["message"]); + }); +}); diff --git a/src/external_workspace_tools.ts b/src/external_workspace_tools.ts new file mode 100644 index 0000000..210f606 --- /dev/null +++ b/src/external_workspace_tools.ts @@ -0,0 +1,141 @@ +export interface ExternalWorkspaceTool { + name: string; + description: string; + inputSchema: { + type: "object"; + properties: Record; + required?: string[]; + }; +} + +export const EXTERNAL_WORKSPACE_MCP_TOOLS: ExternalWorkspaceTool[] = [ + { + name: "delegate_task", + description: + "Delegate a task to a peer workspace via A2A and WAIT for the response (synchronous). " + + "Use for QUICK questions and small sub-tasks; for long-running work use " + + "delegate_task_async + check_task_status so this session does not block.", + inputSchema: { + type: "object", + properties: { + _as_workspace: { type: "string", description: "Watched workspace_id to send AS (omit if only one watched)." }, + workspace_id: { type: "string", description: "Target peer workspace ID (from list_peers)." }, + task: { type: "string", description: "Task description to send to the peer." }, + }, + required: ["workspace_id", "task"], + }, + }, + { + name: "delegate_task_async", + description: + "Send a task to a peer and return immediately with a task_id (non-blocking). " + + "Poll with check_task_status. The platform A2A queue handles delivery + retries.", + inputSchema: { + type: "object", + properties: { + _as_workspace: { type: "string", description: "Watched workspace_id to send AS (omit if only one watched)." }, + workspace_id: { type: "string", description: "Target peer workspace ID (from list_peers)." }, + task: { type: "string", description: "Task description to send to the peer." }, + }, + required: ["workspace_id", "task"], + }, + }, + { + name: "check_task_status", + description: + "Poll the status of a task started with delegate_task_async; returns the result when done. " + + "Statuses: pending/in_progress (peer working - wait), queued (peer busy with prior task - " + + "do not retry), completed (result available), failed (real error).", + inputSchema: { + type: "object", + properties: { + _as_workspace: { type: "string", description: "Watched workspace_id whose delegations to inspect (omit if only one watched)." }, + task_id: { type: "string", description: "task_id (delegation_id) returned by delegate_task_async. Omit to list recent." }, + }, + }, + }, + { + name: "list_peers", + description: + "List the watched workspace's peer agents (siblings, children, parent) as registered " + + "in the canvas. Use first when you need to delegate but do not know the target's ID. " + + "Access control is enforced - you only see peers your workspace can reach.", + inputSchema: { + type: "object", + properties: { + workspace_id: { type: "string", description: "Watched workspace_id to query peers for (omit if only one watched)." }, + q: { type: "string", description: "Optional case-insensitive substring filter on peer name or role." }, + }, + }, + }, + { + name: "get_workspace_info", + description: + "Get the watched workspace's own info - id, name, role, tier, parent, status, agent_card. " + + "Use to introspect identity before reporting back to the user or checking role/tier.", + inputSchema: { + type: "object", + properties: { + _as_workspace: { type: "string", description: "Watched workspace_id to introspect (omit if only one watched)." }, + }, + }, + }, + { + name: "send_message_to_user", + description: + "Send a message to the user's canvas chat - pushed instantly via WebSocket. Use to " + + "(1) acknowledge a task immediately, (2) post mid-flight progress updates, (3) deliver " + + "follow-up results, (4) attach files via the attachments field. Never paste file URLs " + + "in message; always pass absolute paths in attachments so the platform serves them " + + "as download chips.", + inputSchema: { + type: "object", + properties: { + _as_workspace: { type: "string", description: "Watched workspace_id to send AS (omit if only one watched)." }, + message: { type: "string", description: "Caption text for the chat bubble. Required even with attachments." }, + attachments: { + type: "array", + items: { type: "string" }, + description: "Absolute file paths on the local machine. Each is uploaded via /chat/uploads and surfaces as a download chip. 25 MB cap per file.", + }, + }, + required: ["message"], + }, + }, + { + name: "commit_memory", + description: + "Save a fact to persistent memory; survives across sessions and restarts. " + + "Scopes: LOCAL (private to this workspace), TEAM (shared with parent + siblings), " + + "GLOBAL (entire org - only tier-0 roots can write).", + inputSchema: { + type: "object", + properties: { + _as_workspace: { type: "string", description: "Watched workspace_id to commit AS (omit if only one watched)." }, + content: { type: "string", description: "What to remember - be specific." }, + scope: { type: "string", enum: ["LOCAL", "TEAM", "GLOBAL"], description: "Memory scope (default LOCAL)." }, + }, + required: ["content"], + }, + }, + { + name: "recall_memory", + description: + "Search persistent memory; returns matching LOCAL + TEAM + GLOBAL rows. " + + "Empty query returns all accessible memories and avoids missing rows that do not match a narrow keyword.", + inputSchema: { + type: "object", + properties: { + _as_workspace: { type: "string", description: "Watched workspace_id to recall FROM (omit if only one watched)." }, + query: { type: "string", description: "Search query (empty returns all)." }, + scope: { type: "string", enum: ["LOCAL", "TEAM", "GLOBAL", ""], description: "Filter by scope (empty = all accessible)." }, + }, + }, + }, +]; + +export const EXTERNAL_WORKSPACE_TOOL_NAMES = EXTERNAL_WORKSPACE_MCP_TOOLS.map((tool) => tool.name); + +export function externalWorkspaceToolByName(name: string): ExternalWorkspaceTool | undefined { + return EXTERNAL_WORKSPACE_MCP_TOOLS.find((tool) => tool.name === name); +} diff --git a/src/index.ts b/src/index.ts index 2c65c08..31584e2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,6 +35,12 @@ export { PLATFORM_URL, apiCall, isApiError, platformGet, toMcpResult, toMcpText export type { ApiError } from "./api.js"; export { formatTargetSummary, parseWorkspaceTargets } from "./targets.js"; export type { WorkspaceTarget } from "./targets.js"; +export { + EXTERNAL_WORKSPACE_MCP_TOOLS, + EXTERNAL_WORKSPACE_TOOL_NAMES, + externalWorkspaceToolByName, +} from "./external_workspace_tools.js"; +export type { ExternalWorkspaceTool } from "./external_workspace_tools.js"; export { registerWorkspaceTools,