@synoikia/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/dist/app.d.ts +79 -0
- package/dist/app.js +155 -0
- package/dist/app.js.map +1 -0
- package/dist/approvals/links.d.ts +23 -0
- package/dist/approvals/links.js +49 -0
- package/dist/approvals/links.js.map +1 -0
- package/dist/approvals/service.d.ts +110 -0
- package/dist/approvals/service.js +182 -0
- package/dist/approvals/service.js.map +1 -0
- package/dist/audit-query.d.ts +52 -0
- package/dist/audit-query.js +116 -0
- package/dist/audit-query.js.map +1 -0
- package/dist/audit.d.ts +5 -0
- package/dist/audit.js +8 -0
- package/dist/audit.js.map +1 -0
- package/dist/auth/mcp-auth.d.ts +50 -0
- package/dist/auth/mcp-auth.js +160 -0
- package/dist/auth/mcp-auth.js.map +1 -0
- package/dist/auth/mcp-tokens.d.ts +57 -0
- package/dist/auth/mcp-tokens.js +143 -0
- package/dist/auth/mcp-tokens.js.map +1 -0
- package/dist/auth/oauth.d.ts +188 -0
- package/dist/auth/oauth.js +470 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/oidc.d.ts +110 -0
- package/dist/auth/oidc.js +221 -0
- package/dist/auth/oidc.js.map +1 -0
- package/dist/auth/sessions.d.ts +34 -0
- package/dist/auth/sessions.js +83 -0
- package/dist/auth/sessions.js.map +1 -0
- package/dist/auth/throttle.d.ts +31 -0
- package/dist/auth/throttle.js +52 -0
- package/dist/auth/throttle.js.map +1 -0
- package/dist/auth/tokens.d.ts +7 -0
- package/dist/auth/tokens.js +40 -0
- package/dist/auth/tokens.js.map +1 -0
- package/dist/auth/totp.d.ts +14 -0
- package/dist/auth/totp.js +81 -0
- package/dist/auth/totp.js.map +1 -0
- package/dist/auth/users.d.ts +70 -0
- package/dist/auth/users.js +296 -0
- package/dist/auth/users.js.map +1 -0
- package/dist/catalog/groups.d.ts +102 -0
- package/dist/catalog/groups.js +363 -0
- package/dist/catalog/groups.js.map +1 -0
- package/dist/catalog/registry.d.ts +25 -0
- package/dist/catalog/registry.js +80 -0
- package/dist/catalog/registry.js.map +1 -0
- package/dist/catalog/rules.d.ts +128 -0
- package/dist/catalog/rules.js +178 -0
- package/dist/catalog/rules.js.map +1 -0
- package/dist/catalog/sync.d.ts +29 -0
- package/dist/catalog/sync.js +222 -0
- package/dist/catalog/sync.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +83 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/env.d.ts +40 -0
- package/dist/config/env.js +71 -0
- package/dist/config/env.js.map +1 -0
- package/dist/crypto/index.d.ts +28 -0
- package/dist/crypto/index.js +88 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/crypto/rotate.d.ts +15 -0
- package/dist/crypto/rotate.js +61 -0
- package/dist/crypto/rotate.js.map +1 -0
- package/dist/db/index.d.ts +17 -0
- package/dist/db/index.js +29 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +4389 -0
- package/dist/db/schema.js +372 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/endpoints/slug.d.ts +3 -0
- package/dist/endpoints/slug.js +7 -0
- package/dist/endpoints/slug.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +29 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +43 -0
- package/dist/events.js +16 -0
- package/dist/events.js.map +1 -0
- package/dist/gate/access.d.ts +47 -0
- package/dist/gate/access.js +48 -0
- package/dist/gate/access.js.map +1 -0
- package/dist/gate/attestation.d.ts +23 -0
- package/dist/gate/attestation.js +33 -0
- package/dist/gate/attestation.js.map +1 -0
- package/dist/gate/canonical.d.ts +3 -0
- package/dist/gate/canonical.js +11 -0
- package/dist/gate/canonical.js.map +1 -0
- package/dist/gate/match.d.ts +67 -0
- package/dist/gate/match.js +133 -0
- package/dist/gate/match.js.map +1 -0
- package/dist/gate/pipeline.d.ts +61 -0
- package/dist/gate/pipeline.js +284 -0
- package/dist/gate/pipeline.js.map +1 -0
- package/dist/gate/preapproval.d.ts +24 -0
- package/dist/gate/preapproval.js +48 -0
- package/dist/gate/preapproval.js.map +1 -0
- package/dist/gate/rate-limit.d.ts +35 -0
- package/dist/gate/rate-limit.js +82 -0
- package/dist/gate/rate-limit.js.map +1 -0
- package/dist/gate/redact.d.ts +23 -0
- package/dist/gate/redact.js +87 -0
- package/dist/gate/redact.js.map +1 -0
- package/dist/http/admin/auth.d.ts +33 -0
- package/dist/http/admin/auth.js +338 -0
- package/dist/http/admin/auth.js.map +1 -0
- package/dist/http/admin/instances.d.ts +5 -0
- package/dist/http/admin/instances.js +193 -0
- package/dist/http/admin/instances.js.map +1 -0
- package/dist/http/admin/system.d.ts +5 -0
- package/dist/http/admin/system.js +225 -0
- package/dist/http/admin/system.js.map +1 -0
- package/dist/http/admin-app.d.ts +9 -0
- package/dist/http/admin-app.js +51 -0
- package/dist/http/admin-app.js.map +1 -0
- package/dist/http/common.d.ts +32 -0
- package/dist/http/common.js +80 -0
- package/dist/http/common.js.map +1 -0
- package/dist/http/mcp/approval-routes.d.ts +11 -0
- package/dist/http/mcp/approval-routes.js +117 -0
- package/dist/http/mcp/approval-routes.js.map +1 -0
- package/dist/http/mcp/endpoint.d.ts +32 -0
- package/dist/http/mcp/endpoint.js +293 -0
- package/dist/http/mcp/endpoint.js.map +1 -0
- package/dist/http/mcp/oauth-routes.d.ts +29 -0
- package/dist/http/mcp/oauth-routes.js +465 -0
- package/dist/http/mcp/oauth-routes.js.map +1 -0
- package/dist/http/mcp/pages.d.ts +63 -0
- package/dist/http/mcp/pages.js +200 -0
- package/dist/http/mcp/pages.js.map +1 -0
- package/dist/http/mcp-app.d.ts +7 -0
- package/dist/http/mcp-app.js +30 -0
- package/dist/http/mcp-app.js.map +1 -0
- package/dist/instances/connection.d.ts +37 -0
- package/dist/instances/connection.js +109 -0
- package/dist/instances/connection.js.map +1 -0
- package/dist/instances/manager.d.ts +313 -0
- package/dist/instances/manager.js +600 -0
- package/dist/instances/manager.js.map +1 -0
- package/dist/instances/settings.d.ts +42 -0
- package/dist/instances/settings.js +57 -0
- package/dist/instances/settings.js.map +1 -0
- package/dist/lenient.d.ts +16 -0
- package/dist/lenient.js +51 -0
- package/dist/lenient.js.map +1 -0
- package/dist/lock.d.ts +28 -0
- package/dist/lock.js +77 -0
- package/dist/lock.js.map +1 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +38 -0
- package/dist/main.js.map +1 -0
- package/dist/maintenance.d.ts +10 -0
- package/dist/maintenance.js +48 -0
- package/dist/maintenance.js.map +1 -0
- package/dist/notify/service.d.ts +149 -0
- package/dist/notify/service.js +358 -0
- package/dist/notify/service.js.map +1 -0
- package/dist/plugins/discovery.d.ts +39 -0
- package/dist/plugins/discovery.js +157 -0
- package/dist/plugins/discovery.js.map +1 -0
- package/dist/plugins/minisign.d.ts +25 -0
- package/dist/plugins/minisign.js +49 -0
- package/dist/plugins/minisign.js.map +1 -0
- package/dist/plugins/process.d.ts +55 -0
- package/dist/plugins/process.js +162 -0
- package/dist/plugins/process.js.map +1 -0
- package/dist/plugins/repos.d.ts +198 -0
- package/dist/plugins/repos.js +612 -0
- package/dist/plugins/repos.js.map +1 -0
- package/dist/plugins/supervisor.d.ts +50 -0
- package/dist/plugins/supervisor.js +125 -0
- package/dist/plugins/supervisor.js.map +1 -0
- package/dist/runtime/index.d.ts +28 -0
- package/dist/runtime/index.js +248 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/sandbox/index.d.ts +53 -0
- package/dist/sandbox/index.js +197 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/server.d.ts +16 -0
- package/dist/server.js +36 -0
- package/dist/server.js.map +1 -0
- package/dist/settings.d.ts +43 -0
- package/dist/settings.js +99 -0
- package/dist/settings.js.map +1 -0
- package/dist/testing/index.d.ts +73 -0
- package/dist/testing/index.js +174 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/drizzle/0000_init.sql +334 -0
- package/drizzle/0001_totp_last_step.sql +1 -0
- package/drizzle/0002_access_levels.sql +16 -0
- package/drizzle/0003_drop_exclusion_flags.sql +2 -0
- package/drizzle/0004_grant_instance_ids.sql +1 -0
- package/drizzle/0005_rule_strict_miss.sql +1 -0
- package/drizzle/0006_attestation_waived.sql +1 -0
- package/drizzle/meta/0000_snapshot.json +2318 -0
- package/drizzle/meta/0001_snapshot.json +2325 -0
- package/drizzle/meta/0002_snapshot.json +2348 -0
- package/drizzle/meta/0003_snapshot.json +2332 -0
- package/drizzle/meta/0004_snapshot.json +2339 -0
- package/drizzle/meta/0005_snapshot.json +2346 -0
- package/drizzle/meta/0006_snapshot.json +2354 -0
- package/drizzle/meta/_journal.json +55 -0
- package/package.json +58 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { isIP } from 'node:net';
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { authenticateMcp, publicMcpBase } from '../../auth/mcp-auth.js';
|
|
7
|
+
import { executeCode, searchCode } from '../../runtime/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* `/{slug}` Streamable HTTP endpoints (design §2.2): one McpServer + transport per MCP session,
|
|
10
|
+
* bound to its instance and authenticated principal, exposing exactly `search` and `execute`.
|
|
11
|
+
*/
|
|
12
|
+
const SESSION_IDLE_MS = 30 * 60_000;
|
|
13
|
+
/** Open MCP sessions per authenticated principal; a new one past the cap closes that principal's oldest. */
|
|
14
|
+
export const MAX_SESSIONS_PER_PRINCIPAL = 16;
|
|
15
|
+
const SERVER_VERSION = '0.1.0';
|
|
16
|
+
const jsonRpcError = (code, message) => ({
|
|
17
|
+
jsonrpc: '2.0',
|
|
18
|
+
id: null,
|
|
19
|
+
error: { code, message },
|
|
20
|
+
});
|
|
21
|
+
function toolText(result) {
|
|
22
|
+
if (!result.ok) {
|
|
23
|
+
return {
|
|
24
|
+
isError: true,
|
|
25
|
+
content: [
|
|
26
|
+
{
|
|
27
|
+
type: 'text',
|
|
28
|
+
text: JSON.stringify({
|
|
29
|
+
error: result.error.code,
|
|
30
|
+
message: result.error.message,
|
|
31
|
+
...(result.logs.length ? { logs: result.logs } : {}),
|
|
32
|
+
}, null, 2),
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const body = {
|
|
38
|
+
result: result.value,
|
|
39
|
+
...(result.truncated ? { truncated: true } : {}),
|
|
40
|
+
...(result.logs.length ? { logs: result.logs } : {}),
|
|
41
|
+
};
|
|
42
|
+
return { content: [{ type: 'text', text: JSON.stringify(body, null, 2) }] };
|
|
43
|
+
}
|
|
44
|
+
export function describeSearch(manifest) {
|
|
45
|
+
const ops = manifest.labels.operations.toLowerCase();
|
|
46
|
+
const apis = [
|
|
47
|
+
`catalog.find({ text?, group?, kind?, classification?: 'read'|'write'|'locked', includeDisabled?, limit? }) → ${ops} you can call (with includeDisabled, also the unavailable ones and why)`,
|
|
48
|
+
'catalog.get(key) → one entry with its parameter schema and docs',
|
|
49
|
+
'catalog.groups() → access groups with their level (none/read/ask/write) and counts',
|
|
50
|
+
];
|
|
51
|
+
if (manifest.capabilities.registry)
|
|
52
|
+
apis.push('registry.find({ kind?, text?, parent?, domain?, limit? }) → matching upstream objects (areas, entities, …)');
|
|
53
|
+
if (manifest.capabilities.attestation) {
|
|
54
|
+
apis.push('guides.get(key) → best-practice guide; pass its best_practice_key in the params when calling that operation');
|
|
55
|
+
}
|
|
56
|
+
return [
|
|
57
|
+
`Search the ${manifest.name} ${ops} catalog. \`code\` is the body of an async JavaScript function; return a JSON-serializable value.`,
|
|
58
|
+
'Available:',
|
|
59
|
+
...apis.map((a) => `- ${a}`),
|
|
60
|
+
'Use this before execute to find operation keys and parameters. It never contacts the upstream.',
|
|
61
|
+
].join('\n');
|
|
62
|
+
}
|
|
63
|
+
export function describeExecute(manifest) {
|
|
64
|
+
const ns = manifest.binding.namespace;
|
|
65
|
+
const fns = manifest.binding.functions.map((f) => `${ns}.${f}(…)`).join(', ');
|
|
66
|
+
return [
|
|
67
|
+
`Run code against ${manifest.name}. \`code\` is the body of an async JavaScript function; \`await\` ${fns} and return a JSON-serializable result.`,
|
|
68
|
+
'Reads run immediately. Writes either run straight away or pause until a human approves them on an approval page the client is asked to open (see `approval` in catalog entries); denials and other refusals throw an Error with `err.code` (e.g. OPERATION_DISABLED, PERMISSION_DENIED, UPSTREAM_ERROR) that your code can catch.',
|
|
69
|
+
'Calls run one at a time. There is no network, filesystem or timer access. Secrets in results are redacted.',
|
|
70
|
+
].join('\n');
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* DNS-rebinding defence (MCP transport spec): a browser page can make its own domain resolve to this
|
|
74
|
+
* server, but it can't change the Host it sends, nor the Origin. Host must be a name this server was
|
|
75
|
+
* configured for (PUBLIC_MCP_URL, MCP_ALLOWED_HOSTS) or something that can't be rebound (localhost, an
|
|
76
|
+
* IP literal); an Origin, when present, must be one of those too.
|
|
77
|
+
*/
|
|
78
|
+
export function hostAllowed(config, hostHeader) {
|
|
79
|
+
if (!hostHeader)
|
|
80
|
+
return false;
|
|
81
|
+
let host;
|
|
82
|
+
try {
|
|
83
|
+
host = new URL(`http://${hostHeader}`).hostname.toLowerCase();
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
if (host === 'localhost' || isIP(host.replace(/^\[|\]$/g, '')))
|
|
89
|
+
return true;
|
|
90
|
+
if (config.PUBLIC_MCP_URL && new URL(config.PUBLIC_MCP_URL).hostname.toLowerCase() === host)
|
|
91
|
+
return true;
|
|
92
|
+
return config.MCP_ALLOWED_HOSTS.includes(host);
|
|
93
|
+
}
|
|
94
|
+
export function originAllowed(config, origin) {
|
|
95
|
+
if (!origin)
|
|
96
|
+
return true; // not a browser request
|
|
97
|
+
try {
|
|
98
|
+
return hostAllowed(config, new URL(origin).host);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export class McpEndpoints {
|
|
105
|
+
ctx;
|
|
106
|
+
oauth;
|
|
107
|
+
sessions = new Map();
|
|
108
|
+
sweeper;
|
|
109
|
+
constructor(ctx, oauth) {
|
|
110
|
+
this.ctx = ctx;
|
|
111
|
+
this.oauth = oauth;
|
|
112
|
+
this.sweeper = setInterval(() => this.sweep(), 60_000);
|
|
113
|
+
this.sweeper.unref();
|
|
114
|
+
}
|
|
115
|
+
get sessionCount() {
|
|
116
|
+
return this.sessions.size;
|
|
117
|
+
}
|
|
118
|
+
sweep() {
|
|
119
|
+
const cutoff = Date.now() - SESSION_IDLE_MS;
|
|
120
|
+
for (const [id, s] of this.sessions)
|
|
121
|
+
if (s.lastSeen < cutoff)
|
|
122
|
+
void this.closeSession(id);
|
|
123
|
+
}
|
|
124
|
+
async closeSession(id) {
|
|
125
|
+
const s = this.sessions.get(id);
|
|
126
|
+
if (!s)
|
|
127
|
+
return;
|
|
128
|
+
this.sessions.delete(id);
|
|
129
|
+
s.closed.abort();
|
|
130
|
+
await s.transport.close().catch(() => undefined);
|
|
131
|
+
await s.server.close().catch(() => undefined);
|
|
132
|
+
}
|
|
133
|
+
async closeAll() {
|
|
134
|
+
clearInterval(this.sweeper);
|
|
135
|
+
await Promise.all([...this.sessions.keys()].map((id) => this.closeSession(id)));
|
|
136
|
+
}
|
|
137
|
+
buildServer(instanceId, identity, publicBase, closed) {
|
|
138
|
+
const rt = () => this.ctx.instances.runtime(instanceId);
|
|
139
|
+
const manifest = rt().manifest;
|
|
140
|
+
const server = new McpServer({ name: `synoikia/${rt().slug}`, version: SERVER_VERSION }, {
|
|
141
|
+
instructions: `${manifest.name} via Synoikia. Use search to discover operations, then execute to call them.`,
|
|
142
|
+
});
|
|
143
|
+
// Approval prompts (design §5.3): URL mode sends the human to our approval page; form mode is only
|
|
144
|
+
// used where the endpoint opted in. `elicitation: {}` from older clients means form support.
|
|
145
|
+
const prompts = () => {
|
|
146
|
+
const caps = server.server.getClientCapabilities()?.elicitation;
|
|
147
|
+
if (!caps)
|
|
148
|
+
return undefined;
|
|
149
|
+
const timeout = rt().settings.approvalTimeoutMs;
|
|
150
|
+
const out = {};
|
|
151
|
+
if (caps.url) {
|
|
152
|
+
out.url = async (req) => {
|
|
153
|
+
const res = await server.server.elicitInput({ mode: 'url', elicitationId: req.approvalId, url: `${publicBase}${req.path}`, message: req.message }, { timeout });
|
|
154
|
+
return { action: res.action };
|
|
155
|
+
};
|
|
156
|
+
out.urlComplete = (approvalId) => {
|
|
157
|
+
void server.server
|
|
158
|
+
.createElicitationCompletionNotifier(approvalId)()
|
|
159
|
+
.catch(() => undefined);
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
if (caps.form || !caps.url) {
|
|
163
|
+
out.form = async (req) => {
|
|
164
|
+
const res = await server.server.elicitInput({ mode: 'form', message: req.message, requestedSchema: req.requestedSchema }, { timeout });
|
|
165
|
+
return { action: res.action, content: res.content };
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
};
|
|
170
|
+
const caller = (sessionId) => ({
|
|
171
|
+
client: { kind: 'mcp_client', id: identity.label, key: identity.principal },
|
|
172
|
+
mcpSessionId: sessionId,
|
|
173
|
+
principal: { ceiling: identity.access },
|
|
174
|
+
prompts: prompts(),
|
|
175
|
+
});
|
|
176
|
+
const run = async (fn, code, sessionId, request) => {
|
|
177
|
+
try {
|
|
178
|
+
await this.ctx.instances.ensureFresh(instanceId);
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
return toolText({
|
|
182
|
+
ok: false,
|
|
183
|
+
error: { code: 'PLUGIN_UNAVAILABLE', message: err instanceof Error ? err.message : 'Endpoint unavailable' },
|
|
184
|
+
logs: [],
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
// A cancelled request or a closed session ends the run: nothing it queued may run afterwards.
|
|
188
|
+
const signal = AbortSignal.any([request, closed]);
|
|
189
|
+
return toolText(await fn(this.ctx.gateDeps(), rt(), caller(sessionId), code, signal));
|
|
190
|
+
};
|
|
191
|
+
const input = {
|
|
192
|
+
code: z
|
|
193
|
+
.string()
|
|
194
|
+
.min(1)
|
|
195
|
+
.max(100_000)
|
|
196
|
+
.describe('Body of an async JavaScript function. Use `return` for the result.'),
|
|
197
|
+
};
|
|
198
|
+
server.registerTool('search', {
|
|
199
|
+
title: `Search ${manifest.name}`,
|
|
200
|
+
description: describeSearch(manifest),
|
|
201
|
+
inputSchema: input,
|
|
202
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
203
|
+
}, ({ code }, extra) => run(searchCode, code, extra.sessionId, extra.signal));
|
|
204
|
+
server.registerTool('execute', {
|
|
205
|
+
title: `Execute on ${manifest.name}`,
|
|
206
|
+
description: describeExecute(manifest),
|
|
207
|
+
inputSchema: input,
|
|
208
|
+
// Hints for clients that confirm tool calls themselves; the gate never relies on them.
|
|
209
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
210
|
+
}, ({ code }, extra) => run(executeCode, code, extra.sessionId, extra.signal));
|
|
211
|
+
return server;
|
|
212
|
+
}
|
|
213
|
+
/** The request carries a bearer or OAuth access token that is live somewhere on this server. */
|
|
214
|
+
holdsLiveCredential(c) {
|
|
215
|
+
const token = c.req.header('authorization')?.match(/^Bearer\s+(\S+)$/i)?.[1];
|
|
216
|
+
if (!token)
|
|
217
|
+
return false;
|
|
218
|
+
return !!this.ctx.tokens.verify(token) || !!this.oauth.verifyAccess(token, '');
|
|
219
|
+
}
|
|
220
|
+
async handle(c, slug) {
|
|
221
|
+
if (!hostAllowed(this.ctx.config, c.req.header('host')) ||
|
|
222
|
+
!originAllowed(this.ctx.config, c.req.header('origin'))) {
|
|
223
|
+
return c.json(jsonRpcError(-32003, 'Host or Origin not allowed; set PUBLIC_MCP_URL or MCP_ALLOWED_HOSTS'), 403);
|
|
224
|
+
}
|
|
225
|
+
// Before authentication, an unknown slug looks like any endpoint that needs credentials, and a
|
|
226
|
+
// disabled one answers like an enabled one: neither existence nor state is told to strangers.
|
|
227
|
+
const found = this.ctx.instances.bySlug(slug);
|
|
228
|
+
if (!found) {
|
|
229
|
+
if (this.holdsLiveCredential(c))
|
|
230
|
+
return c.json(jsonRpcError(-32001, 'Unknown MCP endpoint'), 404);
|
|
231
|
+
c.header('WWW-Authenticate', 'Bearer');
|
|
232
|
+
return c.json({ error: 'unauthorized', error_description: 'Authentication required' }, 401);
|
|
233
|
+
}
|
|
234
|
+
const { instance, plugin } = found;
|
|
235
|
+
const auth = await authenticateMcp(this.ctx, this.oauth, c, instance);
|
|
236
|
+
if (!auth.ok) {
|
|
237
|
+
if (auth.wwwAuthenticate)
|
|
238
|
+
c.header('WWW-Authenticate', auth.wwwAuthenticate);
|
|
239
|
+
return c.json({ error: auth.error, error_description: auth.message }, auth.status);
|
|
240
|
+
}
|
|
241
|
+
if (!this.ctx.instances.isServing(instance, plugin))
|
|
242
|
+
return c.json(jsonRpcError(-32002, 'This endpoint is disabled'), 503);
|
|
243
|
+
const sessionId = c.req.header('mcp-session-id');
|
|
244
|
+
if (sessionId) {
|
|
245
|
+
const s = this.sessions.get(sessionId);
|
|
246
|
+
// A session id is only valid for the endpoint and principal that created it.
|
|
247
|
+
if (!s || s.instanceId !== instance.id || s.principal !== auth.identity.principal) {
|
|
248
|
+
return c.json(jsonRpcError(-32001, 'Session not found'), 404);
|
|
249
|
+
}
|
|
250
|
+
s.lastSeen = Date.now();
|
|
251
|
+
return s.transport.handleRequest(c.req.raw);
|
|
252
|
+
}
|
|
253
|
+
if (c.req.method !== 'POST')
|
|
254
|
+
return c.json(jsonRpcError(-32000, 'Missing Mcp-Session-Id'), 400);
|
|
255
|
+
try {
|
|
256
|
+
await this.ctx.instances.ensureFresh(instance.id, { forceVersionCheck: true });
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
return c.json(jsonRpcError(-32002, `Endpoint unavailable: ${err instanceof Error ? err.message : String(err)}`), 503);
|
|
260
|
+
}
|
|
261
|
+
// One principal can't pile up servers: past the cap, its least recently used session goes.
|
|
262
|
+
const mine = [...this.sessions].filter(([, s]) => s.principal === auth.identity.principal);
|
|
263
|
+
if (mine.length >= MAX_SESSIONS_PER_PRINCIPAL) {
|
|
264
|
+
const [oldest] = mine.sort(([, a], [, b]) => a.lastSeen - b.lastSeen)[0];
|
|
265
|
+
await this.closeSession(oldest);
|
|
266
|
+
}
|
|
267
|
+
const closed = new AbortController();
|
|
268
|
+
const server = this.buildServer(instance.id, auth.identity, publicMcpBase(this.ctx, c), closed.signal);
|
|
269
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
270
|
+
sessionIdGenerator: () => randomUUID(),
|
|
271
|
+
onsessioninitialized: (id) => {
|
|
272
|
+
this.sessions.set(id, {
|
|
273
|
+
transport,
|
|
274
|
+
server,
|
|
275
|
+
closed,
|
|
276
|
+
instanceId: instance.id,
|
|
277
|
+
principal: auth.identity.principal,
|
|
278
|
+
lastSeen: Date.now(),
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
onsessionclosed: (id) => void this.closeSession(id),
|
|
282
|
+
});
|
|
283
|
+
await server.connect(transport);
|
|
284
|
+
const response = await transport.handleRequest(c.req.raw);
|
|
285
|
+
if (!transport.sessionId) {
|
|
286
|
+
// Not a valid initialize request: nothing to keep.
|
|
287
|
+
await transport.close().catch(() => undefined);
|
|
288
|
+
await server.close().catch(() => undefined);
|
|
289
|
+
}
|
|
290
|
+
return response;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
//# sourceMappingURL=endpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint.js","sourceRoot":"","sources":["../../../src/http/mcp/endpoint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Context, Hono } from 'hono';
|
|
2
|
+
import type { AppContext } from '../../app.js';
|
|
3
|
+
import type { OAuthService } from '../../auth/oauth.js';
|
|
4
|
+
import type { SessionLimits, ValidSession } from '../../auth/sessions.js';
|
|
5
|
+
/**
|
|
6
|
+
* OAuth 2.1 authorization server endpoints and the MCP-port sign-in (design §6.2). The sign-in here
|
|
7
|
+
* reuses portal accounts, TOTP and OIDC, but issues a separate short-lived `oauth_ui` session that
|
|
8
|
+
* only works for consent and approval pages — never for the Admin API.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* MCP-port sign-ins are kept apart by purpose (design §2.1): a session made to consent to a client
|
|
12
|
+
* can't decide approvals, and one made to decide an approval can't authorize clients. Each has its
|
|
13
|
+
* own cookie, scoped to the pages it serves.
|
|
14
|
+
*/
|
|
15
|
+
export type UiPurpose = 'oauth' | 'approval';
|
|
16
|
+
/** Which kind of session a sign-in continuing to `continueTo` creates. */
|
|
17
|
+
export declare const purposeOf: (continueTo: string) => UiPurpose;
|
|
18
|
+
export declare const UI_LIMITS: SessionLimits;
|
|
19
|
+
/** Where a sign-in may continue to: only our own consent and approval pages. */
|
|
20
|
+
export declare const safeContinue: (v: unknown) => string;
|
|
21
|
+
export declare function uiSession(ctx: AppContext, c: Context, purpose: UiPurpose): ValidSession | null;
|
|
22
|
+
/** Double-submit CSRF for the MCP-port forms (SameSite=Strict cookie + hidden field). */
|
|
23
|
+
export declare function uiCsrf(ctx: AppContext, c: Context): string;
|
|
24
|
+
export declare const checkUiCsrf: (c: Context, submitted: unknown) => boolean;
|
|
25
|
+
export declare function recordTotpProof(ctx: AppContext, idHash: string): void;
|
|
26
|
+
/** Milliseconds since this session last proved TOTP, or Infinity if it never did. */
|
|
27
|
+
export declare function sinceTotpProof(ctx: AppContext, session: ValidSession): number;
|
|
28
|
+
export declare function renderLogin(ctx: AppContext, c: Context, continueTo: string, purpose: string, error?: string, status?: 200 | 401 | 403 | 429): Response | Promise<Response>;
|
|
29
|
+
export declare function registerOAuthRoutes(app: Hono, ctx: AppContext, oauth: OAuthService): void;
|