@mcpolyglot/core 0.1.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/LICENSE +21 -0
- package/README.md +31 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/connector.d.ts +136 -0
- package/dist/connector.d.ts.map +1 -0
- package/dist/connector.js +2 -0
- package/dist/connector.js.map +1 -0
- package/dist/errors.d.ts +18 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +35 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +117 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +200 -0
- package/dist/server.js.map +1 -0
- package/dist/tool.d.ts +91 -0
- package/dist/tool.d.ts.map +1 -0
- package/dist/tool.js +11 -0
- package/dist/tool.js.map +1 -0
- package/dist/transport.d.ts +14 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +2 -0
- package/dist/transport.js.map +1 -0
- package/dist/transports/stdio.d.ts +9 -0
- package/dist/transports/stdio.d.ts.map +1 -0
- package/dist/transports/stdio.js +14 -0
- package/dist/transports/stdio.js.map +1 -0
- package/dist/transports/streamable-http.d.ts +28 -0
- package/dist/transports/streamable-http.d.ts.map +1 -0
- package/dist/transports/streamable-http.js +108 -0
- package/dist/transports/streamable-http.js.map +1 -0
- package/package.json +51 -0
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Connector, PerEntityConfig } from './connector.js';
|
|
2
|
+
import type { Transport } from './transport.js';
|
|
3
|
+
import { type Scope, type ToolExecLimits, type ToolResult } from './tool.js';
|
|
4
|
+
/**
|
|
5
|
+
* The non-bypassable pipeline that wraps every tool call. Implementations live in
|
|
6
|
+
* `@mcpolyglot/security`; consumers usually get one via `defaultSecurityHooks()` and don't
|
|
7
|
+
* implement this directly.
|
|
8
|
+
*
|
|
9
|
+
* Every method here corresponds to a numbered phase in `McpolyglotServer.executeTool`.
|
|
10
|
+
*/
|
|
11
|
+
export interface SecurityHooks {
|
|
12
|
+
/** Phase 1 — reject if granted scopes don't cover the tool's required scopes. */
|
|
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>;
|
|
16
|
+
/** Phase 5 — strip secrets, drop denied columns. Returns the new result and the redaction count. */
|
|
17
|
+
redact(toolName: string, result: ToolResult): {
|
|
18
|
+
result: ToolResult;
|
|
19
|
+
redactionsApplied: number;
|
|
20
|
+
};
|
|
21
|
+
/** Phase 6 — hard cap on serialized output bytes. Truncates and flips `metadata.truncated`. */
|
|
22
|
+
enforceSize(toolName: string, result: ToolResult, maxBytes: number): ToolResult;
|
|
23
|
+
/** Phase 7 — wrap the result so the model treats it as data, not instructions. */
|
|
24
|
+
wrapUntrusted(result: ToolResult): ToolResult;
|
|
25
|
+
/** Phase 8 — append one JSONL line. Never receives raw args or result rows. */
|
|
26
|
+
audit(entry: AuditEntry): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
/** Bundle of security primitives `McpolyglotServer` needs. Built by `defaultSecurityHooks()`. */
|
|
29
|
+
export interface SecurityServices {
|
|
30
|
+
hooks: SecurityHooks;
|
|
31
|
+
defaultLimits: ToolExecLimits;
|
|
32
|
+
defaultScopes: readonly Scope[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* One entry in the JSONL audit log. Logged for every call, including failed ones.
|
|
36
|
+
* Args and result rows are deliberately absent — only metadata and a hash of the args.
|
|
37
|
+
*/
|
|
38
|
+
export interface AuditEntry {
|
|
39
|
+
/** ISO-8601 UTC timestamp. */
|
|
40
|
+
ts: string;
|
|
41
|
+
sessionId: string;
|
|
42
|
+
tool: string;
|
|
43
|
+
/** First 16 chars of `sha256(JSON.stringify(args))`. Enough for forensics, not for reconstruction. */
|
|
44
|
+
argsHash: string;
|
|
45
|
+
scopes: Scope[];
|
|
46
|
+
durationMs: number;
|
|
47
|
+
rows?: number;
|
|
48
|
+
truncated?: boolean;
|
|
49
|
+
redactionsApplied?: number;
|
|
50
|
+
error?: {
|
|
51
|
+
code: string;
|
|
52
|
+
message: string;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Constructor options for `McpolyglotServer`. */
|
|
56
|
+
export interface McpolyglotServerOptions {
|
|
57
|
+
name?: string;
|
|
58
|
+
version?: string;
|
|
59
|
+
connectors: Connector[];
|
|
60
|
+
/** Per-source per-entity configuration. Keyed by `connector.id`. */
|
|
61
|
+
perEntity?: Record<string, PerEntityConfig>;
|
|
62
|
+
/** Scopes granted to the calling session. Defaults to `DEFAULT_SCOPES` (read-only). */
|
|
63
|
+
scopes?: readonly Scope[];
|
|
64
|
+
security: SecurityServices;
|
|
65
|
+
logger?: {
|
|
66
|
+
debug: (msg: string, fields?: Record<string, unknown>) => void;
|
|
67
|
+
info: (msg: string, fields?: Record<string, unknown>) => void;
|
|
68
|
+
warn: (msg: string, fields?: Record<string, unknown>) => void;
|
|
69
|
+
error: (msg: string, fields?: Record<string, unknown>) => void;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* MCP server that wires connectors + security hooks + a transport.
|
|
74
|
+
*
|
|
75
|
+
* Every `CallTool` request goes through `executeTool`, which runs the same eight
|
|
76
|
+
* numbered phases (scope → rate-limit → timeout → handler → redact → size cap →
|
|
77
|
+
* wrap → audit) in the same order — connectors cannot opt out.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* import { McpolyglotServer } from '@mcpolyglot/core';
|
|
82
|
+
* import { StdioTransport } from '@mcpolyglot/core/transports/stdio';
|
|
83
|
+
* import { defaultSecurityHooks } from '@mcpolyglot/security';
|
|
84
|
+
*
|
|
85
|
+
* const server = new McpolyglotServer({
|
|
86
|
+
* connectors: [myConnector],
|
|
87
|
+
* security: {
|
|
88
|
+
* hooks: defaultSecurityHooks(),
|
|
89
|
+
* defaultLimits: { rowCap: 200, timeoutMs: 10_000, maxBytes: 256 * 1024 },
|
|
90
|
+
* defaultScopes: ['schema:read', 'tables:read'],
|
|
91
|
+
* },
|
|
92
|
+
* });
|
|
93
|
+
* await server.start(new StdioTransport());
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export declare class McpolyglotServer {
|
|
97
|
+
private readonly server;
|
|
98
|
+
private readonly tools;
|
|
99
|
+
private readonly connectors;
|
|
100
|
+
private readonly perEntity;
|
|
101
|
+
private readonly scopes;
|
|
102
|
+
private readonly security;
|
|
103
|
+
private readonly sessionId;
|
|
104
|
+
private readonly logger;
|
|
105
|
+
private transport?;
|
|
106
|
+
constructor(opts: McpolyglotServerOptions);
|
|
107
|
+
start(transport: Transport): Promise<void>;
|
|
108
|
+
stop(): Promise<void>;
|
|
109
|
+
private registerTool;
|
|
110
|
+
/**
|
|
111
|
+
* Fixed pipeline: scope check → rate limit → timeout → handler →
|
|
112
|
+
* redaction → size cap → untrusted-wrap → audit log → response.
|
|
113
|
+
* Connectors cannot bypass.
|
|
114
|
+
*/
|
|
115
|
+
private executeTool;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EACL,KAAK,KAAK,EAGV,KAAK,cAAc,EACnB,KAAK,UAAU,EAEhB,MAAM,WAAW,CAAC;AAGnB;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,iFAAiF;IACjF,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,KAAK,EAAE,EAAE,OAAO,EAAE,WAAW,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAC7F,uFAAuF;IACvF,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,oGAAoG;IACpG,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG;QAAE,MAAM,EAAE,UAAU,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE,CAAC;IAChG,+FAA+F;IAC/F,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAC;IAChF,kFAAkF;IAClF,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAAC;IAC9C,+EAA+E;IAC/E,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC;AAED,iGAAiG;AACjG,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,aAAa,CAAC;IACrB,aAAa,EAAE,cAAc,CAAC;IAC9B,aAAa,EAAE,SAAS,KAAK,EAAE,CAAC;CACjC;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,8BAA8B;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,sGAAsG;IACtG,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAED,kDAAkD;AAClD,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC5C,uFAAuF;IACvF,MAAM,CAAC,EAAE,SAAS,KAAK,EAAE,CAAC;IAC1B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,MAAM,CAAC,EAAE;QACP,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC/D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC9D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC9D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KAChE,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAC3D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAC5D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgB;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiD;IACxE,OAAO,CAAC,SAAS,CAAC,CAAY;gBAElB,IAAI,EAAE,uBAAuB;IAiCnC,KAAK,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAsB1C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B,OAAO,CAAC,YAAY;IAOpB;;;;OAIG;YACW,WAAW;CAkF1B"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
5
|
+
import { DEFAULT_SCOPES, } from './tool.js';
|
|
6
|
+
import { McpolyglotError, ScopeError, TimeoutError } from './errors.js';
|
|
7
|
+
/**
|
|
8
|
+
* MCP server that wires connectors + security hooks + a transport.
|
|
9
|
+
*
|
|
10
|
+
* Every `CallTool` request goes through `executeTool`, which runs the same eight
|
|
11
|
+
* numbered phases (scope → rate-limit → timeout → handler → redact → size cap →
|
|
12
|
+
* wrap → audit) in the same order — connectors cannot opt out.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { McpolyglotServer } from '@mcpolyglot/core';
|
|
17
|
+
* import { StdioTransport } from '@mcpolyglot/core/transports/stdio';
|
|
18
|
+
* import { defaultSecurityHooks } from '@mcpolyglot/security';
|
|
19
|
+
*
|
|
20
|
+
* const server = new McpolyglotServer({
|
|
21
|
+
* connectors: [myConnector],
|
|
22
|
+
* security: {
|
|
23
|
+
* hooks: defaultSecurityHooks(),
|
|
24
|
+
* defaultLimits: { rowCap: 200, timeoutMs: 10_000, maxBytes: 256 * 1024 },
|
|
25
|
+
* defaultScopes: ['schema:read', 'tables:read'],
|
|
26
|
+
* },
|
|
27
|
+
* });
|
|
28
|
+
* await server.start(new StdioTransport());
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export class McpolyglotServer {
|
|
32
|
+
server;
|
|
33
|
+
tools = new Map();
|
|
34
|
+
connectors;
|
|
35
|
+
perEntity;
|
|
36
|
+
scopes;
|
|
37
|
+
security;
|
|
38
|
+
sessionId = randomUUID();
|
|
39
|
+
logger;
|
|
40
|
+
transport;
|
|
41
|
+
constructor(opts) {
|
|
42
|
+
this.connectors = opts.connectors;
|
|
43
|
+
this.perEntity = opts.perEntity ?? {};
|
|
44
|
+
this.scopes = new Set(opts.scopes ?? DEFAULT_SCOPES);
|
|
45
|
+
this.security = opts.security;
|
|
46
|
+
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) => ({
|
|
50
|
+
name: t.name,
|
|
51
|
+
description: t.description,
|
|
52
|
+
inputSchema: zodToJsonSchema(t.inputSchema, { target: 'jsonSchema7' }),
|
|
53
|
+
})),
|
|
54
|
+
}));
|
|
55
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
56
|
+
const def = this.tools.get(req.params.name);
|
|
57
|
+
if (!def) {
|
|
58
|
+
throw new McpolyglotError('tool.not_found', `Unknown tool: ${req.params.name}`);
|
|
59
|
+
}
|
|
60
|
+
const internal = await this.executeTool(def, req.params.arguments ?? {});
|
|
61
|
+
return toMcpResult(internal);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async start(transport) {
|
|
65
|
+
for (const c of this.connectors) {
|
|
66
|
+
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);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
this.transport = transport;
|
|
78
|
+
await transport.start(this.server);
|
|
79
|
+
this.logger.info('mcpolyglot.started', {
|
|
80
|
+
sessionId: this.sessionId,
|
|
81
|
+
transport: transport.kind,
|
|
82
|
+
tools: this.tools.size,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async stop() {
|
|
86
|
+
await this.transport?.stop();
|
|
87
|
+
for (const c of this.connectors) {
|
|
88
|
+
await c.close();
|
|
89
|
+
}
|
|
90
|
+
await this.server.close();
|
|
91
|
+
}
|
|
92
|
+
registerTool(tool) {
|
|
93
|
+
if (this.tools.has(tool.name)) {
|
|
94
|
+
throw new McpolyglotError('tool.duplicate', `Duplicate tool name: ${tool.name}`);
|
|
95
|
+
}
|
|
96
|
+
this.tools.set(tool.name, tool);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Fixed pipeline: scope check → rate limit → timeout → handler →
|
|
100
|
+
* redaction → size cap → untrusted-wrap → audit log → response.
|
|
101
|
+
* Connectors cannot bypass.
|
|
102
|
+
*/
|
|
103
|
+
async executeTool(def, rawArgs) {
|
|
104
|
+
const started = Date.now();
|
|
105
|
+
const argsHash = hashArgs(rawArgs);
|
|
106
|
+
let result;
|
|
107
|
+
let errorEntry;
|
|
108
|
+
try {
|
|
109
|
+
// 1. scope check
|
|
110
|
+
this.security.hooks.checkScopes(def.name, def.scopes, this.scopes);
|
|
111
|
+
// 2. rate limit
|
|
112
|
+
await this.security.hooks.checkRateLimit(def.name, this.sessionId);
|
|
113
|
+
// 3. timeout
|
|
114
|
+
const ac = new AbortController();
|
|
115
|
+
const timer = setTimeout(() => ac.abort(), this.security.defaultLimits.timeoutMs);
|
|
116
|
+
const ctx = {
|
|
117
|
+
sessionId: this.sessionId,
|
|
118
|
+
scopes: this.scopes,
|
|
119
|
+
signal: ac.signal,
|
|
120
|
+
limits: this.security.defaultLimits,
|
|
121
|
+
logger: this.logger,
|
|
122
|
+
};
|
|
123
|
+
try {
|
|
124
|
+
const parsed = def.inputSchema.parse(rawArgs);
|
|
125
|
+
result = await def.handler(parsed, ctx);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
clearTimeout(timer);
|
|
129
|
+
if (ac.signal.aborted && !result) {
|
|
130
|
+
throw new TimeoutError(`Tool ${def.name} exceeded ${ctx.limits.timeoutMs}ms`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// 4. redaction
|
|
134
|
+
const redacted = this.security.hooks.redact(def.name, result);
|
|
135
|
+
result = redacted.result;
|
|
136
|
+
// 5. size cap
|
|
137
|
+
result = this.security.hooks.enforceSize(def.name, result, this.security.defaultLimits.maxBytes);
|
|
138
|
+
// 6. untrusted-data wrapper
|
|
139
|
+
result = this.security.hooks.wrapUntrusted(result);
|
|
140
|
+
result.metadata = {
|
|
141
|
+
...result.metadata,
|
|
142
|
+
durationMs: Date.now() - started,
|
|
143
|
+
redactionsApplied: redacted.redactionsApplied,
|
|
144
|
+
};
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
const e = err;
|
|
149
|
+
errorEntry = { code: e.code ?? 'internal_error', message: e.message };
|
|
150
|
+
if (err instanceof ScopeError || err instanceof TimeoutError) {
|
|
151
|
+
return {
|
|
152
|
+
content: [{ type: 'text', text: e.message }],
|
|
153
|
+
isError: true,
|
|
154
|
+
metadata: { durationMs: Date.now() - started },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
throw err;
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
await this.security.hooks.audit({
|
|
161
|
+
ts: new Date().toISOString(),
|
|
162
|
+
sessionId: this.sessionId,
|
|
163
|
+
tool: def.name,
|
|
164
|
+
argsHash,
|
|
165
|
+
scopes: Array.from(this.scopes),
|
|
166
|
+
durationMs: Date.now() - started,
|
|
167
|
+
rows: result?.metadata?.rows,
|
|
168
|
+
truncated: result?.metadata?.truncated,
|
|
169
|
+
redactionsApplied: result?.metadata?.redactionsApplied,
|
|
170
|
+
error: errorEntry,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function hashArgs(args) {
|
|
176
|
+
return createHash('sha256')
|
|
177
|
+
.update(JSON.stringify(args ?? {}))
|
|
178
|
+
.digest('hex')
|
|
179
|
+
.slice(0, 16);
|
|
180
|
+
}
|
|
181
|
+
/** Translate mcpolyglot's internal ToolResult into the MCP CallToolResult shape. */
|
|
182
|
+
function toMcpResult(r) {
|
|
183
|
+
return {
|
|
184
|
+
content: r.content.map((b) => {
|
|
185
|
+
if (b.type === 'text')
|
|
186
|
+
return { type: 'text', text: b.text ?? '' };
|
|
187
|
+
return { type: 'text', text: JSON.stringify(b.data ?? null, null, 2) };
|
|
188
|
+
}),
|
|
189
|
+
...(r.isError ? { isError: true } : {}),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function makeNoopLogger() {
|
|
193
|
+
return {
|
|
194
|
+
debug: () => { },
|
|
195
|
+
info: () => { },
|
|
196
|
+
warn: () => { },
|
|
197
|
+
error: () => { },
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/tool.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { ZodTypeAny, infer as ZodInfer } from 'zod';
|
|
2
|
+
/** Every scope mcpolyglot understands. New scopes must be added here so the Zod config schema accepts them. */
|
|
3
|
+
export declare const ALL_SCOPES: readonly ["schema:read", "tables:read", "tables:write", "query:raw", "http:call"];
|
|
4
|
+
/** A capability a tool requires from the calling session. Checked by `ScopeGuard`. */
|
|
5
|
+
export type Scope = (typeof ALL_SCOPES)[number];
|
|
6
|
+
/** Scopes granted to a session by default. Excludes anything write-shaped. */
|
|
7
|
+
export declare const DEFAULT_SCOPES: readonly Scope[];
|
|
8
|
+
/** One block in a `ToolResult.content`. Either pre-rendered text or structured JSON. */
|
|
9
|
+
export interface ContentBlock {
|
|
10
|
+
type: 'text' | 'json';
|
|
11
|
+
text?: string;
|
|
12
|
+
data?: unknown;
|
|
13
|
+
}
|
|
14
|
+
/** What a tool handler returns. Goes through redact → size-cap → wrap before reaching the model. */
|
|
15
|
+
export interface ToolResult {
|
|
16
|
+
content: ContentBlock[];
|
|
17
|
+
/** Set when the handler intentionally surfaces an error (e.g., scope failure rendered as a tool result). */
|
|
18
|
+
isError?: boolean;
|
|
19
|
+
metadata?: {
|
|
20
|
+
rows?: number;
|
|
21
|
+
/** `true` if the size cap or row cap had to truncate the result. */
|
|
22
|
+
truncated?: boolean;
|
|
23
|
+
/** Filled in by the pipeline; handlers don't need to set this. */
|
|
24
|
+
durationMs?: number;
|
|
25
|
+
/** Filled in by the pipeline; handlers don't need to set this. */
|
|
26
|
+
redactionsApplied?: number;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Per-call execution limits. Pulled from config; can be tightened per-source. */
|
|
30
|
+
export interface ToolExecLimits {
|
|
31
|
+
/** Max rows a SQL/Mongo handler may return before truncation. */
|
|
32
|
+
rowCap: number;
|
|
33
|
+
/** Wall-clock budget for the handler. Enforced via `AbortSignal`. */
|
|
34
|
+
timeoutMs: number;
|
|
35
|
+
/** Hard cap on the serialized output size, in bytes. */
|
|
36
|
+
maxBytes: number;
|
|
37
|
+
}
|
|
38
|
+
/** Runtime context handed to a tool handler on every call. */
|
|
39
|
+
export interface ToolExecCtx {
|
|
40
|
+
/** Stable id for the calling session. Used by the rate limiter and audit log. */
|
|
41
|
+
sessionId: string;
|
|
42
|
+
/** Scopes granted to this session. The pipeline already checked the tool's `scopes` against these. */
|
|
43
|
+
scopes: Set<Scope>;
|
|
44
|
+
/** Aborted when `limits.timeoutMs` elapses. Pass to driver-level cancel APIs where supported. */
|
|
45
|
+
signal: AbortSignal;
|
|
46
|
+
limits: ToolExecLimits;
|
|
47
|
+
logger: {
|
|
48
|
+
debug: (msg: string, fields?: Record<string, unknown>) => void;
|
|
49
|
+
info: (msg: string, fields?: Record<string, unknown>) => void;
|
|
50
|
+
warn: (msg: string, fields?: Record<string, unknown>) => void;
|
|
51
|
+
error: (msg: string, fields?: Record<string, unknown>) => void;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Handler signature for a tool. Args are pre-validated against `ToolDefinition.inputSchema`,
|
|
56
|
+
* so the function receives a parsed, typed value — no defensive validation needed.
|
|
57
|
+
*/
|
|
58
|
+
export type ToolHandler<S extends ZodTypeAny = ZodTypeAny> = (args: ZodInfer<S>, ctx: ToolExecCtx) => Promise<ToolResult>;
|
|
59
|
+
/**
|
|
60
|
+
* The contract every connector returns from `listPrimitiveTools()` /
|
|
61
|
+
* `generatePerEntityTools()`. The `handler` is the only point where the connector
|
|
62
|
+
* touches the data source — everything else (auth, redaction, audit) is the pipeline's job.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* import { z } from 'zod';
|
|
67
|
+
*
|
|
68
|
+
* const tool: ToolDefinition = {
|
|
69
|
+
* name: 'pg.main.describe_table',
|
|
70
|
+
* description: 'Describe one Postgres table.',
|
|
71
|
+
* inputSchema: z.object({ name: z.string() }).strict(),
|
|
72
|
+
* scopes: ['schema:read'],
|
|
73
|
+
* readOnly: true,
|
|
74
|
+
* handler: async ({ name }, ctx) => ({ content: [{ type: 'json', data: { name } }] }),
|
|
75
|
+
* };
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export interface ToolDefinition<S extends ZodTypeAny = ZodTypeAny> {
|
|
79
|
+
/** Globally-unique tool name. By convention: `<connector.id>.<verb>`. */
|
|
80
|
+
name: string;
|
|
81
|
+
/** Human-readable description shown to the model in `tools/list`. */
|
|
82
|
+
description: string;
|
|
83
|
+
/** Zod schema for the args. Used both for validation and to render JSON Schema for MCP clients. */
|
|
84
|
+
inputSchema: S;
|
|
85
|
+
/** Scopes the calling session must have. Enforced before the handler runs. */
|
|
86
|
+
scopes: readonly Scope[];
|
|
87
|
+
/** `true` if the handler never mutates the source. Hosts may use this for UI hints. */
|
|
88
|
+
readOnly: boolean;
|
|
89
|
+
handler: ToolHandler<S>;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,QAAQ,EAAE,MAAM,KAAK,CAAC;AAEzD,+GAA+G;AAC/G,eAAO,MAAM,UAAU,mFAMb,CAAC;AAEX,sFAAsF;AACtF,MAAM,MAAM,KAAK,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhD,8EAA8E;AAC9E,eAAO,MAAM,cAAc,EAAE,SAAS,KAAK,EAAmC,CAAC;AAE/E,wFAAwF;AACxF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,oGAAoG;AACpG,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,4GAA4G;IAC5G,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,oEAAoE;QACpE,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,kEAAkE;QAClE,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,kEAAkE;QAClE,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC5B,CAAC;CACH;AAED,kFAAkF;AAClF,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,8DAA8D;AAC9D,MAAM,WAAW,WAAW;IAC1B,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAC;IAClB,sGAAsG;IACtG,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;IACnB,iGAAiG;IACjG,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE;QACN,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC/D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC9D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC9D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KAChE,CAAC;CACH;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IAAI,CAC3D,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EACjB,GAAG,EAAE,WAAW,KACb,OAAO,CAAC,UAAU,CAAC,CAAC;AAEzB;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU;IAC/D,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,mGAAmG;IACnG,WAAW,EAAE,CAAC,CAAC;IACf,8EAA8E;IAC9E,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IACzB,uFAAuF;IACvF,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;CACzB"}
|
package/dist/tool.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Every scope mcpolyglot understands. New scopes must be added here so the Zod config schema accepts them. */
|
|
2
|
+
export const ALL_SCOPES = [
|
|
3
|
+
'schema:read',
|
|
4
|
+
'tables:read',
|
|
5
|
+
'tables:write',
|
|
6
|
+
'query:raw',
|
|
7
|
+
'http:call',
|
|
8
|
+
];
|
|
9
|
+
/** Scopes granted to a session by default. Excludes anything write-shaped. */
|
|
10
|
+
export const DEFAULT_SCOPES = ['schema:read', 'tables:read'];
|
|
11
|
+
//# sourceMappingURL=tool.js.map
|
package/dist/tool.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool.js","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"AAEA,+GAA+G;AAC/G,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,aAAa;IACb,aAAa;IACb,cAAc;IACd,WAAW;IACX,WAAW;CACH,CAAC;AAKX,8EAA8E;AAC9E,MAAM,CAAC,MAAM,cAAc,GAAqB,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Carries an `McpolyglotServer`'s JSON-RPC traffic. Two implementations ship today —
|
|
4
|
+
* `StdioTransport` (Claude Desktop / Cursor / Claude Code) and
|
|
5
|
+
* `StreamableHttpTransport` (long-lived HTTP service with bearer auth).
|
|
6
|
+
*/
|
|
7
|
+
export interface Transport {
|
|
8
|
+
readonly kind: 'stdio' | 'http';
|
|
9
|
+
/** Wire the transport to the MCP server and start accepting traffic. */
|
|
10
|
+
start(server: Server): Promise<void>;
|
|
11
|
+
/** Stop accepting traffic and close any underlying sockets. Must be idempotent. */
|
|
12
|
+
stop(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAExE;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IAChC,wEAAwE;IACxE,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,mFAAmF;IACnF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import type { Transport } from '../transport.js';
|
|
3
|
+
export declare class StdioTransport implements Transport {
|
|
4
|
+
readonly kind: "stdio";
|
|
5
|
+
private inner?;
|
|
6
|
+
start(server: Server): Promise<void>;
|
|
7
|
+
stop(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=stdio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/transports/stdio.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,qBAAa,cAAe,YAAW,SAAS;IAC9C,QAAQ,CAAC,IAAI,EAAG,OAAO,CAAU;IACjC,OAAO,CAAC,KAAK,CAAC,CAAuB;IAE/B,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKpC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAI5B"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
2
|
+
export class StdioTransport {
|
|
3
|
+
kind = 'stdio';
|
|
4
|
+
inner;
|
|
5
|
+
async start(server) {
|
|
6
|
+
this.inner = new StdioServerTransport();
|
|
7
|
+
await server.connect(this.inner);
|
|
8
|
+
}
|
|
9
|
+
async stop() {
|
|
10
|
+
await this.inner?.close();
|
|
11
|
+
this.inner = undefined;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/transports/stdio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAIjF,MAAM,OAAO,cAAc;IAChB,IAAI,GAAG,OAAgB,CAAC;IACzB,KAAK,CAAwB;IAErC,KAAK,CAAC,KAAK,CAAC,MAAc;QACxB,IAAI,CAAC,KAAK,GAAG,IAAI,oBAAoB,EAAE,CAAC;QACxC,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;IACzB,CAAC;CACF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import type { Transport } from '../transport.js';
|
|
3
|
+
export interface StreamableHttpTransportOptions {
|
|
4
|
+
host: string;
|
|
5
|
+
port: number;
|
|
6
|
+
/** Bearer token required on the Authorization header. Auto-generated if omitted. */
|
|
7
|
+
bearerToken?: string;
|
|
8
|
+
/** Where to log server startup banner / auth-failure events. Stderr by default. */
|
|
9
|
+
logger?: {
|
|
10
|
+
info: (msg: string, fields?: Record<string, unknown>) => void;
|
|
11
|
+
warn: (msg: string, fields?: Record<string, unknown>) => void;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export declare class StreamableHttpTransport implements Transport {
|
|
15
|
+
readonly kind: "http";
|
|
16
|
+
readonly bearerToken: string;
|
|
17
|
+
private readonly host;
|
|
18
|
+
private readonly port;
|
|
19
|
+
private readonly logger;
|
|
20
|
+
private inner?;
|
|
21
|
+
private httpServer?;
|
|
22
|
+
constructor(opts: StreamableHttpTransportOptions);
|
|
23
|
+
start(server: McpServer): Promise<void>;
|
|
24
|
+
stop(): Promise<void>;
|
|
25
|
+
private handle;
|
|
26
|
+
private checkAuth;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=streamable-http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streamable-http.d.ts","sourceRoot":"","sources":["../../src/transports/streamable-http.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,2CAA2C,CAAC;AACrF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,WAAW,8BAA8B;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,oFAAoF;IACpF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,MAAM,CAAC,EAAE;QACP,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC9D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KAC/D,CAAC;CACH;AAED,qBAAa,uBAAwB,YAAW,SAAS;IACvD,QAAQ,CAAC,IAAI,EAAG,MAAM,CAAU;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwD;IAC/E,OAAO,CAAC,KAAK,CAAC,CAAgC;IAC9C,OAAO,CAAC,UAAU,CAAC,CAAa;gBAEpB,IAAI,EAAE,8BAA8B;IAY1C,KAAK,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IA0CvC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YASb,MAAM;IAwBpB,OAAO,CAAC,SAAS;CAQlB"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createServer, } from 'node:http';
|
|
2
|
+
import { timingSafeEqual, randomBytes } from 'node:crypto';
|
|
3
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
4
|
+
export class StreamableHttpTransport {
|
|
5
|
+
kind = 'http';
|
|
6
|
+
bearerToken;
|
|
7
|
+
host;
|
|
8
|
+
port;
|
|
9
|
+
logger;
|
|
10
|
+
inner;
|
|
11
|
+
httpServer;
|
|
12
|
+
constructor(opts) {
|
|
13
|
+
this.host = opts.host;
|
|
14
|
+
this.port = opts.port;
|
|
15
|
+
this.bearerToken = opts.bearerToken ?? generateToken();
|
|
16
|
+
this.logger = opts.logger ?? {
|
|
17
|
+
info: (msg, fields) => process.stderr.write(JSON.stringify({ level: 'info', msg, ...(fields ?? {}) }) + '\n'),
|
|
18
|
+
warn: (msg, fields) => process.stderr.write(JSON.stringify({ level: 'warn', msg, ...(fields ?? {}) }) + '\n'),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async start(server) {
|
|
22
|
+
this.inner = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
23
|
+
this.inner.onerror = (err) => {
|
|
24
|
+
this.logger.warn('http.transport.error', { error: err.message });
|
|
25
|
+
};
|
|
26
|
+
await server.connect(this.inner);
|
|
27
|
+
this.httpServer = createServer((req, res) => {
|
|
28
|
+
this.handle(req, res).catch((err) => {
|
|
29
|
+
this.logger.warn('http.handler.error', {
|
|
30
|
+
error: err.message,
|
|
31
|
+
stack: err.stack,
|
|
32
|
+
});
|
|
33
|
+
if (!res.headersSent) {
|
|
34
|
+
res.statusCode = 500;
|
|
35
|
+
res.end();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
await new Promise((resolve, reject) => {
|
|
40
|
+
this.httpServer.once('error', reject);
|
|
41
|
+
this.httpServer.listen(this.port, this.host, () => {
|
|
42
|
+
this.httpServer.off('error', reject);
|
|
43
|
+
resolve();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
this.logger.info('http.listening', {
|
|
47
|
+
host: this.host,
|
|
48
|
+
port: this.port,
|
|
49
|
+
// Tokens are sensitive; emit a fingerprint not the token itself.
|
|
50
|
+
tokenFingerprint: fingerprint(this.bearerToken),
|
|
51
|
+
});
|
|
52
|
+
if (!isLoopback(this.host)) {
|
|
53
|
+
this.logger.warn('http.bound_non_loopback', {
|
|
54
|
+
host: this.host,
|
|
55
|
+
recommendation: 'put behind a reverse proxy with TLS',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async stop() {
|
|
60
|
+
if (this.httpServer) {
|
|
61
|
+
await new Promise((resolve) => this.httpServer.close(() => resolve()));
|
|
62
|
+
this.httpServer = undefined;
|
|
63
|
+
}
|
|
64
|
+
await this.inner?.close();
|
|
65
|
+
this.inner = undefined;
|
|
66
|
+
}
|
|
67
|
+
async handle(req, res) {
|
|
68
|
+
if (req.method === 'GET' && req.url === '/healthz') {
|
|
69
|
+
res.statusCode = 200;
|
|
70
|
+
res.setHeader('content-type', 'application/json');
|
|
71
|
+
res.end(JSON.stringify({ ok: true }));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!this.checkAuth(req)) {
|
|
75
|
+
res.statusCode = 401;
|
|
76
|
+
res.setHeader('WWW-Authenticate', 'Bearer realm="mcpolyglot"');
|
|
77
|
+
res.setHeader('content-type', 'application/json');
|
|
78
|
+
res.end(JSON.stringify({ error: 'unauthorized' }));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!this.inner) {
|
|
82
|
+
res.statusCode = 503;
|
|
83
|
+
res.end();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
await this.inner.handleRequest(req, res);
|
|
87
|
+
}
|
|
88
|
+
checkAuth(req) {
|
|
89
|
+
const header = req.headers.authorization ?? '';
|
|
90
|
+
const expected = `Bearer ${this.bearerToken}`;
|
|
91
|
+
const a = Buffer.from(header);
|
|
92
|
+
const b = Buffer.from(expected);
|
|
93
|
+
if (a.length !== b.length)
|
|
94
|
+
return false;
|
|
95
|
+
return timingSafeEqual(a, b);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function generateToken() {
|
|
99
|
+
return randomBytes(24).toString('base64url');
|
|
100
|
+
}
|
|
101
|
+
function fingerprint(token) {
|
|
102
|
+
// first 4 + last 4 — enough to recognize, not enough to recover
|
|
103
|
+
return token.slice(0, 4) + '…' + token.slice(-4);
|
|
104
|
+
}
|
|
105
|
+
function isLoopback(host) {
|
|
106
|
+
return host === '127.0.0.1' || host === '::1' || host === 'localhost';
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=streamable-http.js.map
|