Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/markdown-reader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions packages/markdown-reader/scripts/smoke-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ async function run(): Promise<void> {
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);

Expand All @@ -31,15 +31,15 @@ async function run(): Promise<void> {
console.log(`✓ tools/list — ${names.join(", ")}`);

const result = await client.callTool({
name: "markdown_reader__read",
name: "read",
arguments: { file: "docs/plans/markdown-reader.md" },
});

const text = result.content[0];
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();
}
Expand Down
3 changes: 2 additions & 1 deletion packages/markdown-reader/src/mcp/run.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
});

Expand Down
3 changes: 2 additions & 1 deletion packages/markdown-reader/src/mcp/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { markdownGroup } from "./group.js";
export async function runMarkdownReaderMcp(): Promise<void> {
await runMCP(markdownGroup, {
name: "markdown-reader",
version: packageJson.version
version: packageJson.version,
omitRootToolNamePrefix: true
});
}
13 changes: 7 additions & 6 deletions packages/markdown-reader/src/mcp/tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ 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";

async function createClientPair() {
const server = createMCPServer(markdownGroup, {
name: "markdown-reader",
version: "0.0.1"
version: "0.0.1",
omitRootToolNamePrefix: true
});

return createSdkTestPair(server, () =>
Expand All @@ -37,7 +38,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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,10 @@ 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):

- `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

Expand Down Expand Up @@ -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`).
Expand Down Expand Up @@ -334,7 +334,7 @@ export const markdownGroup = defineGroup({

// src/mcp/run.ts
export async function runMarkdownReaderMcp(): Promise<void> {
await runMCP(markdownGroup, { name: "markdown-reader", version: packageJson.version });
await runMCP(markdownGroup, { name: "markdown-reader", version: packageJson.version, omitRootToolNamePrefix: true });
}
```

Expand Down Expand Up @@ -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/`.
Expand Down
3 changes: 2 additions & 1 deletion packages/terminal-pilot-mcp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { terminalPilotGroup } from "terminal-pilot/commands";
export async function main(): Promise<void> {
await runMCP(terminalPilotGroup, {
name: "terminal-pilot",
version: "0.0.1"
version: "0.0.1",
omitRootToolNamePrefix: true
});
}
3 changes: 2 additions & 1 deletion packages/terminal-pilot-mcp/src/mcp-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
});

Expand Down
31 changes: 16 additions & 15 deletions packages/terminal-pilot-mcp/src/mcp-tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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
}
Expand Down
12 changes: 7 additions & 5 deletions packages/toolcraft-openapi/src/define-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,17 @@ describe("defineClient", () => {
const server = createMCPServer(client.root, {
name: client.name,
version: "1.0.0",
omitRootToolNamePrefix: true,
});
const { client: mcpClient, cleanup } = await createClientPair(server);

try {
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();
Expand Down Expand Up @@ -178,13 +179,14 @@ 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);

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: {
Expand All @@ -202,7 +204,7 @@ describe("defineClient", () => {
});

const result = await mcpClient.callTool({
name: "internal_agent__bots__view",
name: "bots__view",
arguments: {
bot_handle: "my-bot",
limit: 2,
Expand Down
10 changes: 6 additions & 4 deletions packages/toolcraft-openapi/src/runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -470,7 +471,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({
Expand All @@ -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);

Expand All @@ -504,7 +506,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"]
Expand Down
1 change: 1 addition & 0 deletions packages/toolcraft/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ If you have an existing MCP server you want to keep running, use the MCP proxy:
- `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.
- `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`
Expand Down
7 changes: 4 additions & 3 deletions packages/toolcraft/src/entrypoints-mcp-proxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,13 +267,14 @@ describe("MCP proxy entrypoints", () => {
await createMCPServer(root, {
name: "toolcraft-test",
version: "1.0.0",
omitRootToolNamePrefix: true,
});

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",
]);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ describe("approvals built-in commands", () => {
{
name: "toolcraft-test",
version: "1.0.0",
omitRootToolNamePrefix: true,
humanInLoop: {
taskList,
},
Expand All @@ -321,9 +322,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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand All @@ -105,7 +106,7 @@ describe("human-in-loop MCP runtime", () => {

try {
const result = await client.callTool({
name: "root__deploy__prod",
name: "deploy__prod",
arguments: {
target: "prod",
},
Expand Down Expand Up @@ -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,
},
Expand All @@ -145,7 +147,7 @@ describe("human-in-loop MCP runtime", () => {

try {
const result = await client.callTool({
name: "root__deploy__prod",
name: "deploy__prod",
arguments: {
target: "prod",
},
Expand Down Expand Up @@ -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,
Expand All @@ -198,7 +201,7 @@ describe("human-in-loop MCP runtime", () => {

try {
const result = await client.callTool({
name: "root__deploy__prod",
name: "deploy__prod",
arguments: {
target: "prod",
},
Expand Down Expand Up @@ -228,7 +231,7 @@ describe("human-in-loop MCP runtime", () => {
});

const approvalResult = await client.callTool({
name: "root__approvals__show",
name: "approvals__show",
arguments: {
approval_id: pending.approvalId,
},
Expand Down
Loading
Loading