28b8aa36c6
MCP capability broker gateway with closed 9-tool registry, adapter router, write-method blocklist (INV-7 backstop), token-bucket rate limiter (60/min user, 300/min tenant), SSE stream manager (ULID, per-call, 30s timeout), OpenAI↔MCP translator, in-process custom transport + stdio, mcp_adapters table with RLS, 5 API routes, 4 stub adapters + McpAdapter interface, 7 MCP conformance tests. G-012 (audit type widening), G-015 (INV-7 framing), G-016 (two enforcement models), G-017 (stdio-interop 7th test), G-020 (McpAdapter interface) applied. Tests: 254 green (224 unit + 32 conformance). Coverage: 88.7% on packages/mcp. M1 non-regression: all M1 tests pass. ---ci--- phase: 1 milestone: v0.2 status: complete wave: F phase_role: execution ---/ci---
66 lines
2.4 KiB
TypeScript
66 lines
2.4 KiB
TypeScript
/**
|
|
* translator.test.ts — MCP conformance test 5 (R-001 §5, gate item 15).
|
|
*
|
|
* Asserts the OpenAI ↔ MCP bidirectional translation contract:
|
|
* - tool_calls[i].function.{name, arguments(JSON string)} → params.{name, arguments(object)}
|
|
* - result.content[].text + isError → OpenAI {role:"tool", tool_call_id, content}
|
|
* - parse failures are PROTOCOL errors (TranslationError), not execution errors
|
|
*/
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
import {
|
|
toolCallToMcp,
|
|
mcpResultToToolMessage,
|
|
toolsToOpenAi,
|
|
TranslationError,
|
|
type OpenAiToolCall,
|
|
type McpResult,
|
|
} from "@coreci/mcp";
|
|
import { listTools } from "@coreci/mcp";
|
|
|
|
describe("conformance 5 — OpenAI ↔ MCP translation (R-001 §5)", () => {
|
|
it("parses OpenAI tool_call.arguments (JSON string) → MCP arguments (object)", () => {
|
|
const tc: OpenAiToolCall = {
|
|
id: "call_1",
|
|
type: "function",
|
|
function: { name: "proxmox.list_vms", arguments: '{"node":"pve1"}' },
|
|
};
|
|
expect(toolCallToMcp(tc)).toEqual({ name: "proxmox.list_vms", arguments: { node: "pve1" } });
|
|
});
|
|
|
|
it("throws TranslationError (protocol error) on malformed arguments JSON", () => {
|
|
const tc: OpenAiToolCall = {
|
|
id: "call_2",
|
|
type: "function",
|
|
function: { name: "github.list_repos", arguments: "{not json" },
|
|
};
|
|
expect(() => toolCallToMcp(tc)).toThrow(TranslationError);
|
|
});
|
|
|
|
it("maps MCP isError:false → OpenAI tool message with the text content", () => {
|
|
const result: McpResult = {
|
|
content: [{ type: "text", text: '{"repos":["a"]}' }],
|
|
isError: false,
|
|
};
|
|
expect(mcpResultToToolMessage(result, "call_1")).toEqual({
|
|
role: "tool",
|
|
tool_call_id: "call_1",
|
|
content: '{"repos":["a"]}',
|
|
});
|
|
});
|
|
|
|
it("maps MCP isError:true → OpenAI tool message prefixed with 'ERROR: '", () => {
|
|
const result: McpResult = {
|
|
content: [{ type: "text", text: "upstream 503" }],
|
|
isError: true,
|
|
};
|
|
expect(mcpResultToToolMessage(result, "call_2").content).toBe("ERROR: upstream 503");
|
|
});
|
|
|
|
it("the registry → OpenAI tools param preserves inputSchema as parameters", () => {
|
|
const defs = toolsToOpenAi(listTools());
|
|
expect(defs).toHaveLength(9);
|
|
const listVms = defs.find((d) => d.function.name === "proxmox.list_vms");
|
|
expect(listVms?.function.parameters).toMatchObject({ type: "object", required: ["node"] });
|
|
});
|
|
}); |