fix: disambiguate bridged tool names
This commit is contained in:
parent
bfe727150c
commit
89f5053c15
30
src/serve.ts
30
src/serve.ts
@ -163,26 +163,36 @@ export function selectServedServers(
|
||||
}
|
||||
|
||||
export function encodeToolName(server: string, tool: string): string {
|
||||
return `${server}${TOOL_SEPARATOR}${tool}`;
|
||||
return `${encodeToolNamePart(server)}${TOOL_SEPARATOR}${encodeToolNamePart(tool)}`;
|
||||
}
|
||||
|
||||
export function decodeToolName(
|
||||
name: string,
|
||||
servedServers: readonly Pick<ServedServer, 'name'>[]
|
||||
): { server: string; tool: string } | undefined {
|
||||
const sorted = [...servedServers].toSorted((a, b) => b.name.length - a.name.length);
|
||||
for (const server of sorted) {
|
||||
const prefix = `${server.name}${TOOL_SEPARATOR}`;
|
||||
if (name.startsWith(prefix)) {
|
||||
const tool = name.slice(prefix.length);
|
||||
if (tool.length > 0) {
|
||||
return { server: server.name, tool };
|
||||
}
|
||||
}
|
||||
const separatorIndex = name.indexOf(TOOL_SEPARATOR);
|
||||
if (separatorIndex === -1) {
|
||||
return undefined;
|
||||
}
|
||||
const server = decodeToolNamePart(name.slice(0, separatorIndex));
|
||||
const tool = decodeToolNamePart(name.slice(separatorIndex + TOOL_SEPARATOR.length));
|
||||
if (tool.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (servedServers.some((served) => served.name === server)) {
|
||||
return { server, tool };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function encodeToolNamePart(value: string): string {
|
||||
return value.replaceAll('%', '%25').replaceAll('_', '%5F');
|
||||
}
|
||||
|
||||
function decodeToolNamePart(value: string): string {
|
||||
return value.replaceAll('%5F', '_').replaceAll('%25', '%');
|
||||
}
|
||||
|
||||
function describeTool(server: string, description: string | undefined): string | undefined {
|
||||
if (!description) {
|
||||
return `Tool from MCPorter server '${server}'.`;
|
||||
|
||||
@ -39,12 +39,31 @@ describe('mcporter serve bridge', () => {
|
||||
|
||||
it('encodes and decodes namespaced tool names with longest-prefix matching', () => {
|
||||
expect(encodeToolName('alpha', 'ping')).toBe('alpha__ping');
|
||||
expect(decodeToolName('alpha-long__tool__with__separator', [{ name: 'alpha' }, { name: 'alpha-long' }])).toEqual({
|
||||
expect(
|
||||
decodeToolName('alpha-long__tool%5F%5Fwith%5F%5Fseparator', [{ name: 'alpha' }, { name: 'alpha-long' }])
|
||||
).toEqual({
|
||||
server: 'alpha-long',
|
||||
tool: 'tool__with__separator',
|
||||
});
|
||||
});
|
||||
|
||||
it('escapes namespaced tool parts to avoid server/tool collisions', () => {
|
||||
const first = encodeToolName('alpha', 'beta__ping');
|
||||
const second = encodeToolName('alpha__beta', 'ping');
|
||||
|
||||
expect(first).toBe('alpha__beta%5F%5Fping');
|
||||
expect(second).toBe('alpha%5F%5Fbeta__ping');
|
||||
expect(first).not.toBe(second);
|
||||
expect(decodeToolName(first, [{ name: 'alpha' }, { name: 'alpha__beta' }])).toEqual({
|
||||
server: 'alpha',
|
||||
tool: 'beta__ping',
|
||||
});
|
||||
expect(decodeToolName(second, [{ name: 'alpha' }, { name: 'alpha__beta' }])).toEqual({
|
||||
server: 'alpha__beta',
|
||||
tool: 'ping',
|
||||
});
|
||||
});
|
||||
|
||||
it('exposes daemon tools through a single MCP server', async () => {
|
||||
const runtime = {
|
||||
listTools: vi.fn().mockImplementation(async (server: string) => [
|
||||
|
||||
Loading…
Reference in New Issue
Block a user