@dereekb/dbx-cli 13.13.0 → 13.14.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/firebase-api-manifest/main.js +65 -7
- package/firebase-api-manifest/package.json +3 -3
- package/generate-firestore-indexes/main.js +2 -2
- package/generate-firestore-indexes/package.json +2 -2
- package/generate-mcp-manifest/main.js +94 -14
- package/generate-mcp-manifest/package.json +3 -3
- package/index.cjs.js +120 -0
- package/index.esm.js +113 -1
- package/lint-cache/package.json +2 -2
- package/manifest-extract/index.cjs.js +138 -4
- package/manifest-extract/index.esm.js +138 -4
- package/manifest-extract/package.json +2 -2
- package/manifest-extract/src/lib/types.d.ts +19 -0
- package/package.json +6 -6
- package/src/lib/manifest/types.d.ts +136 -0
- package/test/package.json +9 -9
|
@@ -162,6 +162,7 @@ import { join as join2 } from "node:path";
|
|
|
162
162
|
// packages/dbx-cli/manifest-extract/src/lib/extract-crud.ts
|
|
163
163
|
import { Node as Node2, Project as Project2 } from "ts-morph";
|
|
164
164
|
var SUPPORTED_VERBS = /* @__PURE__ */ new Set(["create", "read", "update", "delete", "query", "invoke"]);
|
|
165
|
+
var DBX_MODEL_API_MCP_RESULT_MARKER = "dbxModelApiMcpResult";
|
|
165
166
|
function extractCrudEntries(source) {
|
|
166
167
|
const project = new Project2({ useInMemoryFileSystem: true, skipAddingFilesFromTsConfig: true });
|
|
167
168
|
const sourceFile = project.createSourceFile(source.name, source.text, { overwrite: true });
|
|
@@ -215,7 +216,7 @@ function extractCrudEntries(source) {
|
|
|
215
216
|
if (!verbValueNode) {
|
|
216
217
|
continue;
|
|
217
218
|
}
|
|
218
|
-
collectVerbEntries({ modelName, verb, valueNode: verbValueNode, entries, fallbackDescription: readJsDocSummary(verbMember), resolveTypeDocs });
|
|
219
|
+
collectVerbEntries({ modelName, verb, valueNode: verbValueNode, entries, fallbackDescription: readJsDocSummary(verbMember), fallbackMcpResultTypeName: readJsDocTagValue(verbMember, DBX_MODEL_API_MCP_RESULT_MARKER), resolveTypeDocs });
|
|
219
220
|
}
|
|
220
221
|
}
|
|
221
222
|
}
|
|
@@ -234,6 +235,8 @@ function extractCrudEntries(source) {
|
|
|
234
235
|
const tuple = valueNode ? readTupleParamsResult(valueNode) : void 0;
|
|
235
236
|
const paramsDocs = resolveTypeDocs(tuple?.params);
|
|
236
237
|
const resultDocs = resolveTypeDocs(tuple?.result);
|
|
238
|
+
const mcpResultTypeName = readJsDocTagValue(member, DBX_MODEL_API_MCP_RESULT_MARKER);
|
|
239
|
+
const mcpResultDocs = resolveTypeDocs(mcpResultTypeName);
|
|
237
240
|
entries.push({
|
|
238
241
|
model: key,
|
|
239
242
|
verb: "standalone",
|
|
@@ -246,7 +249,10 @@ function extractCrudEntries(source) {
|
|
|
246
249
|
paramsFields: paramsDocs?.fields,
|
|
247
250
|
paramsHasApiParamsTag: paramsDocs?.hasApiParamsTag,
|
|
248
251
|
resultTypeDescription: resultDocs?.typeDescription,
|
|
249
|
-
resultFields: resultDocs?.fields
|
|
252
|
+
resultFields: resultDocs?.fields,
|
|
253
|
+
mcpResultTypeName,
|
|
254
|
+
mcpResultTypeDescription: mcpResultDocs?.typeDescription,
|
|
255
|
+
mcpResultFields: mcpResultDocs?.fields
|
|
250
256
|
});
|
|
251
257
|
}
|
|
252
258
|
}
|
|
@@ -314,7 +320,7 @@ function isNullLiteralType(node) {
|
|
|
314
320
|
return result;
|
|
315
321
|
}
|
|
316
322
|
function collectVerbEntries(input) {
|
|
317
|
-
const { modelName, verb, valueNode, entries, fallbackDescription, resolveTypeDocs } = input;
|
|
323
|
+
const { modelName, verb, valueNode, entries, fallbackDescription, fallbackMcpResultTypeName, resolveTypeDocs } = input;
|
|
318
324
|
if (Node2.isTypeLiteral(valueNode)) {
|
|
319
325
|
for (const specMember of valueNode.getMembers()) {
|
|
320
326
|
if (!Node2.isPropertySignature(specMember)) {
|
|
@@ -325,6 +331,8 @@ function collectVerbEntries(input) {
|
|
|
325
331
|
const leaf2 = leafNode ? readTupleParamsResult(leafNode) ?? readBareParams(leafNode) : void 0;
|
|
326
332
|
const paramsDocs2 = resolveTypeDocs(leaf2?.params);
|
|
327
333
|
const resultDocs2 = resolveTypeDocs(leaf2?.result);
|
|
334
|
+
const mcpResultTypeName = readJsDocTagValue(specMember, DBX_MODEL_API_MCP_RESULT_MARKER);
|
|
335
|
+
const mcpResultDocs2 = resolveTypeDocs(mcpResultTypeName);
|
|
328
336
|
entries.push({
|
|
329
337
|
model: modelName,
|
|
330
338
|
verb,
|
|
@@ -337,7 +345,10 @@ function collectVerbEntries(input) {
|
|
|
337
345
|
paramsFields: paramsDocs2?.fields,
|
|
338
346
|
paramsHasApiParamsTag: paramsDocs2?.hasApiParamsTag,
|
|
339
347
|
resultTypeDescription: resultDocs2?.typeDescription,
|
|
340
|
-
resultFields: resultDocs2?.fields
|
|
348
|
+
resultFields: resultDocs2?.fields,
|
|
349
|
+
mcpResultTypeName,
|
|
350
|
+
mcpResultTypeDescription: mcpResultDocs2?.typeDescription,
|
|
351
|
+
mcpResultFields: mcpResultDocs2?.fields
|
|
341
352
|
});
|
|
342
353
|
}
|
|
343
354
|
return;
|
|
@@ -345,6 +356,7 @@ function collectVerbEntries(input) {
|
|
|
345
356
|
const leaf = readTupleParamsResult(valueNode) ?? readBareParams(valueNode);
|
|
346
357
|
const paramsDocs = resolveTypeDocs(leaf?.params);
|
|
347
358
|
const resultDocs = resolveTypeDocs(leaf?.result);
|
|
359
|
+
const mcpResultDocs = resolveTypeDocs(fallbackMcpResultTypeName);
|
|
348
360
|
entries.push({
|
|
349
361
|
model: modelName,
|
|
350
362
|
verb,
|
|
@@ -357,7 +369,10 @@ function collectVerbEntries(input) {
|
|
|
357
369
|
paramsFields: paramsDocs?.fields,
|
|
358
370
|
paramsHasApiParamsTag: paramsDocs?.hasApiParamsTag,
|
|
359
371
|
resultTypeDescription: resultDocs?.typeDescription,
|
|
360
|
-
resultFields: resultDocs?.fields
|
|
372
|
+
resultFields: resultDocs?.fields,
|
|
373
|
+
mcpResultTypeName: fallbackMcpResultTypeName,
|
|
374
|
+
mcpResultTypeDescription: mcpResultDocs?.typeDescription,
|
|
375
|
+
mcpResultFields: mcpResultDocs?.fields
|
|
361
376
|
});
|
|
362
377
|
}
|
|
363
378
|
function readTupleParamsResult(node) {
|
|
@@ -436,6 +451,20 @@ function hasJsDocFlag(node, tagName) {
|
|
|
436
451
|
}
|
|
437
452
|
return result;
|
|
438
453
|
}
|
|
454
|
+
function readJsDocTagValue(node, tagName) {
|
|
455
|
+
let result;
|
|
456
|
+
for (const doc of node.getJsDocs()) {
|
|
457
|
+
for (const tag of doc.getTags()) {
|
|
458
|
+
if (tag.getTagName() === tagName) {
|
|
459
|
+
const token = tag.getCommentText()?.trim().split(/\s+/)[0];
|
|
460
|
+
if (token) {
|
|
461
|
+
result = token;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return result;
|
|
467
|
+
}
|
|
439
468
|
function readJsDocSummary(node) {
|
|
440
469
|
let result;
|
|
441
470
|
const docs = node.getJsDocs();
|
|
@@ -453,7 +482,9 @@ function readJsDocSummary(node) {
|
|
|
453
482
|
import { Node as Node3, Project as Project3 } from "ts-morph";
|
|
454
483
|
var READ_LEVEL_VALUES = /* @__PURE__ */ new Set(["system", "owner", "admin-only", "permissions"]);
|
|
455
484
|
var SERVICE_FACTORY_TAG = "dbxModelServiceFactory";
|
|
485
|
+
var MCP_TOOL_NAME_SEGMENT_TAG = "dbxModelMcpToolNameSegment";
|
|
456
486
|
var MODEL_TYPE_VALUE_PATTERN = /^[a-z][A-Za-z0-9_$]*$/;
|
|
487
|
+
var TOOL_NAME_SEGMENT_PATTERN = /^[A-Za-z][A-Za-z0-9_$]*$/;
|
|
457
488
|
var PASSTHROUGH_TYPE_WRAPPERS = /* @__PURE__ */ new Set(["Partial", "Required", "Readonly", "NonNullable", "MaybeMap", "Pick", "Omit"]);
|
|
458
489
|
var IDENTITY_FN = "firestoreModelIdentity";
|
|
459
490
|
var CONVERTER_FN_NAMES = ["snapshotConverterFunctions", "firestoreSubObject", "firestoreObjectArray"];
|
|
@@ -527,6 +558,7 @@ function buildInterface(decl) {
|
|
|
527
558
|
const jsDocs = decl.getJsDocs();
|
|
528
559
|
const hasDbxModelTag = jsDocsHaveTag(jsDocs, "dbxModel");
|
|
529
560
|
const dbxModelRead = readDbxModelReadTag(jsDocs);
|
|
561
|
+
const mcpToolNameSegment = readMcpToolNameSegmentTag(jsDocs);
|
|
530
562
|
const extendsNames = decl.getExtends().map(resolveExtendsName);
|
|
531
563
|
const props = [];
|
|
532
564
|
for (const prop of decl.getProperties()) {
|
|
@@ -550,9 +582,26 @@ function buildInterface(decl) {
|
|
|
550
582
|
hasDbxModelTag,
|
|
551
583
|
extendsNames,
|
|
552
584
|
props,
|
|
553
|
-
...dbxModelRead === void 0 ? {} : { dbxModelRead }
|
|
585
|
+
...dbxModelRead === void 0 ? {} : { dbxModelRead },
|
|
586
|
+
...mcpToolNameSegment === void 0 ? {} : { mcpToolNameSegment }
|
|
554
587
|
};
|
|
555
588
|
}
|
|
589
|
+
function readMcpToolNameSegmentTag(jsDocs) {
|
|
590
|
+
let result;
|
|
591
|
+
for (const doc of jsDocs) {
|
|
592
|
+
for (const tag of doc.getTags()) {
|
|
593
|
+
if (tag.getTagName() !== MCP_TOOL_NAME_SEGMENT_TAG) continue;
|
|
594
|
+
if (result !== void 0) continue;
|
|
595
|
+
const raw = tag.getCommentText()?.trim();
|
|
596
|
+
if (raw === void 0 || raw.length === 0) continue;
|
|
597
|
+
const firstToken = raw.split(/\s+/)[0];
|
|
598
|
+
if (TOOL_NAME_SEGMENT_PATTERN.test(firstToken)) {
|
|
599
|
+
result = firstToken;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return result;
|
|
604
|
+
}
|
|
556
605
|
function readDbxModelReadTag(jsDocs) {
|
|
557
606
|
let result;
|
|
558
607
|
for (const doc of jsDocs) {
|
|
@@ -1050,6 +1099,7 @@ function buildEntryForIdentity(input) {
|
|
|
1050
1099
|
sourcePackage: source.sourcePackage,
|
|
1051
1100
|
sourceFile: source.sourceFile,
|
|
1052
1101
|
fields,
|
|
1102
|
+
...iface.mcpToolNameSegment ? { mcpToolNameSegment: iface.mcpToolNameSegment } : {},
|
|
1053
1103
|
...iface.dbxModelRead ? { read: iface.dbxModelRead } : {},
|
|
1054
1104
|
...serviceFactory ? { serviceFactory } : {}
|
|
1055
1105
|
};
|
|
@@ -1347,7 +1397,10 @@ function renderEntry({ entry, validatorName }) {
|
|
|
1347
1397
|
entry.paramsTypeDescription ? `paramsTypeDescription: ${JSON.stringify(entry.paramsTypeDescription)}` : void 0,
|
|
1348
1398
|
entry.paramsFields && entry.paramsFields.length > 0 ? `paramsFields: ${renderDocFields(entry.paramsFields)}` : void 0,
|
|
1349
1399
|
entry.resultTypeDescription ? `resultTypeDescription: ${JSON.stringify(entry.resultTypeDescription)}` : void 0,
|
|
1350
|
-
entry.resultFields && entry.resultFields.length > 0 ? `resultFields: ${renderDocFields(entry.resultFields)}` : void 0
|
|
1400
|
+
entry.resultFields && entry.resultFields.length > 0 ? `resultFields: ${renderDocFields(entry.resultFields)}` : void 0,
|
|
1401
|
+
entry.mcpResultTypeName ? `mcpResultTypeName: ${JSON.stringify(entry.mcpResultTypeName)}` : void 0,
|
|
1402
|
+
entry.mcpResultTypeDescription ? `mcpResultTypeDescription: ${JSON.stringify(entry.mcpResultTypeDescription)}` : void 0,
|
|
1403
|
+
entry.mcpResultFields && entry.mcpResultFields.length > 0 ? `mcpResultFields: ${renderDocFields(entry.mcpResultFields)}` : void 0
|
|
1351
1404
|
];
|
|
1352
1405
|
return ` { ${fields.filter((v) => Boolean(v)).join(", ")} }`;
|
|
1353
1406
|
}
|
|
@@ -1375,6 +1428,7 @@ function renderModelEntry(entry, emitConverters) {
|
|
|
1375
1428
|
`sourcePackage: ${JSON.stringify(entry.sourcePackage)}`,
|
|
1376
1429
|
`sourceFile: ${JSON.stringify(entry.sourceFile)}`,
|
|
1377
1430
|
`fields: ${renderModelFields(entry.fields, emitConverters)}`,
|
|
1431
|
+
entry.mcpToolNameSegment ? `mcpToolNameSegment: ${JSON.stringify(entry.mcpToolNameSegment)}` : void 0,
|
|
1378
1432
|
entry.read ? `read: ${JSON.stringify(entry.read)}` : void 0,
|
|
1379
1433
|
entry.serviceFactory ? `serviceFactory: { exportName: ${JSON.stringify(entry.serviceFactory.exportName)}, sourceFile: ${JSON.stringify(entry.serviceFactory.sourceFile)} }` : void 0
|
|
1380
1434
|
];
|
|
@@ -1487,6 +1541,10 @@ async function main() {
|
|
|
1487
1541
|
console.warn(`[no-api-params-tag] ${pkg.packageName} \xB7 ${entry.model}/${entry.verb}${specPart} \u2192 ${entry.paramsTypeName} missing @dbxModelApiParams`);
|
|
1488
1542
|
}
|
|
1489
1543
|
}
|
|
1544
|
+
if (entry.mcpResultTypeName && entry.mcpResultFields == null && entry.mcpResultTypeDescription == null) {
|
|
1545
|
+
const specPart = entry.specifier ? "/" + entry.specifier : "";
|
|
1546
|
+
console.warn(`[mcp-result-type-unresolved] ${pkg.packageName} \xB7 ${entry.model}/${entry.verb}${specPart} \u2192 @dbxModelApiMcpResult ${entry.mcpResultTypeName} not declared in the same .api.ts file`);
|
|
1547
|
+
}
|
|
1490
1548
|
collected.push(enriched);
|
|
1491
1549
|
}
|
|
1492
1550
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-firebase-api-manifest",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.14.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"devDependencies": {
|
|
7
7
|
"ts-morph": "^21.0.0"
|
|
8
8
|
},
|
|
9
9
|
"peerDependencies": {
|
|
10
|
-
"@dereekb/dbx-cli": "13.
|
|
11
|
-
"@dereekb/util": "13.
|
|
10
|
+
"@dereekb/dbx-cli": "13.14.0",
|
|
11
|
+
"@dereekb/util": "13.14.0",
|
|
12
12
|
"prettier": "3.8.3"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -5,14 +5,14 @@ const require = __createRequire(import.meta.url);
|
|
|
5
5
|
// packages/dbx-cli/generate-firestore-indexes/package.json
|
|
6
6
|
var package_default = {
|
|
7
7
|
name: "@dereekb/dbx-cli-generate-firestore-indexes",
|
|
8
|
-
version: "13.
|
|
8
|
+
version: "13.14.0",
|
|
9
9
|
private: true,
|
|
10
10
|
type: "module",
|
|
11
11
|
devDependencies: {
|
|
12
12
|
eslint: "10.4.0"
|
|
13
13
|
},
|
|
14
14
|
peerDependencies: {
|
|
15
|
-
"@dereekb/dbx-cli": "13.
|
|
15
|
+
"@dereekb/dbx-cli": "13.14.0"
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-generate-firestore-indexes",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.14.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"devDependencies": {
|
|
7
7
|
"eslint": "10.4.0"
|
|
8
8
|
},
|
|
9
9
|
"peerDependencies": {
|
|
10
|
-
"@dereekb/dbx-cli": "13.
|
|
10
|
+
"@dereekb/dbx-cli": "13.14.0"
|
|
11
11
|
}
|
|
12
12
|
}
|
|
@@ -79,6 +79,38 @@ function mcpManifestKey(modelType, call, specifier) {
|
|
|
79
79
|
const isDefault = specifier == null || specifier === "_";
|
|
80
80
|
return isDefault ? `${modelType}.${call}._` : `${modelType}.${call}.${specifier}`;
|
|
81
81
|
}
|
|
82
|
+
var DEFAULT_SPECIFIER_KEY = "_";
|
|
83
|
+
var MCP_TOOL_NAME_WARN_LENGTH = 55;
|
|
84
|
+
var MCP_TOOL_NAME_MAX_LENGTH = 64;
|
|
85
|
+
function buildMcpToolName(modelSegment, callType, specifier) {
|
|
86
|
+
const isDefault = specifier == null || specifier === DEFAULT_SPECIFIER_KEY;
|
|
87
|
+
return isDefault ? `${modelSegment}-${callType}` : `${modelSegment}-${specifier}`;
|
|
88
|
+
}
|
|
89
|
+
function validateMcpToolName(name) {
|
|
90
|
+
const length = name.length;
|
|
91
|
+
let level = "ok";
|
|
92
|
+
if (length > MCP_TOOL_NAME_MAX_LENGTH) {
|
|
93
|
+
level = "error";
|
|
94
|
+
} else if (length > MCP_TOOL_NAME_WARN_LENGTH) {
|
|
95
|
+
level = "warn";
|
|
96
|
+
}
|
|
97
|
+
return { name, length, level };
|
|
98
|
+
}
|
|
99
|
+
var MCP_CALL_TYPE_ABBREVIATIONS = {
|
|
100
|
+
create: "c",
|
|
101
|
+
read: "r",
|
|
102
|
+
update: "u",
|
|
103
|
+
delete: "d",
|
|
104
|
+
query: "q",
|
|
105
|
+
invoke: "i"
|
|
106
|
+
};
|
|
107
|
+
function abbreviateMcpCallType(callType) {
|
|
108
|
+
return MCP_CALL_TYPE_ABBREVIATIONS[callType] ?? callType;
|
|
109
|
+
}
|
|
110
|
+
function buildDisambiguatedMcpToolName(modelSegment, callType, specifier) {
|
|
111
|
+
const isDefault = specifier == null || specifier === DEFAULT_SPECIFIER_KEY;
|
|
112
|
+
return isDefault ? `${modelSegment}-${callType}` : `${modelSegment}-${abbreviateMcpCallType(callType)}-${specifier}`;
|
|
113
|
+
}
|
|
82
114
|
|
|
83
115
|
// packages/dbx-cli/src/lib/mcp-scan/registry/auth-runtime.ts
|
|
84
116
|
// @__NO_SIDE_EFFECTS__
|
|
@@ -980,22 +1012,56 @@ function toRelative(absolutePath, cwd) {
|
|
|
980
1012
|
// packages/dbx-cli/generate-mcp-manifest/src/generate-mcp-manifest/render.ts
|
|
981
1013
|
function renderMcpManifest(input, now = /* @__PURE__ */ new Date()) {
|
|
982
1014
|
const tools = {};
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1015
|
+
const warnings = [];
|
|
1016
|
+
const errors = [];
|
|
1017
|
+
const seenNames = /* @__PURE__ */ new Map();
|
|
1018
|
+
const segments = /* @__PURE__ */ new Map();
|
|
1019
|
+
if (input.modelManifest != null) {
|
|
1020
|
+
for (const model of input.modelManifest) {
|
|
1021
|
+
if (model.mcpToolNameSegment != null && model.mcpToolNameSegment.length > 0) {
|
|
1022
|
+
segments.set(model.modelType, model.mcpToolNameSegment);
|
|
1023
|
+
}
|
|
986
1024
|
}
|
|
1025
|
+
}
|
|
1026
|
+
const toolEntries = input.apiManifest.filter((entry) => entry.verb !== "standalone");
|
|
1027
|
+
const nameCounts = /* @__PURE__ */ new Map();
|
|
1028
|
+
for (const entry of toolEntries) {
|
|
1029
|
+
const segment = segments.get(entry.model) ?? entry.model;
|
|
1030
|
+
const baseName = buildMcpToolName(segment, entry.verb, entry.specifier);
|
|
1031
|
+
nameCounts.set(baseName, (nameCounts.get(baseName) ?? 0) + 1);
|
|
1032
|
+
}
|
|
1033
|
+
for (const entry of toolEntries) {
|
|
987
1034
|
const key = mcpManifestKey(entry.model, entry.verb, entry.specifier);
|
|
988
1035
|
tools[key] = buildToolEntry(entry);
|
|
1036
|
+
const segment = segments.get(entry.model) ?? entry.model;
|
|
1037
|
+
const baseName = buildMcpToolName(segment, entry.verb, entry.specifier);
|
|
1038
|
+
const clashes = (nameCounts.get(baseName) ?? 0) > 1;
|
|
1039
|
+
const toolName = clashes ? buildDisambiguatedMcpToolName(segment, entry.verb, entry.specifier) : baseName;
|
|
1040
|
+
if (toolName !== baseName) {
|
|
1041
|
+
warnings.push(`Tool name "${baseName}" is produced by more than one entry; re-derived as "${toolName}" (${key}) with the abbreviated call type to disambiguate.`);
|
|
1042
|
+
}
|
|
1043
|
+
const validation = validateMcpToolName(toolName);
|
|
1044
|
+
if (validation.level === "error") {
|
|
1045
|
+
errors.push(`Tool name "${toolName}" is ${validation.length} chars, over the ${MCP_TOOL_NAME_MAX_LENGTH}-char MCP cap (${key}). Shorten the model/specifier, set a per-model mcpToolNameSegment, or hide the tool.`);
|
|
1046
|
+
} else if (validation.level === "warn") {
|
|
1047
|
+
warnings.push(`Tool name "${toolName}" is ${validation.length} chars, over the ${MCP_TOOL_NAME_WARN_LENGTH}-char soft limit (${key}).`);
|
|
1048
|
+
}
|
|
1049
|
+
const priorKey = seenNames.get(toolName);
|
|
1050
|
+
if (priorKey != null) {
|
|
1051
|
+
warnings.push(`Tool name "${toolName}" is still produced by more than one entry (${priorKey} and ${key}) after disambiguation; one shadows the other unless hidden or renamed at runtime.`);
|
|
1052
|
+
} else {
|
|
1053
|
+
seenNames.set(toolName, key);
|
|
1054
|
+
}
|
|
989
1055
|
}
|
|
990
1056
|
const models = input.modelManifest != null && input.modelManifest.length > 0 ? input.modelManifest.map(projectModelEntry) : void 0;
|
|
991
1057
|
const auth = input.auth == null ? void 0 : projectAuthSection(input.auth.registry, input.auth.app);
|
|
992
1058
|
const base = { version: MCP_MANIFEST_VERSION, generatedAt: now.toISOString(), tools };
|
|
993
|
-
const
|
|
1059
|
+
const manifest = {
|
|
994
1060
|
...base,
|
|
995
1061
|
...models == null ? {} : { models },
|
|
996
1062
|
...auth == null ? {} : { auth }
|
|
997
1063
|
};
|
|
998
|
-
return
|
|
1064
|
+
return { manifest, warnings, errors };
|
|
999
1065
|
}
|
|
1000
1066
|
function projectAuthSection(registry, appSlug) {
|
|
1001
1067
|
const primary = registry.findApp(appSlug);
|
|
@@ -1052,6 +1118,7 @@ function projectModelEntry(entry) {
|
|
|
1052
1118
|
...entry.modelGroup == null ? {} : { modelGroup: entry.modelGroup },
|
|
1053
1119
|
...entry.parentIdentityConst == null ? {} : { parentIdentityConst: entry.parentIdentityConst },
|
|
1054
1120
|
...entry.description == null ? {} : { description: entry.description },
|
|
1121
|
+
...entry.mcpToolNameSegment == null ? {} : { mcpToolNameSegment: entry.mcpToolNameSegment },
|
|
1055
1122
|
...entry.read == null ? {} : { read: entry.read },
|
|
1056
1123
|
...entry.serviceFactory == null ? {} : { serviceFactory: entry.serviceFactory }
|
|
1057
1124
|
};
|
|
@@ -1079,6 +1146,7 @@ function buildToolEntry(entry) {
|
|
|
1079
1146
|
if (description != null) result.description = description;
|
|
1080
1147
|
if (inputSchema != null) result.inputSchema = inputSchema;
|
|
1081
1148
|
if (outputSchema != null) result.outputSchema = outputSchema;
|
|
1149
|
+
if (entry.mcpResultTypeName != null) result.mcpResultTypeName = entry.mcpResultTypeName;
|
|
1082
1150
|
return result;
|
|
1083
1151
|
}
|
|
1084
1152
|
function buildDescription(entry) {
|
|
@@ -1102,18 +1170,20 @@ function buildInputSchema(entry) {
|
|
|
1102
1170
|
return schema;
|
|
1103
1171
|
}
|
|
1104
1172
|
function buildOutputSchema(entry) {
|
|
1105
|
-
const
|
|
1106
|
-
const
|
|
1173
|
+
const fields = entry.mcpResultFields != null && entry.mcpResultFields.length > 0 ? entry.mcpResultFields : entry.resultFields;
|
|
1174
|
+
const description = typeof entry.mcpResultTypeDescription === "string" && entry.mcpResultTypeDescription.length > 0 ? entry.mcpResultTypeDescription : entry.resultTypeDescription;
|
|
1175
|
+
const hasFields = fields != null && fields.length > 0;
|
|
1176
|
+
const hasDescription = typeof description === "string" && description.length > 0;
|
|
1107
1177
|
if (!hasFields && !hasDescription) {
|
|
1108
1178
|
return void 0;
|
|
1109
1179
|
}
|
|
1110
1180
|
const schema = { type: "object" };
|
|
1111
1181
|
if (hasDescription) {
|
|
1112
|
-
schema["description"] =
|
|
1182
|
+
schema["description"] = description;
|
|
1113
1183
|
}
|
|
1114
1184
|
if (hasFields) {
|
|
1115
1185
|
const properties = {};
|
|
1116
|
-
for (const field of
|
|
1186
|
+
for (const field of fields) {
|
|
1117
1187
|
properties[field.name] = buildPropertyFromField(field);
|
|
1118
1188
|
}
|
|
1119
1189
|
schema["properties"] = properties;
|
|
@@ -1212,16 +1282,26 @@ Expected output of \`nx run <cli>:generate-api-manifest\`.`);
|
|
|
1212
1282
|
}
|
|
1213
1283
|
const loaded = await loadManifest(inputPath);
|
|
1214
1284
|
const auth = await maybeLoadAuth(flags);
|
|
1215
|
-
const
|
|
1216
|
-
const
|
|
1285
|
+
const { manifest, warnings, errors } = renderMcpManifest({ ...loaded, ...auth == null ? {} : { auth } });
|
|
1286
|
+
for (const warning of warnings) {
|
|
1287
|
+
console.error(`[generate-mcp-manifest] tool name warning: ${warning}`);
|
|
1288
|
+
}
|
|
1289
|
+
for (const error of errors) {
|
|
1290
|
+
console.error(`[generate-mcp-manifest] tool name error: ${error}`);
|
|
1291
|
+
}
|
|
1292
|
+
if (errors.length > 0) {
|
|
1293
|
+
console.error(`generate-mcp-manifest: ${errors.length} tool name(s) exceed the ${MCP_TOOL_NAME_MAX_LENGTH}-char MCP limit; not writing ${relative2(WORKSPACE_ROOT, outputPath)}.`);
|
|
1294
|
+
process.exit(1);
|
|
1295
|
+
}
|
|
1296
|
+
const serialized = `${JSON.stringify(manifest, null, 2)}
|
|
1217
1297
|
`;
|
|
1218
1298
|
ensureOutputDir(dirname(outputPath));
|
|
1219
1299
|
const tmpPath = `${outputPath}.tmp`;
|
|
1220
1300
|
writeFileSync(tmpPath, serialized);
|
|
1221
1301
|
renameSync(tmpPath, outputPath);
|
|
1222
|
-
const modelCount =
|
|
1223
|
-
const authCount =
|
|
1224
|
-
console.log(`[wrote] ${relative2(WORKSPACE_ROOT, outputPath)} \u2014 ${Object.keys(
|
|
1302
|
+
const modelCount = manifest.models?.length ?? 0;
|
|
1303
|
+
const authCount = manifest.auth?.claims.length ?? 0;
|
|
1304
|
+
console.log(`[wrote] ${relative2(WORKSPACE_ROOT, outputPath)} \u2014 ${Object.keys(manifest.tools).length} tools, ${modelCount} models, ${authCount} auth claims`);
|
|
1225
1305
|
}
|
|
1226
1306
|
async function maybeLoadAuth(flags) {
|
|
1227
1307
|
if (flags.app == null || flags.claimsInputs.length === 0) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-generate-mcp-manifest",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.14.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"peerDependencies": {
|
|
7
|
-
"@dereekb/dbx-cli": "13.
|
|
8
|
-
"@dereekb/model": "13.
|
|
7
|
+
"@dereekb/dbx-cli": "13.14.0",
|
|
8
|
+
"@dereekb/model": "13.14.0",
|
|
9
9
|
"arktype": "^2.2.0",
|
|
10
10
|
"jiti": "2.6.1"
|
|
11
11
|
}
|
package/index.cjs.js
CHANGED
|
@@ -11530,6 +11530,118 @@ function parseJsonData(rawData) {
|
|
|
11530
11530
|
var isDefault = specifier == null || specifier === '_';
|
|
11531
11531
|
return isDefault ? "".concat(modelType, ".").concat(call, "._") : "".concat(modelType, ".").concat(call, ".").concat(specifier);
|
|
11532
11532
|
}
|
|
11533
|
+
/**
|
|
11534
|
+
* Default specifier key used when a handler is not behind a specifier router.
|
|
11535
|
+
*
|
|
11536
|
+
* Mirrors `DEFAULT_SPECIFIER_KEY` in `@dereekb/firebase-server/mcp` — kept in sync so the
|
|
11537
|
+
* build-time renderer composes the same tool names as the runtime generator.
|
|
11538
|
+
*/ var DEFAULT_SPECIFIER_KEY = '_';
|
|
11539
|
+
/**
|
|
11540
|
+
* Soft limit for an MCP tool name. Mirrors `MCP_TOOL_NAME_WARN_LENGTH` in
|
|
11541
|
+
* `@dereekb/firebase-server/mcp`. Names over this are flagged at manifest-generation time.
|
|
11542
|
+
*/ var MCP_TOOL_NAME_WARN_LENGTH = 55;
|
|
11543
|
+
/**
|
|
11544
|
+
* Hard limit for an MCP tool name. Mirrors `MCP_TOOL_NAME_MAX_LENGTH` in
|
|
11545
|
+
* `@dereekb/firebase-server/mcp`. Remote MCP clients reject a `tools/list` payload containing any
|
|
11546
|
+
* tool whose `name` exceeds this, so manifest generation fails when a name is over the cap.
|
|
11547
|
+
*/ var MCP_TOOL_NAME_MAX_LENGTH = 64;
|
|
11548
|
+
/**
|
|
11549
|
+
* Builds the MCP tool name for a (modelSegment, callType, specifier) triple.
|
|
11550
|
+
*
|
|
11551
|
+
* Mirrors `buildMcpToolName` in `@dereekb/firebase-server/mcp` so the build-time manifest validation
|
|
11552
|
+
* sees the same names the runtime advertises. The call-type segment is only emitted for the default
|
|
11553
|
+
* (`_`) specifier; named specifiers drop it (`worker-syncCheckHqEmployee`).
|
|
11554
|
+
*
|
|
11555
|
+
* @param modelSegment - The model segment of the name (model type, or a per-model override).
|
|
11556
|
+
* @param callType - The call type / verb.
|
|
11557
|
+
* @param specifier - The specifier key, or `_` / undefined for the default entry.
|
|
11558
|
+
* @returns The hyphen-joined tool name.
|
|
11559
|
+
*
|
|
11560
|
+
* @example
|
|
11561
|
+
* ```ts
|
|
11562
|
+
* buildMcpToolName('worker', 'update', 'syncCheckHqEmployee'); // 'worker-syncCheckHqEmployee'
|
|
11563
|
+
* ```
|
|
11564
|
+
*/ function buildMcpToolName(modelSegment, callType, specifier) {
|
|
11565
|
+
var isDefault = specifier == null || specifier === DEFAULT_SPECIFIER_KEY;
|
|
11566
|
+
return isDefault ? "".concat(modelSegment, "-").concat(callType) : "".concat(modelSegment, "-").concat(specifier);
|
|
11567
|
+
}
|
|
11568
|
+
/**
|
|
11569
|
+
* Classifies a tool name's length against the soft/hard MCP name-length limits.
|
|
11570
|
+
*
|
|
11571
|
+
* Mirrors `validateMcpToolName` in `@dereekb/firebase-server/mcp`.
|
|
11572
|
+
*
|
|
11573
|
+
* @param name - The fully-resolved tool name.
|
|
11574
|
+
* @returns The length classification — `error` over {@link MCP_TOOL_NAME_MAX_LENGTH}, `warn` over
|
|
11575
|
+
* {@link MCP_TOOL_NAME_WARN_LENGTH}, otherwise `ok`.
|
|
11576
|
+
*
|
|
11577
|
+
* @example
|
|
11578
|
+
* ```ts
|
|
11579
|
+
* validateMcpToolName('worker-create').level; // 'ok'
|
|
11580
|
+
* ```
|
|
11581
|
+
*/ function validateMcpToolName(name) {
|
|
11582
|
+
var length = name.length;
|
|
11583
|
+
var level = 'ok';
|
|
11584
|
+
if (length > MCP_TOOL_NAME_MAX_LENGTH) {
|
|
11585
|
+
level = 'error';
|
|
11586
|
+
} else if (length > MCP_TOOL_NAME_WARN_LENGTH) {
|
|
11587
|
+
level = 'warn';
|
|
11588
|
+
}
|
|
11589
|
+
return {
|
|
11590
|
+
name: name,
|
|
11591
|
+
length: length,
|
|
11592
|
+
level: level
|
|
11593
|
+
};
|
|
11594
|
+
}
|
|
11595
|
+
/**
|
|
11596
|
+
* Single-character abbreviations for the standard CRUDQ + invoke call types.
|
|
11597
|
+
*
|
|
11598
|
+
* Mirrors `MCP_CALL_TYPE_ABBREVIATIONS` in `@dereekb/firebase-server/mcp` so the build-time renderer
|
|
11599
|
+
* disambiguates colliding names exactly like the runtime generator.
|
|
11600
|
+
*/ var MCP_CALL_TYPE_ABBREVIATIONS = {
|
|
11601
|
+
create: 'c',
|
|
11602
|
+
read: 'r',
|
|
11603
|
+
update: 'u',
|
|
11604
|
+
delete: 'd',
|
|
11605
|
+
query: 'q',
|
|
11606
|
+
invoke: 'i'
|
|
11607
|
+
};
|
|
11608
|
+
/**
|
|
11609
|
+
* Abbreviates a call type for a disambiguated tool name; a custom call type is returned unchanged.
|
|
11610
|
+
*
|
|
11611
|
+
* Mirrors `abbreviateMcpCallType` in `@dereekb/firebase-server/mcp`.
|
|
11612
|
+
*
|
|
11613
|
+
* @param callType - The call type / verb.
|
|
11614
|
+
* @returns The single-character abbreviation, or the original string for a custom call type.
|
|
11615
|
+
*
|
|
11616
|
+
* @example
|
|
11617
|
+
* ```ts
|
|
11618
|
+
* abbreviateMcpCallType('update'); // 'u'
|
|
11619
|
+
* ```
|
|
11620
|
+
*/ function abbreviateMcpCallType(callType) {
|
|
11621
|
+
var _MCP_CALL_TYPE_ABBREVIATIONS_callType;
|
|
11622
|
+
return (_MCP_CALL_TYPE_ABBREVIATIONS_callType = MCP_CALL_TYPE_ABBREVIATIONS[callType]) !== null && _MCP_CALL_TYPE_ABBREVIATIONS_callType !== void 0 ? _MCP_CALL_TYPE_ABBREVIATIONS_callType : callType;
|
|
11623
|
+
}
|
|
11624
|
+
/**
|
|
11625
|
+
* Builds the disambiguated MCP tool name — the form used only when {@link buildMcpToolName} collides
|
|
11626
|
+
* with another visible tool. Named specifiers re-insert the abbreviated call type
|
|
11627
|
+
* (`worker-u-syncCheckHqEmployee`); default (`_`) specifiers already carry the full call type and are
|
|
11628
|
+
* returned unchanged.
|
|
11629
|
+
*
|
|
11630
|
+
* Mirrors `buildDisambiguatedMcpToolName` in `@dereekb/firebase-server/mcp`.
|
|
11631
|
+
*
|
|
11632
|
+
* @param modelSegment - The model segment of the name (model type, or a per-model override).
|
|
11633
|
+
* @param callType - The call type / verb.
|
|
11634
|
+
* @param specifier - The specifier key, or `_` / undefined for the default entry.
|
|
11635
|
+
* @returns The hyphen-joined disambiguated tool name.
|
|
11636
|
+
*
|
|
11637
|
+
* @example
|
|
11638
|
+
* ```ts
|
|
11639
|
+
* buildDisambiguatedMcpToolName('worker', 'update', 'syncCheckHqEmployee'); // 'worker-u-syncCheckHqEmployee'
|
|
11640
|
+
* ```
|
|
11641
|
+
*/ function buildDisambiguatedMcpToolName(modelSegment, callType, specifier) {
|
|
11642
|
+
var isDefault = specifier == null || specifier === DEFAULT_SPECIFIER_KEY;
|
|
11643
|
+
return isDefault ? "".concat(modelSegment, "-").concat(callType) : "".concat(modelSegment, "-").concat(abbreviateMcpCallType(callType), "-").concat(specifier);
|
|
11644
|
+
}
|
|
11533
11645
|
|
|
11534
11646
|
/**
|
|
11535
11647
|
* One entry in `<cluster>.scan[]`. Mirrors the shape of the legacy
|
|
@@ -56837,6 +56949,7 @@ exports.DEFAULT_MODEL_INFO_COMMAND_NAME = DEFAULT_MODEL_INFO_COMMAND_NAME;
|
|
|
56837
56949
|
exports.DEFAULT_MODEL_SNAPSHOT_FIELDS_SCAN_OUT_PATH = DEFAULT_MODEL_SNAPSHOT_FIELDS_SCAN_OUT_PATH;
|
|
56838
56950
|
exports.DEFAULT_PIPES_SCAN_OUT_PATH = DEFAULT_PIPES_SCAN_OUT_PATH;
|
|
56839
56951
|
exports.DEFAULT_SCAN_OUT_PATH = DEFAULT_SCAN_OUT_PATH;
|
|
56952
|
+
exports.DEFAULT_SPECIFIER_KEY = DEFAULT_SPECIFIER_KEY;
|
|
56840
56953
|
exports.DEFAULT_UI_COMPONENTS_SCAN_OUT_PATH = DEFAULT_UI_COMPONENTS_SCAN_OUT_PATH;
|
|
56841
56954
|
exports.DEFAULT_UTILS_SCAN_OUT_PATH = DEFAULT_UTILS_SCAN_OUT_PATH;
|
|
56842
56955
|
exports.DOWNSTREAM_CLUSTERS = DOWNSTREAM_CLUSTERS;
|
|
@@ -56891,7 +57004,10 @@ exports.ForgeFieldsScanSection = ForgeFieldsScanSection;
|
|
|
56891
57004
|
exports.GET_COMMAND = GET_COMMAND;
|
|
56892
57005
|
exports.GET_MANY_COMMAND = GET_MANY_COMMAND;
|
|
56893
57006
|
exports.MAX_MODEL_ACCESS_MULTI_READ_KEYS = MAX_MODEL_ACCESS_MULTI_READ_KEYS;
|
|
57007
|
+
exports.MCP_CALL_TYPE_ABBREVIATIONS = MCP_CALL_TYPE_ABBREVIATIONS;
|
|
56894
57008
|
exports.MCP_MANIFEST_VERSION = MCP_MANIFEST_VERSION;
|
|
57009
|
+
exports.MCP_TOOL_NAME_MAX_LENGTH = MCP_TOOL_NAME_MAX_LENGTH;
|
|
57010
|
+
exports.MCP_TOOL_NAME_WARN_LENGTH = MCP_TOOL_NAME_WARN_LENGTH;
|
|
56895
57011
|
exports.MODEL_ARCHETYPES = MODEL_ARCHETYPES;
|
|
56896
57012
|
exports.MODEL_ARCHETYPE_ADDON_SLUGS = MODEL_ARCHETYPE_ADDON_SLUGS;
|
|
56897
57013
|
exports.MODEL_ARCHETYPE_SYNC_MODES = MODEL_ARCHETYPE_SYNC_MODES;
|
|
@@ -56946,6 +57062,7 @@ exports.WORKSPACE_AUTH_APPS = WORKSPACE_AUTH_APPS;
|
|
|
56946
57062
|
exports.WORKSPACE_AUTH_CLAIMS = WORKSPACE_AUTH_CLAIMS;
|
|
56947
57063
|
exports.WORKSPACE_FACTORY_SCAN_EXCLUDE = WORKSPACE_FACTORY_SCAN_EXCLUDE;
|
|
56948
57064
|
exports.WORKSPACE_FACTORY_SCAN_INCLUDE = WORKSPACE_FACTORY_SCAN_INCLUDE;
|
|
57065
|
+
exports.abbreviateMcpCallType = abbreviateMcpCallType;
|
|
56949
57066
|
exports.applyEnvVarOverrides = applyEnvVarOverrides;
|
|
56950
57067
|
exports.buildActionCommands = buildActionCommands;
|
|
56951
57068
|
exports.buildActionsManifest = buildActionsManifest;
|
|
@@ -56953,6 +57070,7 @@ exports.buildAuthorizationUrl = buildAuthorizationUrl;
|
|
|
56953
57070
|
exports.buildCliPaths = buildCliPaths;
|
|
56954
57071
|
exports.buildCssUtilitiesManifest = buildCssUtilitiesManifest;
|
|
56955
57072
|
exports.buildDbxDocsUiExamplesManifest = buildDbxDocsUiExamplesManifest;
|
|
57073
|
+
exports.buildDisambiguatedMcpToolName = buildDisambiguatedMcpToolName;
|
|
56956
57074
|
exports.buildDispatcherCreditByName = buildDispatcherCreditByName;
|
|
56957
57075
|
exports.buildDumpFilePath = buildDumpFilePath;
|
|
56958
57076
|
exports.buildErrorOutput = buildErrorOutput;
|
|
@@ -56960,6 +57078,7 @@ exports.buildFiltersManifest = buildFiltersManifest;
|
|
|
56960
57078
|
exports.buildForgeFieldsManifest = buildForgeFieldsManifest;
|
|
56961
57079
|
exports.buildManifest = buildManifest;
|
|
56962
57080
|
exports.buildManifestCommands = buildManifestCommands;
|
|
57081
|
+
exports.buildMcpToolName = buildMcpToolName;
|
|
56963
57082
|
exports.buildModelDecodeCommand = buildModelDecodeCommand;
|
|
56964
57083
|
exports.buildModelInfoCommand = buildModelInfoCommand;
|
|
56965
57084
|
exports.buildModelSnapshotFieldsManifest = buildModelSnapshotFieldsManifest;
|
|
@@ -57197,6 +57316,7 @@ exports.toFilterEntryInfo = toFilterEntryInfo;
|
|
|
57197
57316
|
exports.toFormFieldInfo = toFormFieldInfo;
|
|
57198
57317
|
exports.tracedFetch = tracedFetch;
|
|
57199
57318
|
exports.unwrapFenced = unwrapFenced;
|
|
57319
|
+
exports.validateMcpToolName = validateMcpToolName;
|
|
57200
57320
|
exports.verboseLog = verboseLog;
|
|
57201
57321
|
exports.withCallModelArgs = withCallModelArgs;
|
|
57202
57322
|
exports.withEnv = withEnv;
|