@winstonsayno/mcp-gateway 2.6.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [2.7.0] - 2026-10-07
13
+
14
+ Bridges: OpenAI-compatible tools proxy and A2A agent card.
15
+
16
+ ### Added
17
+ - **OpenAI-compatible tools proxy** (`openai:`): `GET /openai/v1/tools` (function tools + name mapping),
18
+ `POST /openai/v1/tool_calls` (execute `tool_calls`, get `role: "tool"` messages) and
19
+ `POST /openai/v1/chat/completions` (forward to `openai.upstream`, inject gateway tools, run the tool loop up to
20
+ `maxToolRounds`; caller-supplied tools are returned untouched; `stream: true` passes through).
21
+ - **A2A bridge** (`a2a:`): Agent Card at `/.well-known/agent-card.json` (and `/.well-known/agent.json`), one skill per
22
+ visible tool; JSON-RPC `message/send` / `tasks/get` at `a2a.path` (default `/a2a`); results as A2A Tasks with
23
+ artifacts.
24
+ - Bridge calls run through the regular REST checks (auth, scopes, tenants, policy, quotas, cache, output filter).
25
+ - `validateConfig()` exported from the config loader.
26
+ - Docs: [Bridges](docs/guides/bridges.md).
27
+
12
28
  ## [2.6.0] - 2026-10-07
13
29
 
14
30
  Edge runtimes: Cloudflare Workers, Deno and Bun adapters for the HTTP transport.
package/README.md CHANGED
@@ -579,6 +579,15 @@ await gateway.start();
579
579
  process.on('SIGTERM', () => gateway.stop());
580
580
  ```
581
581
 
582
+ ## What's New in v2.7
583
+
584
+ | Feature | Description |
585
+ |---------|-------------|
586
+ | **OpenAI tools proxy** | `/openai/v1/tools`, `/tool_calls`, `/chat/completions` with an automatic tool loop |
587
+ | **A2A bridge** | Agent Card at `/.well-known/agent-card.json` + JSON-RPC `message/send` — [guide](docs/guides/bridges.md) |
588
+
589
+ Details: [CHANGELOG](CHANGELOG.md).
590
+
582
591
  ## What's New in v2.6
583
592
 
584
593
  | Feature | Description |
@@ -0,0 +1,28 @@
1
+ /**
2
+ * A2A (Agent2Agent) bridge: publishes the gateway's tools as an A2A agent.
3
+ *
4
+ * - `GET /.well-known/agent-card.json` (and the older `/.well-known/agent.json`): the Agent Card — one skill per
5
+ * gateway tool the caller could see (anonymous callers see the card only when `a2a.public` is true or auth is off).
6
+ * - `POST <a2a.path>` (default `/a2a`): JSON-RPC 2.0 with `message/send` and `tasks/get`. A message selects a tool
7
+ * with a data part `{ "kind": "data", "data": { "skill": "<skill id>", "arguments": { … } } }` (or message
8
+ * `metadata.skill` + a data part with the arguments). The tool runs through the regular REST checks; the reply is
9
+ * a completed (or failed) Task whose artifact carries the tool result as text and data parts.
10
+ *
11
+ * Tasks are kept in memory for `a2a.taskRetentionSeconds` (600).
12
+ *
13
+ * @module bridges/a2a
14
+ */
15
+ import express, { type Request, type RequestHandler } from 'express';
16
+ import type { A2ABridgeConfig, ToolInfo } from '../utils/types.js';
17
+ import { type ToolCallResponse } from '../gateway/api.js';
18
+ export declare const A2A_PROTOCOL_VERSION = "0.3.0";
19
+ export interface A2ABridgeDeps {
20
+ config: () => A2ABridgeConfig | undefined;
21
+ tools: () => ToolInfo[];
22
+ naming: () => 'auto' | 'prefix';
23
+ authenticate: RequestHandler;
24
+ authRequired: () => boolean;
25
+ runToolCall: (req: Request, body: Record<string, unknown>, res: ToolCallResponse) => Promise<void>;
26
+ }
27
+ export declare function createA2ARouter(deps: A2ABridgeDeps): express.Router;
28
+ //# sourceMappingURL=a2a.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a2a.d.ts","sourceRoot":"","sources":["../../src/bridges/a2a.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,OAAO,EAAE,EAAE,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAErE,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAInE,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG5E,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,eAAe,GAAG,SAAS,CAAC;IAC1C,KAAK,EAAE,MAAM,QAAQ,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,MAAM,GAAG,QAAQ,CAAC;IAChC,YAAY,EAAE,cAAc,CAAC;IAC7B,YAAY,EAAE,MAAM,OAAO,CAAC;IAC5B,WAAW,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACpG;AAkCD,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CA8GnE"}
@@ -0,0 +1,161 @@
1
+ /**
2
+ * A2A (Agent2Agent) bridge: publishes the gateway's tools as an A2A agent.
3
+ *
4
+ * - `GET /.well-known/agent-card.json` (and the older `/.well-known/agent.json`): the Agent Card — one skill per
5
+ * gateway tool the caller could see (anonymous callers see the card only when `a2a.public` is true or auth is off).
6
+ * - `POST <a2a.path>` (default `/a2a`): JSON-RPC 2.0 with `message/send` and `tasks/get`. A message selects a tool
7
+ * with a data part `{ "kind": "data", "data": { "skill": "<skill id>", "arguments": { … } } }` (or message
8
+ * `metadata.skill` + a data part with the arguments). The tool runs through the regular REST checks; the reply is
9
+ * a completed (or failed) Task whose artifact carries the tool result as text and data parts.
10
+ *
11
+ * Tasks are kept in memory for `a2a.taskRetentionSeconds` (600).
12
+ *
13
+ * @module bridges/a2a
14
+ */
15
+ import express from 'express';
16
+ import { randomUUID } from 'crypto';
17
+ import { toLlmToolSchemas } from '../mcp/llm-schemas.js';
18
+ import { filterToolsByScope } from '../auth/scopes.js';
19
+ import { CapturedResponse } from '../gateway/api.js';
20
+ import { VERSION } from '../utils/version.js';
21
+ export const A2A_PROTOCOL_VERSION = '0.3.0';
22
+ const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
23
+ function skillsFor(scope, deps) {
24
+ const tools = filterToolsByScope(scope, deps.tools());
25
+ const schemas = toLlmToolSchemas(tools, 'openai', deps.naming());
26
+ const byTarget = new Map(tools.map((t) => [`${t.serverId}\u0000${t.name}`, t]));
27
+ return Object.entries(schemas.mapping).map(([id, target]) => {
28
+ const t = byTarget.get(`${target.server}\u0000${target.tool}`);
29
+ return {
30
+ id,
31
+ name: t?.title ?? target.tool,
32
+ description: t?.description ?? `${target.tool} on ${target.server}`,
33
+ tags: [target.server, 'mcp-tool'],
34
+ inputModes: ['application/json'],
35
+ outputModes: ['application/json', 'text/plain'],
36
+ target,
37
+ inputSchema: t?.inputSchema,
38
+ };
39
+ });
40
+ }
41
+ export function createA2ARouter(deps) {
42
+ const router = express.Router();
43
+ const tasks = new Map();
44
+ const path = () => deps.config()?.path ?? '/a2a';
45
+ const enabled = () => deps.config()?.enabled === true;
46
+ const prune = () => {
47
+ const now = Date.now();
48
+ for (const [k, v] of tasks)
49
+ if (v.expires <= now)
50
+ tasks.delete(k);
51
+ };
52
+ const card = (req) => {
53
+ const cfg = deps.config() ?? {};
54
+ const base = cfg.url ?? `${req.protocol}://${req.get('host')}`;
55
+ const skills = skillsFor(req.scope, deps).map(({ target: _t, inputSchema: _s, ...s }) => s);
56
+ return {
57
+ protocolVersion: A2A_PROTOCOL_VERSION,
58
+ name: cfg.name ?? 'mcp-gateway',
59
+ description: cfg.description ?? 'MCP tools exposed as A2A skills by mcp-gateway',
60
+ url: `${base.replace(/\/+$/, '')}${path()}`,
61
+ preferredTransport: 'JSONRPC',
62
+ version: VERSION,
63
+ provider: cfg.provider,
64
+ capabilities: { streaming: false, pushNotifications: false, stateTransitionHistory: false },
65
+ defaultInputModes: ['application/json'],
66
+ defaultOutputModes: ['application/json', 'text/plain'],
67
+ ...(deps.authRequired() ? { securitySchemes: { bearer: { type: 'http', scheme: 'bearer' } }, security: [{ bearer: [] }] } : {}),
68
+ skills,
69
+ };
70
+ };
71
+ // Agent card: authenticated when auth is on and the card is not public (skills reflect the caller's scope).
72
+ const cardAuth = (req, res, next) => {
73
+ if (!enabled())
74
+ return void res.status(404).json({ error: 'Not Found' });
75
+ if (deps.config()?.public && !req.headers.authorization)
76
+ return next();
77
+ return deps.authenticate(req, res, next);
78
+ };
79
+ router.get(['/.well-known/agent-card.json', '/.well-known/agent.json'], cardAuth, (req, res) => {
80
+ res.set('Cache-Control', 'no-store').json(card(req));
81
+ });
82
+ router.post('*', (req, res, next) => {
83
+ if (!enabled() || req.path !== path())
84
+ return next();
85
+ deps.authenticate(req, res, async () => {
86
+ const msg = (req.body ?? {});
87
+ const id = (msg.id ?? null);
88
+ const reply = (body) => res.json({ jsonrpc: '2.0', id, ...body });
89
+ const error = (code, message) => reply({ error: { code, message } });
90
+ if (msg.jsonrpc !== '2.0' || typeof msg.method !== 'string')
91
+ return error(-32600, 'Invalid Request');
92
+ prune();
93
+ try {
94
+ if (msg.method === 'tasks/get') {
95
+ const tid = isObj(msg.params) ? msg.params.id : undefined;
96
+ const t = typeof tid === 'string' ? tasks.get(tid) : undefined;
97
+ if (!t)
98
+ return error(-32001, 'Task not found');
99
+ return reply({ result: t.task });
100
+ }
101
+ if (msg.method === 'tasks/cancel')
102
+ return error(-32002, 'Task cannot be canceled (tasks complete synchronously)');
103
+ if (msg.method !== 'message/send')
104
+ return error(-32601, `Method not found: ${msg.method}`);
105
+ const params = isObj(msg.params) ? msg.params : {};
106
+ const message = isObj(params.message) ? params.message : undefined;
107
+ const parts = message && Array.isArray(message.parts) ? message.parts.filter(isObj) : [];
108
+ if (!message || parts.length === 0)
109
+ return error(-32602, 'params.message with parts is required');
110
+ const data = parts.find((p) => p.kind === 'data' && isObj(p.data))?.data;
111
+ const meta = isObj(message.metadata) ? message.metadata : {};
112
+ const skillId = typeof data?.skill === 'string' ? data.skill : typeof meta.skill === 'string' ? meta.skill : undefined;
113
+ const skills = skillsFor(req.scope, deps);
114
+ const skill = skillId ? skills.find((s) => s.id === skillId) : skills.length === 1 ? skills[0] : undefined;
115
+ const contextId = typeof message.contextId === 'string' ? message.contextId : randomUUID();
116
+ const task = { kind: 'task', id: randomUUID(), contextId, status: { state: 'completed', timestamp: new Date().toISOString() }, history: [message] };
117
+ if (!skill) {
118
+ task.status = {
119
+ state: 'rejected',
120
+ timestamp: new Date().toISOString(),
121
+ message: { kind: 'message', role: 'agent', messageId: randomUUID(), parts: [{ kind: 'text', text: skillId ? `Unknown skill "${skillId}"` : 'Choose a skill: send a data part { "skill": "<id>", "arguments": {…} }' }] },
122
+ };
123
+ }
124
+ else {
125
+ const args = isObj(data?.arguments) ? data.arguments : isObj(data) && !('skill' in data) ? data : {};
126
+ const out = new CapturedResponse();
127
+ await deps.runToolCall(req, { tool: skill.target.tool, server: skill.target.server, arguments: args }, out);
128
+ const body = isObj(out.body) ? out.body : {};
129
+ if (out.statusCode === 200) {
130
+ const result = body.result;
131
+ const text = (result?.content ?? []).filter((c) => c.type === 'text').map((c) => c.text ?? '').join('\n');
132
+ task.artifacts = [
133
+ {
134
+ artifactId: randomUUID(),
135
+ name: `${skill.id} result`,
136
+ parts: [...(text ? [{ kind: 'text', text }] : []), { kind: 'data', data: (result?.structuredContent ?? result ?? null) }],
137
+ },
138
+ ];
139
+ if (result?.isError)
140
+ task.status = { state: 'failed', timestamp: new Date().toISOString() };
141
+ }
142
+ else {
143
+ task.status = {
144
+ state: out.statusCode === 403 || out.statusCode === 429 ? 'rejected' : 'failed',
145
+ timestamp: new Date().toISOString(),
146
+ message: { kind: 'message', role: 'agent', messageId: randomUUID(), parts: [{ kind: 'text', text: String(body.message ?? body.error ?? `HTTP ${out.statusCode}`) }], metadata: { httpStatus: out.statusCode, code: body.code } },
147
+ };
148
+ }
149
+ task.metadata = { skill: skill.id, server: skill.target.server, tool: skill.target.tool };
150
+ }
151
+ tasks.set(task.id, { task, expires: Date.now() + (deps.config()?.taskRetentionSeconds ?? 600) * 1000 });
152
+ return reply({ result: task });
153
+ }
154
+ catch (err) {
155
+ return error(-32603, err instanceof Error ? err.message : String(err));
156
+ }
157
+ });
158
+ });
159
+ return router;
160
+ }
161
+ //# sourceMappingURL=a2a.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a2a.js","sourceRoot":"","sources":["../../src/bridges/a2a.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * OpenAI-compatible tools proxy.
3
+ *
4
+ * - `GET <path>/tools` — the caller's gateway tools as OpenAI function tools (+ name mapping).
5
+ * - `POST <path>/tool_calls` — execute OpenAI `tool_calls` (or an assistant message carrying them) and get the
6
+ * `role: "tool"` messages to append to the conversation.
7
+ * - `POST <path>/chat/completions` — when `openai.upstream` is set: forward to an OpenAI-compatible API, inject the
8
+ * gateway tools, execute gateway tool calls and loop until the model answers (`maxToolRounds`). Calls to tools the
9
+ * caller supplied itself are returned to the caller untouched. `stream: true` is forwarded as is (no tool loop).
10
+ *
11
+ * Tool calls go through the same checks as `POST /api/v1/tools/call` (scopes, tenants, policy, quotas, cache).
12
+ *
13
+ * @module bridges/openai
14
+ */
15
+ import express, { type Request, type RequestHandler } from 'express';
16
+ import type { OpenAIBridgeConfig, ToolInfo } from '../utils/types.js';
17
+ import { type LlmToolSchemas } from '../mcp/llm-schemas.js';
18
+ import { type ToolCallResponse } from '../gateway/api.js';
19
+ export interface OpenAIBridgeDeps {
20
+ config: () => OpenAIBridgeConfig | undefined;
21
+ tools: () => ToolInfo[];
22
+ naming: () => 'auto' | 'prefix';
23
+ authenticate: RequestHandler;
24
+ runToolCall: (req: Request, body: Record<string, unknown>, res: ToolCallResponse) => Promise<void>;
25
+ fetch?: typeof fetch;
26
+ }
27
+ interface OpenAIToolCall {
28
+ id: string;
29
+ type?: string;
30
+ function: {
31
+ name: string;
32
+ arguments?: string;
33
+ };
34
+ }
35
+ export declare function executeToolCalls(req: Request, calls: OpenAIToolCall[], schemas: LlmToolSchemas, run: OpenAIBridgeDeps['runToolCall']): Promise<Array<{
36
+ role: 'tool';
37
+ tool_call_id: string;
38
+ content: string;
39
+ }>>;
40
+ export declare function createOpenAIRouter(deps: OpenAIBridgeDeps): express.Router;
41
+ export {};
42
+ //# sourceMappingURL=openai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/bridges/openai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,OAAO,EAAE,EAAE,KAAK,OAAO,EAAE,KAAK,cAAc,EAAiB,MAAM,SAAS,CAAC;AACpF,OAAO,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG9E,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG5E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,kBAAkB,GAAG,SAAS,CAAC;IAC7C,KAAK,EAAE,MAAM,QAAQ,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,MAAM,GAAG,QAAQ,CAAC;IAChC,YAAY,EAAE,cAAc,CAAC;IAC7B,WAAW,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnG,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAChD;AAqBD,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,cAAc,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE,gBAAgB,CAAC,aAAa,CAAC,GACnC,OAAO,CAAC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAgBzE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAmFzE"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * OpenAI-compatible tools proxy.
3
+ *
4
+ * - `GET <path>/tools` — the caller's gateway tools as OpenAI function tools (+ name mapping).
5
+ * - `POST <path>/tool_calls` — execute OpenAI `tool_calls` (or an assistant message carrying them) and get the
6
+ * `role: "tool"` messages to append to the conversation.
7
+ * - `POST <path>/chat/completions` — when `openai.upstream` is set: forward to an OpenAI-compatible API, inject the
8
+ * gateway tools, execute gateway tool calls and loop until the model answers (`maxToolRounds`). Calls to tools the
9
+ * caller supplied itself are returned to the caller untouched. `stream: true` is forwarded as is (no tool loop).
10
+ *
11
+ * Tool calls go through the same checks as `POST /api/v1/tools/call` (scopes, tenants, policy, quotas, cache).
12
+ *
13
+ * @module bridges/openai
14
+ */
15
+ import express from 'express';
16
+ import { toLlmToolSchemas } from '../mcp/llm-schemas.js';
17
+ import { filterToolsByScope } from '../auth/scopes.js';
18
+ import { CapturedResponse } from '../gateway/api.js';
19
+ import { logger } from '../utils/logger.js';
20
+ const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
21
+ function schemasFor(req, deps) {
22
+ const scope = req.scope;
23
+ return toLlmToolSchemas(filterToolsByScope(scope, deps.tools()), 'openai', deps.naming());
24
+ }
25
+ /** Text content for a `role: "tool"` message from a gateway call outcome. */
26
+ function toolContent(status, body) {
27
+ if (status === 200 && isObj(body)) {
28
+ const result = body.result;
29
+ if (result?.structuredContent !== undefined)
30
+ return JSON.stringify(result.structuredContent);
31
+ const text = (result?.content ?? []).filter((c) => c.type === 'text' && typeof c.text === 'string').map((c) => c.text).join('\n');
32
+ return text || JSON.stringify(result ?? null);
33
+ }
34
+ const msg = isObj(body) ? (body.message ?? body.error) : body;
35
+ return JSON.stringify({ error: String(msg ?? `HTTP ${status}`), status });
36
+ }
37
+ export async function executeToolCalls(req, calls, schemas, run) {
38
+ return Promise.all(calls.map(async (c) => {
39
+ const target = schemas.mapping[c.function?.name];
40
+ if (!target)
41
+ return { role: 'tool', tool_call_id: c.id, content: JSON.stringify({ error: `Unknown tool "${c.function?.name}"` }) };
42
+ let args = {};
43
+ try {
44
+ args = c.function.arguments ? JSON.parse(c.function.arguments) : {};
45
+ }
46
+ catch {
47
+ return { role: 'tool', tool_call_id: c.id, content: JSON.stringify({ error: 'arguments are not valid JSON' }) };
48
+ }
49
+ const out = new CapturedResponse();
50
+ await run(req, { tool: target.tool, server: target.server, arguments: args }, out);
51
+ return { role: 'tool', tool_call_id: c.id, content: toolContent(out.statusCode, out.body) };
52
+ }));
53
+ }
54
+ export function createOpenAIRouter(deps) {
55
+ const router = express.Router();
56
+ const fetchImpl = deps.fetch ?? ((...a) => fetch(...a));
57
+ const enabled = (_req, res, next) => (!deps.config() || deps.config()?.enabled === false ? void res.status(404).json({ error: 'Not Found' }) : next());
58
+ router.get('/tools', enabled, deps.authenticate, (req, res) => {
59
+ res.json(schemasFor(req, deps));
60
+ });
61
+ router.post('/tool_calls', enabled, deps.authenticate, async (req, res, next) => {
62
+ try {
63
+ const b = (req.body ?? {});
64
+ const calls = (Array.isArray(b.tool_calls) ? b.tool_calls : isObj(b.message) && Array.isArray(b.message.tool_calls) ? b.message.tool_calls : undefined);
65
+ if (!calls || !calls.every((c) => isObj(c) && typeof c.id === 'string' && isObj(c.function) && typeof c.function.name === 'string')) {
66
+ return void res.status(400).json({ error: 'Bad Request', message: 'Body must be { "tool_calls": [{ "id", "function": { "name", "arguments" } }] }' });
67
+ }
68
+ res.json({ messages: await executeToolCalls(req, calls, schemasFor(req, deps), deps.runToolCall) });
69
+ }
70
+ catch (err) {
71
+ next(err);
72
+ }
73
+ });
74
+ router.post('/chat/completions', enabled, deps.authenticate, async (req, res, next) => {
75
+ const cfg = deps.config() ?? {};
76
+ const up = cfg.upstream;
77
+ if (!up?.baseUrl)
78
+ return void res.status(501).json({ error: 'Not Implemented', message: 'openai.upstream.baseUrl is not configured' });
79
+ try {
80
+ const body = { ...(req.body ?? {}) };
81
+ const schemas = schemasFor(req, deps);
82
+ const ownTools = Array.isArray(body.tools) ? body.tools : [];
83
+ const ownNames = new Set(ownTools.map((t) => t.function?.name).filter(Boolean));
84
+ if (cfg.injectTools !== false && schemas.tools.length > 0) {
85
+ body.tools = [...ownTools, ...schemas.tools.filter((t) => !ownNames.has(t.function.name))];
86
+ }
87
+ const headers = { 'content-type': 'application/json', ...(up.headers ?? {}) };
88
+ if (up.apiKey)
89
+ headers.authorization = `Bearer ${up.apiKey}`;
90
+ const url = `${up.baseUrl.replace(/\/+$/, '')}/chat/completions`;
91
+ const send = (payload) => fetchImpl(url, { method: 'POST', headers, body: JSON.stringify(payload), signal: AbortSignal.timeout(up.timeoutMs ?? 120_000) });
92
+ if (body.stream === true) {
93
+ const r = await send(body);
94
+ res.status(r.status);
95
+ r.headers.forEach((v, k) => {
96
+ if (!['content-length', 'content-encoding', 'transfer-encoding', 'connection'].includes(k))
97
+ res.setHeader(k, v);
98
+ });
99
+ if (!r.body)
100
+ return void res.end();
101
+ for await (const chunk of r.body)
102
+ res.write(chunk);
103
+ return void res.end();
104
+ }
105
+ const messages = Array.isArray(body.messages) ? [...body.messages] : [];
106
+ const rounds = cfg.maxToolRounds ?? 5;
107
+ let executed = 0;
108
+ for (let round = 0;; round++) {
109
+ const r = await send({ ...body, messages });
110
+ const text = await r.text();
111
+ let data;
112
+ try {
113
+ data = JSON.parse(text);
114
+ }
115
+ catch {
116
+ return void res.status(502).json({ error: 'Bad Gateway', message: 'Upstream returned non-JSON', status: r.status });
117
+ }
118
+ if (!r.ok)
119
+ return void res.status(r.status).json(data);
120
+ const choice = isObj(data) && Array.isArray(data.choices) ? data.choices[0] : undefined;
121
+ const msg = choice && isObj(choice.message) ? choice.message : undefined;
122
+ const calls = (msg && Array.isArray(msg.tool_calls) ? msg.tool_calls : []);
123
+ const mine = calls.filter((c) => schemas.mapping[c.function?.name] && !ownNames.has(c.function.name));
124
+ // Done: no tool calls, some are the caller's own tools, or out of rounds.
125
+ if (mine.length === 0 || mine.length !== calls.length || round >= rounds) {
126
+ if (isObj(data))
127
+ data['x_mcp_gateway'] = { toolRounds: round, toolCallsExecuted: executed };
128
+ return void res.status(200).json(data);
129
+ }
130
+ messages.push(msg);
131
+ messages.push(...(await executeToolCalls(req, mine, schemas, deps.runToolCall)));
132
+ executed += mine.length;
133
+ logger.debug(`OpenAI bridge: executed ${mine.length} tool call(s), round ${round + 1}`);
134
+ }
135
+ }
136
+ catch (err) {
137
+ next(err);
138
+ }
139
+ });
140
+ return router;
141
+ }
142
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/bridges/openai.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,8 @@ import type { GatewayConfig } from '../utils/types.js';
8
8
  * given, otherwise the first match in the default search paths.
9
9
  */
10
10
  export declare function resolveConfigPath(configPath?: string): string | undefined;
11
+ /** Validate (and apply defaults to) a raw config object; throws a readable error listing every issue. */
12
+ export declare function validateConfig(raw: unknown): GatewayConfig;
11
13
  export declare function loadConfig(configPath?: string): Promise<GatewayConfig>;
12
14
  export declare function generateDefaultConfig(): string;
13
15
  //# sourceMappingURL=loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AA8hBvD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMzE;AAED,wBAAsB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAwB5E;AAyCD,wBAAgB,qBAAqB,IAAI,MAAM,CAiH9C"}
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AA6jBvD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMzE;AAED,yGAAyG;AACzG,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,CAQ1D;AAED,wBAAsB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAiB5E;AAyCD,wBAAgB,qBAAqB,IAAI,MAAM,CAiH9C"}
@@ -463,6 +463,37 @@ const GatewayConfigSchema = z.object({
463
463
  })
464
464
  .strict()
465
465
  .optional(),
466
+ openai: z
467
+ .object({
468
+ enabled: z.boolean().optional(),
469
+ path: z.string().regex(/^\/[^\s]*$/, 'must start with "/"').optional(),
470
+ injectTools: z.boolean().optional(),
471
+ maxToolRounds: z.number().int().min(0).max(50).optional(),
472
+ upstream: z
473
+ .object({
474
+ baseUrl: z.string().url(),
475
+ apiKey: z.string().min(1).optional(),
476
+ headers: z.record(z.string()).optional(),
477
+ timeoutMs: z.number().int().positive().optional(),
478
+ })
479
+ .strict()
480
+ .optional(),
481
+ })
482
+ .strict()
483
+ .optional(),
484
+ a2a: z
485
+ .object({
486
+ enabled: z.boolean().optional(),
487
+ path: z.string().regex(/^\/[^\s]*$/, 'must start with "/"').optional(),
488
+ url: z.string().url().optional(),
489
+ name: z.string().min(1).optional(),
490
+ description: z.string().optional(),
491
+ provider: z.object({ organization: z.string().min(1), url: z.string().url().optional() }).strict().optional(),
492
+ public: z.boolean().optional(),
493
+ taskRetentionSeconds: z.number().int().positive().optional(),
494
+ })
495
+ .strict()
496
+ .optional(),
466
497
  plugins: z
467
498
  .array(z
468
499
  .object({
@@ -541,6 +572,14 @@ export function resolveConfigPath(configPath) {
541
572
  }
542
573
  return undefined;
543
574
  }
575
+ /** Validate (and apply defaults to) a raw config object; throws a readable error listing every issue. */
576
+ export function validateConfig(raw) {
577
+ const result = GatewayConfigSchema.safeParse(raw);
578
+ if (!result.success) {
579
+ throw new Error(`Invalid configuration:\n${result.error.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\n')}`);
580
+ }
581
+ return result.data;
582
+ }
544
583
  export async function loadConfig(configPath) {
545
584
  const filePath = resolveConfigPath(configPath);
546
585
  let raw = filePath ? await readConfigFile(filePath) : {};
@@ -553,11 +592,7 @@ export async function loadConfig(configPath) {
553
592
  }
554
593
  // Apply environment variable overrides
555
594
  raw = applyEnvOverrides(raw);
556
- const result = GatewayConfigSchema.safeParse(raw);
557
- if (!result.success) {
558
- throw new Error(`Invalid configuration:\n${result.error.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\n')}`);
559
- }
560
- const config = result.data;
595
+ const config = validateConfig(raw);
561
596
  if (filePath)
562
597
  config.configDir = dirname(filePath);
563
598
  return config;