@mcpolyglot/core 0.2.0 → 0.3.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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=pipeline.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/pipeline.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,179 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { z } from 'zod';
3
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
4
+ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
5
+ import { McpolyglotServer } from '../server.js';
6
+ import { McpolyglotError } from '../errors.js';
7
+ const SECRET_ARG = 'super-secret-arg-value';
8
+ let handlerCalls = 0;
9
+ function fakeConnector() {
10
+ return {
11
+ id: 'fake',
12
+ kind: 'sql',
13
+ init: async () => { },
14
+ close: async () => { },
15
+ health: async () => ({ ok: true, latencyMs: 0 }),
16
+ introspect: async () => ({ kind: 'sql', tables: [] }),
17
+ generatePerEntityTools: () => [],
18
+ listPrimitiveTools: () => [
19
+ {
20
+ name: 'fake.ok',
21
+ description: 'ok',
22
+ inputSchema: z.object({ q: z.string() }),
23
+ scopes: [],
24
+ readOnly: true,
25
+ handler: async () => ({
26
+ content: [{ type: 'json', data: [{ secret_row: 'row-data' }] }],
27
+ metadata: { rows: 1 },
28
+ }),
29
+ },
30
+ {
31
+ name: 'fake.denied',
32
+ description: 'denied',
33
+ inputSchema: z.object({ q: z.string() }),
34
+ scopes: [],
35
+ readOnly: true,
36
+ handler: async () => {
37
+ throw new McpolyglotError('forbidden.policy', 'Table "x" is not accessible.');
38
+ },
39
+ },
40
+ {
41
+ name: 'fake.scoped',
42
+ description: 'needs a scope nobody has',
43
+ inputSchema: z.object({}),
44
+ scopes: ['tables:write'],
45
+ readOnly: false,
46
+ handler: async () => {
47
+ handlerCalls += 1;
48
+ return { content: [] };
49
+ },
50
+ },
51
+ {
52
+ name: 'fake.slow',
53
+ description: 'ignores the abort signal and never returns in time',
54
+ inputSchema: z.object({}),
55
+ scopes: [],
56
+ readOnly: true,
57
+ handler: () => new Promise(() => { }),
58
+ },
59
+ {
60
+ name: 'fake.broken',
61
+ description: 'broken',
62
+ inputSchema: z.object({ q: z.string() }),
63
+ scopes: [],
64
+ readOnly: true,
65
+ handler: async () => {
66
+ throw new Error('boom');
67
+ },
68
+ },
69
+ ],
70
+ };
71
+ }
72
+ async function boot(opts = {}) {
73
+ const entries = [];
74
+ const rate = { taken: 0, released: 0 };
75
+ const passthrough = (_, r) => r;
76
+ const server = new McpolyglotServer({
77
+ connectors: [fakeConnector()],
78
+ security: {
79
+ hooks: {
80
+ checkScopes: (_name, required, granted) => {
81
+ const missing = required.filter((s) => !granted.has(s));
82
+ if (missing.length)
83
+ throw new McpolyglotError('forbidden.scope', `missing ${missing}`);
84
+ },
85
+ checkRateLimit: async () => {
86
+ rate.taken += 1;
87
+ return () => {
88
+ rate.released += 1;
89
+ };
90
+ },
91
+ redact: (_t, result) => ({ result, redactionsApplied: 0 }),
92
+ enforceSize: passthrough,
93
+ wrapUntrusted: (r) => r,
94
+ audit: async (e) => {
95
+ entries.push(e);
96
+ },
97
+ },
98
+ defaultLimits: { rowCap: 10, timeoutMs: opts.timeoutMs ?? 5_000, maxBytes: 65_536 },
99
+ defaultScopes: [],
100
+ },
101
+ });
102
+ const [clientSide, serverSide] = InMemoryTransport.createLinkedPair();
103
+ const transport = {
104
+ kind: 'stdio',
105
+ start: (create) => create().connect(serverSide),
106
+ stop: async () => { },
107
+ };
108
+ await server.start(transport);
109
+ const client = new Client({ name: 'test-agent', version: '1.0.0' });
110
+ await client.connect(clientSide);
111
+ return { client, entries, server, rate };
112
+ }
113
+ describe('audit entries', () => {
114
+ it('records allow / deny / error with agent id, reason, rows, latency — never raw args or rows', async () => {
115
+ const { client, entries, server } = await boot();
116
+ await client.callTool({ name: 'fake.ok', arguments: { q: SECRET_ARG } });
117
+ await client.callTool({ name: 'fake.denied', arguments: { q: SECRET_ARG } });
118
+ await client.callTool({ name: 'fake.broken', arguments: { q: SECRET_ARG } }).catch(() => { });
119
+ expect(entries.map((e) => [e.tool, e.decision])).toEqual([
120
+ ['fake.ok', 'allow'],
121
+ ['fake.denied', 'deny'],
122
+ ['fake.broken', 'error'],
123
+ ]);
124
+ for (const e of entries) {
125
+ expect(e.agentId).toBe('test-agent');
126
+ expect(e.argsHash).toMatch(/^[0-9a-f]{16}$/);
127
+ expect(typeof e.durationMs).toBe('number');
128
+ expect(Date.parse(e.ts)).not.toBeNaN();
129
+ }
130
+ expect(entries[0].rows).toBe(1);
131
+ expect(entries[0].reason).toBeUndefined();
132
+ expect(entries[1].reason).toContain('not accessible');
133
+ expect(entries[2].reason).toBe('boom');
134
+ const serialized = JSON.stringify(entries);
135
+ expect(serialized).not.toContain(SECRET_ARG);
136
+ expect(serialized).not.toContain('row-data');
137
+ await server.stop();
138
+ });
139
+ it('same args → same hash; different args → different hash', async () => {
140
+ const { client, entries, server } = await boot();
141
+ await client.callTool({ name: 'fake.ok', arguments: { q: 'a' } });
142
+ await client.callTool({ name: 'fake.ok', arguments: { q: 'a' } });
143
+ await client.callTool({ name: 'fake.ok', arguments: { q: 'b' } });
144
+ expect(entries[0].argsHash).toBe(entries[1].argsHash);
145
+ expect(entries[0].argsHash).not.toBe(entries[2].argsHash);
146
+ await server.stop();
147
+ });
148
+ });
149
+ describe('pipeline guarantees', () => {
150
+ it('scope check rejects before the handler runs', async () => {
151
+ const { client, entries, server } = await boot();
152
+ handlerCalls = 0;
153
+ const r = await client.callTool({ name: 'fake.scoped', arguments: {} });
154
+ expect(r.isError).toBe(true);
155
+ expect(handlerCalls).toBe(0);
156
+ expect(entries[0]).toMatchObject({ decision: 'deny', error: { code: 'forbidden.scope' } });
157
+ await server.stop();
158
+ });
159
+ it('a handler that never returns is cut off at the timeout', async () => {
160
+ const { client, entries, server } = await boot({ timeoutMs: 100 });
161
+ const t0 = Date.now();
162
+ const r = await client.callTool({ name: 'fake.slow', arguments: {} });
163
+ expect(Date.now() - t0).toBeLessThan(2_000);
164
+ expect(r.isError).toBe(true);
165
+ expect(entries[0]).toMatchObject({ decision: 'deny', error: { code: 'timeout' } });
166
+ await server.stop();
167
+ });
168
+ it('releases the rate-limit slot on success, deny, timeout and error', async () => {
169
+ const { client, server, rate } = await boot({ timeoutMs: 100 });
170
+ for (const name of ['fake.ok', 'fake.denied', 'fake.slow', 'fake.broken']) {
171
+ await client
172
+ .callTool({ name, arguments: name === 'fake.slow' ? {} : { q: 'x' } })
173
+ .catch(() => { });
174
+ }
175
+ expect(rate).toEqual({ taken: 4, released: 4 });
176
+ await server.stop();
177
+ });
178
+ });
179
+ //# sourceMappingURL=pipeline.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.test.js","sourceRoot":"","sources":["../../src/__tests__/pipeline.test.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { afterEach, describe, expect, it } from 'vitest';
2
2
  import { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js';
3
- import { StreamableHttpTransport } from '../transports/streamable-http.js';
3
+ import { hashToken, matchAgent, StreamableHttpTransport } from '../transports/streamable-http.js';
4
4
  // Silence the JSON banner the transport emits to stderr during tests.
5
5
  const silentLogger = { info: () => { }, warn: () => { } };
6
6
  async function fetchStatus(url, init) {
@@ -25,7 +25,7 @@ describe('StreamableHttpTransport', () => {
25
25
  ...opts,
26
26
  logger: silentLogger,
27
27
  });
28
- await transport.start(mcpServer);
28
+ await transport.start(() => mcpServer);
29
29
  // We bind on port 0 so the OS picks; recover the real port from the
30
30
  // listening socket. Field access is intentional — the public class
31
31
  // doesn't expose the address since the CLI already knows it.
@@ -60,5 +60,46 @@ describe('StreamableHttpTransport', () => {
60
60
  expect(typeof transport.bearerToken).toBe('string');
61
61
  expect(transport.bearerToken.length).toBeGreaterThan(16);
62
62
  });
63
+ it('agents mode: known token passes, revoked and unknown tokens get 401', async () => {
64
+ await boot({
65
+ host: '127.0.0.1',
66
+ port: 0,
67
+ auth: {
68
+ kind: 'agents',
69
+ agents: [
70
+ {
71
+ id: 'a',
72
+ tokens: [{ hash: hashToken('old'), revoked: true }, { hash: hashToken('new') }],
73
+ },
74
+ ],
75
+ },
76
+ });
77
+ expect(transport.bearerToken).toBeUndefined();
78
+ const call = (token) => fetchStatus(`${baseUrl}/mcp`, {
79
+ method: 'POST',
80
+ headers: { authorization: `Bearer ${token}` },
81
+ });
82
+ expect((await call('old')).status).toBe(401);
83
+ expect((await call('nope')).status).toBe(401);
84
+ // Past auth; the SDK then rejects the empty body, which is not a 401.
85
+ expect((await call('new')).status).not.toBe(401);
86
+ });
87
+ });
88
+ describe('agent token hashing', () => {
89
+ it('hashToken is sha256 hex', () => {
90
+ // sha256("abc"), FIPS 180-2 test vector.
91
+ expect(hashToken('abc')).toBe('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad');
92
+ });
93
+ it('matchAgent finds the owner and nothing for unknown or malformed hashes', () => {
94
+ const hashes = [
95
+ { agentId: 'bad', hash: Buffer.from('abcd', 'hex') },
96
+ { agentId: 'a', hash: Buffer.from(hashToken('ta'), 'hex') },
97
+ { agentId: 'b', hash: Buffer.from(hashToken('tb'), 'hex') },
98
+ ];
99
+ expect(matchAgent(hashes, 'ta')).toBe('a');
100
+ expect(matchAgent(hashes, 'tb')).toBe('b');
101
+ expect(matchAgent(hashes, 'tc')).toBeUndefined();
102
+ expect(matchAgent([], 'ta')).toBeUndefined();
103
+ });
63
104
  });
64
105
  //# sourceMappingURL=streamable-http.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"streamable-http.test.js","sourceRoot":"","sources":["../../src/__tests__/streamable-http.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"streamable-http.test.js","sourceRoot":"","sources":["../../src/__tests__/streamable-http.test.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export type { Connector, ConnectorInitCtx, ConnectorKind, PerEntityConfig, SchemaSnapshot, TableSchema, CollectionSchema, OperationSchema, ColumnSchema, } from './connector.js';
2
2
  export type { ToolDefinition, ToolHandler, ToolExecCtx, ToolExecLimits, ToolResult, ContentBlock, Scope, } from './tool.js';
3
3
  export { ALL_SCOPES, DEFAULT_SCOPES } from './tool.js';
4
- export { McpolyglotServer, type McpolyglotServerOptions, type SecurityServices, type SecurityHooks, } from './server.js';
4
+ export { McpolyglotServer, type McpolyglotServerOptions, type AgentGrant, type SecurityServices, type SecurityHooks, } from './server.js';
5
5
  export type { Transport } from './transport.js';
6
6
  export { McpolyglotError, ScopeError, RateLimitError, TimeoutError, ConfigError, } from './errors.js';
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,YAAY,GACb,MAAM,gBAAgB,CAAC;AAExB,YAAY,EACV,cAAc,EACd,WAAW,EACX,WAAW,EACX,cAAc,EACd,UAAU,EACV,YAAY,EACZ,KAAK,GACN,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EACrB,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EACL,eAAe,EACf,UAAU,EACV,cAAc,EACd,YAAY,EACZ,WAAW,GACZ,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,YAAY,GACb,MAAM,gBAAgB,CAAC;AAExB,YAAY,EACV,cAAc,EACd,WAAW,EACX,WAAW,EACX,cAAc,EACd,UAAU,EACV,YAAY,EACZ,KAAK,GACN,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EACL,eAAe,EACf,UAAU,EACV,cAAc,EACd,YAAY,EACZ,WAAW,GACZ,MAAM,aAAa,CAAC"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EACL,gBAAgB,GAIjB,MAAM,aAAa,CAAC;AAIrB,OAAO,EACL,eAAe,EACf,UAAU,EACV,cAAc,EACd,YAAY,EACZ,WAAW,GACZ,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EACL,gBAAgB,GAKjB,MAAM,aAAa,CAAC;AAIrB,OAAO,EACL,eAAe,EACf,UAAU,EACV,cAAc,EACd,YAAY,EACZ,WAAW,GACZ,MAAM,aAAa,CAAC"}
package/dist/server.d.ts CHANGED
@@ -11,8 +11,11 @@ import { type Scope, type ToolExecLimits, type ToolResult } from './tool.js';
11
11
  export interface SecurityHooks {
12
12
  /** Phase 1 — reject if granted scopes don't cover the tool's required scopes. */
13
13
  checkScopes(toolName: string, required: readonly Scope[], granted: ReadonlySet<Scope>): void;
14
- /** Phase 2 — token-bucket + concurrency gate. Throws `RateLimitError` on violation. */
15
- checkRateLimit(toolName: string, sessionId: string): Promise<void>;
14
+ /**
15
+ * Phase 2 — token-bucket + concurrency gate. Throws `RateLimitError` on violation. May
16
+ * return a release function; the server calls it when the call finishes.
17
+ */
18
+ checkRateLimit(toolName: string, sessionId: string): Promise<void | (() => void)>;
16
19
  /** Phase 5 — strip secrets, drop denied columns. Returns the new result and the redaction count. */
17
20
  redact(toolName: string, result: ToolResult): {
18
21
  result: ToolResult;
@@ -39,7 +42,16 @@ export interface AuditEntry {
39
42
  /** ISO-8601 UTC timestamp. */
40
43
  ts: string;
41
44
  sessionId: string;
45
+ /**
46
+ * The agent authenticated by its HTTP token when `agents` is configured. Otherwise
47
+ * client-asserted: the `x-mcpolyglot-agent` header (HTTP), else MCP `clientInfo.name`.
48
+ */
49
+ agentId?: string;
42
50
  tool: string;
51
+ /** `deny` = rejected by policy/scope/rate limit/timeout; `error` = anything else that failed. */
52
+ decision: 'allow' | 'deny' | 'error';
53
+ /** Why the call was denied or failed. */
54
+ reason?: string;
43
55
  /** First 16 chars of `sha256(JSON.stringify(args))`. Enough for forensics, not for reconstruction. */
44
56
  argsHash: string;
45
57
  scopes: Scope[];
@@ -62,6 +74,12 @@ export interface McpolyglotServerOptions {
62
74
  /** Scopes granted to the calling session. Defaults to `DEFAULT_SCOPES` (read-only). */
63
75
  scopes?: readonly Scope[];
64
76
  security: SecurityServices;
77
+ /**
78
+ * Per-agent grants. A request whose transport authenticated one of these ids (via
79
+ * `authInfo.clientId`) sees only that agent's connectors and scopes. Requests without an
80
+ * authenticated agent (stdio, plain bearer) use `connectors` + `scopes` as before.
81
+ */
82
+ agents?: AgentGrant[];
65
83
  logger?: {
66
84
  debug: (msg: string, fields?: Record<string, unknown>) => void;
67
85
  info: (msg: string, fields?: Record<string, unknown>) => void;
@@ -69,6 +87,13 @@ export interface McpolyglotServerOptions {
69
87
  error: (msg: string, fields?: Record<string, unknown>) => void;
70
88
  };
71
89
  }
90
+ /** What one authenticated agent may use. */
91
+ export interface AgentGrant {
92
+ id: string;
93
+ scopes: readonly Scope[];
94
+ /** Connectors this agent sees; may be per-agent instances (e.g. with their own policy). */
95
+ connectors: Connector[];
96
+ }
72
97
  /**
73
98
  * MCP server that wires connectors + security hooks + a transport.
74
99
  *
@@ -94,19 +119,31 @@ export interface McpolyglotServerOptions {
94
119
  * ```
95
120
  */
96
121
  export declare class McpolyglotServer {
97
- private readonly server;
122
+ private readonly info;
98
123
  private readonly tools;
99
124
  private readonly connectors;
100
125
  private readonly perEntity;
101
126
  private readonly scopes;
127
+ private readonly agents;
102
128
  private readonly security;
103
129
  private readonly sessionId;
104
130
  private readonly logger;
105
131
  private transport?;
106
132
  constructor(opts: McpolyglotServerOptions);
133
+ /**
134
+ * A fresh MCP protocol server bound to this instance's tools and pipeline. Stdio makes one;
135
+ * the stateless HTTP transport makes one per request (the SDK forbids reusing either).
136
+ */
137
+ private createMcpServer;
107
138
  start(transport: Transport): Promise<void>;
108
139
  stop(): Promise<void>;
109
- private registerTool;
140
+ private allConnectors;
141
+ private toolsOf;
142
+ /**
143
+ * Who is calling. Only `authInfo` set by our HTTP transport identifies an agent; headers
144
+ * and clientInfo are client-asserted and only used as an audit label when it's absent.
145
+ */
146
+ private caller;
110
147
  /**
111
148
  * Fixed pipeline: scope check → rate limit → timeout → handler →
112
149
  * redaction → size cap → untrusted-wrap → audit log → response.
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/dist/server.js CHANGED
@@ -29,11 +29,12 @@ import { McpolyglotError, TimeoutError } from './errors.js';
29
29
  * ```
30
30
  */
31
31
  export class McpolyglotServer {
32
- server;
32
+ info;
33
33
  tools = new Map();
34
34
  connectors;
35
35
  perEntity;
36
36
  scopes;
37
+ agents;
37
38
  security;
38
39
  sessionId = randomUUID();
39
40
  logger;
@@ -44,38 +45,51 @@ export class McpolyglotServer {
44
45
  this.scopes = new Set(opts.scopes ?? DEFAULT_SCOPES);
45
46
  this.security = opts.security;
46
47
  this.logger = opts.logger ?? makeNoopLogger();
47
- this.server = new Server({ name: opts.name ?? 'mcpolyglot', version: opts.version ?? '0.0.1' }, { capabilities: { tools: {} } });
48
- this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
49
- tools: Array.from(this.tools.values()).map((t) => ({
48
+ this.agents = new Map((opts.agents ?? []).map((a) => [a.id, { ...a, tools: new Map() }]));
49
+ this.info = { name: opts.name ?? 'mcpolyglot', version: opts.version ?? '0.0.1' };
50
+ }
51
+ /**
52
+ * A fresh MCP protocol server bound to this instance's tools and pipeline. Stdio makes one;
53
+ * the stateless HTTP transport makes one per request (the SDK forbids reusing either).
54
+ */
55
+ createMcpServer() {
56
+ const server = new Server(this.info, { capabilities: { tools: {} } });
57
+ server.setRequestHandler(ListToolsRequestSchema, async (_req, extra) => ({
58
+ tools: Array.from(this.caller(server, extra).tools.values()).map((t) => ({
50
59
  name: t.name,
51
60
  description: t.description,
52
61
  inputSchema: z.toJSONSchema(t.inputSchema, { target: 'draft-7' }),
53
62
  })),
54
63
  }));
55
- this.server.setRequestHandler(CallToolRequestSchema, async (req) => {
56
- const def = this.tools.get(req.params.name);
64
+ server.setRequestHandler(CallToolRequestSchema, async (req, extra) => {
65
+ const caller = this.caller(server, extra);
66
+ const def = caller.tools.get(req.params.name);
57
67
  if (!def) {
58
68
  throw new McpolyglotError('tool.not_found', `Unknown tool: ${req.params.name}`);
59
69
  }
60
- const internal = await this.executeTool(def, req.params.arguments ?? {});
70
+ const internal = await this.executeTool(def, req.params.arguments ?? {}, caller);
61
71
  return toMcpResult(internal);
62
72
  });
73
+ return server;
63
74
  }
64
75
  async start(transport) {
65
- for (const c of this.connectors) {
76
+ for (const c of this.allConnectors())
66
77
  await c.init({ logger: this.logger });
67
- for (const tool of c.listPrimitiveTools()) {
68
- this.registerTool(tool);
69
- }
70
- const cfg = this.perEntity[c.id];
71
- if (cfg?.enabled) {
72
- for (const tool of c.generatePerEntityTools(cfg)) {
73
- this.registerTool(tool);
78
+ for (const c of this.connectors) {
79
+ for (const tool of this.toolsOf(c))
80
+ registerTool(this.tools, tool);
81
+ }
82
+ for (const a of this.agents.values()) {
83
+ for (const c of a.connectors) {
84
+ for (const tool of this.toolsOf(c)) {
85
+ // Agents only see tools their scopes fully cover.
86
+ if (tool.scopes.every((s) => a.scopes.includes(s)))
87
+ registerTool(a.tools, tool);
74
88
  }
75
89
  }
76
90
  }
77
91
  this.transport = transport;
78
- await transport.start(this.server);
92
+ await transport.start(() => this.createMcpServer());
79
93
  this.logger.info('mcpolyglot.started', {
80
94
  sessionId: this.sessionId,
81
95
  transport: transport.kind,
@@ -84,51 +98,76 @@ export class McpolyglotServer {
84
98
  }
85
99
  async stop() {
86
100
  await this.transport?.stop();
87
- for (const c of this.connectors) {
101
+ for (const c of this.allConnectors()) {
88
102
  await c.close();
89
103
  }
90
- await this.server.close();
91
104
  }
92
- registerTool(tool) {
93
- if (this.tools.has(tool.name)) {
94
- throw new McpolyglotError('tool.duplicate', `Duplicate tool name: ${tool.name}`);
105
+ allConnectors() {
106
+ return new Set([...this.connectors, ...[...this.agents.values()].flatMap((a) => a.connectors)]);
107
+ }
108
+ toolsOf(c) {
109
+ const cfg = this.perEntity[c.id];
110
+ return [...c.listPrimitiveTools(), ...(cfg?.enabled ? c.generatePerEntityTools(cfg) : [])];
111
+ }
112
+ /**
113
+ * Who is calling. Only `authInfo` set by our HTTP transport identifies an agent; headers
114
+ * and clientInfo are client-asserted and only used as an audit label when it's absent.
115
+ */
116
+ caller(server, extra) {
117
+ const agent = extra.authInfo ? this.agents.get(extra.authInfo.clientId) : undefined;
118
+ if (agent) {
119
+ return {
120
+ agentId: agent.id,
121
+ scopes: new Set(agent.scopes),
122
+ tools: agent.tools,
123
+ rateKey: `agent:${agent.id}`,
124
+ };
95
125
  }
96
- this.tools.set(tool.name, tool);
126
+ const header = extra.requestInfo?.headers['x-mcpolyglot-agent'];
127
+ return {
128
+ agentId: (Array.isArray(header) ? header[0] : header) ?? server.getClientVersion()?.name,
129
+ scopes: this.scopes,
130
+ tools: this.tools,
131
+ rateKey: this.sessionId,
132
+ };
97
133
  }
98
134
  /**
99
135
  * Fixed pipeline: scope check → rate limit → timeout → handler →
100
136
  * redaction → size cap → untrusted-wrap → audit log → response.
101
137
  * Connectors cannot bypass.
102
138
  */
103
- async executeTool(def, rawArgs) {
139
+ async executeTool(def, rawArgs, caller) {
140
+ const { agentId, scopes } = caller;
104
141
  const started = Date.now();
105
142
  const argsHash = hashArgs(rawArgs);
106
143
  let result;
107
144
  let errorEntry;
145
+ let decision = 'allow';
146
+ let release = undefined;
108
147
  try {
109
148
  // 1. scope check
110
- this.security.hooks.checkScopes(def.name, def.scopes, this.scopes);
149
+ this.security.hooks.checkScopes(def.name, def.scopes, scopes);
111
150
  // 2. rate limit
112
- await this.security.hooks.checkRateLimit(def.name, this.sessionId);
151
+ release = await this.security.hooks.checkRateLimit(def.name, caller.rateKey);
113
152
  // 3. timeout
114
153
  const ac = new AbortController();
115
154
  const timer = setTimeout(() => ac.abort(), this.security.defaultLimits.timeoutMs);
116
155
  const ctx = {
117
156
  sessionId: this.sessionId,
118
- scopes: this.scopes,
157
+ scopes,
119
158
  signal: ac.signal,
120
159
  limits: this.security.defaultLimits,
121
160
  logger: this.logger,
122
161
  };
162
+ // Race the handler against the timer: a handler that ignores `signal` (a sync driver,
163
+ // a hung socket) must still not hold the call open past the limit.
164
+ const timedOut = new Promise((_, reject) => ac.signal.addEventListener('abort', () => reject(new TimeoutError(`Tool ${def.name} exceeded ${ctx.limits.timeoutMs}ms`))));
123
165
  try {
124
166
  const parsed = def.inputSchema.parse(rawArgs);
125
- result = await def.handler(parsed, ctx);
167
+ result = await Promise.race([def.handler(parsed, ctx), timedOut]);
126
168
  }
127
169
  finally {
128
170
  clearTimeout(timer);
129
- if (ac.signal.aborted && !result) {
130
- throw new TimeoutError(`Tool ${def.name} exceeded ${ctx.limits.timeoutMs}ms`);
131
- }
132
171
  }
133
172
  // 4. redaction
134
173
  const redacted = this.security.hooks.redact(def.name, result);
@@ -147,6 +186,7 @@ export class McpolyglotServer {
147
186
  catch (err) {
148
187
  const e = err;
149
188
  errorEntry = { code: e.code ?? 'internal_error', message: e.message };
189
+ decision = err instanceof McpolyglotError && isPolicyRejection(err.code) ? 'deny' : 'error';
150
190
  // Policy rejections (scope, read-only, rate limit, timeout) are tool errors the model
151
191
  // can read and correct, not protocol failures.
152
192
  if (err instanceof McpolyglotError && isPolicyRejection(err.code)) {
@@ -159,12 +199,16 @@ export class McpolyglotServer {
159
199
  throw err;
160
200
  }
161
201
  finally {
202
+ release?.();
162
203
  await this.security.hooks.audit({
163
204
  ts: new Date().toISOString(),
164
205
  sessionId: this.sessionId,
206
+ ...(agentId ? { agentId } : {}),
165
207
  tool: def.name,
208
+ decision,
209
+ ...(errorEntry ? { reason: errorEntry.message } : {}),
166
210
  argsHash,
167
- scopes: Array.from(this.scopes),
211
+ scopes: Array.from(scopes),
168
212
  durationMs: Date.now() - started,
169
213
  rows: result?.metadata?.rows,
170
214
  truncated: result?.metadata?.truncated,
@@ -202,4 +246,10 @@ function makeNoopLogger() {
202
246
  error: () => { },
203
247
  };
204
248
  }
249
+ function registerTool(map, tool) {
250
+ if (map.has(tool.name)) {
251
+ throw new McpolyglotError('tool.duplicate', `Duplicate tool name: ${tool.name}`);
252
+ }
253
+ map.set(tool.name, tool);
254
+ }
205
255
  //# sourceMappingURL=server.js.map