@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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/pipeline.test.d.ts +2 -0
- package/dist/__tests__/pipeline.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline.test.js +179 -0
- package/dist/__tests__/pipeline.test.js.map +1 -0
- package/dist/__tests__/streamable-http.test.js +43 -2
- package/dist/__tests__/streamable-http.test.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +41 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +81 -31
- package/dist/server.js.map +1 -1
- package/dist/tool.d.ts.map +1 -1
- package/dist/tool.js.map +1 -1
- package/dist/transport.d.ts +5 -2
- package/dist/transport.d.ts.map +1 -1
- package/dist/transports/stdio.d.ts +1 -1
- package/dist/transports/stdio.d.ts.map +1 -1
- package/dist/transports/stdio.js +2 -2
- package/dist/transports/stdio.js.map +1 -1
- package/dist/transports/streamable-http.d.ts +28 -4
- package/dist/transports/streamable-http.d.ts.map +1 -1
- package/dist/transports/streamable-http.js +59 -13
- package/dist/transports/streamable-http.js.map +1 -1
- package/package.json +1 -1
|
@@ -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":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,2CAA2C,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
-
/**
|
|
15
|
-
|
|
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
|
|
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
|
|
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.
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
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.
|
|
48
|
-
this.
|
|
49
|
-
|
|
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
|
-
|
|
56
|
-
const
|
|
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.
|
|
76
|
+
for (const c of this.allConnectors())
|
|
66
77
|
await c.init({ logger: this.logger });
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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.
|
|
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.
|
|
101
|
+
for (const c of this.allConnectors()) {
|
|
88
102
|
await c.close();
|
|
89
103
|
}
|
|
90
|
-
await this.server.close();
|
|
91
104
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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,
|
|
149
|
+
this.security.hooks.checkScopes(def.name, def.scopes, scopes);
|
|
111
150
|
// 2. rate limit
|
|
112
|
-
await this.security.hooks.checkRateLimit(def.name,
|
|
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
|
|
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(
|
|
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
|