@dereekb/firebase-server 13.36.0 → 13.37.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/calcom/package.json +10 -10
- package/discord/package.json +10 -10
- package/index.cjs.js +1 -1
- package/index.esm.js +1 -1
- package/mailgun/package.json +9 -9
- package/mcp/index.cjs.js +282 -63
- package/mcp/index.esm.js +280 -61
- package/mcp/package.json +13 -12
- package/mcp/src/lib/controller/mcp.controller.d.ts +28 -3
- package/mcp/src/lib/controller/mcp.wellknown.controller.d.ts +21 -1
- package/mcp/src/lib/mcp.config.d.ts +9 -7
- package/mcp/src/lib/mcp.module.d.ts +2 -1
- package/mcp/src/lib/service/mcp.server.factory.d.ts +5 -4
- package/mcp/src/lib/service/mcp.tool-generator.d.ts +1 -1
- package/mcp/src/lib/service/mcp.visibility.d.ts +1 -1
- package/mcp/src/lib/transport/streamable-http.transport.d.ts +16 -11
- package/model/index.cjs.js +1 -1
- package/model/index.esm.js +1 -1
- package/model/package.json +9 -9
- package/oidc/index.cjs.js +257 -67
- package/oidc/index.esm.js +256 -69
- package/oidc/package.json +10 -10
- package/oidc/src/lib/profile.d.ts +21 -0
- package/oidc/src/lib/service/index.d.ts +1 -0
- package/oidc/src/lib/service/oidc.config.service.d.ts +14 -0
- package/oidc/src/lib/service/oidc.interaction-policy.d.ts +35 -0
- package/oidc/src/lib/service/oidc.service.d.ts +29 -0
- package/package.json +14 -13
- package/test/index.cjs.js +8 -8
- package/test/index.esm.js +8 -8
- package/test/package.json +11 -11
- package/twilio/package.json +8 -8
- package/zoho/package.json +10 -10
package/mcp/index.cjs.js
CHANGED
|
@@ -2,15 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
var common = require('@nestjs/common');
|
|
4
4
|
var node_fs = require('node:fs');
|
|
5
|
-
var
|
|
6
|
-
var types_js = require('@modelcontextprotocol/sdk/types.js');
|
|
5
|
+
var server = require('@modelcontextprotocol/server');
|
|
7
6
|
var firebase = require('@dereekb/firebase');
|
|
8
7
|
var oidc = require('@dereekb/firebase-server/oidc');
|
|
9
8
|
var util = require('@dereekb/util');
|
|
10
9
|
var firebaseServer = require('@dereekb/firebase-server');
|
|
11
10
|
var node_util = require('node:util');
|
|
12
11
|
var model = require('@dereekb/model');
|
|
13
|
-
var
|
|
12
|
+
var node = require('@modelcontextprotocol/node');
|
|
14
13
|
|
|
15
14
|
function _class_call_check$5(instance, Constructor) {
|
|
16
15
|
if (!(instance instanceof Constructor)) {
|
|
@@ -102,11 +101,12 @@ function _define_property$9(obj, key, value) {
|
|
|
102
101
|
* protected-resource discovery document's `scopes_supported` (RFC 9728 §2).
|
|
103
102
|
*
|
|
104
103
|
* The base list is pulled automatically from the OIDC provider config via the
|
|
105
|
-
* injected {@link OidcProviderConfigService.
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
104
|
+
* injected {@link OidcProviderConfigService.clientRequestableScopesSupported}, so
|
|
105
|
+
* the MCP resource advertises the scopes the issuer grants an arbitrary client
|
|
106
|
+
* without the app restating them. Scopes only an admin-assigned provider profile
|
|
107
|
+
* unlocks are already excluded — requesting one is fatal at the consent unlock
|
|
108
|
+
* gate. Provide this only to narrow the set further; it returns the subset to
|
|
109
|
+
* advertise. When unset, the whole base list is advertised.
|
|
110
110
|
*
|
|
111
111
|
* Advertising these matters because dynamic-registration MCP clients (the Claude
|
|
112
112
|
* Code CLI) read `scopes_supported` to decide which scopes to request on the
|
|
@@ -2038,8 +2038,8 @@ function modelGetToolHandler(args, ctx, deps) {
|
|
|
2038
2038
|
})();
|
|
2039
2039
|
}
|
|
2040
2040
|
function parseModelGetInput(args) {
|
|
2041
|
-
var modelType = args
|
|
2042
|
-
var keys = args
|
|
2041
|
+
var modelType = args['modelType'];
|
|
2042
|
+
var keys = args['keys'];
|
|
2043
2043
|
if (typeof modelType !== 'string' || modelType.length === 0) {
|
|
2044
2044
|
throw new Error('model-get: "modelType" is required and must be a non-empty string.');
|
|
2045
2045
|
}
|
|
@@ -2940,7 +2940,7 @@ var MODEL_ROW_SCHEMA = {
|
|
|
2940
2940
|
}
|
|
2941
2941
|
};
|
|
2942
2942
|
// MCP's `tools/list` validator requires `outputSchema.type === 'object'` at the root
|
|
2943
|
-
// (see
|
|
2943
|
+
// (see the MCP SDK's `ToolSchema`), so the variants are expressed via a top-level
|
|
2944
2944
|
// object whose nested `oneOf` discriminates by the `mode` literal.
|
|
2945
2945
|
var MODEL_INFO_OUTPUT_SCHEMA = {
|
|
2946
2946
|
type: 'object',
|
|
@@ -3742,7 +3742,7 @@ function whoamiToolHandler(args, ctx, deps) {
|
|
|
3742
3742
|
}
|
|
3743
3743
|
// Coerces the `detail` input into a WhoamiDetailLevel, defaulting unknown or absent values to `summary`.
|
|
3744
3744
|
function parseDetailLevel(args) {
|
|
3745
|
-
var value = args
|
|
3745
|
+
var value = args['detail'];
|
|
3746
3746
|
return value === 'claims' || value === 'full' ? value : 'summary';
|
|
3747
3747
|
}
|
|
3748
3748
|
function buildResult(output, text) {
|
|
@@ -4337,30 +4337,30 @@ function urlModelsToolHandler(args, ctx, deps) {
|
|
|
4337
4337
|
})();
|
|
4338
4338
|
}
|
|
4339
4339
|
function parseUrlModelsInput(args) {
|
|
4340
|
-
var url = args
|
|
4340
|
+
var url = args['url'];
|
|
4341
4341
|
if (typeof url !== 'string' || url.length === 0) {
|
|
4342
4342
|
throw new Error('url-models: "url" is required and must be a non-empty string.');
|
|
4343
4343
|
}
|
|
4344
|
-
var keysOnly = args
|
|
4345
|
-
var load = args
|
|
4344
|
+
var keysOnly = args['keysOnly'] === true;
|
|
4345
|
+
var load = args['load'] === true;
|
|
4346
4346
|
if (keysOnly && load) {
|
|
4347
4347
|
throw new Error('url-models: "keysOnly" and "load" cannot be combined — "keysOnly" returns keys without loading documents.');
|
|
4348
4348
|
}
|
|
4349
4349
|
var models;
|
|
4350
|
-
if (args
|
|
4351
|
-
if (!Array.isArray(args
|
|
4350
|
+
if (args['models'] !== undefined) {
|
|
4351
|
+
if (!Array.isArray(args['models']) || args['models'].some(function(m) {
|
|
4352
4352
|
return typeof m !== 'string';
|
|
4353
4353
|
})) {
|
|
4354
4354
|
throw new Error('url-models: "models" must be an array of model-type strings.');
|
|
4355
4355
|
}
|
|
4356
|
-
models = args
|
|
4356
|
+
models = args['models'];
|
|
4357
4357
|
}
|
|
4358
4358
|
var currentUserUid;
|
|
4359
|
-
if (args
|
|
4360
|
-
if (typeof args
|
|
4359
|
+
if (args['currentUserUid'] !== undefined) {
|
|
4360
|
+
if (typeof args['currentUserUid'] !== 'string' || args['currentUserUid'].length === 0) {
|
|
4361
4361
|
throw new Error('url-models: "currentUserUid" must be a non-empty string.');
|
|
4362
4362
|
}
|
|
4363
|
-
currentUserUid = args
|
|
4363
|
+
currentUserUid = args['currentUserUid'];
|
|
4364
4364
|
}
|
|
4365
4365
|
return _object_spread$2(_object_spread_props$1(_object_spread$2({
|
|
4366
4366
|
url: url
|
|
@@ -5308,19 +5308,19 @@ function batchExecuteToolHandler(args, ctx, deps) {
|
|
|
5308
5308
|
// MARK: Input parsing
|
|
5309
5309
|
function parseBatchExecuteInput(args) {
|
|
5310
5310
|
var _args_format, _args_maxParallel;
|
|
5311
|
-
var uploadPath = args
|
|
5311
|
+
var uploadPath = args['uploadPath'];
|
|
5312
5312
|
if (typeof uploadPath !== 'string' || uploadPath.length === 0) {
|
|
5313
5313
|
throw new Error('batch-execute: "uploadPath" is required and must be a non-empty string.');
|
|
5314
5314
|
}
|
|
5315
|
-
var bucketId = args
|
|
5315
|
+
var bucketId = args['bucketId'];
|
|
5316
5316
|
if (bucketId != null && (typeof bucketId !== 'string' || bucketId.length === 0)) {
|
|
5317
5317
|
throw new Error('batch-execute: "bucketId" must be a non-empty string when provided.');
|
|
5318
5318
|
}
|
|
5319
|
-
var format = (_args_format = args
|
|
5319
|
+
var format = (_args_format = args['format']) !== null && _args_format !== void 0 ? _args_format : 'ndjson';
|
|
5320
5320
|
if (format !== 'json' && format !== 'ndjson') {
|
|
5321
5321
|
throw new Error('batch-execute: "format" must be "json" or "ndjson".');
|
|
5322
5322
|
}
|
|
5323
|
-
var maxParallel = (_args_maxParallel = args
|
|
5323
|
+
var maxParallel = (_args_maxParallel = args['maxParallel']) !== null && _args_maxParallel !== void 0 ? _args_maxParallel : DEFAULT_BATCH_EXECUTE_MAX_PARALLEL;
|
|
5324
5324
|
if (typeof maxParallel !== 'number' || !Number.isInteger(maxParallel) || maxParallel < 1) {
|
|
5325
5325
|
throw new Error('batch-execute: "maxParallel" must be a positive integer.');
|
|
5326
5326
|
}
|
|
@@ -5329,8 +5329,8 @@ function parseBatchExecuteInput(args) {
|
|
|
5329
5329
|
bucketId: bucketId !== null && bucketId !== void 0 ? bucketId : undefined,
|
|
5330
5330
|
format: format,
|
|
5331
5331
|
maxParallel: maxParallel,
|
|
5332
|
-
stopOnError: args
|
|
5333
|
-
deleteUploadOnSuccess: args
|
|
5332
|
+
stopOnError: args['stopOnError'] === true,
|
|
5333
|
+
deleteUploadOnSuccess: args['deleteUploadOnSuccess'] !== false
|
|
5334
5334
|
};
|
|
5335
5335
|
}
|
|
5336
5336
|
function readOperationsFile(input) {
|
|
@@ -5411,9 +5411,9 @@ function validateOperation(value, index) {
|
|
|
5411
5411
|
throw new Error("batch-execute: operation[".concat(index, "] must be an object."));
|
|
5412
5412
|
}
|
|
5413
5413
|
var candidate = value;
|
|
5414
|
-
var modelType = candidate
|
|
5415
|
-
var call = candidate
|
|
5416
|
-
var specifier = candidate
|
|
5414
|
+
var modelType = candidate['modelType'];
|
|
5415
|
+
var call = candidate['call'];
|
|
5416
|
+
var specifier = candidate['specifier'];
|
|
5417
5417
|
if (typeof modelType !== 'string' || modelType.length === 0) {
|
|
5418
5418
|
throw new Error("batch-execute: operation[".concat(index, '] is missing a non-empty "modelType".'));
|
|
5419
5419
|
}
|
|
@@ -5427,7 +5427,7 @@ function validateOperation(value, index) {
|
|
|
5427
5427
|
call: call,
|
|
5428
5428
|
modelType: modelType,
|
|
5429
5429
|
specifier: specifier !== null && specifier !== void 0 ? specifier : undefined,
|
|
5430
|
-
data: candidate
|
|
5430
|
+
data: candidate['data']
|
|
5431
5431
|
};
|
|
5432
5432
|
}
|
|
5433
5433
|
function collectPreflightErrors(operations, authorizeOperation) {
|
|
@@ -6055,9 +6055,10 @@ var McpServerFactoryService_1;
|
|
|
6055
6055
|
* Injectable factory that builds {@link McpServer} instances pre-wired to the
|
|
6056
6056
|
* call model dispatch chain.
|
|
6057
6057
|
*
|
|
6058
|
-
* The factory is invoked per Streamable HTTP request
|
|
6059
|
-
*
|
|
6060
|
-
*
|
|
6058
|
+
* The factory is invoked per Streamable HTTP request. That is the shape the MCP
|
|
6059
|
+
* SDK's 2026-07-28 entry (`createMcpHandler`) requires — it builds a fresh server
|
|
6060
|
+
* per request and has no session concept at all — and it is equally what the SDK
|
|
6061
|
+
* recommends for stateless 2025-era serving, so one factory backs both eras.
|
|
6061
6062
|
*/ exports.McpServerFactoryService = McpServerFactoryService_1 = /*#__PURE__*/ function() {
|
|
6062
6063
|
function McpServerFactoryService(mcpConfig, dispatchService, modelApiGetService, roleReader, analyticsService, storageService) {
|
|
6063
6064
|
_class_call_check$3(this, McpServerFactoryService);
|
|
@@ -6100,13 +6101,13 @@ var McpServerFactoryService_1;
|
|
|
6100
6101
|
var _this_mcpConfig_serverName, _this_mcpConfig_serverVersion, _this_mcpConfig_serverInstructions;
|
|
6101
6102
|
var baseServerName = (_this_mcpConfig_serverName = this.mcpConfig.serverName) !== null && _this_mcpConfig_serverName !== void 0 ? _this_mcpConfig_serverName : DEFAULT_MCP_SERVER_NAME;
|
|
6102
6103
|
var serverName = this.mcpConfig.readOnly === true ? "".concat(baseServerName, " (read-only)") : baseServerName;
|
|
6103
|
-
var server = new
|
|
6104
|
+
var server$1 = new server.McpServer({
|
|
6104
6105
|
name: serverName,
|
|
6105
6106
|
version: (_this_mcpConfig_serverVersion = this.mcpConfig.serverVersion) !== null && _this_mcpConfig_serverVersion !== void 0 ? _this_mcpConfig_serverVersion : '0.0.0'
|
|
6106
6107
|
}, {
|
|
6107
6108
|
instructions: (_this_mcpConfig_serverInstructions = this.mcpConfig.serverInstructions) !== null && _this_mcpConfig_serverInstructions !== void 0 ? _this_mcpConfig_serverInstructions : DEFAULT_MCP_SERVER_INSTRUCTIONS
|
|
6108
6109
|
});
|
|
6109
|
-
server.server.registerCapabilities({
|
|
6110
|
+
server$1.server.registerCapabilities({
|
|
6110
6111
|
tools: {}
|
|
6111
6112
|
});
|
|
6112
6113
|
var toolGeneration = this._resolveToolDefinitions();
|
|
@@ -6135,7 +6136,7 @@ var McpServerFactoryService_1;
|
|
|
6135
6136
|
batchTool
|
|
6136
6137
|
]);
|
|
6137
6138
|
}
|
|
6138
|
-
server.server.setRequestHandler(
|
|
6139
|
+
server$1.server.setRequestHandler('tools/list', function() {
|
|
6139
6140
|
return _async_to_generator$2(function() {
|
|
6140
6141
|
var _this;
|
|
6141
6142
|
return _ts_generator$2(this, function(_state) {
|
|
@@ -6143,6 +6144,10 @@ var McpServerFactoryService_1;
|
|
|
6143
6144
|
return [
|
|
6144
6145
|
2,
|
|
6145
6146
|
{
|
|
6147
|
+
// McpToolListEntry types its schemas as opaque `object`: they are read from a manifest file
|
|
6148
|
+
// or produced by arktype's JSON Schema export, so the `type: 'object'` root the SDK's wire
|
|
6149
|
+
// type mandates holds at runtime but cannot be proven statically. Every schema reaching here
|
|
6150
|
+
// either came from arktypeToJsonSchemaForExport or defaulted to `{ type: 'object' }`.
|
|
6146
6151
|
tools: listedTools.map(function(tool) {
|
|
6147
6152
|
return _this._buildToolListEntry(tool, ctx, scopes);
|
|
6148
6153
|
})
|
|
@@ -6151,7 +6156,7 @@ var McpServerFactoryService_1;
|
|
|
6151
6156
|
});
|
|
6152
6157
|
}).call(_this);
|
|
6153
6158
|
});
|
|
6154
|
-
server.server.setRequestHandler(
|
|
6159
|
+
server$1.server.setRequestHandler('tools/call', function(request) {
|
|
6155
6160
|
return _async_to_generator$2(function() {
|
|
6156
6161
|
return _ts_generator$2(this, function(_state) {
|
|
6157
6162
|
return [
|
|
@@ -6161,7 +6166,7 @@ var McpServerFactoryService_1;
|
|
|
6161
6166
|
});
|
|
6162
6167
|
}).call(_this);
|
|
6163
6168
|
});
|
|
6164
|
-
return server;
|
|
6169
|
+
return server$1;
|
|
6165
6170
|
}
|
|
6166
6171
|
},
|
|
6167
6172
|
{
|
|
@@ -7236,29 +7241,168 @@ function _ts_generator$1(thisArg, body) {
|
|
|
7236
7241
|
}
|
|
7237
7242
|
}
|
|
7238
7243
|
/**
|
|
7239
|
-
* Handles a single Streamable HTTP JSON-RPC request
|
|
7244
|
+
* Handles a single Streamable HTTP JSON-RPC request, building a fresh MCP server for it.
|
|
7240
7245
|
*
|
|
7241
|
-
*
|
|
7242
|
-
*
|
|
7243
|
-
*
|
|
7244
|
-
*
|
|
7246
|
+
* Both protocol eras are served, each by its own leg:
|
|
7247
|
+
*
|
|
7248
|
+
* - **2026-07-28 (modern)** — `createMcpHandler`, which is per-request and stateless by
|
|
7249
|
+
* construction: there is no `Mcp-Session-Id` and no transport bookkeeping.
|
|
7250
|
+
* - **2025-era (legacy)** — a `NodeStreamableHTTPServerTransport` in stateless mode, which is
|
|
7251
|
+
* byte-for-byte what this endpoint served before the SDK v2 upgrade.
|
|
7252
|
+
*
|
|
7253
|
+
* The legacy leg is wired by hand (the SDK's documented `isLegacyRequest` composition) rather
|
|
7254
|
+
* than left to `createMcpHandler`'s built-in `legacy: 'stateless'` fallback, because that
|
|
7255
|
+
* fallback builds its transport with `sessionIdGenerator` alone and therefore answers in
|
|
7256
|
+
* `text/event-stream`. See {@link handleLegacyMcpRequest} for why that framing is not viable
|
|
7257
|
+
* here.
|
|
7245
7258
|
*
|
|
7246
7259
|
* The caller is expected to have already validated the bearer token (via the
|
|
7247
7260
|
* OIDC bearer middleware) before this function runs.
|
|
7248
7261
|
*
|
|
7249
7262
|
* @param req - The Express request carrying the JSON-RPC body.
|
|
7250
7263
|
* @param res - The Express response.
|
|
7264
|
+
* @param buildServer - Factory producing the MCP server that backs this request.
|
|
7265
|
+
*/ function handleStreamableHttpMcpRequest(req, res, buildServer) {
|
|
7266
|
+
return _async_to_generator$1(function() {
|
|
7267
|
+
var probe;
|
|
7268
|
+
return _ts_generator$1(this, function(_state) {
|
|
7269
|
+
switch(_state.label){
|
|
7270
|
+
case 0:
|
|
7271
|
+
// Hand-wired compositions must reject non-JSON POST bodies themselves — neither the modern
|
|
7272
|
+
// entry's routing nor the legacy transport does it on our behalf once we classify up front.
|
|
7273
|
+
if (!server.isJsonContentType(req.headers['content-type'])) {
|
|
7274
|
+
res.status(415).json({
|
|
7275
|
+
statusCode: 415,
|
|
7276
|
+
message: 'Unsupported Media Type'
|
|
7277
|
+
});
|
|
7278
|
+
return [
|
|
7279
|
+
2
|
|
7280
|
+
];
|
|
7281
|
+
}
|
|
7282
|
+
return [
|
|
7283
|
+
4,
|
|
7284
|
+
node.toWebRequest(req, req.body)
|
|
7285
|
+
];
|
|
7286
|
+
case 1:
|
|
7287
|
+
probe = _state.sent();
|
|
7288
|
+
return [
|
|
7289
|
+
4,
|
|
7290
|
+
server.isLegacyRequest(probe)
|
|
7291
|
+
];
|
|
7292
|
+
case 2:
|
|
7293
|
+
if (!_state.sent()) return [
|
|
7294
|
+
3,
|
|
7295
|
+
4
|
|
7296
|
+
];
|
|
7297
|
+
return [
|
|
7298
|
+
4,
|
|
7299
|
+
handleLegacyMcpRequest(req, res, buildServer())
|
|
7300
|
+
];
|
|
7301
|
+
case 3:
|
|
7302
|
+
_state.sent();
|
|
7303
|
+
return [
|
|
7304
|
+
3,
|
|
7305
|
+
6
|
|
7306
|
+
];
|
|
7307
|
+
case 4:
|
|
7308
|
+
return [
|
|
7309
|
+
4,
|
|
7310
|
+
handleModernMcpRequest(req, res, buildServer)
|
|
7311
|
+
];
|
|
7312
|
+
case 5:
|
|
7313
|
+
_state.sent();
|
|
7314
|
+
_state.label = 6;
|
|
7315
|
+
case 6:
|
|
7316
|
+
return [
|
|
7317
|
+
2
|
|
7318
|
+
];
|
|
7319
|
+
}
|
|
7320
|
+
});
|
|
7321
|
+
})();
|
|
7322
|
+
}
|
|
7323
|
+
/**
|
|
7324
|
+
* Serves one 2026-07-28 request through `createMcpHandler`.
|
|
7325
|
+
*
|
|
7326
|
+
* `legacy: 'reject'` keeps this leg modern-only — 2025-era traffic never reaches it, having
|
|
7327
|
+
* already been routed to {@link handleLegacyMcpRequest}.
|
|
7328
|
+
*
|
|
7329
|
+
* `responseMode: 'json'` is the modern-leg equivalent of the legacy transport's
|
|
7330
|
+
* `enableJsonResponse`, keeping the endpoint on a single `application/json` body. Safe here
|
|
7331
|
+
* because no tool handler emits mid-call notifications, which are the only thing `'json'` drops.
|
|
7332
|
+
*
|
|
7333
|
+
* @param req - The Express request carrying the JSON-RPC body.
|
|
7334
|
+
* @param res - The Express response.
|
|
7335
|
+
* @param buildServer - Factory producing the MCP server that backs this request.
|
|
7336
|
+
*/ function handleModernMcpRequest(req, res, buildServer) {
|
|
7337
|
+
return _async_to_generator$1(function() {
|
|
7338
|
+
var handler;
|
|
7339
|
+
return _ts_generator$1(this, function(_state) {
|
|
7340
|
+
switch(_state.label){
|
|
7341
|
+
case 0:
|
|
7342
|
+
handler = server.createMcpHandler(buildServer, {
|
|
7343
|
+
legacy: 'reject',
|
|
7344
|
+
responseMode: 'json'
|
|
7345
|
+
});
|
|
7346
|
+
_state.label = 1;
|
|
7347
|
+
case 1:
|
|
7348
|
+
_state.trys.push([
|
|
7349
|
+
1,
|
|
7350
|
+
,
|
|
7351
|
+
3,
|
|
7352
|
+
5
|
|
7353
|
+
]);
|
|
7354
|
+
return [
|
|
7355
|
+
4,
|
|
7356
|
+
node.toNodeHandler(handler)(req, res, req.body)
|
|
7357
|
+
];
|
|
7358
|
+
case 2:
|
|
7359
|
+
_state.sent();
|
|
7360
|
+
return [
|
|
7361
|
+
3,
|
|
7362
|
+
5
|
|
7363
|
+
];
|
|
7364
|
+
case 3:
|
|
7365
|
+
return [
|
|
7366
|
+
4,
|
|
7367
|
+
handler.close()
|
|
7368
|
+
];
|
|
7369
|
+
case 4:
|
|
7370
|
+
_state.sent();
|
|
7371
|
+
return [
|
|
7372
|
+
7
|
|
7373
|
+
];
|
|
7374
|
+
case 5:
|
|
7375
|
+
return [
|
|
7376
|
+
2
|
|
7377
|
+
];
|
|
7378
|
+
}
|
|
7379
|
+
});
|
|
7380
|
+
})();
|
|
7381
|
+
}
|
|
7382
|
+
/**
|
|
7383
|
+
* Serves one 2025-era request through a stateless `NodeStreamableHTTPServerTransport`.
|
|
7384
|
+
*
|
|
7385
|
+
* `sessionIdGenerator: undefined` selects stateless mode: the transport binds the server,
|
|
7386
|
+
* processes one request, and is discarded.
|
|
7387
|
+
*
|
|
7388
|
+
* `enableJsonResponse: true` replies with a single `application/json` body instead of framing it
|
|
7389
|
+
* as `text/event-stream`. Stateless mode answers exactly one JSON-RPC message per request and the
|
|
7390
|
+
* controller only implements POST (there is no standalone SSE stream to keep open), so SSE framing
|
|
7391
|
+
* adds nothing while making the endpoint fragile behind proxies that buffer or rewrite streamed
|
|
7392
|
+
* responses — the failure that originally pushed the demo app off its dev proxy.
|
|
7393
|
+
*
|
|
7394
|
+
* @param req - The Express request carrying the JSON-RPC body.
|
|
7395
|
+
* @param res - The Express response.
|
|
7251
7396
|
* @param server - The MCP server instance to back the transport.
|
|
7252
|
-
|
|
7253
|
-
*/ function handleStreamableHttpMcpRequest(req, res, server) {
|
|
7397
|
+
*/ function handleLegacyMcpRequest(req, res, server) {
|
|
7254
7398
|
return _async_to_generator$1(function() {
|
|
7255
7399
|
var transport;
|
|
7256
7400
|
return _ts_generator$1(this, function(_state) {
|
|
7257
7401
|
switch(_state.label){
|
|
7258
7402
|
case 0:
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7403
|
+
transport = new node.NodeStreamableHTTPServerTransport({
|
|
7404
|
+
sessionIdGenerator: undefined,
|
|
7405
|
+
enableJsonResponse: true
|
|
7262
7406
|
});
|
|
7263
7407
|
res.on('close', function() {
|
|
7264
7408
|
void transport.close();
|
|
@@ -7450,9 +7594,11 @@ var McpController_1;
|
|
|
7450
7594
|
* which must include `'/mcp'` in its `protectedPaths`. By the time the request reaches
|
|
7451
7595
|
* this controller, `req.auth` is populated with the authenticated user's data.
|
|
7452
7596
|
*
|
|
7453
|
-
* Each request gets a fresh
|
|
7454
|
-
*
|
|
7455
|
-
*
|
|
7597
|
+
* Each request gets a fresh MCP server (stateless mode), which is adequate for Claude
|
|
7598
|
+
* custom-connector style usage. Both protocol eras are served — 2026-07-28 through the
|
|
7599
|
+
* SDK's per-request `createMcpHandler` entry, and 2025-era through a stateless Streamable
|
|
7600
|
+
* HTTP transport. A session-tracked variant can be layered on later if streaming tool
|
|
7601
|
+
* output becomes a requirement.
|
|
7456
7602
|
*/ exports.McpController = McpController_1 = /*#__PURE__*/ function() {
|
|
7457
7603
|
function McpController(factory) {
|
|
7458
7604
|
_class_call_check$2(this, McpController);
|
|
@@ -7465,15 +7611,12 @@ var McpController_1;
|
|
|
7465
7611
|
key: "handleMcpRequest",
|
|
7466
7612
|
value: function handleMcpRequest(req, res) {
|
|
7467
7613
|
return _async_to_generator(function() {
|
|
7468
|
-
var
|
|
7614
|
+
var _this, auth, error;
|
|
7469
7615
|
return _ts_generator(this, function(_state) {
|
|
7470
7616
|
switch(_state.label){
|
|
7471
7617
|
case 0:
|
|
7618
|
+
_this = this;
|
|
7472
7619
|
auth = req.auth;
|
|
7473
|
-
server = this.factory.createServer({
|
|
7474
|
-
auth: auth,
|
|
7475
|
-
rawRequest: req
|
|
7476
|
-
});
|
|
7477
7620
|
_state.label = 1;
|
|
7478
7621
|
case 1:
|
|
7479
7622
|
_state.trys.push([
|
|
@@ -7484,7 +7627,12 @@ var McpController_1;
|
|
|
7484
7627
|
]);
|
|
7485
7628
|
return [
|
|
7486
7629
|
4,
|
|
7487
|
-
handleStreamableHttpMcpRequest(req, res,
|
|
7630
|
+
handleStreamableHttpMcpRequest(req, res, function() {
|
|
7631
|
+
return _this.factory.createServer({
|
|
7632
|
+
auth: auth,
|
|
7633
|
+
rawRequest: req
|
|
7634
|
+
});
|
|
7635
|
+
})
|
|
7488
7636
|
];
|
|
7489
7637
|
case 2:
|
|
7490
7638
|
_state.sent();
|
|
@@ -7513,6 +7661,45 @@ var McpController_1;
|
|
|
7513
7661
|
});
|
|
7514
7662
|
}).call(this);
|
|
7515
7663
|
}
|
|
7664
|
+
},
|
|
7665
|
+
{
|
|
7666
|
+
/**
|
|
7667
|
+
* Rejects the Streamable HTTP transport's optional `GET` method, which opens a standalone SSE
|
|
7668
|
+
* stream. Not applicable in stateless mode. Separate from {@link handleUnsupportedDelete} because
|
|
7669
|
+
* NestJS binds one HTTP method per handler — stacking route decorators would silently drop one.
|
|
7670
|
+
*
|
|
7671
|
+
* @param res - The Express response to write the rejection to.
|
|
7672
|
+
*/ key: "handleUnsupportedGet",
|
|
7673
|
+
value: function handleUnsupportedGet(res) {
|
|
7674
|
+
this._rejectUnsupportedMethod(res);
|
|
7675
|
+
}
|
|
7676
|
+
},
|
|
7677
|
+
{
|
|
7678
|
+
/**
|
|
7679
|
+
* Rejects the Streamable HTTP transport's optional `DELETE` method, which tears down a session.
|
|
7680
|
+
* Not applicable in stateless mode — no session is ever issued.
|
|
7681
|
+
*
|
|
7682
|
+
* @param res - The Express response to write the rejection to.
|
|
7683
|
+
*/ key: "handleUnsupportedDelete",
|
|
7684
|
+
value: function handleUnsupportedDelete(res) {
|
|
7685
|
+
this._rejectUnsupportedMethod(res);
|
|
7686
|
+
}
|
|
7687
|
+
},
|
|
7688
|
+
{
|
|
7689
|
+
/**
|
|
7690
|
+
* Answers with a spec-conformant `405 Method Not Allowed` + `Allow` header. Without these
|
|
7691
|
+
* handlers NestJS answers `404`, which some clients treat as a hard failure rather than
|
|
7692
|
+
* "the server doesn't offer this".
|
|
7693
|
+
*
|
|
7694
|
+
* @param res - The Express response to write the rejection to.
|
|
7695
|
+
*/ key: "_rejectUnsupportedMethod",
|
|
7696
|
+
value: function _rejectUnsupportedMethod(res) {
|
|
7697
|
+
res.setHeader('Allow', 'POST');
|
|
7698
|
+
res.status(common.HttpStatus.METHOD_NOT_ALLOWED).json({
|
|
7699
|
+
statusCode: common.HttpStatus.METHOD_NOT_ALLOWED,
|
|
7700
|
+
message: 'Method Not Allowed'
|
|
7701
|
+
});
|
|
7702
|
+
}
|
|
7516
7703
|
}
|
|
7517
7704
|
]);
|
|
7518
7705
|
return McpController;
|
|
@@ -7522,6 +7709,14 @@ __decorate([
|
|
|
7522
7709
|
__param(0, common.Req()),
|
|
7523
7710
|
__param(1, common.Res())
|
|
7524
7711
|
], exports.McpController.prototype, "handleMcpRequest", null);
|
|
7712
|
+
__decorate([
|
|
7713
|
+
common.Get(),
|
|
7714
|
+
__param(0, common.Res())
|
|
7715
|
+
], exports.McpController.prototype, "handleUnsupportedGet", null);
|
|
7716
|
+
__decorate([
|
|
7717
|
+
common.Delete(),
|
|
7718
|
+
__param(0, common.Res())
|
|
7719
|
+
], exports.McpController.prototype, "handleUnsupportedDelete", null);
|
|
7525
7720
|
exports.McpController = McpController_1 = __decorate([
|
|
7526
7721
|
common.Controller('mcp'),
|
|
7527
7722
|
__param(0, common.Inject(exports.McpServerFactoryService))
|
|
@@ -7607,6 +7802,13 @@ function _unsupported_iterable_to_array$2(o, minLen) {
|
|
|
7607
7802
|
* URIs must live at the host root. Apps need to exclude `.well-known/{*path}`
|
|
7608
7803
|
* from any global API route prefix (see `FIREBASE_SERVER_OIDC_ROUTES_FOR_GLOBAL_ROUTE_EXCLUDE`
|
|
7609
7804
|
* in `firebase-server/oidc` for the corresponding OIDC excludes).
|
|
7805
|
+
*
|
|
7806
|
+
* Because the document must be reachable at the origin root, the MCP endpoint has to be
|
|
7807
|
+
* exposed through a host that serves the app at `/` — Firebase Hosting, or the hosting
|
|
7808
|
+
* emulator in development. Pointing a client straight at the Functions emulator origin
|
|
7809
|
+
* (`http://localhost:<port>/<project>/<region>/<function>/mcp`) breaks discovery: that
|
|
7810
|
+
* runtime only routes `/<project>/<region>/<function>/…`, so nothing answers at the root
|
|
7811
|
+
* and a client that has not yet seen a 401 challenge cannot find the issuer.
|
|
7610
7812
|
*/ exports.McpWellKnownController = /*#__PURE__*/ function() {
|
|
7611
7813
|
function McpWellKnownController(mcpConfig, oidcProviderConfigService) {
|
|
7612
7814
|
_class_call_check$1(this, McpWellKnownController);
|
|
@@ -7617,12 +7819,26 @@ function _unsupported_iterable_to_array$2(o, minLen) {
|
|
|
7617
7819
|
}
|
|
7618
7820
|
_create_class$1(McpWellKnownController, [
|
|
7619
7821
|
{
|
|
7620
|
-
|
|
7822
|
+
/**
|
|
7823
|
+
* Serves both RFC 9728 discovery URLs — the primary form (§3.1), which inserts the resource's
|
|
7824
|
+
* path after the well-known prefix (`/.well-known/oauth-protected-resource/mcp`), and the bare
|
|
7825
|
+
* form (`/.well-known/oauth-protected-resource`) that clients probe as a fallback.
|
|
7826
|
+
*
|
|
7827
|
+
* The document is identical either way; the resource identity comes from
|
|
7828
|
+
* {@link McpModuleConfig.mcpUrl}, not from the request path. Serving the primary form means
|
|
7829
|
+
* discovery succeeds on a client's first probe rather than depending on it implementing the
|
|
7830
|
+
* fallback.
|
|
7831
|
+
*
|
|
7832
|
+
* @returns The protected-resource metadata document.
|
|
7833
|
+
*/ key: "getProtectedResourceMetadata",
|
|
7621
7834
|
value: function getProtectedResourceMetadata() {
|
|
7622
7835
|
var _this_mcpConfig = this.mcpConfig, mcpUrl = _this_mcpConfig.mcpUrl, oidcIssuer = _this_mcpConfig.oidcIssuer, scopesFilter = _this_mcpConfig.scopesSupported;
|
|
7623
|
-
// Base scope list is the
|
|
7624
|
-
//
|
|
7625
|
-
|
|
7836
|
+
// Base scope list is what the issuer grants an arbitrary client, NOT its full
|
|
7837
|
+
// `scopes_supported`: a dynamic-registration client copies this list verbatim onto
|
|
7838
|
+
// `/authorize`, and a provider-profile-gated scope in it would be hard-rejected at the
|
|
7839
|
+
// consent unlock gate (`access_denied`) for every client lacking that profile assignment.
|
|
7840
|
+
// The optional filter narrows the list further.
|
|
7841
|
+
var providerScopes = this.oidcProviderConfigService.clientRequestableScopesSupported;
|
|
7626
7842
|
var scopes = scopesFilter ? scopesFilter(providerScopes) : providerScopes;
|
|
7627
7843
|
return _object_spread({
|
|
7628
7844
|
resource: mcpUrl,
|
|
@@ -7638,7 +7854,10 @@ function _unsupported_iterable_to_array$2(o, minLen) {
|
|
|
7638
7854
|
return McpWellKnownController;
|
|
7639
7855
|
}();
|
|
7640
7856
|
__decorate([
|
|
7641
|
-
common.Get(
|
|
7857
|
+
common.Get([
|
|
7858
|
+
'oauth-protected-resource',
|
|
7859
|
+
'oauth-protected-resource/{*path}'
|
|
7860
|
+
])
|
|
7642
7861
|
], exports.McpWellKnownController.prototype, "getProtectedResourceMetadata", null);
|
|
7643
7862
|
exports.McpWellKnownController = __decorate([
|
|
7644
7863
|
common.Controller('.well-known'),
|