fix(mcp): implement tools.discovery handler returning flat tool catalog
The tools.discovery tool was declared in the catalog (line 1300) but no tools/call dispatch branch existed for it — every call fell through to 'Unknown tool: tools.discovery'. Functionality was reachable only via the parallel GET /api/mcp endpoint, which the JSON-RPC-only MCP clients in n8n couldn't use. Add a real handler that returns the same `tools` array as tools/list, wrapped in the standard JSON-RPC content envelope. Mirrors GET /api/mcp for JSON-RPC-only clients. Also add a Vitest case that asserts the catalog shape (array of tools, each with name+description+inputSchema, and tools.discovery in the list). Deployment note: this fix is in source; the live apphub.co.za server runs a built bundle and will keep returning 'Unknown tool' until rebuilt. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
e132319910
commit
ff1709c2da
2 changed files with 31 additions and 0 deletions
|
|
@ -301,6 +301,28 @@ describe("POST /api/mcp", () => {
|
|||
expect(body.error.message).toMatch(/Unknown tool/);
|
||||
});
|
||||
|
||||
it("tools/call tools.discovery returns the flat tool catalog (JSON-RPC form of GET /api/mcp)", async () => {
|
||||
const res = await POST(
|
||||
mcpRequest({
|
||||
method: "tools/call",
|
||||
id: 8,
|
||||
params: { name: "tools.discovery", arguments: {} },
|
||||
})
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json();
|
||||
const payload = JSON.parse(body.result.content[0].text);
|
||||
expect(Array.isArray(payload.tools)).toBe(true);
|
||||
expect(payload.tools.length).toBeGreaterThan(60);
|
||||
// Every entry has name + description + inputSchema
|
||||
for (const t of payload.tools) {
|
||||
expect(typeof t.name).toBe("string");
|
||||
expect(typeof t.description).toBe("string");
|
||||
expect(t.inputSchema).toBeDefined();
|
||||
}
|
||||
expect(payload.tools.map((t: { name: string }) => t.name)).toContain("tools.discovery");
|
||||
});
|
||||
|
||||
it("tools/call quotes.create_from_email delegates to the service with the full payload", async () => {
|
||||
createQuoteFromEmail.mockResolvedValueOnce({
|
||||
quote: { id: "q1" },
|
||||
|
|
|
|||
|
|
@ -2489,6 +2489,15 @@ export async function POST(req: NextRequest) {
|
|||
return jsonRpcOk(body.id, { deleted: args.id });
|
||||
}
|
||||
|
||||
// tools.discovery — flat tool catalog (no JSON-RPC envelope inside
|
||||
// the text payload). Mirrors `GET /api/mcp` for clients that can
|
||||
// only build a JSON-RPC request. Returns the same `tools` array as
|
||||
// `tools/list` — handy when an agent wants a self-describing
|
||||
// summary without paying the schema-drift tax of two surfaces.
|
||||
if (toolName === "tools.discovery") {
|
||||
return jsonRpcOk(body.id, { tools });
|
||||
}
|
||||
|
||||
throw new Error(`Unknown tool: ${toolName}`);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue