@deadragdoll/tellymcp 0.0.12 → 0.0.14
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/.env.example.client +5 -1
- package/.env.example.gateway +16 -1
- package/CHANGELOG.md +30 -0
- package/README-ru.md +33 -0
- package/README.md +33 -0
- package/TOOLS.md +384 -9
- package/config/templates/env.both.template +15 -2
- package/config/templates/env.client.template +5 -2
- package/config/templates/env.gateway.template +15 -2
- package/dist/cli.js +137 -2
- package/dist/services/features/telegram-mcp/browser.service.js +56 -1
- package/dist/services/features/telegram-mcp/file-content.service.js +94 -0
- package/dist/services/features/telegram-mcp/gateway-delivery.service.js +5 -1
- package/dist/services/features/telegram-mcp/gateway-socket.service.js +43 -11
- package/dist/services/features/telegram-mcp/mcp-http.service.js +1 -0
- package/dist/services/features/telegram-mcp/mcp-server.service.js +30 -0
- package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +23 -1
- package/dist/services/features/telegram-mcp/src/app/config/env.js +132 -2
- package/dist/services/features/telegram-mcp/src/app/http.js +139 -99
- package/dist/services/features/telegram-mcp/src/app/oauthFacade.js +642 -0
- package/dist/services/features/telegram-mcp/src/app/webapp/assets.js +151 -170
- package/dist/services/features/telegram-mcp/src/app/webapp/auth.js +96 -99
- package/dist/services/features/telegram-mcp/src/entities/request/model/schema.js +205 -17
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserAttachActiveTabTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserAttachTabTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserClickTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserDetachTabTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserDomTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserFillTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserInjectScriptTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserListAttachedInstancesTool.js +33 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserListTabsTool.js +33 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserPressTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserRecordingStartTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserRecordingStatusTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserRecordingStopTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserScreenshotTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserService.js +890 -23
- package/dist/services/features/telegram-mcp/src/features/browser-attach/model/browserRecordingBundle.js +536 -0
- package/dist/services/features/telegram-mcp/src/features/browser-attach/model/firefoxAttachRegistry.js +80 -0
- package/dist/services/features/telegram-mcp/src/features/browser-attach/model/firefoxAttachServer.js +736 -0
- package/dist/services/features/telegram-mcp/src/features/browser-attach/model/types.js +188 -0
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/collaborationService.js +2 -0
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileService.js +6 -3
- package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayClientAccess.js +4 -1
- package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +33 -35
- package/dist/services/features/telegram-mcp/src/features/file-content/model/getFileListTool.js +33 -0
- package/dist/services/features/telegram-mcp/src/features/file-content/model/getFileService.js +327 -0
- package/dist/services/features/telegram-mcp/src/features/file-content/model/getFileTool.js +81 -0
- package/dist/services/features/telegram-mcp/src/features/file-content/model/temporaryFileLinkStore.js +307 -0
- package/dist/services/features/telegram-mcp/src/features/file-content/model/workspaceFilePolicy.js +115 -0
- package/dist/services/features/telegram-mcp/src/features/notify/model/notifyService.js +5 -1
- package/dist/services/features/telegram-mcp/src/shared/integrations/redis/stateStore.js +28 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +3 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFileHandoffActions.js +6 -3
- package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/client.js +29 -6
- package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/ptyRegistry.js +100 -2
- package/dist/services/features/telegram-mcp/src/shared/lib/bodyLimits.js +63 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/gatewayAuth.js +13 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/time/localTimestamp.js +21 -0
- package/docs/CHAT_CONNECTOR.md +134 -0
- package/docs/STANDALONE-ru.md +55 -6
- package/docs/STANDALONE.md +55 -6
- package/package.json +10 -5
- package/packages/chrome-attach-extension/dist/background.js +1735 -0
- package/packages/chrome-attach-extension/dist/icon.svg +6 -0
- package/packages/chrome-attach-extension/dist/manifest.json +37 -0
- package/packages/chrome-attach-extension/dist/options.html +312 -0
- package/packages/chrome-attach-extension/dist/options.js +606 -0
- package/packages/chrome-attach-extension/dist/popup.html +93 -0
- package/packages/chrome-attach-extension/dist/popup.js +79 -0
- package/packages/chrome-attach-extension/dist/recorder-content.js +96 -0
- package/packages/chrome-attach-extension/dist/recorder-page.js +282 -0
- package/packages/firefox-attach-extension/README.md +13 -0
- package/packages/firefox-attach-extension/dist/background.js +1622 -0
- package/packages/firefox-attach-extension/dist/icon.svg +6 -0
- package/packages/firefox-attach-extension/dist/manifest.json +44 -0
- package/packages/firefox-attach-extension/dist/options.html +312 -0
- package/packages/firefox-attach-extension/dist/options.js +540 -0
- package/packages/firefox-attach-extension/dist/popup.html +93 -0
- package/packages/firefox-attach-extension/dist/popup.js +64 -0
- package/packages/firefox-attach-extension/dist/recorder-content.js +90 -0
- package/packages/firefox-attach-extension/dist/recorder-page.js +318 -0
|
@@ -12,6 +12,8 @@ const transport_1 = require("../../shared/integrations/telegram/transport");
|
|
|
12
12
|
const minioExchangeStore_1 = require("../../shared/integrations/object-storage/minioExchangeStore");
|
|
13
13
|
const gatewayHttpService_1 = require("../../features/distributed-gateway/model/gatewayHttpService");
|
|
14
14
|
const gatewayClientAccess_1 = require("../../features/distributed-client/model/gatewayClientAccess");
|
|
15
|
+
const firefoxAttachServer_1 = require("../../features/browser-attach/model/firefoxAttachServer");
|
|
16
|
+
const temporaryFileLinkStore_1 = require("../../features/file-content/model/temporaryFileLinkStore");
|
|
15
17
|
const client_2 = require("../../shared/integrations/terminal/client");
|
|
16
18
|
const ptyRegistry_1 = require("../../shared/integrations/terminal/ptyRegistry");
|
|
17
19
|
async function createAppRuntime(input) {
|
|
@@ -33,6 +35,7 @@ async function createAppRuntime(input) {
|
|
|
33
35
|
httpPort: config.mcp.httpPort,
|
|
34
36
|
httpPath: config.mcp.httpPath,
|
|
35
37
|
bearerAuthEnabled: Boolean(config.mcp.bearerToken),
|
|
38
|
+
oauthConnectorEnabled: Boolean(config.oauth),
|
|
36
39
|
enableDebugRoutes: config.mcp.enableDebugRoutes,
|
|
37
40
|
enablePruneRoute: config.mcp.enablePruneRoute,
|
|
38
41
|
},
|
|
@@ -55,7 +58,6 @@ async function createAppRuntime(input) {
|
|
|
55
58
|
initDataTtlSeconds: config.webapp.initDataTtlSeconds,
|
|
56
59
|
sessionTtlSeconds: config.webapp.sessionTtlSeconds,
|
|
57
60
|
launchMode: config.webapp.launchMode,
|
|
58
|
-
pollIntervalMs: config.webapp.pollIntervalMs,
|
|
59
61
|
actionCooldownMs: config.webapp.actionCooldownMs,
|
|
60
62
|
},
|
|
61
63
|
terminal: {
|
|
@@ -63,6 +65,15 @@ async function createAppRuntime(input) {
|
|
|
63
65
|
nudgeDebounceSeconds: config.terminal.nudgeDebounceSeconds,
|
|
64
66
|
nudgeCooldownSeconds: config.terminal.nudgeCooldownSeconds,
|
|
65
67
|
},
|
|
68
|
+
browser: {
|
|
69
|
+
enabled: config.browser.enabled,
|
|
70
|
+
headless: config.browser.headless,
|
|
71
|
+
devtools: config.browser.devtools,
|
|
72
|
+
attachEnabled: config.browser.attach.enabled,
|
|
73
|
+
attachHost: config.browser.attach.host,
|
|
74
|
+
attachPort: config.browser.attach.port,
|
|
75
|
+
attachPath: config.browser.attach.path,
|
|
76
|
+
},
|
|
66
77
|
telegram: {
|
|
67
78
|
webhookEnabled: config.telegram.webhook.enabled,
|
|
68
79
|
webhookPath: config.telegram.webhook.path,
|
|
@@ -200,6 +211,13 @@ async function createAppRuntime(input) {
|
|
|
200
211
|
await telegramTransport.sendStartupNotifications();
|
|
201
212
|
logger.info("Startup Telegram notifications completed");
|
|
202
213
|
const gatewayHttpService = new gatewayHttpService_1.GatewayHttpService(config, input.callBroker);
|
|
214
|
+
const temporaryFileLinkStore = config.distributed.mode !== "client" &&
|
|
215
|
+
config.distributed.gatewayPublicUrl?.trim()
|
|
216
|
+
? new temporaryFileLinkStore_1.TemporaryFileLinkStore(config.distributed.gatewayPublicUrl, logger)
|
|
217
|
+
: null;
|
|
218
|
+
await temporaryFileLinkStore?.start();
|
|
219
|
+
const firefoxAttachServer = new firefoxAttachServer_1.FirefoxAttachServer(config, logger, stateStore, stateStore);
|
|
220
|
+
await firefoxAttachServer.start();
|
|
203
221
|
return {
|
|
204
222
|
callBroker: input.callBroker,
|
|
205
223
|
config,
|
|
@@ -217,8 +235,12 @@ async function createAppRuntime(input) {
|
|
|
217
235
|
projectIdentityResolver,
|
|
218
236
|
webAppLaunchRegistry,
|
|
219
237
|
gatewayHttpService,
|
|
238
|
+
firefoxAttachServer,
|
|
239
|
+
temporaryFileLinkStore,
|
|
220
240
|
shutdown: async () => {
|
|
221
241
|
logger.info("Shutdown started");
|
|
242
|
+
await temporaryFileLinkStore?.stop();
|
|
243
|
+
await firefoxAttachServer.stop();
|
|
222
244
|
await telegramTransport.stop();
|
|
223
245
|
(0, ptyRegistry_1.stopAllPtyTargets)();
|
|
224
246
|
redis.disconnect();
|
|
@@ -93,6 +93,22 @@ const envSchema = z.object({
|
|
|
93
93
|
MCP_HTTP_PORT: z.coerce.number().int().positive().default(8787),
|
|
94
94
|
MCP_HTTP_PATH: z.string().min(1).default("/mcp"),
|
|
95
95
|
MCP_HTTP_BEARER_TOKEN: optionalNonEmptyString,
|
|
96
|
+
TELLYMCP_PUBLIC_URL: optionalUrlString,
|
|
97
|
+
TELLYMCP_OAUTH_ISSUER: optionalUrlString,
|
|
98
|
+
TELLYMCP_OAUTH_AUDIENCE: optionalUrlString,
|
|
99
|
+
TELLYMCP_MAGIC_TOKEN: optionalNonEmptyString,
|
|
100
|
+
TELLYMCP_MAGIC_TOKEN_HASH: optionalNonEmptyString,
|
|
101
|
+
TELLYMCP_OAUTH_CLIENT_ID: optionalNonEmptyString,
|
|
102
|
+
TELLYMCP_OAUTH_CLIENT_SECRET: optionalNonEmptyString,
|
|
103
|
+
TELLYMCP_ALLOWED_REDIRECT_URIS: optionalNonEmptyString,
|
|
104
|
+
TELLYMCP_OAUTH_PRIVATE_KEY_PEM: optionalNonEmptyString,
|
|
105
|
+
TELLYMCP_AUTH_CODE_TTL_SECONDS: z.coerce
|
|
106
|
+
.number()
|
|
107
|
+
.int()
|
|
108
|
+
.positive()
|
|
109
|
+
.default(300),
|
|
110
|
+
TELLYMCP_OAUTH_SCOPES: z.string().min(1).default("tellymcp:read tellymcp:write"),
|
|
111
|
+
TELLYMCP_OAUTH_KEY_ID: z.string().min(1).default("tellymcp-oauth"),
|
|
96
112
|
MCP_HTTP_ENABLE_DEBUG_ROUTES: z
|
|
97
113
|
.string()
|
|
98
114
|
.optional()
|
|
@@ -145,7 +161,6 @@ const envSchema = z.object({
|
|
|
145
161
|
.enum(["default", "expand", "fullscreen"])
|
|
146
162
|
.default("fullscreen"),
|
|
147
163
|
WEBAPP_VISIBLE_SCREENS: z.coerce.number().int().positive().default(2),
|
|
148
|
-
WEBAPP_POLL_INTERVAL_MS: z.coerce.number().int().positive().default(2000),
|
|
149
164
|
WEBAPP_ACTION_COOLDOWN_MS: z.coerce.number().int().nonnegative().default(150),
|
|
150
165
|
MCP_XCHANGE_DIR: z.string().min(1).default(".mcp-xchange"),
|
|
151
166
|
TERMINAL_SHELL: z.string().min(1).default(process.env.SHELL || "bash"),
|
|
@@ -211,6 +226,13 @@ const envSchema = z.object({
|
|
|
211
226
|
BROWSER_EXECUTABLE_PATH: optionalNonEmptyString,
|
|
212
227
|
BROWSER_CHANNEL: z.preprocess(emptyStringToUndefined, z.enum(["chrome", "chromium", "msedge"]).optional()),
|
|
213
228
|
BROWSER_SLOW_MO_MS: z.coerce.number().int().nonnegative().default(0),
|
|
229
|
+
BROWSER_ATTACH_ENABLED: z
|
|
230
|
+
.string()
|
|
231
|
+
.optional()
|
|
232
|
+
.transform((value) => value === "true"),
|
|
233
|
+
BROWSER_ATTACH_WS_HOST: z.string().min(1).default("127.0.0.1"),
|
|
234
|
+
BROWSER_ATTACH_WS_PORT: z.coerce.number().int().positive().default(9999),
|
|
235
|
+
BROWSER_ATTACH_WS_PATH: z.string().min(1).default("/browser-attach/ws"),
|
|
214
236
|
PROXY_USE: z.preprocess(emptyStringToUndefined, z.enum(["http", "socks5"]).optional()),
|
|
215
237
|
HTTP_PROXY: optionalNonEmptyString,
|
|
216
238
|
SOCKS5_PROXY: optionalNonEmptyString,
|
|
@@ -239,6 +261,16 @@ function loadConfig() {
|
|
|
239
261
|
!parsed.TELEGRAM_BOT_TOKEN?.trim()) {
|
|
240
262
|
throw new Error("TELEGRAM_BOT_TOKEN is required for gateway and both distributed modes.");
|
|
241
263
|
}
|
|
264
|
+
const gatewayServerEnabled = parsed.DISTRIBUTED_MODE === "gateway" ||
|
|
265
|
+
parsed.DISTRIBUTED_MODE === "both";
|
|
266
|
+
const gatewayClientConfigured = parsed.DISTRIBUTED_MODE === "client" &&
|
|
267
|
+
Boolean(parsed.GATEWAY_PUBLIC_URL || parsed.GATEWAY_WS_URL);
|
|
268
|
+
if ((gatewayServerEnabled || gatewayClientConfigured) &&
|
|
269
|
+
!parsed.GATEWAY_AUTH_TOKEN?.trim()) {
|
|
270
|
+
throw new Error(gatewayServerEnabled
|
|
271
|
+
? "GATEWAY_AUTH_TOKEN is required for gateway and both distributed modes."
|
|
272
|
+
: "GATEWAY_AUTH_TOKEN is required when a client connects to a gateway.");
|
|
273
|
+
}
|
|
242
274
|
if (parsed.ADMIN_TOKEN?.trim() && !parsed.TELEGRAM_BOT_TOKEN?.trim()) {
|
|
243
275
|
throw new Error("ADMIN_TOKEN requires TELEGRAM_BOT_TOKEN because admin mode runs through the gateway bot.");
|
|
244
276
|
}
|
|
@@ -250,6 +282,69 @@ function loadConfig() {
|
|
|
250
282
|
throw new Error("TELEGRAM_WEBHOOK_ENABLED=true requires TELEGRAM_WEBHOOK_SECRET.");
|
|
251
283
|
}
|
|
252
284
|
}
|
|
285
|
+
const oauthConfigured = Boolean(parsed.TELLYMCP_PUBLIC_URL ||
|
|
286
|
+
parsed.TELLYMCP_OAUTH_ISSUER ||
|
|
287
|
+
parsed.TELLYMCP_OAUTH_AUDIENCE ||
|
|
288
|
+
parsed.TELLYMCP_MAGIC_TOKEN ||
|
|
289
|
+
parsed.TELLYMCP_MAGIC_TOKEN_HASH ||
|
|
290
|
+
parsed.TELLYMCP_OAUTH_CLIENT_ID ||
|
|
291
|
+
parsed.TELLYMCP_OAUTH_CLIENT_SECRET ||
|
|
292
|
+
parsed.TELLYMCP_ALLOWED_REDIRECT_URIS ||
|
|
293
|
+
parsed.TELLYMCP_OAUTH_PRIVATE_KEY_PEM);
|
|
294
|
+
if (oauthConfigured && !parsed.TELLYMCP_PUBLIC_URL) {
|
|
295
|
+
throw new Error("TELLYMCP_PUBLIC_URL is required when OAuth connector settings are configured.");
|
|
296
|
+
}
|
|
297
|
+
if (oauthConfigured &&
|
|
298
|
+
!parsed.TELLYMCP_MAGIC_TOKEN &&
|
|
299
|
+
!parsed.TELLYMCP_MAGIC_TOKEN_HASH) {
|
|
300
|
+
throw new Error("TELLYMCP_MAGIC_TOKEN or TELLYMCP_MAGIC_TOKEN_HASH is required when OAuth is configured.");
|
|
301
|
+
}
|
|
302
|
+
if (parsed.TELLYMCP_MAGIC_TOKEN && parsed.TELLYMCP_MAGIC_TOKEN_HASH) {
|
|
303
|
+
throw new Error("Configure only one of TELLYMCP_MAGIC_TOKEN and TELLYMCP_MAGIC_TOKEN_HASH.");
|
|
304
|
+
}
|
|
305
|
+
if (parsed.TELLYMCP_MAGIC_TOKEN_HASH &&
|
|
306
|
+
!/^sha256:[a-f0-9]{64}$/iu.test(parsed.TELLYMCP_MAGIC_TOKEN_HASH)) {
|
|
307
|
+
throw new Error("TELLYMCP_MAGIC_TOKEN_HASH must use the format sha256:<64 hex characters>.");
|
|
308
|
+
}
|
|
309
|
+
if (parsed.TELLYMCP_OAUTH_CLIENT_SECRET && !parsed.TELLYMCP_OAUTH_CLIENT_ID) {
|
|
310
|
+
throw new Error("TELLYMCP_OAUTH_CLIENT_SECRET requires TELLYMCP_OAUTH_CLIENT_ID.");
|
|
311
|
+
}
|
|
312
|
+
for (const [name, value] of [
|
|
313
|
+
["TELLYMCP_PUBLIC_URL", parsed.TELLYMCP_PUBLIC_URL],
|
|
314
|
+
["TELLYMCP_OAUTH_ISSUER", parsed.TELLYMCP_OAUTH_ISSUER],
|
|
315
|
+
["TELLYMCP_OAUTH_AUDIENCE", parsed.TELLYMCP_OAUTH_AUDIENCE],
|
|
316
|
+
]) {
|
|
317
|
+
if (!value) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const url = new URL(value);
|
|
321
|
+
if (url.search || url.hash) {
|
|
322
|
+
throw new Error(`${name} must not contain a query string or fragment.`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const oauthScopes = parsed.TELLYMCP_OAUTH_SCOPES
|
|
326
|
+
.split(/[\s,]+/u)
|
|
327
|
+
.map((scope) => scope.trim())
|
|
328
|
+
.filter(Boolean);
|
|
329
|
+
if (oauthConfigured && oauthScopes.length === 0) {
|
|
330
|
+
throw new Error("TELLYMCP_OAUTH_SCOPES must contain at least one scope.");
|
|
331
|
+
}
|
|
332
|
+
const allowedRedirectUris = parsed.TELLYMCP_ALLOWED_REDIRECT_URIS
|
|
333
|
+
? parsed.TELLYMCP_ALLOWED_REDIRECT_URIS.split(",")
|
|
334
|
+
.map((redirectUri) => redirectUri.trim())
|
|
335
|
+
.filter(Boolean)
|
|
336
|
+
: [];
|
|
337
|
+
for (const redirectUri of allowedRedirectUris) {
|
|
338
|
+
try {
|
|
339
|
+
const parsedRedirectUri = new URL(redirectUri);
|
|
340
|
+
if (parsedRedirectUri.hash) {
|
|
341
|
+
throw new Error("fragment");
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
throw new Error(`TELLYMCP_ALLOWED_REDIRECT_URIS contains an invalid URL: ${redirectUri}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
253
348
|
const telegramProxy = parsed.PROXY_USE === "http"
|
|
254
349
|
? parsed.HTTP_PROXY
|
|
255
350
|
? {
|
|
@@ -321,6 +416,36 @@ function loadConfig() {
|
|
|
321
416
|
enableDebugRoutes: parsed.MCP_HTTP_ENABLE_DEBUG_ROUTES,
|
|
322
417
|
enablePruneRoute: parsed.MCP_HTTP_ENABLE_PRUNE_ROUTE,
|
|
323
418
|
},
|
|
419
|
+
...(oauthConfigured && parsed.TELLYMCP_PUBLIC_URL
|
|
420
|
+
? {
|
|
421
|
+
oauth: {
|
|
422
|
+
publicUrl: parsed.TELLYMCP_PUBLIC_URL.replace(/\/+$/u, ""),
|
|
423
|
+
issuer: (parsed.TELLYMCP_OAUTH_ISSUER ?? parsed.TELLYMCP_PUBLIC_URL).replace(/\/+$/u, ""),
|
|
424
|
+
audience: (parsed.TELLYMCP_OAUTH_AUDIENCE ?? parsed.TELLYMCP_PUBLIC_URL).replace(/\/+$/u, ""),
|
|
425
|
+
...(parsed.TELLYMCP_MAGIC_TOKEN
|
|
426
|
+
? { magicToken: parsed.TELLYMCP_MAGIC_TOKEN }
|
|
427
|
+
: {}),
|
|
428
|
+
...(parsed.TELLYMCP_MAGIC_TOKEN_HASH
|
|
429
|
+
? { magicTokenHash: parsed.TELLYMCP_MAGIC_TOKEN_HASH }
|
|
430
|
+
: {}),
|
|
431
|
+
...(parsed.TELLYMCP_OAUTH_CLIENT_ID
|
|
432
|
+
? { clientId: parsed.TELLYMCP_OAUTH_CLIENT_ID }
|
|
433
|
+
: {}),
|
|
434
|
+
...(parsed.TELLYMCP_OAUTH_CLIENT_SECRET
|
|
435
|
+
? { clientSecret: parsed.TELLYMCP_OAUTH_CLIENT_SECRET }
|
|
436
|
+
: {}),
|
|
437
|
+
allowedRedirectUris,
|
|
438
|
+
...(parsed.TELLYMCP_OAUTH_PRIVATE_KEY_PEM
|
|
439
|
+
? {
|
|
440
|
+
privateKeyPem: parsed.TELLYMCP_OAUTH_PRIVATE_KEY_PEM.replace(/\\n/gu, "\n"),
|
|
441
|
+
}
|
|
442
|
+
: {}),
|
|
443
|
+
authCodeTtlSeconds: parsed.TELLYMCP_AUTH_CODE_TTL_SECONDS,
|
|
444
|
+
scopes: oauthScopes,
|
|
445
|
+
keyId: parsed.TELLYMCP_OAUTH_KEY_ID,
|
|
446
|
+
},
|
|
447
|
+
}
|
|
448
|
+
: {}),
|
|
324
449
|
distributed: {
|
|
325
450
|
mode: parsed.DISTRIBUTED_MODE,
|
|
326
451
|
...(parsed.GATEWAY_PUBLIC_URL
|
|
@@ -379,7 +504,6 @@ function loadConfig() {
|
|
|
379
504
|
sessionTtlSeconds: parsed.WEBAPP_SESSION_TTL_SECONDS,
|
|
380
505
|
launchMode: parsed.WEBAPP_LAUNCH_MODE,
|
|
381
506
|
visibleScreens: parsed.WEBAPP_VISIBLE_SCREENS,
|
|
382
|
-
pollIntervalMs: parsed.WEBAPP_POLL_INTERVAL_MS,
|
|
383
507
|
actionCooldownMs: parsed.WEBAPP_ACTION_COOLDOWN_MS,
|
|
384
508
|
},
|
|
385
509
|
exchange: {
|
|
@@ -417,6 +541,12 @@ function loadConfig() {
|
|
|
417
541
|
: {}),
|
|
418
542
|
...(parsed.BROWSER_CHANNEL ? { channel: parsed.BROWSER_CHANNEL } : {}),
|
|
419
543
|
slowMoMs: parsed.BROWSER_SLOW_MO_MS,
|
|
544
|
+
attach: {
|
|
545
|
+
enabled: parsed.BROWSER_ATTACH_ENABLED,
|
|
546
|
+
host: parsed.BROWSER_ATTACH_WS_HOST,
|
|
547
|
+
port: parsed.BROWSER_ATTACH_WS_PORT,
|
|
548
|
+
path: parsed.BROWSER_ATTACH_WS_PATH,
|
|
549
|
+
},
|
|
420
550
|
},
|
|
421
551
|
project: {
|
|
422
552
|
...(parsed.PROJECT_NAME ? { name: parsed.PROJECT_NAME } : {}),
|
|
@@ -3,15 +3,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveMcpHttpAuthorization = resolveMcpHttpAuthorization;
|
|
7
|
+
exports.isMcpHttpRequestAuthorized = isMcpHttpRequestAuthorized;
|
|
6
8
|
exports.createMcpHttpHandler = createMcpHttpHandler;
|
|
7
9
|
const node_crypto_1 = require("node:crypto");
|
|
8
10
|
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
9
11
|
const ws_1 = __importDefault(require("ws"));
|
|
12
|
+
const oauthFacade_1 = require("./oauthFacade");
|
|
10
13
|
const auth_1 = require("./webapp/auth");
|
|
11
14
|
const relay_1 = require("./webapp/relay");
|
|
12
15
|
const assets_1 = require("./webapp/assets");
|
|
13
16
|
const client_1 = require("../shared/integrations/terminal/client");
|
|
14
17
|
const terminal_1 = require("./webapp/terminal");
|
|
18
|
+
const bodyLimits_1 = require("../shared/lib/bodyLimits");
|
|
15
19
|
function formatTerminalHttpError(error, fallback) {
|
|
16
20
|
if ((0, terminal_1.isTerminalUnavailableError)(error)) {
|
|
17
21
|
return "terminal runtime is unavailable";
|
|
@@ -32,6 +36,13 @@ function isInitializeRequest(body) {
|
|
|
32
36
|
const method = Reflect.get(body, "method");
|
|
33
37
|
return method === "initialize";
|
|
34
38
|
}
|
|
39
|
+
function readMcpRpcMethod(body) {
|
|
40
|
+
if (!body || typeof body !== "object") {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const method = Reflect.get(body, "method");
|
|
44
|
+
return typeof method === "string" ? method : undefined;
|
|
45
|
+
}
|
|
35
46
|
function readHeader(req, headerName) {
|
|
36
47
|
const value = req.headers[headerName];
|
|
37
48
|
if (Array.isArray(value)) {
|
|
@@ -44,24 +55,21 @@ async function readJsonBody(req) {
|
|
|
44
55
|
if (knownParams &&
|
|
45
56
|
typeof knownParams === "object" &&
|
|
46
57
|
Object.keys(knownParams).length > 0) {
|
|
58
|
+
(0, bodyLimits_1.assertSerializedBodySize)(knownParams);
|
|
47
59
|
return knownParams;
|
|
48
60
|
}
|
|
49
61
|
const knownBody = req.body;
|
|
50
62
|
if (knownBody !== undefined) {
|
|
63
|
+
(0, bodyLimits_1.assertSerializedBodySize)(knownBody);
|
|
51
64
|
return knownBody;
|
|
52
65
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return undefined;
|
|
59
|
-
}
|
|
60
|
-
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
61
|
-
if (!raw) {
|
|
62
|
-
return undefined;
|
|
66
|
+
return (0, bodyLimits_1.readLimitedJsonBody)(req);
|
|
67
|
+
}
|
|
68
|
+
function ignoreInvalidJsonBody(error) {
|
|
69
|
+
if ((0, bodyLimits_1.isBodySizeLimitError)(error)) {
|
|
70
|
+
throw error;
|
|
63
71
|
}
|
|
64
|
-
return
|
|
72
|
+
return undefined;
|
|
65
73
|
}
|
|
66
74
|
function writeJson(res, statusCode, payload) {
|
|
67
75
|
const body = JSON.stringify(payload);
|
|
@@ -103,6 +111,19 @@ function readBearerToken(req) {
|
|
|
103
111
|
}
|
|
104
112
|
return header.slice("Bearer ".length).trim() || null;
|
|
105
113
|
}
|
|
114
|
+
function resolveMcpHttpAuthorization(req, internalBearerToken, oauthFacade) {
|
|
115
|
+
if (!internalBearerToken && !oauthFacade) {
|
|
116
|
+
return "public";
|
|
117
|
+
}
|
|
118
|
+
if (internalBearerToken && isAuthorized(req, internalBearerToken)) {
|
|
119
|
+
return "internal_bearer";
|
|
120
|
+
}
|
|
121
|
+
const token = readBearerToken(req);
|
|
122
|
+
return token && oauthFacade?.verifyAccessToken(token) ? "oauth" : null;
|
|
123
|
+
}
|
|
124
|
+
function isMcpHttpRequestAuthorized(req, internalBearerToken, oauthFacade) {
|
|
125
|
+
return resolveMcpHttpAuthorization(req, internalBearerToken, oauthFacade) !== null;
|
|
126
|
+
}
|
|
106
127
|
function isDuplicateSseStreamError(error) {
|
|
107
128
|
const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
|
|
108
129
|
return message.includes("Only one SSE stream is allowed per session");
|
|
@@ -143,7 +164,10 @@ function isRelayTerminalUnavailableMessage(error) {
|
|
|
143
164
|
function createMcpHttpHandler(runtime, input) {
|
|
144
165
|
const transports = new Map();
|
|
145
166
|
const webAppSessions = new auth_1.WebAppSessionRegistry();
|
|
146
|
-
const liveWsServer = new WebSocketServer({
|
|
167
|
+
const liveWsServer = new WebSocketServer({
|
|
168
|
+
noServer: true,
|
|
169
|
+
maxPayload: bodyLimits_1.MAX_BODY_SIZE_BYTES,
|
|
170
|
+
});
|
|
147
171
|
const webAppBasePath = runtime.config.webapp.basePath.replace(/\/+$/u, "") || "/webapp";
|
|
148
172
|
const rootPrefix = normalizeBasePath(process.env.ROOT_PREFIX || "/api");
|
|
149
173
|
const publicWebAppBasePath = rootPrefix === "/"
|
|
@@ -153,6 +177,13 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
153
177
|
const webAppLivePrefix = `${webAppBasePath}/live/`;
|
|
154
178
|
const telegramWebhookPath = runtime.config.telegram.webhook.path.replace(/\/+$/u, "") ||
|
|
155
179
|
"/telegram/webhook";
|
|
180
|
+
const mcpHttpPath = runtime.config.mcp.httpPath.replace(/\/+$/u, "") || "/mcp";
|
|
181
|
+
const oauthFacade = runtime.config.oauth
|
|
182
|
+
? (0, oauthFacade_1.createOAuthFacade)(runtime.config.oauth, mcpHttpPath, runtime.logger)
|
|
183
|
+
: null;
|
|
184
|
+
if (oauthFacade?.ephemeralSigningKey) {
|
|
185
|
+
runtime.logger.warn("OAuth connector is using an ephemeral signing key; access tokens will stop working after restart");
|
|
186
|
+
}
|
|
156
187
|
const closeSessionEntry = async (entry) => {
|
|
157
188
|
await entry.close();
|
|
158
189
|
};
|
|
@@ -220,6 +251,11 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
220
251
|
socket.on("close", () => {
|
|
221
252
|
void cleanup();
|
|
222
253
|
});
|
|
254
|
+
socket.on("error", (error) => {
|
|
255
|
+
runtime.logger.warn("Telegram WebApp live WS socket error", {
|
|
256
|
+
error: error instanceof Error ? error.message : String(error),
|
|
257
|
+
});
|
|
258
|
+
});
|
|
223
259
|
socket.on("message", (raw) => {
|
|
224
260
|
void (async () => {
|
|
225
261
|
const parsed = raw && typeof raw === "string"
|
|
@@ -288,6 +324,15 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
288
324
|
}
|
|
289
325
|
if (resolved.session?.terminalTarget && (0, client_1.isStreamableTerminalTarget)(resolved.session.terminalTarget)) {
|
|
290
326
|
(0, client_1.resizeForegroundTerminal)(resolved.session.terminalTarget, Math.max(20, Math.min(400, Math.round(cols))), Math.max(5, Math.min(200, Math.round(rows))));
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (resolved.relayTarget) {
|
|
330
|
+
await input.getGatewaySocketService?.()?.requestLiveRelay?.({
|
|
331
|
+
clientUuid: resolved.relayTarget.clientUuid,
|
|
332
|
+
localSessionId: resolved.relayTarget.localSessionId,
|
|
333
|
+
requestType: "resize",
|
|
334
|
+
payload: { cols, rows },
|
|
335
|
+
});
|
|
291
336
|
}
|
|
292
337
|
}
|
|
293
338
|
})().catch((error) => {
|
|
@@ -356,11 +401,39 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
356
401
|
socket.close(1011, "Live stream bootstrap failed");
|
|
357
402
|
});
|
|
358
403
|
});
|
|
359
|
-
const
|
|
404
|
+
const handleRequestInternal = async (req, res, pathname) => {
|
|
360
405
|
const method = req.method ?? "GET";
|
|
361
406
|
const normalizedPathname = normalizePrefixedPathname(pathname);
|
|
362
407
|
const requestUrl = new URL(req.url ?? normalizedPathname, `http://gateway.local`);
|
|
363
408
|
requestUrl.pathname = normalizedPathname;
|
|
409
|
+
if (runtime.temporaryFileLinkStore &&
|
|
410
|
+
(await runtime.temporaryFileLinkStore.handleRequest(req, res, requestUrl.pathname))) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (oauthFacade) {
|
|
414
|
+
const authorizationHeader = readHeader(req, "authorization");
|
|
415
|
+
const connectorRequestContext = {
|
|
416
|
+
method,
|
|
417
|
+
rawPath: pathname,
|
|
418
|
+
path: requestUrl.pathname,
|
|
419
|
+
queryParameters: [...requestUrl.searchParams.keys()],
|
|
420
|
+
userAgent: readHeader(req, "user-agent"),
|
|
421
|
+
accept: readHeader(req, "accept"),
|
|
422
|
+
contentType: readHeader(req, "content-type"),
|
|
423
|
+
authorizationHeaderPresent: Boolean(authorizationHeader),
|
|
424
|
+
authorizationScheme: authorizationHeader?.split(/\s+/u, 1)[0],
|
|
425
|
+
mcpSessionId: readHeader(req, "mcp-session-id"),
|
|
426
|
+
remoteAddress: req.socket.remoteAddress,
|
|
427
|
+
};
|
|
428
|
+
runtime.logger.info("OAuth connector HTTP request received", connectorRequestContext);
|
|
429
|
+
res.once("finish", () => {
|
|
430
|
+
runtime.logger.info("OAuth connector HTTP response completed", {
|
|
431
|
+
...connectorRequestContext,
|
|
432
|
+
statusCode: res.statusCode,
|
|
433
|
+
responseContentType: String(res.getHeader("content-type") ?? ""),
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
}
|
|
364
437
|
if (requestUrl.pathname === "/healthz") {
|
|
365
438
|
writeJson(res, 200, {
|
|
366
439
|
ok: true,
|
|
@@ -369,6 +442,10 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
369
442
|
});
|
|
370
443
|
return;
|
|
371
444
|
}
|
|
445
|
+
if (oauthFacade &&
|
|
446
|
+
(await oauthFacade.handleRequest(req, res, requestUrl.pathname))) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
372
449
|
if (runtime.config.telegram.webhook.enabled &&
|
|
373
450
|
requestUrl.pathname === telegramWebhookPath) {
|
|
374
451
|
if (method !== "POST") {
|
|
@@ -457,7 +534,7 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
457
534
|
method,
|
|
458
535
|
path: requestUrl.pathname,
|
|
459
536
|
});
|
|
460
|
-
const body = await readJsonBody(req).catch(
|
|
537
|
+
const body = await readJsonBody(req).catch(ignoreInvalidJsonBody);
|
|
461
538
|
let sessionId = body &&
|
|
462
539
|
typeof body === "object" &&
|
|
463
540
|
typeof Reflect.get(body, "sessionId") === "string"
|
|
@@ -529,7 +606,6 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
529
606
|
session_id: relayBootstrap.session_id,
|
|
530
607
|
session_label: relayBootstrap.session_label,
|
|
531
608
|
terminal_target: relayBootstrap.terminal_target,
|
|
532
|
-
poll_interval_ms: relayBootstrap.poll_interval_ms,
|
|
533
609
|
expires_at: new Date(record.expiresAtMs).toISOString(),
|
|
534
610
|
});
|
|
535
611
|
if (trustedTelegramUserId !== null) {
|
|
@@ -569,17 +645,6 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
569
645
|
}
|
|
570
646
|
try {
|
|
571
647
|
const validated = (0, auth_1.validateTelegramWebAppInitData)(initDataRaw, initDataUnsafe, requireTelegramBotToken(runtime, "validate Telegram WebApp bootstrap"), runtime.config.webapp.initDataTtlSeconds);
|
|
572
|
-
runtime.logger.info("Telegram WebApp initData validation debug", {
|
|
573
|
-
sessionId: sessionId || null,
|
|
574
|
-
telegramUserId: validated.user.id,
|
|
575
|
-
providedHash: validated.validationDebug.providedHash,
|
|
576
|
-
officialRawMatches: validated.validationDebug.officialRaw.matches,
|
|
577
|
-
officialRawCheckString: validated.validationDebug.officialRaw.checkString,
|
|
578
|
-
officialRawComputedHash: validated.validationDebug.officialRaw.computedHash,
|
|
579
|
-
userFieldsMatches: validated.validationDebug.userFields?.matches ?? null,
|
|
580
|
-
userFieldsCheckString: validated.validationDebug.userFields?.checkString ?? null,
|
|
581
|
-
userFieldsComputedHash: validated.validationDebug.userFields?.computedHash ?? null,
|
|
582
|
-
});
|
|
583
648
|
if (!sessionId) {
|
|
584
649
|
sessionId =
|
|
585
650
|
(await runtime.bindingStore.getActiveSessionIdForTelegramUser(validated.user.id)) ?? "";
|
|
@@ -615,7 +680,6 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
615
680
|
session_id: sessionId,
|
|
616
681
|
session_label: session?.label ?? null,
|
|
617
682
|
terminal_target: Boolean(session?.terminalTarget),
|
|
618
|
-
poll_interval_ms: runtime.config.webapp.pollIntervalMs,
|
|
619
683
|
expires_at: new Date(record.expiresAtMs).toISOString(),
|
|
620
684
|
});
|
|
621
685
|
if (launchRecord?.telegramChatId !== undefined &&
|
|
@@ -640,7 +704,6 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
640
704
|
runtime.logger.warn("Telegram WebApp bootstrap rejected", {
|
|
641
705
|
sessionId,
|
|
642
706
|
initDataLength: initDataRaw.length,
|
|
643
|
-
initDataPreview: initDataRaw.slice(0, 160),
|
|
644
707
|
hasUnsafeUser: Boolean(initDataUnsafe?.user) &&
|
|
645
708
|
typeof initDataUnsafe?.user?.id === "number",
|
|
646
709
|
error: error instanceof Error
|
|
@@ -651,74 +714,6 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
651
714
|
}
|
|
652
715
|
return;
|
|
653
716
|
}
|
|
654
|
-
if (requestUrl.pathname === `${webAppBasePath}/api/view`) {
|
|
655
|
-
if (method !== "GET") {
|
|
656
|
-
writeText(res, 405, "Method not allowed");
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
const token = readBearerToken(req);
|
|
660
|
-
const webAppSession = token ? webAppSessions.get(token) : null;
|
|
661
|
-
if (!webAppSession) {
|
|
662
|
-
writeText(res, 401, "Unauthorized");
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
const relayTarget = (0, relay_1.parseLiveRelaySessionId)(webAppSession.sessionId);
|
|
666
|
-
if (relayTarget) {
|
|
667
|
-
try {
|
|
668
|
-
const result = await runtime.gatewayHttpService.requestLiveRelayView({
|
|
669
|
-
clientUuid: relayTarget.clientUuid,
|
|
670
|
-
localSessionId: relayTarget.localSessionId,
|
|
671
|
-
});
|
|
672
|
-
writeJson(res, 200, result);
|
|
673
|
-
}
|
|
674
|
-
catch (error) {
|
|
675
|
-
runtime.logger.error("Telegram WebApp relay visible buffer capture failed", {
|
|
676
|
-
sessionId: webAppSession.sessionId,
|
|
677
|
-
clientUuid: relayTarget.clientUuid,
|
|
678
|
-
localSessionId: relayTarget.localSessionId,
|
|
679
|
-
error: error instanceof Error
|
|
680
|
-
? (error.stack ?? error.message)
|
|
681
|
-
: String(error),
|
|
682
|
-
});
|
|
683
|
-
writeText(res, isRelayTerminalUnavailableMessage(error) ? 503 : 500, error instanceof Error ? error.message : "Failed to capture relay terminal buffer");
|
|
684
|
-
}
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
const binding = await runtime.bindingStore.getBinding(webAppSession.sessionId);
|
|
688
|
-
if (!binding ||
|
|
689
|
-
binding.telegramUserId !== webAppSession.telegramUserId) {
|
|
690
|
-
writeText(res, 403, "Session binding is no longer valid");
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
|
-
const session = await runtime.sessionStore.getSession(webAppSession.sessionId);
|
|
694
|
-
if (!session?.terminalTarget) {
|
|
695
|
-
writeText(res, 409, "terminal target is not configured for this session");
|
|
696
|
-
return;
|
|
697
|
-
}
|
|
698
|
-
try {
|
|
699
|
-
const terminalSize = await (0, terminal_1.getTerminalWindowSize)(runtime.config.terminal, session.terminalTarget);
|
|
700
|
-
const content = await (0, terminal_1.captureVisibleTerminal)(runtime.config.terminal, session.terminalTarget, runtime.config.terminal.captureLines, runtime.config.webapp.visibleScreens);
|
|
701
|
-
const ansi = await (0, terminal_1.captureVisibleTerminalAnsi)(runtime.config.terminal, session.terminalTarget, runtime.config.terminal.captureLines, runtime.config.webapp.visibleScreens);
|
|
702
|
-
writeJson(res, 200, {
|
|
703
|
-
session_id: session.sessionId,
|
|
704
|
-
session_label: session.label ?? null,
|
|
705
|
-
captured_at: new Date().toISOString(),
|
|
706
|
-
content,
|
|
707
|
-
ansi,
|
|
708
|
-
...(terminalSize ? terminalSize : {}),
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
catch (error) {
|
|
712
|
-
runtime.logger.error("Telegram WebApp visible buffer capture failed", {
|
|
713
|
-
sessionId: webAppSession.sessionId,
|
|
714
|
-
error: error instanceof Error
|
|
715
|
-
? (error.stack ?? error.message)
|
|
716
|
-
: String(error),
|
|
717
|
-
});
|
|
718
|
-
writeText(res, (0, terminal_1.isTerminalUnavailableError)(error) ? 503 : 500, formatTerminalHttpError(error, "Failed to capture visible terminal buffer"));
|
|
719
|
-
}
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
717
|
if (requestUrl.pathname === `${webAppBasePath}/api/action`) {
|
|
723
718
|
if (method !== "POST") {
|
|
724
719
|
writeText(res, 405, "Method not allowed");
|
|
@@ -730,7 +725,7 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
730
725
|
writeText(res, 401, "Unauthorized");
|
|
731
726
|
return;
|
|
732
727
|
}
|
|
733
|
-
const body = await readJsonBody(req).catch(
|
|
728
|
+
const body = await readJsonBody(req).catch(ignoreInvalidJsonBody);
|
|
734
729
|
const action = body &&
|
|
735
730
|
typeof body === "object" &&
|
|
736
731
|
typeof Reflect.get(body, "action") === "string"
|
|
@@ -921,16 +916,22 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
921
916
|
});
|
|
922
917
|
return;
|
|
923
918
|
}
|
|
924
|
-
if (requestUrl.pathname !==
|
|
919
|
+
if (requestUrl.pathname !== mcpHttpPath &&
|
|
920
|
+
requestUrl.pathname !== `${mcpHttpPath}/`) {
|
|
925
921
|
writeText(res, 404, "Not found");
|
|
926
922
|
return;
|
|
927
923
|
}
|
|
928
|
-
|
|
924
|
+
const authorizationMode = resolveMcpHttpAuthorization(req, runtime.config.mcp.bearerToken, oauthFacade);
|
|
925
|
+
if (!authorizationMode) {
|
|
926
|
+
const authorizationHeader = readHeader(req, "authorization");
|
|
929
927
|
runtime.logger.warn("Unauthorized MCP HTTP request rejected", {
|
|
930
928
|
method,
|
|
931
929
|
path: requestUrl.pathname,
|
|
932
930
|
remoteAddress: req.socket.remoteAddress,
|
|
931
|
+
authorizationHeaderPresent: Boolean(authorizationHeader),
|
|
932
|
+
authorizationScheme: authorizationHeader?.split(/\s+/u, 1)[0],
|
|
933
933
|
});
|
|
934
|
+
oauthFacade?.writeMcpChallenge(res);
|
|
934
935
|
writeText(res, 401, "Unauthorized");
|
|
935
936
|
return;
|
|
936
937
|
}
|
|
@@ -938,12 +939,33 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
938
939
|
const parsedBody = method === "POST" || method === "DELETE"
|
|
939
940
|
? await readJsonBody(req)
|
|
940
941
|
: undefined;
|
|
942
|
+
const rpcMethod = readMcpRpcMethod(parsedBody);
|
|
941
943
|
runtime.logger.debug("MCP HTTP request received", {
|
|
942
944
|
method,
|
|
943
945
|
path: requestUrl.pathname,
|
|
944
946
|
sessionId,
|
|
947
|
+
authorizationMode,
|
|
948
|
+
rpcMethod,
|
|
945
949
|
hasBody: parsedBody !== undefined,
|
|
946
950
|
});
|
|
951
|
+
if (rpcMethod === "initialize" || rpcMethod === "tools/list") {
|
|
952
|
+
runtime.logger.info("Chat connector MCP handshake request received", {
|
|
953
|
+
rpcMethod,
|
|
954
|
+
authorizationMode,
|
|
955
|
+
sessionId,
|
|
956
|
+
userAgent: readHeader(req, "user-agent"),
|
|
957
|
+
});
|
|
958
|
+
res.once("finish", () => {
|
|
959
|
+
runtime.logger.info("Chat connector MCP handshake response completed", {
|
|
960
|
+
rpcMethod,
|
|
961
|
+
authorizationMode,
|
|
962
|
+
statusCode: res.statusCode,
|
|
963
|
+
sessionId: readHeader(req, "mcp-session-id") ??
|
|
964
|
+
(String(res.getHeader("mcp-session-id") ?? "") || undefined),
|
|
965
|
+
contentType: String(res.getHeader("content-type") ?? ""),
|
|
966
|
+
});
|
|
967
|
+
});
|
|
968
|
+
}
|
|
947
969
|
try {
|
|
948
970
|
if (method === "POST") {
|
|
949
971
|
if (sessionId) {
|
|
@@ -1090,6 +1112,24 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
1090
1112
|
}
|
|
1091
1113
|
}
|
|
1092
1114
|
};
|
|
1115
|
+
const handleRequest = async (req, res, pathname) => {
|
|
1116
|
+
try {
|
|
1117
|
+
await handleRequestInternal(req, res, pathname);
|
|
1118
|
+
}
|
|
1119
|
+
catch (error) {
|
|
1120
|
+
if (!(0, bodyLimits_1.isBodySizeLimitError)(error)) {
|
|
1121
|
+
throw error;
|
|
1122
|
+
}
|
|
1123
|
+
if (!res.headersSent) {
|
|
1124
|
+
writeJson(res, 413, {
|
|
1125
|
+
error: `Request body exceeds the ${bodyLimits_1.MAX_BODY_SIZE} MiB limit`,
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
else if (!res.writableEnded) {
|
|
1129
|
+
res.end();
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1093
1133
|
let shuttingDown = false;
|
|
1094
1134
|
const handleUpgrade = async (req, socket, head, pathname) => {
|
|
1095
1135
|
const normalizedPathname = normalizePrefixedPathname(pathname);
|