Skip to content

Commit 360a999

Browse files
Merge pull request #395 from corbitsdev/cl-5673-mcp-tool-identifier-mcp__server__tool-is-built-independently
Build the MCP tool identifier in one place
2 parents c74aafb + 2b5159e commit 360a999

4 files changed

Lines changed: 37 additions & 12 deletions

File tree

src/mcp/plugin.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ import { gateToolCall } from "../plugins/permission-plugin.js";
55
import { scrubSecretShapedToolResultContent } from "../plugins/tool-result-secret-scrub.js";
66
import { truncateToolResultContent } from "../plugins/result-truncation-plugin.js";
77
import type { MCPClient } from "./client.js";
8-
9-
function mcpToolName(serverName: string, toolName: string): string {
10-
return `mcp__${serverName}__${toolName}`;
11-
}
8+
import { mcpToolName } from "./tool-name.js";
129

1310
// MCP results never reach the posix runner, so the secret-scrub and truncation
1411
// middleware in src/plugins never see them. Apply the same scrub-then-truncate

src/mcp/tool-name.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { mcpToolName, mcpToolPrefix, parseMcpToolName } from "./tool-name.js";
3+
4+
describe("mcpToolName", () => {
5+
test("builds the mcp__<server>__<tool> identifier", () => {
6+
expect(mcpToolName("linear", "list_projects")).toBe("mcp__linear__list_projects");
7+
});
8+
9+
test("round-trips with parseMcpToolName", () => {
10+
const name = mcpToolName("railway", "get_logs");
11+
expect(parseMcpToolName(name)).toEqual({ server: "railway", tool: "get_logs" });
12+
});
13+
});
14+
15+
describe("mcpToolPrefix", () => {
16+
test("matches the prefix of a built name for the same server", () => {
17+
const server = "linear";
18+
expect(mcpToolName(server, "list_projects").startsWith(mcpToolPrefix(server))).toBe(true);
19+
});
20+
21+
test("builds mcp__<server>__", () => {
22+
expect(mcpToolPrefix("linear")).toBe("mcp__linear__");
23+
});
24+
});

src/mcp/tool-name.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ export function isMcpToolName(name: string): boolean {
99
return name.startsWith(MCP_PREFIX);
1010
}
1111

12+
export function mcpToolPrefix(serverName: string): string {
13+
return `${MCP_PREFIX}${serverName}__`;
14+
}
15+
16+
export function mcpToolName(serverName: string, toolName: string): string {
17+
return `${mcpToolPrefix(serverName)}${toolName}`;
18+
}
19+
1220
export function parseMcpToolName(name: string): { server: string; tool: string } | null {
1321
if (!isMcpToolName(name)) return null;
1422
const rest = name.slice(MCP_PREFIX.length);

src/mcp/tool-permissions.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Tier } from "../permission/classify.js";
2-
import { isReadOnlyMcpTool } from "./tool-name.js";
2+
import { isReadOnlyMcpTool, mcpToolName, mcpToolPrefix } from "./tool-name.js";
33

44
// Subset of MCP ToolAnnotations used for permission tiering (hints from tools/list).
55
export type McpToolAnnotations = {
@@ -23,7 +23,7 @@ export function createMcpToolPermissionRegistry(): McpToolPermissionRegistry {
2323
tiers.set(name, tier);
2424
},
2525
removeToolsForServer(serverName) {
26-
const prefix = `mcp__${serverName}__`;
26+
const prefix = mcpToolPrefix(serverName);
2727
for (const key of tiers.keys()) {
2828
if (key.startsWith(prefix)) tiers.delete(key);
2929
}
@@ -37,10 +37,6 @@ export function createMcpToolPermissionRegistry(): McpToolPermissionRegistry {
3737
};
3838
}
3939

40-
function mcpAgentToolName(serverName: string, toolName: string): string {
41-
return `mcp__${serverName}__${toolName}`;
42-
}
43-
4440
function hasAnnotationHints(annotations: McpToolAnnotations | undefined): boolean {
4541
if (annotations === undefined) return false;
4642
return (
@@ -60,7 +56,7 @@ export function tierFromMcpTool(
6056
if (hasAnnotationHints(annotations)) {
6157
return annotations!.readOnlyHint === true ? "allow" : "ask";
6258
}
63-
return isReadOnlyMcpTool(mcpAgentToolName(serverName, toolName)) ? "allow" : "ask";
59+
return isReadOnlyMcpTool(mcpToolName(serverName, toolName)) ? "allow" : "ask";
6460
}
6561

6662
export function registerMcpClientTools(
@@ -69,6 +65,6 @@ export function registerMcpClientTools(
6965
tools: ReadonlyArray<{ name: string; annotations?: McpToolAnnotations }>,
7066
): void {
7167
for (const tool of tools) {
72-
registry.setTier(mcpAgentToolName(serverName, tool.name), tierFromMcpTool(tool.annotations, serverName, tool.name));
68+
registry.setTier(mcpToolName(serverName, tool.name), tierFromMcpTool(tool.annotations, serverName, tool.name));
7369
}
7470
}

0 commit comments

Comments
 (0)