@mcpolyglot/core 0.1.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__/oauth.test.d.ts +2 -0
- package/dist/__tests__/oauth.test.d.ts.map +1 -0
- package/dist/__tests__/oauth.test.js +158 -0
- package/dist/__tests__/oauth.test.js.map +1 -0
- 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.d.ts +2 -0
- package/dist/__tests__/streamable-http.test.d.ts.map +1 -0
- package/dist/__tests__/streamable-http.test.js +105 -0
- package/dist/__tests__/streamable-http.test.js.map +1 -0
- 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 +91 -36
- package/dist/server.js.map +1 -1
- package/dist/tool.d.ts +12 -3
- 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/oauth.d.ts +56 -0
- package/dist/transports/oauth.d.ts.map +1 -0
- package/dist/transports/oauth.js +65 -0
- package/dist/transports/oauth.js.map +1 -0
- 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 +52 -4
- package/dist/transports/streamable-http.d.ts.map +1 -1
- package/dist/transports/streamable-http.js +136 -26
- package/dist/transports/streamable-http.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { exportJWK, generateKeyPair, SignJWT } from 'jose';
|
|
4
|
+
import { createOAuthVerifier } from '../transports/oauth.js';
|
|
5
|
+
async function makeKey(kid) {
|
|
6
|
+
const { publicKey, privateKey } = await generateKeyPair('RS256');
|
|
7
|
+
const publicJwk = await exportJWK(publicKey);
|
|
8
|
+
publicJwk.kid = kid;
|
|
9
|
+
publicJwk.alg = 'RS256';
|
|
10
|
+
publicJwk.use = 'sig';
|
|
11
|
+
return { privateKey, publicJwk, kid };
|
|
12
|
+
}
|
|
13
|
+
async function sign(key, claims, opts) {
|
|
14
|
+
const jwt = new SignJWT(claims)
|
|
15
|
+
.setProtectedHeader({ alg: 'RS256', kid: key.kid })
|
|
16
|
+
.setIssuedAt()
|
|
17
|
+
.setIssuer(opts.issuer)
|
|
18
|
+
.setAudience(opts.audience);
|
|
19
|
+
if (opts.expiresIn)
|
|
20
|
+
jwt.setExpirationTime(opts.expiresIn);
|
|
21
|
+
return jwt.sign(key.privateKey);
|
|
22
|
+
}
|
|
23
|
+
describe('createOAuthVerifier', () => {
|
|
24
|
+
let server;
|
|
25
|
+
let port;
|
|
26
|
+
let issuer;
|
|
27
|
+
let audience;
|
|
28
|
+
let key;
|
|
29
|
+
let otherKey;
|
|
30
|
+
let jwks;
|
|
31
|
+
beforeAll(async () => {
|
|
32
|
+
key = await makeKey('test-key-1');
|
|
33
|
+
otherKey = await makeKey('test-key-2');
|
|
34
|
+
jwks = { keys: [key.publicJwk] };
|
|
35
|
+
server = createServer((req, res) => {
|
|
36
|
+
if (req.url === '/jwks.json') {
|
|
37
|
+
res.statusCode = 200;
|
|
38
|
+
res.setHeader('content-type', 'application/json');
|
|
39
|
+
res.end(JSON.stringify(jwks));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
res.statusCode = 404;
|
|
43
|
+
res.end();
|
|
44
|
+
});
|
|
45
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', () => resolve()));
|
|
46
|
+
port = server.address().port;
|
|
47
|
+
issuer = `http://127.0.0.1:${port}`;
|
|
48
|
+
audience = 'mcpolyglot-test';
|
|
49
|
+
});
|
|
50
|
+
afterAll(async () => {
|
|
51
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
52
|
+
});
|
|
53
|
+
it('accepts a well-formed token', async () => {
|
|
54
|
+
const verifier = createOAuthVerifier({
|
|
55
|
+
issuer,
|
|
56
|
+
audience,
|
|
57
|
+
jwksUri: `${issuer}/jwks.json`,
|
|
58
|
+
});
|
|
59
|
+
const token = await sign(key, { sub: 'alice' }, { issuer, audience, expiresIn: '5m' });
|
|
60
|
+
const result = await verifier.verify(token);
|
|
61
|
+
expect(result.ok).toBe(true);
|
|
62
|
+
if (result.ok) {
|
|
63
|
+
expect(result.sub).toBe('alice');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
it('rejects a token with the wrong issuer', async () => {
|
|
67
|
+
const verifier = createOAuthVerifier({
|
|
68
|
+
issuer,
|
|
69
|
+
audience,
|
|
70
|
+
jwksUri: `${issuer}/jwks.json`,
|
|
71
|
+
});
|
|
72
|
+
const token = await sign(key, { sub: 'alice' }, { issuer: 'https://evil.example', audience, expiresIn: '5m' });
|
|
73
|
+
const result = await verifier.verify(token);
|
|
74
|
+
expect(result).toEqual({ ok: false, reason: 'claim_validation_failed' });
|
|
75
|
+
});
|
|
76
|
+
it('rejects a token with the wrong audience', async () => {
|
|
77
|
+
const verifier = createOAuthVerifier({
|
|
78
|
+
issuer,
|
|
79
|
+
audience,
|
|
80
|
+
jwksUri: `${issuer}/jwks.json`,
|
|
81
|
+
});
|
|
82
|
+
const token = await sign(key, { sub: 'alice' }, { issuer, audience: 'someone-else', expiresIn: '5m' });
|
|
83
|
+
const result = await verifier.verify(token);
|
|
84
|
+
expect(result).toEqual({ ok: false, reason: 'claim_validation_failed' });
|
|
85
|
+
});
|
|
86
|
+
it('rejects an expired token', async () => {
|
|
87
|
+
const verifier = createOAuthVerifier({
|
|
88
|
+
issuer,
|
|
89
|
+
audience,
|
|
90
|
+
jwksUri: `${issuer}/jwks.json`,
|
|
91
|
+
// No clock tolerance, so a token in the past fails immediately.
|
|
92
|
+
clockToleranceSeconds: 0,
|
|
93
|
+
});
|
|
94
|
+
// jose accepts negative expiresIn / past timestamps via setExpirationTime.
|
|
95
|
+
const jwt = new SignJWT({ sub: 'alice' })
|
|
96
|
+
.setProtectedHeader({ alg: 'RS256', kid: key.kid })
|
|
97
|
+
.setIssuedAt(Math.floor(Date.now() / 1000) - 600)
|
|
98
|
+
.setIssuer(issuer)
|
|
99
|
+
.setAudience(audience)
|
|
100
|
+
.setExpirationTime(Math.floor(Date.now() / 1000) - 60);
|
|
101
|
+
const token = await jwt.sign(key.privateKey);
|
|
102
|
+
const result = await verifier.verify(token);
|
|
103
|
+
expect(result).toEqual({ ok: false, reason: 'token_expired' });
|
|
104
|
+
});
|
|
105
|
+
it('rejects a token signed by an unknown key', async () => {
|
|
106
|
+
const verifier = createOAuthVerifier({
|
|
107
|
+
issuer,
|
|
108
|
+
audience,
|
|
109
|
+
jwksUri: `${issuer}/jwks.json`,
|
|
110
|
+
});
|
|
111
|
+
// Signed by a key whose JWK is not in the JWKS.
|
|
112
|
+
const token = await sign(otherKey, { sub: 'alice' }, { issuer, audience, expiresIn: '5m' });
|
|
113
|
+
const result = await verifier.verify(token);
|
|
114
|
+
expect(result.ok).toBe(false);
|
|
115
|
+
if (!result.ok) {
|
|
116
|
+
// jose can surface this as either no matching key or signature failure
|
|
117
|
+
// depending on whether the kid matches; both are acceptable rejections.
|
|
118
|
+
expect(['unknown_key', 'signature_invalid']).toContain(result.reason);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
it('rejects garbage as `invalid_token`', async () => {
|
|
122
|
+
const verifier = createOAuthVerifier({
|
|
123
|
+
issuer,
|
|
124
|
+
audience,
|
|
125
|
+
jwksUri: `${issuer}/jwks.json`,
|
|
126
|
+
});
|
|
127
|
+
const result = await verifier.verify('not.a.jwt');
|
|
128
|
+
expect(result.ok).toBe(false);
|
|
129
|
+
if (!result.ok)
|
|
130
|
+
expect(result.reason).toBe('invalid_token');
|
|
131
|
+
});
|
|
132
|
+
it('defaults the JWKS URI to issuer/.well-known/jwks.json', async () => {
|
|
133
|
+
// Hot-swap the route on the test server to serve the default path.
|
|
134
|
+
const altServer = createServer((req, res) => {
|
|
135
|
+
if (req.url === '/.well-known/jwks.json') {
|
|
136
|
+
res.statusCode = 200;
|
|
137
|
+
res.setHeader('content-type', 'application/json');
|
|
138
|
+
res.end(JSON.stringify(jwks));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
res.statusCode = 404;
|
|
142
|
+
res.end();
|
|
143
|
+
});
|
|
144
|
+
await new Promise((resolve) => altServer.listen(0, '127.0.0.1', () => resolve()));
|
|
145
|
+
const altPort = altServer.address().port;
|
|
146
|
+
const altIssuer = `http://127.0.0.1:${altPort}`;
|
|
147
|
+
try {
|
|
148
|
+
const verifier = createOAuthVerifier({ issuer: altIssuer, audience });
|
|
149
|
+
const token = await sign(key, { sub: 'alice' }, { issuer: altIssuer, audience, expiresIn: '5m' });
|
|
150
|
+
const result = await verifier.verify(token);
|
|
151
|
+
expect(result.ok).toBe(true);
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
await new Promise((resolve) => altServer.close(() => resolve()));
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
//# sourceMappingURL=oauth.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.test.js","sourceRoot":"","sources":["../../src/__tests__/oauth.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACnE,OAAO,EAAE,YAAY,EAA6B,MAAM,WAAW,CAAC;AAEpE,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAY,MAAM,MAAM,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAQ7D,KAAK,UAAU,OAAO,CAAC,GAAW;IAChC,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC;IACjE,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7C,SAAS,CAAC,GAAG,GAAG,GAAG,CAAC;IACpB,SAAS,CAAC,GAAG,GAAG,OAAO,CAAC;IACxB,SAAS,CAAC,GAAG,GAAG,KAAK,CAAC;IACtB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;AACxC,CAAC;AAED,KAAK,UAAU,IAAI,CACjB,GAAY,EACZ,MAA+B,EAC/B,IAA8D;IAE9D,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC;SAC5B,kBAAkB,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;SAClD,WAAW,EAAE;SACb,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;SACtB,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9B,IAAI,IAAI,CAAC,SAAS;QAAE,GAAG,CAAC,iBAAiB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1D,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;AAClC,CAAC;AAED,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,IAAI,MAAkB,CAAC;IACvB,IAAI,IAAY,CAAC;IACjB,IAAI,MAAc,CAAC;IACnB,IAAI,QAAgB,CAAC;IACrB,IAAI,GAAY,CAAC;IACjB,IAAI,QAAiB,CAAC;IACtB,IAAI,IAAqB,CAAC;IAE1B,SAAS,CAAC,KAAK,IAAI,EAAE;QACnB,GAAG,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;QAClC,QAAQ,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;QACvC,IAAI,GAAG,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;QAEjC,MAAM,GAAG,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACjC,IAAI,GAAG,CAAC,GAAG,KAAK,YAAY,EAAE,CAAC;gBAC7B,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;gBACrB,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;gBAClD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC9B,OAAO;YACT,CAAC;YACD,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;YACrB,GAAG,CAAC,GAAG,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACrF,IAAI,GAAI,MAAM,CAAC,OAAO,EAAkB,CAAC,IAAI,CAAC;QAC9C,MAAM,GAAG,oBAAoB,IAAI,EAAE,CAAC;QACpC,QAAQ,GAAG,iBAAiB,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,KAAK,IAAI,EAAE;QAClB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC3C,MAAM,QAAQ,GAAG,mBAAmB,CAAC;YACnC,MAAM;YACN,QAAQ;YACR,OAAO,EAAE,GAAG,MAAM,YAAY;SAC/B,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvF,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;YACd,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,QAAQ,GAAG,mBAAmB,CAAC;YACnC,MAAM;YACN,QAAQ;YACR,OAAO,EAAE,GAAG,MAAM,YAAY;SAC/B,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,IAAI,CACtB,GAAG,EACH,EAAE,GAAG,EAAE,OAAO,EAAE,EAChB,EAAE,MAAM,EAAE,sBAAsB,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,CAC9D,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,QAAQ,GAAG,mBAAmB,CAAC;YACnC,MAAM;YACN,QAAQ;YACR,OAAO,EAAE,GAAG,MAAM,YAAY;SAC/B,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,IAAI,CACtB,GAAG,EACH,EAAE,GAAG,EAAE,OAAO,EAAE,EAChB,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,IAAI,EAAE,CACtD,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE;QACxC,MAAM,QAAQ,GAAG,mBAAmB,CAAC;YACnC,MAAM;YACN,QAAQ;YACR,OAAO,EAAE,GAAG,MAAM,YAAY;YAC9B,gEAAgE;YAChE,qBAAqB,EAAE,CAAC;SACzB,CAAC,CAAC;QACH,2EAA2E;QAC3E,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;aACtC,kBAAkB,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;aAClD,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC;aAChD,SAAS,CAAC,MAAM,CAAC;aACjB,WAAW,CAAC,QAAQ,CAAC;aACrB,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;QACxD,MAAM,QAAQ,GAAG,mBAAmB,CAAC;YACnC,MAAM;YACN,QAAQ;YACR,OAAO,EAAE,GAAG,MAAM,YAAY;SAC/B,CAAC,CAAC;QACH,gDAAgD;QAChD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5F,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,uEAAuE;YACvE,wEAAwE;YACxE,MAAM,CAAC,CAAC,aAAa,EAAE,mBAAmB,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,QAAQ,GAAG,mBAAmB,CAAC;YACnC,MAAM;YACN,QAAQ;YACR,OAAO,EAAE,GAAG,MAAM,YAAY;SAC/B,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAClD,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,mEAAmE;QACnE,MAAM,SAAS,GAAG,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YAC1C,IAAI,GAAG,CAAC,GAAG,KAAK,wBAAwB,EAAE,CAAC;gBACzC,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;gBACrB,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;gBAClD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC9B,OAAO;YACT,CAAC;YACD,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;YACrB,GAAG,CAAC,GAAG,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACxF,MAAM,OAAO,GAAI,SAAS,CAAC,OAAO,EAAkB,CAAC,IAAI,CAAC;QAC1D,MAAM,SAAS,GAAG,oBAAoB,OAAO,EAAE,CAAC;QAEhD,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,mBAAmB,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;YACtE,MAAM,KAAK,GAAG,MAAM,IAAI,CACtB,GAAG,EACH,EAAE,GAAG,EAAE,OAAO,EAAE,EAChB,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,CACjD,CAAC;YACF,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5C,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;gBAAS,CAAC;YACT,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACzE,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -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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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streamable-http.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/streamable-http.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { hashToken, matchAgent, StreamableHttpTransport } from '../transports/streamable-http.js';
|
|
4
|
+
// Silence the JSON banner the transport emits to stderr during tests.
|
|
5
|
+
const silentLogger = { info: () => { }, warn: () => { } };
|
|
6
|
+
async function fetchStatus(url, init) {
|
|
7
|
+
const res = await fetch(url, init);
|
|
8
|
+
const body = await res.text();
|
|
9
|
+
return { status: res.status, body };
|
|
10
|
+
}
|
|
11
|
+
describe('StreamableHttpTransport', () => {
|
|
12
|
+
let transport;
|
|
13
|
+
let mcpServer;
|
|
14
|
+
let baseUrl = '';
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
if (transport)
|
|
17
|
+
await transport.stop();
|
|
18
|
+
transport = undefined;
|
|
19
|
+
mcpServer = undefined;
|
|
20
|
+
baseUrl = '';
|
|
21
|
+
});
|
|
22
|
+
async function boot(opts) {
|
|
23
|
+
mcpServer = new McpServer({ name: 'mcpolyglot-test', version: '0.0.0' }, { capabilities: { tools: {} } });
|
|
24
|
+
transport = new StreamableHttpTransport({
|
|
25
|
+
...opts,
|
|
26
|
+
logger: silentLogger,
|
|
27
|
+
});
|
|
28
|
+
await transport.start(() => mcpServer);
|
|
29
|
+
// We bind on port 0 so the OS picks; recover the real port from the
|
|
30
|
+
// listening socket. Field access is intentional — the public class
|
|
31
|
+
// doesn't expose the address since the CLI already knows it.
|
|
32
|
+
const httpServer = transport
|
|
33
|
+
.httpServer;
|
|
34
|
+
baseUrl = `http://${opts.host}:${httpServer.address().port}`;
|
|
35
|
+
}
|
|
36
|
+
it('serves /healthz without auth', async () => {
|
|
37
|
+
await boot({ host: '127.0.0.1', port: 0, auth: { kind: 'bearer', token: 'shh' } });
|
|
38
|
+
const res = await fetchStatus(`${baseUrl}/healthz`);
|
|
39
|
+
expect(res.status).toBe(200);
|
|
40
|
+
expect(JSON.parse(res.body)).toEqual({ ok: true });
|
|
41
|
+
});
|
|
42
|
+
it('rejects requests without a Bearer header', async () => {
|
|
43
|
+
await boot({ host: '127.0.0.1', port: 0, auth: { kind: 'bearer', token: 'shh' } });
|
|
44
|
+
const res = await fetchStatus(`${baseUrl}/mcp`, { method: 'POST' });
|
|
45
|
+
expect(res.status).toBe(401);
|
|
46
|
+
expect(JSON.parse(res.body).reason).toBe('missing_bearer');
|
|
47
|
+
});
|
|
48
|
+
it('rejects a wrong bearer token', async () => {
|
|
49
|
+
await boot({ host: '127.0.0.1', port: 0, auth: { kind: 'bearer', token: 'shh' } });
|
|
50
|
+
const res = await fetchStatus(`${baseUrl}/mcp`, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: { authorization: 'Bearer not-shh' },
|
|
53
|
+
});
|
|
54
|
+
expect(res.status).toBe(401);
|
|
55
|
+
expect(JSON.parse(res.body).reason).toBe('invalid_token');
|
|
56
|
+
});
|
|
57
|
+
it('exposes the auto-generated bearer token in `bearerToken`', async () => {
|
|
58
|
+
await boot({ host: '127.0.0.1', port: 0 });
|
|
59
|
+
expect(transport.authKind).toBe('bearer');
|
|
60
|
+
expect(typeof transport.bearerToken).toBe('string');
|
|
61
|
+
expect(transport.bearerToken.length).toBeGreaterThan(16);
|
|
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
|
+
});
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=streamable-http.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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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"}
|