@dereekb/firebase-server 13.13.0 → 13.15.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/index.cjs.js +13 -8
- package/index.esm.js +13 -8
- package/mailgun/package.json +9 -9
- package/mcp/index.cjs.js +1715 -212
- package/mcp/index.esm.js +1704 -215
- package/mcp/package.json +11 -11
- package/mcp/src/lib/service/index.d.ts +1 -0
- package/mcp/src/lib/service/mcp.manifest.d.ts +13 -0
- package/mcp/src/lib/service/mcp.response-formatter.d.ts +10 -6
- package/mcp/src/lib/service/mcp.server.factory.d.ts +54 -2
- package/mcp/src/lib/service/mcp.tool-generator.d.ts +194 -13
- package/mcp/src/lib/service/tools/mcp.tool.batch-execute.d.ts +151 -0
- package/model/package.json +9 -9
- package/oidc/package.json +10 -10
- package/package.json +10 -10
- package/src/lib/auth/auth.service.d.ts +23 -3
- package/src/lib/nest/model/api.details.d.ts +117 -32
- package/test/package.json +11 -11
- package/twilio/package.json +8 -8
- package/zoho/package.json +9 -9
package/mcp/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/firebase-server/mcp",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.15.0",
|
|
4
4
|
"peerDependencies": {
|
|
5
|
-
"@dereekb/analytics": "13.
|
|
6
|
-
"@dereekb/date": "13.
|
|
7
|
-
"@dereekb/firebase": "13.
|
|
8
|
-
"@dereekb/firebase-server": "13.
|
|
9
|
-
"@dereekb/firebase-server/oidc": "13.
|
|
10
|
-
"@dereekb/model": "13.
|
|
11
|
-
"@dereekb/nestjs": "13.
|
|
12
|
-
"@dereekb/rxjs": "13.
|
|
13
|
-
"@dereekb/util": "13.
|
|
14
|
-
"@dereekb/zoho": "13.
|
|
5
|
+
"@dereekb/analytics": "13.15.0",
|
|
6
|
+
"@dereekb/date": "13.15.0",
|
|
7
|
+
"@dereekb/firebase": "13.15.0",
|
|
8
|
+
"@dereekb/firebase-server": "13.15.0",
|
|
9
|
+
"@dereekb/firebase-server/oidc": "13.15.0",
|
|
10
|
+
"@dereekb/model": "13.15.0",
|
|
11
|
+
"@dereekb/nestjs": "13.15.0",
|
|
12
|
+
"@dereekb/rxjs": "13.15.0",
|
|
13
|
+
"@dereekb/util": "13.15.0",
|
|
14
|
+
"@dereekb/zoho": "13.15.0",
|
|
15
15
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
16
16
|
"@nestjs/common": "^11.1.19",
|
|
17
17
|
"@nestjs/core": "^11.1.19",
|
|
@@ -18,6 +18,12 @@ export interface McpManifestToolEntry {
|
|
|
18
18
|
readonly description?: string;
|
|
19
19
|
readonly inputSchema?: object;
|
|
20
20
|
readonly outputSchema?: object;
|
|
21
|
+
/**
|
|
22
|
+
* Name of the MCP-mapped result interface (from `@dbxModelApiMcpResult`) when the output schema was
|
|
23
|
+
* built from a mapped type. Used at boot to detect a `mapSuccessfulResult` handler whose `.api.ts`
|
|
24
|
+
* leaf was never annotated (the output schema would then describe the raw, un-mapped result).
|
|
25
|
+
*/
|
|
26
|
+
readonly mcpResultTypeName?: string;
|
|
21
27
|
}
|
|
22
28
|
/**
|
|
23
29
|
* One persisted field on a {@link McpManifestModelEntry}.
|
|
@@ -55,6 +61,13 @@ export interface McpManifestModelEntry {
|
|
|
55
61
|
readonly sourcePackage: string;
|
|
56
62
|
readonly sourceFile: string;
|
|
57
63
|
readonly fields: readonly McpManifestModelField[];
|
|
64
|
+
/**
|
|
65
|
+
* Per-model override of the model segment used in generated MCP tool names (from
|
|
66
|
+
* `@dbxModelMcpToolNameSegment` on the model interface). When present it replaces the model type
|
|
67
|
+
* in tool names (e.g. the collection prefix), trading readability for shorter names. Absent when
|
|
68
|
+
* the model omits the tag — names then use the model type.
|
|
69
|
+
*/
|
|
70
|
+
readonly mcpToolNameSegment?: string;
|
|
58
71
|
/**
|
|
59
72
|
* Read posture declared by `@dbxModelRead <level>` on the model interface (`system` /
|
|
60
73
|
* `owner` / `admin-only` / `permissions`). Absent when the source model omits the tag.
|
|
@@ -13,20 +13,24 @@ export declare const DEFAULT_VOID_MCP_SUCCESS_VALUE: {
|
|
|
13
13
|
/**
|
|
14
14
|
* Resolves a dispatch result + handler API details into the MCP `CallToolResult` shape.
|
|
15
15
|
*
|
|
16
|
+
* When `mcp.mapSuccessfulResult` is set, the raw result is first mapped (async-capable) to the value
|
|
17
|
+
* exposed via MCP; the tiers and the default path then operate on the mapped value. The tier
|
|
18
|
+
* callbacks receive `(value, context)` where `context` carries both the raw + mapped values + params.
|
|
19
|
+
*
|
|
16
20
|
* Three-tier resolution as documented on {@link OnCallModelFunctionApiDetails.mcp}:
|
|
17
21
|
*
|
|
18
22
|
* - **Tier 3** — when `mcp.formatResponse` is set, its return value is used verbatim.
|
|
19
23
|
* - **Tier 2** — when `mcp.summarizeResponse` is set, the summary string is wrapped into a
|
|
20
|
-
* single text content block with the
|
|
21
|
-
* - **Tier 1** — default: JSON-stringify
|
|
22
|
-
* exposing
|
|
24
|
+
* single text content block with the (mapped) value exposed as `structuredContent`.
|
|
25
|
+
* - **Tier 1** — default: JSON-stringify the (mapped) value as a single text content block, also
|
|
26
|
+
* exposing it as `structuredContent`.
|
|
23
27
|
*
|
|
24
|
-
* @param result - The handler's return value.
|
|
28
|
+
* @param result - The handler's raw return value.
|
|
25
29
|
* @param params - The {@link OnCallTypedModelParams} that were dispatched.
|
|
26
|
-
* @param details - The handler-level API details (carries Tier 2/3 formatters).
|
|
30
|
+
* @param details - The handler-level API details (carries the mapper + Tier 2/3 formatters).
|
|
27
31
|
* @returns The MCP tool response content.
|
|
28
32
|
*/
|
|
29
|
-
export declare function formatMcpToolResponse(result: unknown, params: OnCallTypedModelParams, details: OnCallModelFunctionApiDetails | undefined): McpToolResponseContent
|
|
33
|
+
export declare function formatMcpToolResponse(result: unknown, params: OnCallTypedModelParams, details: OnCallModelFunctionApiDetails | undefined): Promise<McpToolResponseContent>;
|
|
30
34
|
/**
|
|
31
35
|
* Converts an error thrown from the dispatch chain into the MCP error response shape.
|
|
32
36
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { type Request } from 'express';
|
|
3
|
-
import { ModelApiCallModelDispatchService, ModelApiGetService, type FirebaseServerAuthData } from '@dereekb/firebase-server';
|
|
3
|
+
import { ModelApiCallModelDispatchService, ModelApiGetService, FirebaseServerStorageService, type FirebaseServerAuthData } from '@dereekb/firebase-server';
|
|
4
4
|
import { McpModuleConfig, type McpAuthRoleReader } from '../mcp.config';
|
|
5
5
|
import { type McpAnalyticsService } from './analytics/mcp.analytics.handler';
|
|
6
6
|
/**
|
|
@@ -26,6 +26,7 @@ export declare class McpServerFactoryService {
|
|
|
26
26
|
private readonly dispatchService;
|
|
27
27
|
private readonly modelApiGetService?;
|
|
28
28
|
private readonly roleReader?;
|
|
29
|
+
private readonly storageService?;
|
|
29
30
|
private readonly _logger;
|
|
30
31
|
private _cachedTools;
|
|
31
32
|
private _cachedStaticTools;
|
|
@@ -36,7 +37,7 @@ export declare class McpServerFactoryService {
|
|
|
36
37
|
private _loggedSkips;
|
|
37
38
|
private _warnedMissingRoleReader;
|
|
38
39
|
private readonly _analyticsService;
|
|
39
|
-
constructor(mcpConfig: McpModuleConfig, dispatchService: ModelApiCallModelDispatchService, modelApiGetService?: ModelApiGetService | undefined, roleReader?: McpAuthRoleReader | undefined, analyticsService?: McpAnalyticsService);
|
|
40
|
+
constructor(mcpConfig: McpModuleConfig, dispatchService: ModelApiCallModelDispatchService, modelApiGetService?: ModelApiGetService | undefined, roleReader?: McpAuthRoleReader | undefined, analyticsService?: McpAnalyticsService, storageService?: FirebaseServerStorageService | undefined);
|
|
40
41
|
/**
|
|
41
42
|
* Builds a configured MCP server with tool listing + dispatch handlers wired up.
|
|
42
43
|
*
|
|
@@ -53,6 +54,30 @@ export declare class McpServerFactoryService {
|
|
|
53
54
|
* @returns The cached or freshly-generated tool generation result.
|
|
54
55
|
*/
|
|
55
56
|
private _resolveToolDefinitions;
|
|
57
|
+
/**
|
|
58
|
+
* Builds the per-model tool-name segment overrides from the loaded manifest's `models` catalog.
|
|
59
|
+
*
|
|
60
|
+
* Sourcing the segments from the manifest (rather than runtime config) keeps the runtime in
|
|
61
|
+
* agreement with the build-time manifest validation, which reads the same `mcpToolNameSegment`.
|
|
62
|
+
*
|
|
63
|
+
* @returns Naming options carrying the segment map, or `undefined` when no model declares one.
|
|
64
|
+
*/
|
|
65
|
+
private _resolveToolNamingOptions;
|
|
66
|
+
/**
|
|
67
|
+
* Logs one skipped tool at the appropriate level: name-cap and collision skips are errors (they
|
|
68
|
+
* would otherwise break or shadow tools on the wire), the rest are warnings.
|
|
69
|
+
*
|
|
70
|
+
* @param skip - The skipped-tool report to log.
|
|
71
|
+
*/
|
|
72
|
+
private _logSkip;
|
|
73
|
+
/**
|
|
74
|
+
* Renders a human-readable boot-time warning for an MCP-result mapping inconsistency between a
|
|
75
|
+
* handler's `mapSuccessfulResult` and the build-time manifest.
|
|
76
|
+
*
|
|
77
|
+
* @param warning - The tool-generation warning to describe.
|
|
78
|
+
* @returns The log line to emit at startup.
|
|
79
|
+
*/
|
|
80
|
+
private _describeToolGenerationWarning;
|
|
56
81
|
/**
|
|
57
82
|
* Builds the list of statically-registered (non-callModel) MCP tools.
|
|
58
83
|
*
|
|
@@ -65,6 +90,33 @@ export declare class McpServerFactoryService {
|
|
|
65
90
|
* @returns The cached array of static tool definitions, filtered for collisions with generated tools.
|
|
66
91
|
*/
|
|
67
92
|
private _resolveStaticTools;
|
|
93
|
+
/**
|
|
94
|
+
* Builds the per-request `batch-execute` tool, or `undefined` when it should not be offered.
|
|
95
|
+
*
|
|
96
|
+
* Unlike the cached static tools this is rebuilt per request, because it closes over the caller's
|
|
97
|
+
* resolved visible tool set: each batched operation is authorized against exactly the callModel
|
|
98
|
+
* tools this caller may invoke directly. Offered only when a storage service is wired (it reads
|
|
99
|
+
* the uploaded operations file), the caller is authenticated, and the server is not in read-only
|
|
100
|
+
* mode (the tool performs writes).
|
|
101
|
+
*
|
|
102
|
+
* @param ctx - The per-request context (auth, raw request) forwarded to dispatched operations.
|
|
103
|
+
* @param definitionsByName - The caller's visible tool definitions, keyed by name.
|
|
104
|
+
* @returns The batch tool definition, or `undefined` when unavailable for this request.
|
|
105
|
+
*/
|
|
106
|
+
private _resolveBatchTool;
|
|
107
|
+
/**
|
|
108
|
+
* Authorizes a single batched operation against the caller's visible callModel tool coordinates.
|
|
109
|
+
*
|
|
110
|
+
* Re-applies the same gate the `tools/list` filter already enforced: an operation is allowed only
|
|
111
|
+
* when its `(call, modelType, specifier)` coordinate matches a tool this caller can see. This is
|
|
112
|
+
* the critical guard — `dispatchService.dispatch` bypasses the MCP visibility filter, so without
|
|
113
|
+
* this check a batch could reach scope-, role-, or read-only-gated handlers.
|
|
114
|
+
*
|
|
115
|
+
* @param operation - The operation to authorize.
|
|
116
|
+
* @param authorizedCoords - The set of dispatchable coordinate keys visible to this caller.
|
|
117
|
+
* @returns Whether the operation may be dispatched, with a reason when it may not.
|
|
118
|
+
*/
|
|
119
|
+
private _authorizeBatchOperation;
|
|
68
120
|
/**
|
|
69
121
|
* Reads the pre-rendered MCP manifest JSON once, validates its version, and caches the
|
|
70
122
|
* resulting `key → entry` map plus the optional `models` catalog for the process lifetime.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Maybe } from '@dereekb/util';
|
|
2
|
+
import { type KnownOnCallFunctionType } from '@dereekb/firebase';
|
|
2
3
|
import { type ModelApiDetailsResult, type OnCallModelFunctionApiDetails, type FirebaseServerAuthData, type McpToolDetailsBuilder } from '@dereekb/firebase-server';
|
|
3
4
|
import { type Request } from 'express';
|
|
4
5
|
import { type CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
@@ -24,12 +25,14 @@ export interface McpToolDefinition {
|
|
|
24
25
|
/**
|
|
25
26
|
* Tool name advertised on `tools/list`.
|
|
26
27
|
*
|
|
27
|
-
* Format: `<
|
|
28
|
-
* `<
|
|
28
|
+
* Format: `<modelSegment>-<callType>` for default (`_`) specifiers; named specifiers drop the
|
|
29
|
+
* call-type segment (`<modelSegment>-<specifier>`) to stay short. When two visible tools would
|
|
30
|
+
* resolve to the same name, both are disambiguated with the abbreviated call type
|
|
31
|
+
* (`<modelSegment>-<abbrev>-<specifier>`); see {@link buildDisambiguatedMcpToolName}.
|
|
29
32
|
*
|
|
30
33
|
* @example 'guestbook-create'
|
|
31
|
-
* @example 'profile-
|
|
32
|
-
* @example '
|
|
34
|
+
* @example 'profile-username'
|
|
35
|
+
* @example 'profile-u-username'
|
|
33
36
|
*/
|
|
34
37
|
readonly name: string;
|
|
35
38
|
/**
|
|
@@ -144,8 +147,15 @@ export interface JsonSchemaGenerationOptions {
|
|
|
144
147
|
export declare const DEFAULT_JSON_SCHEMA_GENERATION_OPTIONS: JsonSchemaGenerationOptions;
|
|
145
148
|
/**
|
|
146
149
|
* Reason a tool was skipped during generation.
|
|
150
|
+
*
|
|
151
|
+
* - `missing_input_type` — the handler has no `inputType` to build a schema from.
|
|
152
|
+
* - `schema_generation_failed` — `toJsonSchema()` threw for the handler's `inputType`.
|
|
153
|
+
* - `name_too_long` — the resolved name exceeds {@link MCP_TOOL_NAME_MAX_LENGTH}; advertising it
|
|
154
|
+
* would make remote clients reject the whole `tools/list` payload, so it is dropped.
|
|
155
|
+
* - `duplicate_name` — another already-registered visible tool resolved to the same name (e.g. two
|
|
156
|
+
* specifiers collide once the call-type segment is dropped); the later tool is dropped.
|
|
147
157
|
*/
|
|
148
|
-
export type McpToolGenerationSkipReason = 'missing_input_type' | 'schema_generation_failed';
|
|
158
|
+
export type McpToolGenerationSkipReason = 'missing_input_type' | 'schema_generation_failed' | 'name_too_long' | 'duplicate_name';
|
|
149
159
|
/**
|
|
150
160
|
* One tool that was skipped during generation.
|
|
151
161
|
*/
|
|
@@ -155,6 +165,35 @@ export interface McpToolGenerationSkip {
|
|
|
155
165
|
readonly dispatch: McpToolDispatchTarget;
|
|
156
166
|
readonly error?: Error;
|
|
157
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Reason a generated tool's handler and build-time manifest disagree about MCP result mapping.
|
|
170
|
+
*
|
|
171
|
+
* - `mapper_without_mapped_manifest` — the handler declares `mcp.mapSuccessfulResult` but the
|
|
172
|
+
* manifest entry was not built from a mapped result type, i.e. the `.api.ts` leaf is missing its
|
|
173
|
+
* `@dbxModelApiMcpResult <TypeName>` annotation, so the advertised output schema describes the raw
|
|
174
|
+
* (un-mapped) result.
|
|
175
|
+
* - `mapped_manifest_without_mapper` — the manifest entry is annotated for a mapped result but the
|
|
176
|
+
* handler no longer declares `mapSuccessfulResult` (stale annotation).
|
|
177
|
+
*
|
|
178
|
+
* Both are runtime↔manifest consistency checks the build-time manifest renderer cannot perform on
|
|
179
|
+
* its own (it has the `.api.ts` annotation but not the handler's wired `mapSuccessfulResult`), so the
|
|
180
|
+
* runtime keeps them. Purely build-time-detectable conditions are deliberately *not* warned here to
|
|
181
|
+
* keep the server boot quiet:
|
|
182
|
+
* - Name length over the soft limit is surfaced by the manifest renderer, not at runtime (the hard
|
|
183
|
+
* cap is still enforced as a `name_too_long` skip).
|
|
184
|
+
* - A preferred name that collided with another visible tool is re-derived with the abbreviated call
|
|
185
|
+
* type ({@link buildDisambiguatedMcpToolName}) **silently** at runtime; the manifest renderer flags it.
|
|
186
|
+
*/
|
|
187
|
+
export type McpToolGenerationWarningReason = 'mapper_without_mapped_manifest' | 'mapped_manifest_without_mapper';
|
|
188
|
+
/**
|
|
189
|
+
* One generated tool whose handler / manifest MCP-result mapping is inconsistent. The tool is still
|
|
190
|
+
* generated; the warning is surfaced so the caller can log the drift at startup.
|
|
191
|
+
*/
|
|
192
|
+
export interface McpToolGenerationWarning {
|
|
193
|
+
readonly toolName: string;
|
|
194
|
+
readonly reason: McpToolGenerationWarningReason;
|
|
195
|
+
readonly dispatch: McpToolDispatchTarget;
|
|
196
|
+
}
|
|
158
197
|
/**
|
|
159
198
|
* Aggregated output of {@link generateMcpToolDefinitions}.
|
|
160
199
|
*/
|
|
@@ -174,6 +213,11 @@ export interface McpToolGenerationResult {
|
|
|
174
213
|
* Surfaced so the caller can log them at startup.
|
|
175
214
|
*/
|
|
176
215
|
readonly skipped: ReadonlyArray<McpToolGenerationSkip>;
|
|
216
|
+
/**
|
|
217
|
+
* Generated tools whose handler / manifest MCP-result mapping is inconsistent (only computed when a
|
|
218
|
+
* manifest is supplied). Surfaced so the caller can log the drift at startup.
|
|
219
|
+
*/
|
|
220
|
+
readonly warnings: ReadonlyArray<McpToolGenerationWarning>;
|
|
177
221
|
}
|
|
178
222
|
interface BuildStaticWireEntryInput {
|
|
179
223
|
readonly name: string;
|
|
@@ -196,17 +240,120 @@ export declare function buildStaticWireEntry(input: BuildStaticWireEntryInput):
|
|
|
196
240
|
*/
|
|
197
241
|
export declare const DEFAULT_SPECIFIER_KEY = "_";
|
|
198
242
|
/**
|
|
199
|
-
*
|
|
243
|
+
* Soft limit for an MCP tool name. Names longer than this still register, but the generator
|
|
244
|
+
* surfaces a `name_length_warning` so the drift toward the hard cap is visible at boot / build.
|
|
245
|
+
*/
|
|
246
|
+
export declare const MCP_TOOL_NAME_WARN_LENGTH = 55;
|
|
247
|
+
/**
|
|
248
|
+
* Hard limit for an MCP tool name. Remote MCP clients reject a `tools/list` payload that contains
|
|
249
|
+
* any tool whose `name` exceeds this (`FrontendRemoteMcpToolDefinition.name: String should have at
|
|
250
|
+
* most 64 characters`), which fails the whole connection. Names over this are not registered.
|
|
251
|
+
*/
|
|
252
|
+
export declare const MCP_TOOL_NAME_MAX_LENGTH = 64;
|
|
253
|
+
/**
|
|
254
|
+
* Severity of a tool name's length relative to {@link MCP_TOOL_NAME_WARN_LENGTH} /
|
|
255
|
+
* {@link MCP_TOOL_NAME_MAX_LENGTH}.
|
|
256
|
+
*
|
|
257
|
+
* - `error` — over the hard cap; the tool must not be advertised.
|
|
258
|
+
* - `warn` — over the soft limit but within the hard cap; advertised, but flagged.
|
|
259
|
+
* - `ok` — within the soft limit.
|
|
260
|
+
*/
|
|
261
|
+
export type McpToolNameLengthLevel = 'ok' | 'warn' | 'error';
|
|
262
|
+
/**
|
|
263
|
+
* The outcome of validating a tool name's length.
|
|
264
|
+
*/
|
|
265
|
+
export interface McpToolNameValidation {
|
|
266
|
+
readonly name: string;
|
|
267
|
+
readonly length: number;
|
|
268
|
+
readonly level: McpToolNameLengthLevel;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Classifies a tool name's length against the soft/hard MCP name-length limits.
|
|
272
|
+
*
|
|
273
|
+
* Shared by the runtime generator and the build-time manifest renderer so both apply the
|
|
274
|
+
* same thresholds and never drift.
|
|
275
|
+
*
|
|
276
|
+
* @param name - The fully-resolved tool name (including any per-handler override).
|
|
277
|
+
* @returns The length classification — `error` over {@link MCP_TOOL_NAME_MAX_LENGTH}, `warn` over
|
|
278
|
+
* {@link MCP_TOOL_NAME_WARN_LENGTH}, otherwise `ok`.
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```ts
|
|
282
|
+
* validateMcpToolName('worker-create'); // { name: 'worker-create', length: 13, level: 'ok' }
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
export declare function validateMcpToolName(name: string): McpToolNameValidation;
|
|
286
|
+
/**
|
|
287
|
+
* Builds the MCP tool name for a (modelSegment, callType, specifier) triple.
|
|
200
288
|
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
289
|
+
* The call-type segment is only emitted for the default (`_`) specifier, where it carries the
|
|
290
|
+
* meaning (`worker-create`, `worker-update`). Named specifiers drop it — the specifier already
|
|
291
|
+
* disambiguates (`worker-syncCheckHqEmployee`), which keeps names short and within the MCP
|
|
292
|
+
* 64-character cap. Apps can override the whole name per handler via
|
|
293
|
+
* {@link OnCallModelFunctionApiDetails.mcp.name}.
|
|
203
294
|
*
|
|
204
|
-
*
|
|
295
|
+
* This is the preferred (short) form. When two visible tools collide on it — two call types share a
|
|
296
|
+
* specifier once the call-type segment is dropped — the generator re-derives both names with
|
|
297
|
+
* {@link buildDisambiguatedMcpToolName} instead.
|
|
298
|
+
*
|
|
299
|
+
* @param modelSegment - The model segment of the name. Defaults to the model type, but may be a
|
|
300
|
+
* shorter per-model override (e.g. the collection prefix) resolved by the caller.
|
|
205
301
|
* @param callType - The call type (e.g., `invoke`).
|
|
206
302
|
* @param specifier - The specifier key, or `_` / undefined for the default entry.
|
|
207
303
|
* @returns The hyphen-joined tool name advertised on `tools/list`.
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```ts
|
|
307
|
+
* buildMcpToolName('worker', 'create'); // 'worker-create'
|
|
308
|
+
* buildMcpToolName('worker', 'update', 'syncCheckHqEmployee'); // 'worker-syncCheckHqEmployee'
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
export declare function buildMcpToolName(modelSegment: string, callType: string, specifier?: Maybe<string>): string;
|
|
312
|
+
/**
|
|
313
|
+
* Single-character abbreviations for the standard CRUDQ + invoke call types, used to disambiguate
|
|
314
|
+
* colliding tool names without re-introducing the full call-type segment everywhere.
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* MCP_CALL_TYPE_ABBREVIATIONS.update; // 'u'
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
export declare const MCP_CALL_TYPE_ABBREVIATIONS: Readonly<Record<KnownOnCallFunctionType, string>>;
|
|
322
|
+
/**
|
|
323
|
+
* Abbreviates a call type for use in a disambiguated tool name. Known CRUDQ + invoke types collapse
|
|
324
|
+
* to a single character; a custom call type is returned unchanged so the name stays unambiguous.
|
|
325
|
+
*
|
|
326
|
+
* @param callType - The call type / verb.
|
|
327
|
+
* @returns The single-character abbreviation, or the original string for a custom call type.
|
|
328
|
+
*
|
|
329
|
+
* @example
|
|
330
|
+
* ```ts
|
|
331
|
+
* abbreviateMcpCallType('update'); // 'u'
|
|
332
|
+
* abbreviateMcpCallType('recompute'); // 'recompute'
|
|
333
|
+
* ```
|
|
334
|
+
*/
|
|
335
|
+
export declare function abbreviateMcpCallType(callType: string): string;
|
|
336
|
+
/**
|
|
337
|
+
* Builds the disambiguated MCP tool name for a (modelSegment, callType, specifier) triple — the form
|
|
338
|
+
* used only when the preferred {@link buildMcpToolName} output collides with another visible tool.
|
|
339
|
+
*
|
|
340
|
+
* Named specifiers re-insert the call type, abbreviated, between the segment and specifier
|
|
341
|
+
* (`worker-u-syncCheckHqEmployee`); since the two colliding tools differ only by call type, their
|
|
342
|
+
* abbreviations differ and the names no longer clash. Default (`_`) specifiers already carry the
|
|
343
|
+
* full call type, so they are returned in their {@link buildMcpToolName} form unchanged.
|
|
344
|
+
*
|
|
345
|
+
* @param modelSegment - The model segment of the name (model type, or a per-model override).
|
|
346
|
+
* @param callType - The call type / verb.
|
|
347
|
+
* @param specifier - The specifier key, or `_` / undefined for the default entry.
|
|
348
|
+
* @returns The hyphen-joined disambiguated tool name.
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* ```ts
|
|
352
|
+
* buildDisambiguatedMcpToolName('worker', 'update', 'syncCheckHqEmployee'); // 'worker-u-syncCheckHqEmployee'
|
|
353
|
+
* buildDisambiguatedMcpToolName('worker', 'create'); // 'worker-create'
|
|
354
|
+
* ```
|
|
208
355
|
*/
|
|
209
|
-
export declare function
|
|
356
|
+
export declare function buildDisambiguatedMcpToolName(modelSegment: string, callType: string, specifier?: Maybe<string>): string;
|
|
210
357
|
/**
|
|
211
358
|
* Builds the default description used when no build-time MCP manifest
|
|
212
359
|
* entry is available for the (modelType, callType, specifier) tuple.
|
|
@@ -217,6 +364,32 @@ export declare function buildMcpToolName(modelType: string, callType: string, sp
|
|
|
217
364
|
* @returns A human-readable fallback description for the tool.
|
|
218
365
|
*/
|
|
219
366
|
export declare function buildDefaultMcpToolDescription(modelType: string, callType: string, specifier?: Maybe<string>): string;
|
|
367
|
+
/**
|
|
368
|
+
* Optional naming inputs for {@link generateMcpToolDefinitions}.
|
|
369
|
+
*/
|
|
370
|
+
export interface McpToolGenerationNamingOptions {
|
|
371
|
+
/**
|
|
372
|
+
* Per-model override of the tool-name model segment, keyed by model type. When a model type is
|
|
373
|
+
* present, its segment (e.g. the collection prefix) replaces the model type in generated names;
|
|
374
|
+
* otherwise the model type is used.
|
|
375
|
+
*/
|
|
376
|
+
readonly modelSegments?: ReadonlyMap<string, string>;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Optional build-time context for {@link generateMcpToolDefinitions}. Grouped into one object so the
|
|
380
|
+
* function stays at three parameters and new build-time inputs extend it rather than the arg list.
|
|
381
|
+
*/
|
|
382
|
+
export interface GenerateMcpToolDefinitionsContext {
|
|
383
|
+
/**
|
|
384
|
+
* Build-time manifest map supplying overrides for descriptions and input/output schemas, keyed by
|
|
385
|
+
* {@link mcpManifestKey}.
|
|
386
|
+
*/
|
|
387
|
+
readonly manifest?: ReadonlyMap<string, McpManifestToolEntry>;
|
|
388
|
+
/**
|
|
389
|
+
* Per-model name segment overrides (e.g. collection prefixes).
|
|
390
|
+
*/
|
|
391
|
+
readonly naming?: McpToolGenerationNamingOptions;
|
|
392
|
+
}
|
|
220
393
|
/**
|
|
221
394
|
* Generates MCP tool definitions from a model-first API details tree.
|
|
222
395
|
*
|
|
@@ -224,12 +397,20 @@ export declare function buildDefaultMcpToolDescription(modelType: string, callTy
|
|
|
224
397
|
* `inputType.toJsonSchema(options)` for the schema, and applies any handler-level
|
|
225
398
|
* MCP `name` override. Descriptions and input/output schemas are pulled from the
|
|
226
399
|
* build-time manifest when supplied. Tools without an `inputType` are skipped and
|
|
227
|
-
* reported so callers can log the gap at startup.
|
|
400
|
+
* reported so callers can log the gap at startup. Tools whose resolved name exceeds
|
|
401
|
+
* {@link MCP_TOOL_NAME_MAX_LENGTH} are skipped so the advertised `tools/list` stays valid.
|
|
402
|
+
*
|
|
403
|
+
* Generation runs in two passes so a name clash is known before any name is finalized: the first
|
|
404
|
+
* pass plans every candidate and counts how many visible auto-named tools share each preferred name;
|
|
405
|
+
* the second builds each tool, re-deriving the colliding ones with the abbreviated call type
|
|
406
|
+
* ({@link buildDisambiguatedMcpToolName}) so both survive instead of one shadowing the other. A
|
|
407
|
+
* residual collision that disambiguation cannot resolve (e.g. an `mcp.name` override matching an auto
|
|
408
|
+
* name) drops the later tool so the dispatch map stays unambiguous.
|
|
228
409
|
*
|
|
229
410
|
* @param apiDetails - The model-first API details tree returned by `getModelApiDetails(callModelFn)`.
|
|
230
411
|
* @param options - Optional schema generation options forwarded to `toJsonSchema()`. Defaults to {@link DEFAULT_JSON_SCHEMA_GENERATION_OPTIONS}.
|
|
231
|
-
* @param
|
|
412
|
+
* @param context - Optional build-time context (manifest overrides + per-model name segments).
|
|
232
413
|
* @returns The list of generated tool definitions plus any skip reports.
|
|
233
414
|
*/
|
|
234
|
-
export declare function generateMcpToolDefinitions(apiDetails: ModelApiDetailsResult, options?: JsonSchemaGenerationOptions,
|
|
415
|
+
export declare function generateMcpToolDefinitions(apiDetails: ModelApiDetailsResult, options?: JsonSchemaGenerationOptions, context?: GenerateMcpToolDefinitionsContext): McpToolGenerationResult;
|
|
235
416
|
export {};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { type Request } from 'express';
|
|
2
|
+
import { type StorageSlashPath, type StorageBucketId, type OnCallTypedModelParams } from '@dereekb/firebase';
|
|
3
|
+
import { type FirebaseServerStorageService, type FirebaseServerAuthData } from '@dereekb/firebase-server';
|
|
4
|
+
import { type Maybe } from '@dereekb/util';
|
|
5
|
+
import { type McpToolDefinition, type McpToolDispatchTarget } from '../mcp.tool-generator';
|
|
6
|
+
/**
|
|
7
|
+
* Reserved tool name for the built-in `batch-execute` static tool.
|
|
8
|
+
*/
|
|
9
|
+
export declare const BATCH_EXECUTE_TOOL_NAME = "batch-execute";
|
|
10
|
+
/**
|
|
11
|
+
* Synthetic call type used in the tool's dispatch identity (analytics only — the tool re-dispatches
|
|
12
|
+
* to other callModel tools rather than resolving a `batch` call type of its own).
|
|
13
|
+
*/
|
|
14
|
+
export declare const BATCH_EXECUTE_DISPATCH_CALL = "batch";
|
|
15
|
+
/**
|
|
16
|
+
* Synthetic model type used in the tool's dispatch identity. The tool isn't bound to a model type
|
|
17
|
+
* (each operation carries its own), so we use the literal "batch" as the identity. Apps avoiding
|
|
18
|
+
* collision should not register a real model literally named "batch".
|
|
19
|
+
*/
|
|
20
|
+
export declare const BATCH_EXECUTE_DISPATCH_MODEL_TYPE = "batch";
|
|
21
|
+
/**
|
|
22
|
+
* Default per-operation concurrency when the caller does not specify `maxParallel`.
|
|
23
|
+
*/
|
|
24
|
+
export declare const DEFAULT_BATCH_EXECUTE_MAX_PARALLEL = 5;
|
|
25
|
+
/**
|
|
26
|
+
* Hard upper bound on operations accepted in a single batch file, guarding against an
|
|
27
|
+
* accidentally enormous upload exhausting the function's memory/time budget.
|
|
28
|
+
*/
|
|
29
|
+
export declare const MAX_BATCH_EXECUTE_OPERATIONS = 1000;
|
|
30
|
+
/**
|
|
31
|
+
* Maximum bytes read from the operations file. NDJSON of typical CRUD payloads stays well under
|
|
32
|
+
* this; an oversized file is rejected rather than streamed.
|
|
33
|
+
*/
|
|
34
|
+
export declare const MAX_BATCH_EXECUTE_FILE_BYTES: number;
|
|
35
|
+
/**
|
|
36
|
+
* A single operation in the uploaded batch file — the same `(call, modelType, specifier?, data)`
|
|
37
|
+
* shape every other MCP tool ultimately dispatches.
|
|
38
|
+
*/
|
|
39
|
+
export type BatchExecuteOperation = OnCallTypedModelParams;
|
|
40
|
+
/**
|
|
41
|
+
* Result of authorizing one operation against the caller's visible tool set.
|
|
42
|
+
*
|
|
43
|
+
* `allowed: false` carries a human-readable reason so the pre-flight error names exactly which
|
|
44
|
+
* operation is forbidden and why (unknown coordinate vs. hidden-for-this-caller).
|
|
45
|
+
*/
|
|
46
|
+
export type BatchOperationAuthorization = {
|
|
47
|
+
readonly allowed: true;
|
|
48
|
+
} | {
|
|
49
|
+
readonly allowed: false;
|
|
50
|
+
readonly reason: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Dispatches one already-authorized operation through the callModel chain.
|
|
54
|
+
*
|
|
55
|
+
* Wired by the server factory to `ModelApiCallModelDispatchService.dispatch`, closing over the
|
|
56
|
+
* request's auth + raw request so each operation runs with the caller's identity and full
|
|
57
|
+
* per-handler param validation.
|
|
58
|
+
*/
|
|
59
|
+
export type BatchExecuteDispatchFn = (operation: OnCallTypedModelParams, auth: Maybe<FirebaseServerAuthData>, rawRequest: Request) => Promise<unknown>;
|
|
60
|
+
/**
|
|
61
|
+
* Constructor dependencies for {@link createBatchExecuteTool}.
|
|
62
|
+
*/
|
|
63
|
+
export interface CreateBatchExecuteToolDeps {
|
|
64
|
+
/**
|
|
65
|
+
* Reads the uploaded operations file from storage.
|
|
66
|
+
*/
|
|
67
|
+
readonly storageService: FirebaseServerStorageService;
|
|
68
|
+
/**
|
|
69
|
+
* Re-dispatches a single operation through the callModel chain.
|
|
70
|
+
*/
|
|
71
|
+
readonly dispatch: BatchExecuteDispatchFn;
|
|
72
|
+
/**
|
|
73
|
+
* Authorizes one operation against the request's visible tool set. Provided by the factory so the
|
|
74
|
+
* check uses the same scope / role / read-only filtering already applied to `tools/list`.
|
|
75
|
+
*/
|
|
76
|
+
readonly authorizeOperation: (operation: OnCallTypedModelParams) => BatchOperationAuthorization;
|
|
77
|
+
/**
|
|
78
|
+
* Overrides the maximum number of operations accepted per batch. Defaults to
|
|
79
|
+
* {@link MAX_BATCH_EXECUTE_OPERATIONS}.
|
|
80
|
+
*/
|
|
81
|
+
readonly maxOperations?: number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Shape of the `batch-execute` tool input.
|
|
85
|
+
*/
|
|
86
|
+
export interface BatchExecuteToolInput {
|
|
87
|
+
readonly uploadPath: StorageSlashPath;
|
|
88
|
+
readonly bucketId?: StorageBucketId;
|
|
89
|
+
readonly format: 'json' | 'ndjson';
|
|
90
|
+
readonly maxParallel: number;
|
|
91
|
+
readonly stopOnError: boolean;
|
|
92
|
+
readonly deleteUploadOnSuccess: boolean;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Per-operation failure entry in the batch summary, carrying the original file index so the caller
|
|
96
|
+
* can map a failure back to the line/element that produced it.
|
|
97
|
+
*/
|
|
98
|
+
export interface BatchExecuteErrorEntry {
|
|
99
|
+
readonly index: number;
|
|
100
|
+
readonly modelType: string;
|
|
101
|
+
readonly call?: string;
|
|
102
|
+
readonly specifier?: string;
|
|
103
|
+
readonly message: string;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Aggregate batch summary returned to the MCP client. Intentionally omits per-operation success
|
|
107
|
+
* payloads — only counts plus the failure list cross the wire, so 100 operations collapse to one
|
|
108
|
+
* compact result instead of 100 result blobs.
|
|
109
|
+
*
|
|
110
|
+
* `successCount + failureCount + skippedCount === total`. `skippedCount` is non-zero only when
|
|
111
|
+
* `stopOnError` halted the run before every operation was attempted.
|
|
112
|
+
*/
|
|
113
|
+
export interface BatchExecuteToolResult {
|
|
114
|
+
readonly total: number;
|
|
115
|
+
readonly successCount: number;
|
|
116
|
+
readonly failureCount: number;
|
|
117
|
+
readonly skippedCount: number;
|
|
118
|
+
readonly errors: ReadonlyArray<BatchExecuteErrorEntry>;
|
|
119
|
+
readonly uploadDeleted: boolean;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Builds the lookup key identifying a callModel dispatch coordinate. The factory indexes the
|
|
123
|
+
* caller's visible callModel tools by this key; the batch handler looks each operation up by the
|
|
124
|
+
* same key, so authorization reuses the exact tool set advertised on `tools/list`.
|
|
125
|
+
*
|
|
126
|
+
* @param dispatch - The `(call, modelType, specifier?)` coordinate to key.
|
|
127
|
+
* @returns A stable string key. Uses a NUL separator so segment values can't collide.
|
|
128
|
+
*/
|
|
129
|
+
export declare function batchOperationCoordKey(dispatch: Pick<McpToolDispatchTarget, 'call' | 'modelType' | 'specifier'>): string;
|
|
130
|
+
/**
|
|
131
|
+
* Builds the built-in `batch-execute` MCP tool definition.
|
|
132
|
+
*
|
|
133
|
+
* The tool ingests an uploaded JSON / NDJSON file of `(call, modelType, specifier?, data)`
|
|
134
|
+
* operations and runs them server-side, returning a single success/failure summary. It exists so an
|
|
135
|
+
* agent can perform a bulk mutation (e.g. "update 200 workers") by *generating* the operations file
|
|
136
|
+
* with a script and uploading it — rather than emitting each payload as tool-call output and paying
|
|
137
|
+
* one model turn per record.
|
|
138
|
+
*
|
|
139
|
+
* Safety model:
|
|
140
|
+
* - Every operation is validated and authorized against the caller's visible tool set *before any
|
|
141
|
+
* dispatch*. A malformed or forbidden operation aborts the whole batch with a pre-flight error and
|
|
142
|
+
* nothing runs — `dispatch` would otherwise bypass the `tools/list` visibility filter.
|
|
143
|
+
* - Runtime failures are best-effort by default (collected into the summary); `stopOnError` halts at
|
|
144
|
+
* the first failure (forcing sequential execution).
|
|
145
|
+
*
|
|
146
|
+
* @param deps - Storage reader, dispatch fn, and per-operation authorizer (typically wired in the
|
|
147
|
+
* server factory to the request's visible tool set).
|
|
148
|
+
* @returns A statically-registered {@link McpToolDefinition} ready to be appended to the MCP server
|
|
149
|
+
* factory's tool registry.
|
|
150
|
+
*/
|
|
151
|
+
export declare function createBatchExecuteTool(deps: CreateBatchExecuteToolDeps): McpToolDefinition;
|
package/model/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/firebase-server/model",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.15.0",
|
|
4
4
|
"peerDependencies": {
|
|
5
|
-
"@dereekb/analytics": "13.
|
|
6
|
-
"@dereekb/date": "13.
|
|
7
|
-
"@dereekb/firebase": "13.
|
|
8
|
-
"@dereekb/firebase-server": "13.
|
|
9
|
-
"@dereekb/model": "13.
|
|
10
|
-
"@dereekb/nestjs": "13.
|
|
11
|
-
"@dereekb/rxjs": "13.
|
|
12
|
-
"@dereekb/util": "13.
|
|
5
|
+
"@dereekb/analytics": "13.15.0",
|
|
6
|
+
"@dereekb/date": "13.15.0",
|
|
7
|
+
"@dereekb/firebase": "13.15.0",
|
|
8
|
+
"@dereekb/firebase-server": "13.15.0",
|
|
9
|
+
"@dereekb/model": "13.15.0",
|
|
10
|
+
"@dereekb/nestjs": "13.15.0",
|
|
11
|
+
"@dereekb/rxjs": "13.15.0",
|
|
12
|
+
"@dereekb/util": "13.15.0",
|
|
13
13
|
"@nestjs/common": "^11.1.19",
|
|
14
14
|
"@nestjs/config": "^4.0.4",
|
|
15
15
|
"archiver": "^7.0.1",
|