From 56fcc6a58b52d99515ad5221b4b28a347c2682e7 Mon Sep 17 00:00:00 2001 From: "poe-code-agent[bot]" <254571472+poe-code-agent[bot]@users.noreply.github.com> Date: Sun, 3 May 2026 23:50:10 +0000 Subject: [PATCH 1/2] fix(toolcraft): omit root group from mcp tool names --- packages/markdown-reader/README.md | 4 +-- .../markdown-reader/scripts/smoke-test.ts | 6 ++-- .../markdown-reader/src/mcp/tools.test.ts | 10 +++---- .../testing/fixtures/markdown-reader-plan.md | 6 ++-- .../terminal-pilot-mcp/src/mcp-tools.test.ts | 30 +++++++++---------- .../src/define-client.test.ts | 10 +++---- .../toolcraft-openapi/src/runtime.test.ts | 4 +-- packages/toolcraft/README.md | 2 +- .../src/entrypoints-mcp-proxy.test.ts | 6 ++-- .../human-in-loop/approvals-commands.test.ts | 6 ++-- .../mcp-runtime.integration.test.ts | 8 ++--- .../src/mcp-proxy-integration.test.ts | 4 +-- .../toolcraft/src/mcp-runtime-options.test.ts | 2 +- packages/toolcraft/src/mcp.ts | 8 ++--- packages/toolcraft/src/toolcraft.test.ts | 26 ++++++++-------- 15 files changed, 65 insertions(+), 67 deletions(-) diff --git a/packages/markdown-reader/README.md b/packages/markdown-reader/README.md index eacd7d0e..d81bc357 100644 --- a/packages/markdown-reader/README.md +++ b/packages/markdown-reader/README.md @@ -17,8 +17,8 @@ const { markdown, section } = await readSection({ file, section: "2.1" }); ## MCP tool names -- `markdown_reader__read` -- `markdown_reader__read_section` +- `read` +- `read_section` ## Standalone server invocation diff --git a/packages/markdown-reader/scripts/smoke-test.ts b/packages/markdown-reader/scripts/smoke-test.ts index 87edc103..876dcae1 100644 --- a/packages/markdown-reader/scripts/smoke-test.ts +++ b/packages/markdown-reader/scripts/smoke-test.ts @@ -21,7 +21,7 @@ async function run(): Promise { const { tools } = await client.listTools(); const names = tools.map((t) => t.name).sort(); - const expected = ["markdown_reader__read", "markdown_reader__read_section"]; + const expected = ["read", "read_section"]; const namesMatch = names.length === expected.length && expected.every((n, i) => names[i] === n); @@ -31,7 +31,7 @@ async function run(): Promise { console.log(`✓ tools/list — ${names.join(", ")}`); const result = await client.callTool({ - name: "markdown_reader__read", + name: "read", arguments: { file: "docs/plans/markdown-reader.md" }, }); @@ -39,7 +39,7 @@ async function run(): Promise { if (text?.type !== "text" || !text.text.includes("2.1")) { throw new Error(`tools/call response missing TOC: ${JSON.stringify(result)}`); } - console.log("✓ tools/call markdown_reader__read — TOC contains 2.1"); + console.log("✓ tools/call read — TOC contains 2.1"); } finally { await client.close(); } diff --git a/packages/markdown-reader/src/mcp/tools.test.ts b/packages/markdown-reader/src/mcp/tools.test.ts index acfc50db..06a2afc0 100644 --- a/packages/markdown-reader/src/mcp/tools.test.ts +++ b/packages/markdown-reader/src/mcp/tools.test.ts @@ -4,10 +4,10 @@ import { McpClient, createSdkTestPair } from "tiny-mcp-client"; import { markdownGroup } from "./group.js"; const EXPECTED_TOOL_NAMES = [ - "markdown_reader__read", - "markdown_reader__read_section", - "markdown_reader__approvals__list", - "markdown_reader__approvals__show" + "read", + "read_section", + "approvals__list", + "approvals__show" ]; const FIXTURE_PATH = "packages/markdown-reader/src/testing/fixtures/with-frontmatter.md"; @@ -37,7 +37,7 @@ describe("markdown-reader MCP tools", () => { expect(tools.tools.map((tool) => tool.name)).toEqual(EXPECTED_TOOL_NAMES); const result = await client.callTool({ - name: "markdown_reader__read", + name: "read", arguments: { file: FIXTURE_PATH } diff --git a/packages/markdown-reader/src/testing/fixtures/markdown-reader-plan.md b/packages/markdown-reader/src/testing/fixtures/markdown-reader-plan.md index f27422d6..6f21fc72 100644 --- a/packages/markdown-reader/src/testing/fixtures/markdown-reader-plan.md +++ b/packages/markdown-reader/src/testing/fixtures/markdown-reader-plan.md @@ -154,8 +154,8 @@ Agent-side configuration example (Claude Code `~/.claude.json` or `.mcp.json`): MCP tool names exposed by this server (derived by toolcraft from `group.name + command.name`): -- `markdown_reader__read` — params `{ file, depth? }`. -- `markdown_reader__read_section` — params `{ file, section, includeChildren? }`. +- `read` — params `{ file, depth? }`. +- `read_section` — params `{ file, section, includeChildren? }`. ### Error cases users see @@ -384,7 +384,7 @@ All tests are vitest, colocated, run under the package's `npm test` script that - `npm run dev -- plan markdown-read-section docs/plans/markdown-reader.md "Command: plan markdown-read"` returns the same body. - `npm run dev -- plan markdown-read missing.md` exits non-zero with a `UserError`-style message, not a stack trace. - `npm run dev -- plan --help` shows the three new subcommands. - - `npm run dev -- plan markdown-reader-mcp` passes an MCP handshake smoke test: an `initialize` request over stdio returns a capabilities payload, a `tools/list` request returns exactly `markdown_reader__read` and `markdown_reader__read_section`, and a `tools/call` on `markdown_reader__read` with `{ file: "docs/plans/markdown-reader.md" }` returns a TOC. Pattern to copy: [packages/terminal-pilot-mcp/scripts/smoke-test.ts](packages/terminal-pilot-mcp/scripts/smoke-test.ts). + - `npm run dev -- plan markdown-reader-mcp` passes an MCP handshake smoke test: an `initialize` request over stdio returns a capabilities payload, a `tools/list` request returns exactly `read` and `read_section`, and a `tools/call` on `read` with `{ file: "docs/plans/markdown-reader.md" }` returns a TOC. Pattern to copy: [packages/terminal-pilot-mcp/scripts/smoke-test.ts](packages/terminal-pilot-mcp/scripts/smoke-test.ts). - `npm run screenshot-poe-code -- plan markdown-read docs/plans/markdown-reader.md` — visually validate the terminal renderer once, attach to PR. - **Verification commands** (exact): `npm run build`, `npm test`, `npm run lint`, `npm run dev -- plan markdown-read docs/plans/markdown-reader.md`, `npm run dev -- plan markdown-read-section docs/plans/markdown-reader.md 2.1`, `npm run dev -- plan markdown-read-section docs/plans/markdown-reader.md 2.1 --no-include-children`, MCP smoke script against `npm run dev -- plan markdown-reader-mcp`. - **Fixtures / setup**: none external. All fixtures are in-repo markdown strings under `src/testing/fixtures/`. diff --git a/packages/terminal-pilot-mcp/src/mcp-tools.test.ts b/packages/terminal-pilot-mcp/src/mcp-tools.test.ts index 5a6d9ffd..33689711 100644 --- a/packages/terminal-pilot-mcp/src/mcp-tools.test.ts +++ b/packages/terminal-pilot-mcp/src/mcp-tools.test.ts @@ -5,21 +5,21 @@ import { terminalPilotGroup } from "terminal-pilot/commands"; import type { TerminalPilotRuntime } from "terminal-pilot/commands"; const EXPECTED_TOOL_NAMES = [ - "terminal_pilot__create_session", - "terminal_pilot__fill", - "terminal_pilot__type", - "terminal_pilot__press_key", - "terminal_pilot__send_signal", - "terminal_pilot__wait_for", - "terminal_pilot__wait_for_exit", - "terminal_pilot__read_screen", - "terminal_pilot__read_history", - "terminal_pilot__resize", - "terminal_pilot__close_session", - "terminal_pilot__get_session", - "terminal_pilot__list_sessions", - "terminal_pilot__approvals__list", - "terminal_pilot__approvals__show" + "create_session", + "fill", + "type", + "press_key", + "send_signal", + "wait_for", + "wait_for_exit", + "read_screen", + "read_history", + "resize", + "close_session", + "get_session", + "list_sessions", + "approvals__list", + "approvals__show" ]; const runtime: TerminalPilotRuntime = { diff --git a/packages/toolcraft-openapi/src/define-client.test.ts b/packages/toolcraft-openapi/src/define-client.test.ts index 67e2ad4d..cb0d220a 100644 --- a/packages/toolcraft-openapi/src/define-client.test.ts +++ b/packages/toolcraft-openapi/src/define-client.test.ts @@ -130,9 +130,9 @@ describe("defineClient", () => { const result = await mcpClient.listTools(); expect(result.tools.map((tool) => tool.name)).toEqual([ - "internal_agent__bots__list", - "internal_agent__approvals__list", - "internal_agent__approvals__show", + "bots__list", + "approvals__list", + "approvals__show", ]); } finally { await cleanup(); @@ -184,7 +184,7 @@ describe("defineClient", () => { try { const tools = await mcpClient.listTools(); - const tool = tools.tools.find((candidate) => candidate.name === "internal_agent__bots__view"); + const tool = tools.tools.find((candidate) => candidate.name === "bots__view"); expect(tool).toMatchObject({ inputSchema: { @@ -202,7 +202,7 @@ describe("defineClient", () => { }); const result = await mcpClient.callTool({ - name: "internal_agent__bots__view", + name: "bots__view", arguments: { bot_handle: "my-bot", limit: 2, diff --git a/packages/toolcraft-openapi/src/runtime.test.ts b/packages/toolcraft-openapi/src/runtime.test.ts index 59b0ae37..f0e6f86c 100644 --- a/packages/toolcraft-openapi/src/runtime.test.ts +++ b/packages/toolcraft-openapi/src/runtime.test.ts @@ -470,7 +470,7 @@ describe("commandsFromSpec", () => { expect(tools.tools).toContainEqual( expect.objectContaining({ - name: "internal_agent__bots__set_official_bot", + name: "bots__set_official_bot", inputSchema: expect.objectContaining({ required: ["bot_handle", "official"], properties: expect.objectContaining({ @@ -504,7 +504,7 @@ describe("commandsFromSpec", () => { expect(tools.tools).toContainEqual( expect.objectContaining({ - name: "internal_agent__campaigns__update_campaign", + name: "campaigns__update_campaign", inputSchema: expect.objectContaining({ additionalProperties: false, required: ["campaign_id", "name"] diff --git a/packages/toolcraft/README.md b/packages/toolcraft/README.md index cac98876..adb63334 100644 --- a/packages/toolcraft/README.md +++ b/packages/toolcraft/README.md @@ -458,7 +458,7 @@ If you have an existing MCP server you want to keep running, use the MCP proxy: - `version: string` - `services?` / `humanInLoop?` / `apiVersion?` - `projectRoot?: string` — root used for MCP proxy cache files (`.toolcraft/mcp/*.json`). -- `tools?: string[]` — allowlist of MCP tool names or group prefixes. Tool names are `__`-joined snake_case path segments (`root__bot__create`); a prefix like `root__bot` includes every descendant tool. +- `tools?: string[]` — allowlist of MCP tool names or group prefixes. Tool names are `__`-joined snake_case path segments without the root group name (`bot__create`); a prefix like `bot` includes every descendant tool. - `casing?: "snake" | "camel"` — affects MCP **input-schema property names** only. Tool names always stay `__`-joined snake_case. ### `HumanInLoopRuntimeOptions` diff --git a/packages/toolcraft/src/entrypoints-mcp-proxy.test.ts b/packages/toolcraft/src/entrypoints-mcp-proxy.test.ts index 673f08de..6a9ae547 100644 --- a/packages/toolcraft/src/entrypoints-mcp-proxy.test.ts +++ b/packages/toolcraft/src/entrypoints-mcp-proxy.test.ts @@ -271,9 +271,9 @@ describe("MCP proxy entrypoints", () => { expect(serverState.created).toHaveLength(1); expect(serverState.created[0]?.tools).toEqual([ - "root__github__create_issue", - "root__approvals__list", - "root__approvals__show", + "github__create_issue", + "approvals__list", + "approvals__show", ]); }); diff --git a/packages/toolcraft/src/human-in-loop/approvals-commands.test.ts b/packages/toolcraft/src/human-in-loop/approvals-commands.test.ts index e54cec28..f407b479 100644 --- a/packages/toolcraft/src/human-in-loop/approvals-commands.test.ts +++ b/packages/toolcraft/src/human-in-loop/approvals-commands.test.ts @@ -321,9 +321,9 @@ describe("approvals built-in commands", () => { const { tools } = await client.listTools(); const toolNames = tools.map((tool) => tool.name); - expect(toolNames).toContain("root__approvals__list"); - expect(toolNames).toContain("root__approvals__show"); - expect(toolNames).not.toContain("root__approvals__run"); + expect(toolNames).toContain("approvals__list"); + expect(toolNames).toContain("approvals__show"); + expect(toolNames).not.toContain("approvals__run"); } finally { await cleanup(); } diff --git a/packages/toolcraft/src/human-in-loop/mcp-runtime.integration.test.ts b/packages/toolcraft/src/human-in-loop/mcp-runtime.integration.test.ts index a0a3c64e..0c119d45 100644 --- a/packages/toolcraft/src/human-in-loop/mcp-runtime.integration.test.ts +++ b/packages/toolcraft/src/human-in-loop/mcp-runtime.integration.test.ts @@ -105,7 +105,7 @@ describe("human-in-loop MCP runtime", () => { try { const result = await client.callTool({ - name: "root__deploy__prod", + name: "deploy__prod", arguments: { target: "prod", }, @@ -145,7 +145,7 @@ describe("human-in-loop MCP runtime", () => { try { const result = await client.callTool({ - name: "root__deploy__prod", + name: "deploy__prod", arguments: { target: "prod", }, @@ -198,7 +198,7 @@ describe("human-in-loop MCP runtime", () => { try { const result = await client.callTool({ - name: "root__deploy__prod", + name: "deploy__prod", arguments: { target: "prod", }, @@ -228,7 +228,7 @@ describe("human-in-loop MCP runtime", () => { }); const approvalResult = await client.callTool({ - name: "root__approvals__show", + name: "approvals__show", arguments: { approval_id: pending.approvalId, }, diff --git a/packages/toolcraft/src/mcp-proxy-integration.test.ts b/packages/toolcraft/src/mcp-proxy-integration.test.ts index 3e17082c..6264a428 100644 --- a/packages/toolcraft/src/mcp-proxy-integration.test.ts +++ b/packages/toolcraft/src/mcp-proxy-integration.test.ts @@ -916,9 +916,9 @@ describe("mcp proxy integration", () => { try { const { tools } = await client.listTools(); - expect(tools.map((tool) => tool.name)).toContain("root__github__sub__renamed"); + expect(tools.map((tool) => tool.name)).toContain("github__sub__renamed"); const result = await client.callTool({ - name: "root__github__sub__renamed", + name: "github__sub__renamed", arguments: { text: "xyz" }, }); diff --git a/packages/toolcraft/src/mcp-runtime-options.test.ts b/packages/toolcraft/src/mcp-runtime-options.test.ts index 74b6b33c..9679ec4a 100644 --- a/packages/toolcraft/src/mcp-runtime-options.test.ts +++ b/packages/toolcraft/src/mcp-runtime-options.test.ts @@ -70,7 +70,7 @@ describe("createMCPServer human-in-loop runtime options plumbing", () => { try { await expect( client.callTool({ - name: "root__deploy", + name: "deploy", arguments: { target: "prod", }, diff --git a/packages/toolcraft/src/mcp.ts b/packages/toolcraft/src/mcp.ts index 15994695..0df169d2 100644 --- a/packages/toolcraft/src/mcp.ts +++ b/packages/toolcraft/src/mcp.ts @@ -56,9 +56,9 @@ export interface RunMCPOptions( } } - const rootPath = root.name.length === 0 ? [] : [root.name]; - for (const child of root.children) { - visit(child, rootPath, []); + visit(child, [], []); } return tools; diff --git a/packages/toolcraft/src/toolcraft.test.ts b/packages/toolcraft/src/toolcraft.test.ts index b601b918..49d862e7 100644 --- a/packages/toolcraft/src/toolcraft.test.ts +++ b/packages/toolcraft/src/toolcraft.test.ts @@ -839,14 +839,14 @@ describe("createMCPServer", () => { const server = createMCPServer(root, { name: "toolcraft-test", version: "1.0.0", - tools: ["root__usage", "root__bot"], + tools: ["usage", "bot"], }); const { client, cleanup } = await createClient(server); try { const result = await client.listTools(); - expect(result.tools.map((tool) => tool.name)).toEqual(["root__usage", "root__bot__create"]); + expect(result.tools.map((tool) => tool.name)).toEqual(["usage", "bot__create"]); expect(result.tools[0]?.inputSchema).toEqual({ type: "object", properties: { @@ -961,7 +961,7 @@ describe("createMCPServer", () => { const result = await client.listTools(); expect(result.tools).toHaveLength(3); - expect(result.tools[0]?.name).toBe("root__run"); + expect(result.tools[0]?.name).toBe("run"); expect(result.tools[0]?.inputSchema).toEqual({ type: "object", properties: { @@ -1017,7 +1017,7 @@ describe("createMCPServer", () => { }); const callResult = await client.callTool({ - name: "root__run", + name: "run", arguments: { limit: null, }, @@ -1055,7 +1055,7 @@ describe("createMCPServer", () => { try { await expect( client.callTool({ - name: "root__run", + name: "run", arguments: { count: 1.5, }, @@ -1155,7 +1155,7 @@ describe("createMCPServer", () => { const server = createMCPServer(root, { name: "toolcraft-test", version: "1.0.0", - tools: ["root__bot_admin__bot"], + tools: ["bot_admin__bot"], casing: "camel", }); const { client, cleanup } = await createClient(server); @@ -1164,8 +1164,8 @@ describe("createMCPServer", () => { const result = await client.listTools(); expect(result.tools.map((tool) => tool.name)).toEqual([ - "root__bot_admin__bot__create_bot", - "root__bot_admin__bot__remove_bot", + "bot_admin__bot__create_bot", + "bot_admin__bot__remove_bot", ]); expect(result.tools[0]).toMatchObject({ description: "Create a bot Parameters: botName (required), botConfig.apiKey (required).", @@ -1305,7 +1305,7 @@ describe("createMCPServer", () => { try { const callPromise = client.callTool({ - name: "root__deploy", + name: "deploy", arguments: {}, }); @@ -1347,7 +1347,7 @@ describe("createMCPServer", () => { const { client, cleanup } = await createClient(server); try { - await expect(client.callTool({ name: "root__deploy", arguments: {} })).rejects.toSatisfy((error: unknown) => { + await expect(client.callTool({ name: "deploy", arguments: {} })).rejects.toSatisfy((error: unknown) => { expect(error).toBeInstanceOf(McpError); expect(error).toMatchObject({ code: ERROR_INVALID_PARAMS, @@ -1383,7 +1383,7 @@ describe("createMCPServer", () => { try { const callPromise = client.callTool({ - name: "root__deploy", + name: "deploy", arguments: {}, }); @@ -1472,7 +1472,7 @@ describe("createMCPServer", () => { try { const result = await client.callTool({ - name: "root__deploy", + name: "deploy", arguments: { botName: "demo", }, @@ -1520,7 +1520,7 @@ describe("createMCPServer", () => { try { const callPromise = client.callTool({ - name: "root__explode", + name: "explode", arguments: {}, }); From 3fca4f96da00e3ba58c47e68d0cab81bc39ee72e Mon Sep 17 00:00:00 2001 From: "poe-code-agent[bot]" <254571472+poe-code-agent[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 00:36:46 +0000 Subject: [PATCH 2/2] fix(toolcraft): keep mcp root prefix default --- packages/markdown-reader/src/mcp/run.test.ts | 3 +- packages/markdown-reader/src/mcp/run.ts | 3 +- .../markdown-reader/src/mcp/tools.test.ts | 3 +- .../testing/fixtures/markdown-reader-plan.md | 6 +-- packages/terminal-pilot-mcp/src/index.ts | 3 +- .../terminal-pilot-mcp/src/mcp-server.test.ts | 3 +- .../terminal-pilot-mcp/src/mcp-tools.test.ts | 1 + .../src/define-client.test.ts | 2 + .../toolcraft-openapi/src/runtime.test.ts | 6 ++- packages/toolcraft/README.md | 3 +- .../src/entrypoints-mcp-proxy.test.ts | 1 + .../human-in-loop/approvals-commands.test.ts | 1 + .../mcp-runtime.integration.test.ts | 3 ++ .../src/mcp-proxy-integration.test.ts | 1 + .../toolcraft/src/mcp-runtime-options.test.ts | 1 + packages/toolcraft/src/mcp.ts | 19 ++++++--- packages/toolcraft/src/toolcraft.test.ts | 41 +++++++++++++++++++ 17 files changed, 84 insertions(+), 16 deletions(-) diff --git a/packages/markdown-reader/src/mcp/run.test.ts b/packages/markdown-reader/src/mcp/run.test.ts index 8c569dea..c6ed4ae9 100644 --- a/packages/markdown-reader/src/mcp/run.test.ts +++ b/packages/markdown-reader/src/mcp/run.test.ts @@ -28,7 +28,8 @@ describe("runMarkdownReaderMcp", () => { expect(runMCPMock).toHaveBeenCalledTimes(1); expect(runMCPMock).toHaveBeenCalledWith(markdownGroupMock, { name: "markdown-reader", - version: "0.0.1" + version: "0.0.1", + omitRootToolNamePrefix: true }); }); diff --git a/packages/markdown-reader/src/mcp/run.ts b/packages/markdown-reader/src/mcp/run.ts index a3af0592..7bc81886 100644 --- a/packages/markdown-reader/src/mcp/run.ts +++ b/packages/markdown-reader/src/mcp/run.ts @@ -5,6 +5,7 @@ import { markdownGroup } from "./group.js"; export async function runMarkdownReaderMcp(): Promise { await runMCP(markdownGroup, { name: "markdown-reader", - version: packageJson.version + version: packageJson.version, + omitRootToolNamePrefix: true }); } diff --git a/packages/markdown-reader/src/mcp/tools.test.ts b/packages/markdown-reader/src/mcp/tools.test.ts index 06a2afc0..e0bd88e7 100644 --- a/packages/markdown-reader/src/mcp/tools.test.ts +++ b/packages/markdown-reader/src/mcp/tools.test.ts @@ -14,7 +14,8 @@ const FIXTURE_PATH = "packages/markdown-reader/src/testing/fixtures/with-frontma async function createClientPair() { const server = createMCPServer(markdownGroup, { name: "markdown-reader", - version: "0.0.1" + version: "0.0.1", + omitRootToolNamePrefix: true }); return createSdkTestPair(server, () => diff --git a/packages/markdown-reader/src/testing/fixtures/markdown-reader-plan.md b/packages/markdown-reader/src/testing/fixtures/markdown-reader-plan.md index 6f21fc72..4da52840 100644 --- a/packages/markdown-reader/src/testing/fixtures/markdown-reader-plan.md +++ b/packages/markdown-reader/src/testing/fixtures/markdown-reader-plan.md @@ -152,7 +152,7 @@ Agent-side configuration example (Claude Code `~/.claude.json` or `.mcp.json`): } ``` -MCP tool names exposed by this server (derived by toolcraft from `group.name + command.name`): +MCP tool names exposed by this server (derived by toolcraft from command names with the root prefix omitted): - `read` — params `{ file, depth? }`. - `read_section` — params `{ file, section, includeChildren? }`. @@ -184,7 +184,7 @@ const { markdown, section } = await readSection({ file, section: "2.1" }); - `src/core/read-markdown.ts`, `src/core/read-section.ts` — orchestrators used by both the SDK and MCP tool handlers. File I/O goes through an injectable `fs` so tests use `memfs`. - `src/mcp/tools.ts` — two `defineCommand` entries (`scope: ["mcp"]`) that wrap the orchestrators as MCP tools. - `src/mcp/group.ts` — `markdownGroup = defineGroup({ name: "markdown-reader", scope: ["mcp"], children: [readTool, readSectionTool] })`. - - `src/mcp/run.ts` — `runMarkdownReaderMcp()` → `runMCP(markdownGroup, { name: "markdown-reader", version })` from `toolcraft/mcp`. Mirrors [packages/terminal-pilot-mcp/src/index.ts](packages/terminal-pilot-mcp/src/index.ts). + - `src/mcp/run.ts` — `runMarkdownReaderMcp()` → `runMCP(markdownGroup, { name: "markdown-reader", version, omitRootToolNamePrefix: true })` from `toolcraft/mcp`. Mirrors [packages/terminal-pilot-mcp/src/index.ts](packages/terminal-pilot-mcp/src/index.ts). - `src/testing/fixtures/*.md` — static sample docs used by unit tests. - CLI wiring lives in [src/cli/commands/plan.ts](src/cli/commands/plan.ts). Three new commander subcommands under the existing `plan` group: - `plan markdown-read` — imports `readMarkdown` from `@poe-code/markdown-reader`, prints via the existing `writeOutput` + `resolveOutputOption` helpers in that file (`terminal` / `md` / `json`). @@ -334,7 +334,7 @@ export const markdownGroup = defineGroup({ // src/mcp/run.ts export async function runMarkdownReaderMcp(): Promise { - await runMCP(markdownGroup, { name: "markdown-reader", version: packageJson.version }); + await runMCP(markdownGroup, { name: "markdown-reader", version: packageJson.version, omitRootToolNamePrefix: true }); } ``` diff --git a/packages/terminal-pilot-mcp/src/index.ts b/packages/terminal-pilot-mcp/src/index.ts index a9fabe96..afa39e88 100644 --- a/packages/terminal-pilot-mcp/src/index.ts +++ b/packages/terminal-pilot-mcp/src/index.ts @@ -4,6 +4,7 @@ import { terminalPilotGroup } from "terminal-pilot/commands"; export async function main(): Promise { await runMCP(terminalPilotGroup, { name: "terminal-pilot", - version: "0.0.1" + version: "0.0.1", + omitRootToolNamePrefix: true }); } diff --git a/packages/terminal-pilot-mcp/src/mcp-server.test.ts b/packages/terminal-pilot-mcp/src/mcp-server.test.ts index 850d0bd3..8fc9ea32 100644 --- a/packages/terminal-pilot-mcp/src/mcp-server.test.ts +++ b/packages/terminal-pilot-mcp/src/mcp-server.test.ts @@ -28,7 +28,8 @@ describe("terminal-pilot-mcp entry point", () => { expect(runMCPMock).toHaveBeenCalledTimes(1); expect(runMCPMock).toHaveBeenCalledWith(terminalPilotGroupMock, { name: "terminal-pilot", - version: "0.0.1" + version: "0.0.1", + omitRootToolNamePrefix: true }); }); diff --git a/packages/terminal-pilot-mcp/src/mcp-tools.test.ts b/packages/terminal-pilot-mcp/src/mcp-tools.test.ts index 33689711..15e2a1b4 100644 --- a/packages/terminal-pilot-mcp/src/mcp-tools.test.ts +++ b/packages/terminal-pilot-mcp/src/mcp-tools.test.ts @@ -102,6 +102,7 @@ describe("terminal-pilot-mcp tool surface", () => { const server = createMCPServer(terminalPilotGroup, { name: "terminal-pilot", version: "0.0.1", + omitRootToolNamePrefix: true, services: { terminalPilotRuntime: runtime } diff --git a/packages/toolcraft-openapi/src/define-client.test.ts b/packages/toolcraft-openapi/src/define-client.test.ts index cb0d220a..993a8367 100644 --- a/packages/toolcraft-openapi/src/define-client.test.ts +++ b/packages/toolcraft-openapi/src/define-client.test.ts @@ -123,6 +123,7 @@ describe("defineClient", () => { const server = createMCPServer(client.root, { name: client.name, version: "1.0.0", + omitRootToolNamePrefix: true, }); const { client: mcpClient, cleanup } = await createClientPair(server); @@ -178,6 +179,7 @@ describe("defineClient", () => { const server = createMCPServer(client.root, { name: client.name, version: "1.0.0", + omitRootToolNamePrefix: true, services: client.services, }); const { client: mcpClient, cleanup } = await createClientPair(server); diff --git a/packages/toolcraft-openapi/src/runtime.test.ts b/packages/toolcraft-openapi/src/runtime.test.ts index f0e6f86c..a0d4d2c7 100644 --- a/packages/toolcraft-openapi/src/runtime.test.ts +++ b/packages/toolcraft-openapi/src/runtime.test.ts @@ -447,7 +447,8 @@ describe("commandsFromSpec", () => { }); const server = createMCPServer(client.root, { name: client.name, - version: "1.0.0" + version: "1.0.0", + omitRootToolNamePrefix: true }); const { client: mcpClient, cleanup } = await createClientPair(server); @@ -495,7 +496,8 @@ describe("commandsFromSpec", () => { }); const server = createMCPServer(client.root, { name: client.name, - version: "1.0.0" + version: "1.0.0", + omitRootToolNamePrefix: true }); const { client: mcpClient, cleanup } = await createClientPair(server); diff --git a/packages/toolcraft/README.md b/packages/toolcraft/README.md index adb63334..94a0b525 100644 --- a/packages/toolcraft/README.md +++ b/packages/toolcraft/README.md @@ -458,7 +458,8 @@ If you have an existing MCP server you want to keep running, use the MCP proxy: - `version: string` - `services?` / `humanInLoop?` / `apiVersion?` - `projectRoot?: string` — root used for MCP proxy cache files (`.toolcraft/mcp/*.json`). -- `tools?: string[]` — allowlist of MCP tool names or group prefixes. Tool names are `__`-joined snake_case path segments without the root group name (`bot__create`); a prefix like `bot` includes every descendant tool. +- `tools?: string[]` — allowlist of MCP tool names or group prefixes. Tool names are `__`-joined snake_case path segments (`root__bot__create`); a prefix like `root__bot` includes every descendant tool. +- `omitRootToolNamePrefix?: boolean` — defaults to `false`. Set to `true` to omit the root group name from single-root MCP tool names (`bot__create`). - `casing?: "snake" | "camel"` — affects MCP **input-schema property names** only. Tool names always stay `__`-joined snake_case. ### `HumanInLoopRuntimeOptions` diff --git a/packages/toolcraft/src/entrypoints-mcp-proxy.test.ts b/packages/toolcraft/src/entrypoints-mcp-proxy.test.ts index 6a9ae547..5eee02cb 100644 --- a/packages/toolcraft/src/entrypoints-mcp-proxy.test.ts +++ b/packages/toolcraft/src/entrypoints-mcp-proxy.test.ts @@ -267,6 +267,7 @@ describe("MCP proxy entrypoints", () => { await createMCPServer(root, { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, }); expect(serverState.created).toHaveLength(1); diff --git a/packages/toolcraft/src/human-in-loop/approvals-commands.test.ts b/packages/toolcraft/src/human-in-loop/approvals-commands.test.ts index f407b479..4f2a7226 100644 --- a/packages/toolcraft/src/human-in-loop/approvals-commands.test.ts +++ b/packages/toolcraft/src/human-in-loop/approvals-commands.test.ts @@ -303,6 +303,7 @@ describe("approvals built-in commands", () => { { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, humanInLoop: { taskList, }, diff --git a/packages/toolcraft/src/human-in-loop/mcp-runtime.integration.test.ts b/packages/toolcraft/src/human-in-loop/mcp-runtime.integration.test.ts index 0c119d45..65e2644a 100644 --- a/packages/toolcraft/src/human-in-loop/mcp-runtime.integration.test.ts +++ b/packages/toolcraft/src/human-in-loop/mcp-runtime.integration.test.ts @@ -97,6 +97,7 @@ describe("human-in-loop MCP runtime", () => { const server = createMCPServer(createRoot("sync", handler), { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, humanInLoop: { provider, }, @@ -137,6 +138,7 @@ describe("human-in-loop MCP runtime", () => { const server = createMCPServer(createRoot("sync", handler), { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, humanInLoop: { provider, }, @@ -185,6 +187,7 @@ describe("human-in-loop MCP runtime", () => { const server = createMCPServer(createRoot("async", handler), { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, humanInLoop: { provider, taskList, diff --git a/packages/toolcraft/src/mcp-proxy-integration.test.ts b/packages/toolcraft/src/mcp-proxy-integration.test.ts index 6264a428..f891201a 100644 --- a/packages/toolcraft/src/mcp-proxy-integration.test.ts +++ b/packages/toolcraft/src/mcp-proxy-integration.test.ts @@ -903,6 +903,7 @@ describe("mcp proxy integration", () => { const server = createMCPServer(createProxyRoot(harness, { rename }).root, { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, }); const { client, cleanup } = await createSdkTestPair(server, () => new McpClient({ diff --git a/packages/toolcraft/src/mcp-runtime-options.test.ts b/packages/toolcraft/src/mcp-runtime-options.test.ts index 9679ec4a..32a82096 100644 --- a/packages/toolcraft/src/mcp-runtime-options.test.ts +++ b/packages/toolcraft/src/mcp-runtime-options.test.ts @@ -63,6 +63,7 @@ describe("createMCPServer human-in-loop runtime options plumbing", () => { { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, } ); const { client, cleanup } = await createClient(server); diff --git a/packages/toolcraft/src/mcp.ts b/packages/toolcraft/src/mcp.ts index 0df169d2..b4f92770 100644 --- a/packages/toolcraft/src/mcp.ts +++ b/packages/toolcraft/src/mcp.ts @@ -56,12 +56,18 @@ export interface RunMCPOptions( root: Group, casing: Casing, - allowlist: string[] | undefined + allowlist: string[] | undefined, + omitRootToolNamePrefix: boolean ): ToolDefinition[] { const tools: ToolDefinition[] = []; @@ -323,8 +330,10 @@ function enumerateTools( } } + const rootPath = omitRootToolNamePrefix || root.name.length === 0 ? [] : [root.name]; + for (const child of root.children) { - visit(child, [], []); + visit(child, rootPath, []); } return tools; @@ -569,7 +578,7 @@ function createResolvedMCPServer); - const tools = enumerateTools(root, casing, options.tools); + const tools = enumerateTools(root, casing, options.tools, options.omitRootToolNamePrefix ?? false); const version = resolveMCPVersion(options.version); const server = createServer({ name: options.name, version }); diff --git a/packages/toolcraft/src/toolcraft.test.ts b/packages/toolcraft/src/toolcraft.test.ts index 49d862e7..9540e42d 100644 --- a/packages/toolcraft/src/toolcraft.test.ts +++ b/packages/toolcraft/src/toolcraft.test.ts @@ -784,6 +784,34 @@ describe("createMCPServer", () => { ); } + it("keeps the root group name in MCP tool names by default", async () => { + const root = defineGroup({ + name: "root", + children: [ + defineCommand({ + name: "deploy", + scope: ["mcp"], + params: S.Object({}), + handler: async () => "ok", + }), + ], + }); + + const server = createMCPServer(root, { + name: "toolcraft-test", + version: "1.0.0", + }); + const { client, cleanup } = await createClient(server); + + try { + const result = await client.listTools(); + + expect(result.tools.map((tool) => tool.name)).toContain("root__deploy"); + } finally { + await cleanup(); + } + }); + it("lists only mcp-scoped commands that match the allowlist and applies schema casing", async () => { const usage = defineCommand({ name: "usage", @@ -839,6 +867,7 @@ describe("createMCPServer", () => { const server = createMCPServer(root, { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, tools: ["usage", "bot"], }); const { client, cleanup } = await createClient(server); @@ -911,6 +940,7 @@ describe("createMCPServer", () => { const server = createMCPServer(root, { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, }); const { client, cleanup } = await createClient(server); @@ -954,6 +984,7 @@ describe("createMCPServer", () => { const server = createMCPServer(root, { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, }); const { client, cleanup } = await createClient(server); @@ -999,6 +1030,7 @@ describe("createMCPServer", () => { const server = createMCPServer(root, { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, }); const { client, cleanup } = await createClient(server); @@ -1049,6 +1081,7 @@ describe("createMCPServer", () => { const server = createMCPServer(root, { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, }); const { client, cleanup } = await createClient(server); @@ -1092,6 +1125,7 @@ describe("createMCPServer", () => { const server = createMCPServer(root, { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, }); const { client, cleanup } = await createClient(server); @@ -1155,6 +1189,7 @@ describe("createMCPServer", () => { const server = createMCPServer(root, { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, tools: ["bot_admin__bot"], casing: "camel", }); @@ -1300,6 +1335,7 @@ describe("createMCPServer", () => { const server = createMCPServer(root, { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, }); const { client, cleanup } = await createClient(server); @@ -1343,6 +1379,7 @@ describe("createMCPServer", () => { const server = createMCPServer(root, { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, }); const { client, cleanup } = await createClient(server); @@ -1378,6 +1415,7 @@ describe("createMCPServer", () => { const server = createMCPServer(root, { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, }); const { client, cleanup } = await createClient(server); @@ -1409,6 +1447,7 @@ describe("createMCPServer", () => { const server = createMCPServer(root, { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, }); const { client, cleanup } = await createClient(server); @@ -1466,6 +1505,7 @@ describe("createMCPServer", () => { services: { region: "us", }, + omitRootToolNamePrefix: true, casing: "camel", }); const { client, cleanup } = await createClient(server); @@ -1515,6 +1555,7 @@ describe("createMCPServer", () => { const server = createMCPServer(root, { name: "toolcraft-test", version: "1.0.0", + omitRootToolNamePrefix: true, }); const { client, cleanup } = await createClient(server);