diff --git a/src/app/api/mcp/route.test.ts b/src/app/api/mcp/route.test.ts index a2bc70e..4b194b8 100644 --- a/src/app/api/mcp/route.test.ts +++ b/src/app/api/mcp/route.test.ts @@ -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" }, diff --git a/src/app/api/mcp/route.ts b/src/app/api/mcp/route.ts index d87dde7..838bb24 100644 --- a/src/app/api/mcp/route.ts +++ b/src/app/api/mcp/route.ts @@ -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}`); }