@happyvertical/smrt-core 0.40.59 → 0.40.61
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/dist/generated-client-runtime.d.ts +6 -5
- package/dist/generated-client-runtime.d.ts.map +1 -1
- package/dist/generated-client-runtime.js +49 -14
- package/dist/generated-client-runtime.js.map +1 -1
- package/dist/generators/index.d.ts +3 -2
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +3 -2
- package/dist/generators/mcp-runtime-template.d.ts +13 -0
- package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
- package/dist/generators/mcp-runtime-template.js +83 -32
- package/dist/generators/mcp-runtime-template.js.map +1 -1
- package/dist/generators/mcp.d.ts +79 -18
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +429 -177
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/rest.d.ts.map +1 -1
- package/dist/generators/rest.js +3 -0
- package/dist/generators/rest.js.map +1 -1
- package/dist/generators/tool-schema.d.ts +32 -1
- package/dist/generators/tool-schema.d.ts.map +1 -1
- package/dist/generators/tool-schema.js +108 -36
- package/dist/generators/tool-schema.js.map +1 -1
- package/dist/generators/typed-http-error.d.ts +16 -0
- package/dist/generators/typed-http-error.d.ts.map +1 -0
- package/dist/generators/typed-http-error.js +17 -0
- package/dist/generators/typed-http-error.js.map +1 -0
- package/dist/generators.js +3 -2
- package/dist/index.js +3 -2
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/prebuild/index.d.ts.map +1 -1
- package/dist/prebuild/index.js +14 -1
- package/dist/prebuild/index.js.map +1 -1
- package/dist/registry/types.d.ts +15 -0
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +3 -3
- package/dist/vite-plugin/index.d.ts +2 -0
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +25 -11
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +94 -26
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/web-collections.d.ts +6 -3
- package/dist/vite-plugin/web-collections.d.ts.map +1 -1
- package/dist/vite-plugin/web-collections.js +24 -6
- package/dist/vite-plugin/web-collections.js.map +1 -1
- package/package.json +5 -5
- package/dist/manifest/test-manifest-loader.d.ts +0 -3
- package/dist/manifest/test-manifest-loader.d.ts.map +0 -1
- package/dist/manifest/test-manifest-stub.d.ts +0 -4
- package/dist/manifest/test-manifest-stub.d.ts.map +0 -1
- package/dist/manifest/test-manifest-stub.js +0 -75816
- package/dist/manifest/test-manifest-stub.js.map +0 -1
package/dist/generators/mcp.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY,
|
|
1
|
+
import { SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildCustomActionInvocationArgs, customActionParameterInputName, normalizeCustomActionFailure, resolveCustomActionMetadata } from "./custom-action.js";
|
|
2
2
|
import { ObjectRegistry } from "../registry.js";
|
|
3
3
|
import { SmrtCollection } from "../collection.js";
|
|
4
4
|
import { runWithTenantGate } from "./tenant-gate.js";
|
|
5
5
|
import { generateClaudeConfig, generateMCPDocumentation, generateMCPScript, generateRuntimeBootstrap } from "./mcp-runtime-template.js";
|
|
6
|
+
import { buildToolInputSchema, fieldTypeToJsonSchema, finalizeMcpJsonSchema } from "./tool-schema.js";
|
|
6
7
|
import { dirname, resolve } from "node:path";
|
|
7
8
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
8
9
|
//#region src/generators/mcp.ts
|
|
@@ -11,6 +12,27 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
11
12
|
*
|
|
12
13
|
* Exposes smrt objects as AI tools for Claude, GPT, and other AI models
|
|
13
14
|
*/
|
|
15
|
+
var MCP_STABLE_CATALOG_TTL_MS = 864e5;
|
|
16
|
+
/**
|
|
17
|
+
* Resolve the generated tools/list cache policy at generation time.
|
|
18
|
+
*
|
|
19
|
+
* A shared cache may otherwise serve one tenant's tool catalog to another, so
|
|
20
|
+
* public caching is deliberately double opt-in and unavailable when a
|
|
21
|
+
* generated server exposes any tenant-scoped object.
|
|
22
|
+
*/
|
|
23
|
+
function resolveMCPToolListCacheHint(options, hasTenantScopedTools) {
|
|
24
|
+
const ttlMs = options?.ttlMs ?? 864e5;
|
|
25
|
+
if (!Number.isSafeInteger(ttlMs) || ttlMs < 0) throw new RangeError("MCP tools/list cache ttlMs must be a non-negative safe integer.");
|
|
26
|
+
if (options?.cacheScope !== void 0 && options.cacheScope !== "private" && options.cacheScope !== "public") throw new RangeError("MCP tools/list cacheScope must be 'private' or 'public'.");
|
|
27
|
+
return {
|
|
28
|
+
ttlMs,
|
|
29
|
+
cacheScope: !hasTenantScopedTools && options?.cacheScope === "public" && options.publicCatalog === true ? "public" : "private"
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Return a copied, canonical tool sequence for byte-stable tools/list output. */
|
|
33
|
+
function sortMCPTools(tools) {
|
|
34
|
+
return [...tools].sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
35
|
+
}
|
|
14
36
|
var CustomActionFailureError = class extends Error {
|
|
15
37
|
failure;
|
|
16
38
|
constructor(failure) {
|
|
@@ -83,7 +105,7 @@ var MCPGenerator = class {
|
|
|
83
105
|
const objectTools = await this.generateObjectTools(simpleName, shouldInclude);
|
|
84
106
|
tools.push(...objectTools);
|
|
85
107
|
}
|
|
86
|
-
return tools;
|
|
108
|
+
return sortMCPTools(tools);
|
|
87
109
|
}
|
|
88
110
|
/**
|
|
89
111
|
* Generate tools for a specific object
|
|
@@ -96,96 +118,32 @@ var MCPGenerator = class {
|
|
|
96
118
|
if (shouldInclude("list")) tools.push({
|
|
97
119
|
name: `${lowerName}_list`,
|
|
98
120
|
description: `List ${objectName} objects with optional filtering`,
|
|
99
|
-
inputSchema:
|
|
100
|
-
|
|
101
|
-
properties: {
|
|
102
|
-
limit: {
|
|
103
|
-
type: "integer",
|
|
104
|
-
description: "Maximum number of items to return",
|
|
105
|
-
default: 50,
|
|
106
|
-
minimum: 1,
|
|
107
|
-
maximum: 1e3
|
|
108
|
-
},
|
|
109
|
-
offset: {
|
|
110
|
-
type: "integer",
|
|
111
|
-
description: "Number of items to skip",
|
|
112
|
-
default: 0,
|
|
113
|
-
minimum: 0
|
|
114
|
-
},
|
|
115
|
-
orderBy: {
|
|
116
|
-
type: "string",
|
|
117
|
-
description: "Field to order by (e.g., \"created_at DESC\")"
|
|
118
|
-
},
|
|
119
|
-
where: {
|
|
120
|
-
type: "object",
|
|
121
|
-
description: "Filter conditions as key-value pairs",
|
|
122
|
-
additionalProperties: true
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
121
|
+
inputSchema: this.buildInputSchema(objectName, "list", fields),
|
|
122
|
+
outputSchema: this.buildOutputSchema(objectName, "list", fields)
|
|
126
123
|
});
|
|
127
124
|
if (shouldInclude("get")) tools.push({
|
|
128
125
|
name: `${lowerName}_get`,
|
|
129
126
|
description: `Get a specific ${objectName} by ID or slug`,
|
|
130
|
-
inputSchema:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
127
|
+
inputSchema: this.buildInputSchema(objectName, "get", fields),
|
|
128
|
+
outputSchema: this.buildOutputSchema(objectName, "get", fields)
|
|
129
|
+
});
|
|
130
|
+
if (shouldInclude("create")) tools.push({
|
|
131
|
+
name: `${lowerName}_create`,
|
|
132
|
+
description: `Create a new ${objectName}`,
|
|
133
|
+
inputSchema: this.buildInputSchema(objectName, "create", fields),
|
|
134
|
+
outputSchema: this.buildOutputSchema(objectName, "create", fields)
|
|
135
|
+
});
|
|
136
|
+
if (shouldInclude("update")) tools.push({
|
|
137
|
+
name: `${lowerName}_update`,
|
|
138
|
+
description: `Update an existing ${objectName}`,
|
|
139
|
+
inputSchema: this.buildInputSchema(objectName, "update", fields),
|
|
140
|
+
outputSchema: this.buildOutputSchema(objectName, "update", fields)
|
|
144
141
|
});
|
|
145
|
-
if (shouldInclude("create")) {
|
|
146
|
-
const properties = {};
|
|
147
|
-
const required = [];
|
|
148
|
-
for (const [fieldName, field] of fields) {
|
|
149
|
-
properties[fieldName] = this.fieldToMCPSchema(field);
|
|
150
|
-
if (field._meta?.required) required.push(fieldName);
|
|
151
|
-
}
|
|
152
|
-
tools.push({
|
|
153
|
-
name: `${lowerName}_create`,
|
|
154
|
-
description: `Create a new ${objectName}`,
|
|
155
|
-
inputSchema: {
|
|
156
|
-
type: "object",
|
|
157
|
-
properties,
|
|
158
|
-
required
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
if (shouldInclude("update")) {
|
|
163
|
-
const properties = { id: {
|
|
164
|
-
type: "string",
|
|
165
|
-
description: "ID of the object to update"
|
|
166
|
-
} };
|
|
167
|
-
for (const [fieldName, field] of fields) properties[fieldName] = this.fieldToMCPSchema(field);
|
|
168
|
-
tools.push({
|
|
169
|
-
name: `${lowerName}_update`,
|
|
170
|
-
description: `Update an existing ${objectName}`,
|
|
171
|
-
inputSchema: {
|
|
172
|
-
type: "object",
|
|
173
|
-
properties,
|
|
174
|
-
required: ["id"]
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
142
|
if (shouldInclude("delete")) tools.push({
|
|
179
143
|
name: `${lowerName}_delete`,
|
|
180
144
|
description: `Delete a ${objectName} by ID`,
|
|
181
|
-
inputSchema:
|
|
182
|
-
|
|
183
|
-
properties: { id: {
|
|
184
|
-
type: "string",
|
|
185
|
-
description: "ID of the object to delete"
|
|
186
|
-
} },
|
|
187
|
-
required: ["id"]
|
|
188
|
-
}
|
|
145
|
+
inputSchema: this.buildInputSchema(objectName, "delete", fields),
|
|
146
|
+
outputSchema: this.buildOutputSchema(objectName, "delete", fields)
|
|
189
147
|
});
|
|
190
148
|
if (classInfo) {
|
|
191
149
|
const mcpConfig = ObjectRegistry.getConfig(objectName).mcp;
|
|
@@ -227,7 +185,8 @@ var MCPGenerator = class {
|
|
|
227
185
|
return {
|
|
228
186
|
name: `${lowerName}_${methodName}`.toLowerCase(),
|
|
229
187
|
description: `Execute ${methodName} action on ${objectName}`,
|
|
230
|
-
inputSchema:
|
|
188
|
+
inputSchema: this.buildInputSchema(objectName, methodName, ObjectRegistry.getFields(objectName), metadata),
|
|
189
|
+
outputSchema: this.buildOutputSchema(objectName, methodName, ObjectRegistry.getFields(objectName))
|
|
231
190
|
};
|
|
232
191
|
}
|
|
233
192
|
resolveCustomActionMetadata(objectName, action, method, collectionReceiver = false) {
|
|
@@ -254,45 +213,193 @@ var MCPGenerator = class {
|
|
|
254
213
|
return false;
|
|
255
214
|
}
|
|
256
215
|
}
|
|
216
|
+
/** Normalize registry fields for the transport-neutral schema emitter. */
|
|
217
|
+
toToolFields(fields) {
|
|
218
|
+
return Array.from(fields, ([name, field]) => ({
|
|
219
|
+
name,
|
|
220
|
+
type: field.type,
|
|
221
|
+
required: field.required ?? field._meta?.required,
|
|
222
|
+
nullable: field._meta?.nullable === true,
|
|
223
|
+
description: typeof field._meta?.description === "string" ? field._meta.description : void 0,
|
|
224
|
+
default: field._meta?.default,
|
|
225
|
+
maxLength: field._meta?.maxLength,
|
|
226
|
+
minLength: field._meta?.minLength,
|
|
227
|
+
min: field._meta?.min,
|
|
228
|
+
max: field._meta?.max,
|
|
229
|
+
related: field.related
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
257
232
|
/**
|
|
258
|
-
*
|
|
233
|
+
* Add the optional STI discriminator branches to write schemas. The legacy
|
|
234
|
+
* branch preserves existing base-class creates that let SMRT pick the base
|
|
235
|
+
* type; an explicit `_meta_type` selects a known child collection below.
|
|
259
236
|
*/
|
|
260
|
-
|
|
261
|
-
const schema =
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
237
|
+
buildInputSchema(objectName, action, fields, customAction) {
|
|
238
|
+
const schema = buildToolInputSchema(action, this.toToolFields(fields), customAction, ObjectRegistry.getConfig(objectName).idType);
|
|
239
|
+
if (action !== "create" && action !== "update") return schema;
|
|
240
|
+
const variants = this.getStiVariants(objectName);
|
|
241
|
+
if (variants.length === 0) return schema;
|
|
242
|
+
const properties = {
|
|
243
|
+
...schema.properties ?? {},
|
|
244
|
+
_meta_type: {
|
|
245
|
+
type: "string",
|
|
246
|
+
description: "Optional STI discriminator. When provided for create, selects the declared subtype."
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
return finalizeMcpJsonSchema({
|
|
250
|
+
...schema,
|
|
251
|
+
properties,
|
|
252
|
+
oneOf: [{ not: { required: ["_meta_type"] } }, ...variants.map(({ name, discriminator }) => {
|
|
253
|
+
const variantFields = ObjectRegistry.getFields(name);
|
|
254
|
+
return {
|
|
255
|
+
properties: {
|
|
256
|
+
...this.buildFieldSchemaProperties(variantFields),
|
|
257
|
+
_meta_type: { const: discriminator }
|
|
258
|
+
},
|
|
259
|
+
required: ["_meta_type", ...this.toToolFields(variantFields).filter((field) => field.required).map((field) => field.name)]
|
|
260
|
+
};
|
|
261
|
+
})]
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Public output schemas follow the actual `toPublicJSON()` boundary: known
|
|
266
|
+
* non-sensitive fields are described, while `additionalProperties` keeps
|
|
267
|
+
* framework/system fields and application transforms honest.
|
|
268
|
+
*/
|
|
269
|
+
buildOutputSchema(objectName, action, fields) {
|
|
270
|
+
const errorSchema = {
|
|
271
|
+
type: "object",
|
|
272
|
+
properties: { error: {
|
|
273
|
+
type: "object",
|
|
274
|
+
additionalProperties: true
|
|
275
|
+
} },
|
|
276
|
+
required: ["error"]
|
|
277
|
+
};
|
|
278
|
+
if (![
|
|
279
|
+
"list",
|
|
280
|
+
"get",
|
|
281
|
+
"create",
|
|
282
|
+
"update",
|
|
283
|
+
"delete"
|
|
284
|
+
].includes(action)) return finalizeMcpJsonSchema({
|
|
285
|
+
type: "object",
|
|
286
|
+
anyOf: [{
|
|
287
|
+
type: "object",
|
|
288
|
+
properties: { data: {} },
|
|
289
|
+
required: ["data"]
|
|
290
|
+
}, { $ref: "#/$defs/error" }],
|
|
291
|
+
$defs: { error: errorSchema }
|
|
292
|
+
});
|
|
293
|
+
const itemSchema = this.buildPublicItemSchema(objectName, fields);
|
|
294
|
+
if (action === "list") return finalizeMcpJsonSchema({
|
|
295
|
+
type: "object",
|
|
296
|
+
anyOf: [{
|
|
297
|
+
type: "object",
|
|
298
|
+
properties: {
|
|
299
|
+
data: {
|
|
300
|
+
type: "array",
|
|
301
|
+
items: { $ref: "#/$defs/publicItem" }
|
|
302
|
+
},
|
|
303
|
+
meta: {
|
|
304
|
+
type: "object",
|
|
305
|
+
properties: {
|
|
306
|
+
total: {
|
|
307
|
+
type: "integer",
|
|
308
|
+
minimum: 0
|
|
309
|
+
},
|
|
310
|
+
limit: {
|
|
311
|
+
type: "integer",
|
|
312
|
+
minimum: 0
|
|
313
|
+
},
|
|
314
|
+
offset: {
|
|
315
|
+
type: "integer",
|
|
316
|
+
minimum: 0
|
|
317
|
+
},
|
|
318
|
+
count: {
|
|
319
|
+
type: "integer",
|
|
320
|
+
minimum: 0
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
required: [
|
|
324
|
+
"total",
|
|
325
|
+
"limit",
|
|
326
|
+
"offset",
|
|
327
|
+
"count"
|
|
328
|
+
]
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
required: ["data", "meta"]
|
|
332
|
+
}, { $ref: "#/$defs/error" }],
|
|
333
|
+
$defs: {
|
|
334
|
+
publicItem: itemSchema,
|
|
335
|
+
error: errorSchema
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
if (action === "delete") return finalizeMcpJsonSchema({
|
|
339
|
+
type: "object",
|
|
340
|
+
anyOf: [{
|
|
341
|
+
type: "object",
|
|
342
|
+
properties: {
|
|
343
|
+
success: { const: true },
|
|
344
|
+
message: { type: "string" }
|
|
345
|
+
},
|
|
346
|
+
required: ["success", "message"]
|
|
347
|
+
}, { $ref: "#/$defs/error" }],
|
|
348
|
+
$defs: { error: errorSchema }
|
|
349
|
+
});
|
|
350
|
+
return finalizeMcpJsonSchema({
|
|
351
|
+
type: "object",
|
|
352
|
+
anyOf: [itemSchema, { $ref: "#/$defs/error" }],
|
|
353
|
+
$defs: { error: errorSchema }
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
buildPublicItemSchema(objectName, fields) {
|
|
357
|
+
const properties = this.buildFieldSchemaProperties(fields, true);
|
|
358
|
+
const variants = this.getStiVariants(objectName);
|
|
359
|
+
if (variants.length > 0) return { oneOf: variants.map(({ name, discriminator }) => ({
|
|
360
|
+
type: "object",
|
|
361
|
+
properties: {
|
|
362
|
+
...this.buildFieldSchemaProperties(ObjectRegistry.getFields(name), true),
|
|
363
|
+
_meta_type: { const: discriminator }
|
|
364
|
+
},
|
|
365
|
+
required: ["_meta_type"],
|
|
366
|
+
additionalProperties: true
|
|
367
|
+
})) };
|
|
368
|
+
return {
|
|
369
|
+
type: "object",
|
|
370
|
+
properties,
|
|
371
|
+
additionalProperties: true
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
buildFieldSchemaProperties(fields, publicOnly = false) {
|
|
375
|
+
const properties = {};
|
|
376
|
+
for (const [name, field] of fields) {
|
|
377
|
+
if (publicOnly && (field._meta?.sensitive === true || field._meta?.transient === true)) continue;
|
|
378
|
+
const [toolField] = this.toToolFields(/* @__PURE__ */ new Map([[name, field]]));
|
|
379
|
+
if (!toolField) continue;
|
|
380
|
+
properties[name] = { ...fieldTypeToJsonSchema(toolField) };
|
|
293
381
|
}
|
|
294
|
-
|
|
295
|
-
|
|
382
|
+
return properties;
|
|
383
|
+
}
|
|
384
|
+
getStiVariants(objectName) {
|
|
385
|
+
if (ObjectRegistry.getTableStrategy(objectName) !== "sti") return [];
|
|
386
|
+
const base = ObjectRegistry.getClass(objectName);
|
|
387
|
+
const baseNames = new Set([
|
|
388
|
+
objectName,
|
|
389
|
+
base?.name,
|
|
390
|
+
base?.qualifiedName
|
|
391
|
+
].filter((name) => typeof name === "string"));
|
|
392
|
+
const variants = /* @__PURE__ */ new Map();
|
|
393
|
+
for (const [key, info] of ObjectRegistry.getAllClasses()) {
|
|
394
|
+
const name = info.name || key;
|
|
395
|
+
if (!ObjectRegistry.getInheritanceChain(name).some((ancestor) => baseNames.has(ancestor))) continue;
|
|
396
|
+
const discriminator = info.qualifiedName || name;
|
|
397
|
+
variants.set(discriminator, {
|
|
398
|
+
name,
|
|
399
|
+
discriminator
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
return Array.from(variants.values()).sort((left, right) => left.discriminator.localeCompare(right.discriminator));
|
|
296
403
|
}
|
|
297
404
|
/**
|
|
298
405
|
* Handle MCP tool calls
|
|
@@ -319,25 +426,56 @@ var MCPGenerator = class {
|
|
|
319
426
|
if (!classInfo) throw new Error(`Object type '${objectName}' not found`);
|
|
320
427
|
const collection = await this.getCollection(actualObjectName, classInfo);
|
|
321
428
|
const result = await this.executeAction(collection, action, args, actualObjectName);
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
429
|
+
const publicResult = this.toJsonValue(result);
|
|
430
|
+
const structuredContent = this.toStructuredContent(action, publicResult);
|
|
431
|
+
return {
|
|
432
|
+
content: [{
|
|
433
|
+
type: "text",
|
|
434
|
+
text: JSON.stringify(publicResult, null, 2)
|
|
435
|
+
}],
|
|
436
|
+
structuredContent
|
|
437
|
+
};
|
|
326
438
|
} catch (error) {
|
|
327
|
-
if (error instanceof CustomActionFailureError)
|
|
439
|
+
if (error instanceof CustomActionFailureError) {
|
|
440
|
+
const structuredContent = { error: error.failure };
|
|
441
|
+
return {
|
|
442
|
+
content: [{
|
|
443
|
+
type: "text",
|
|
444
|
+
text: JSON.stringify(structuredContent)
|
|
445
|
+
}],
|
|
446
|
+
isError: true,
|
|
447
|
+
structuredContent,
|
|
448
|
+
_meta: { [SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY]: error.failure }
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
452
|
+
return {
|
|
328
453
|
content: [{
|
|
329
454
|
type: "text",
|
|
330
|
-
text:
|
|
455
|
+
text: `Error: ${message}`
|
|
331
456
|
}],
|
|
332
457
|
isError: true,
|
|
333
|
-
|
|
458
|
+
structuredContent: { error: { message } }
|
|
334
459
|
};
|
|
335
|
-
return { content: [{
|
|
336
|
-
type: "text",
|
|
337
|
-
text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
338
|
-
}] };
|
|
339
460
|
}
|
|
340
461
|
}
|
|
462
|
+
/** Convert runtime values to the JSON values MCP structuredContent permits. */
|
|
463
|
+
toJsonValue(value) {
|
|
464
|
+
const serialized = JSON.stringify(value);
|
|
465
|
+
return serialized === void 0 ? null : JSON.parse(serialized);
|
|
466
|
+
}
|
|
467
|
+
/** Build the MCP-required object root without changing legacy text payloads. */
|
|
468
|
+
toStructuredContent(action, publicResult) {
|
|
469
|
+
if (![
|
|
470
|
+
"list",
|
|
471
|
+
"get",
|
|
472
|
+
"create",
|
|
473
|
+
"update",
|
|
474
|
+
"delete"
|
|
475
|
+
].includes(action)) return { data: publicResult };
|
|
476
|
+
if (publicResult === null || typeof publicResult !== "object" || Array.isArray(publicResult)) throw new Error(`Expected object result for MCP ${action} action`);
|
|
477
|
+
return publicResult;
|
|
478
|
+
}
|
|
341
479
|
/**
|
|
342
480
|
* Get or create collection for an object
|
|
343
481
|
*/
|
|
@@ -433,14 +571,24 @@ var MCPGenerator = class {
|
|
|
433
571
|
if (!this.context.user) throw new Error("Authentication required");
|
|
434
572
|
}
|
|
435
573
|
async executeAction(collection, action, args, objectName) {
|
|
574
|
+
let targetCollection = collection;
|
|
575
|
+
let targetObjectName = objectName;
|
|
576
|
+
if (action === "create" && objectName && typeof args._meta_type === "string") {
|
|
577
|
+
const variant = this.getStiVariants(objectName).find((candidate) => candidate.discriminator === args._meta_type);
|
|
578
|
+
if (!variant) throw new Error(`Unknown STI discriminator: ${args._meta_type}`);
|
|
579
|
+
const classInfo = ObjectRegistry.getClass(variant.name);
|
|
580
|
+
if (!classInfo) throw new Error(`STI subtype '${variant.name}' is not registered`);
|
|
581
|
+
targetCollection = await this.getCollection(variant.name, classInfo);
|
|
582
|
+
targetObjectName = variant.name;
|
|
583
|
+
}
|
|
436
584
|
const mutating = action !== "list" && action !== "get";
|
|
437
|
-
this.requireToolAuth(
|
|
585
|
+
this.requireToolAuth(targetObjectName, mutating);
|
|
438
586
|
return runWithTenantGate({
|
|
439
|
-
className:
|
|
587
|
+
className: targetObjectName,
|
|
440
588
|
tenantId: this.context.tenantId,
|
|
441
589
|
allowCrossTenant: this.context.allowCrossTenant,
|
|
442
590
|
surface: "MCP"
|
|
443
|
-
}, () => this.runAction(
|
|
591
|
+
}, () => this.runAction(targetCollection, action, args, targetObjectName));
|
|
444
592
|
}
|
|
445
593
|
/**
|
|
446
594
|
* Derive the set of tenant-scoped object names (lowercased simple names) from
|
|
@@ -482,6 +630,29 @@ var MCPGenerator = class {
|
|
|
482
630
|
return Array.from(scoped);
|
|
483
631
|
}
|
|
484
632
|
/**
|
|
633
|
+
* Whether a catalog contains a tenant-scoped class for cache isolation.
|
|
634
|
+
*
|
|
635
|
+
* Unlike the emitted runtime tenant gate, cache visibility must also fail
|
|
636
|
+
* closed for core-declared `@smrt({ tenantScoped })` models when the optional
|
|
637
|
+
* tenancy package is not installed. The registry covers that form, while
|
|
638
|
+
* `tenantScopedObjectNames()` covers the tenancy-owned decorator form.
|
|
639
|
+
*/
|
|
640
|
+
async hasTenantScopedTools(tools) {
|
|
641
|
+
if ((await this.tenantScopedObjectNames(tools)).length > 0) return true;
|
|
642
|
+
for (const tool of tools) {
|
|
643
|
+
const [objectName] = tool.name.split("_");
|
|
644
|
+
if (!objectName) continue;
|
|
645
|
+
for (const [key, info] of ObjectRegistry.getAllClasses()) {
|
|
646
|
+
const simpleName = info.name || key;
|
|
647
|
+
if (simpleName.toLowerCase() === objectName.toLowerCase()) {
|
|
648
|
+
if (ObjectRegistry.isTenantScoped(simpleName)) return true;
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
485
656
|
* Execute a resolved MCP action (CRUD or custom) against a collection. Always
|
|
486
657
|
* invoked inside the tenant gate established by {@link executeAction}.
|
|
487
658
|
*/
|
|
@@ -638,6 +809,8 @@ var MCPGenerator = class {
|
|
|
638
809
|
if (modular) await this.generateModularServer(outputDir, serverName, serverVersion, debug);
|
|
639
810
|
else {
|
|
640
811
|
const tools = await this.generateTools();
|
|
812
|
+
const tenantScopedObjects = await this.tenantScopedObjectNames(tools);
|
|
813
|
+
const hasTenantScopedTools = tenantScopedObjects.length > 0 || await this.hasTenantScopedTools(tools);
|
|
641
814
|
await writeFile(resolvedPath, generateRuntimeBootstrap({
|
|
642
815
|
name: serverName,
|
|
643
816
|
version: serverVersion,
|
|
@@ -647,7 +820,9 @@ var MCPGenerator = class {
|
|
|
647
820
|
debug,
|
|
648
821
|
tools,
|
|
649
822
|
customActions: await this.runtimeCustomActions(tools),
|
|
650
|
-
tenantScopedObjects
|
|
823
|
+
tenantScopedObjects,
|
|
824
|
+
stiTargets: this.runtimeStiTargets(tools),
|
|
825
|
+
toolListCacheHint: resolveMCPToolListCacheHint(this.config.cache?.toolsList, hasTenantScopedTools)
|
|
651
826
|
}), "utf-8");
|
|
652
827
|
console.log(`✅ Generated MCP server: ${resolvedPath}`);
|
|
653
828
|
}
|
|
@@ -703,6 +878,28 @@ var MCPGenerator = class {
|
|
|
703
878
|
return metadata;
|
|
704
879
|
}
|
|
705
880
|
/**
|
|
881
|
+
* Emit only the STI discriminator targets advertised by create-tool schemas.
|
|
882
|
+
* Generated processes start with an empty registry, so resolving the
|
|
883
|
+
* qualified target through `getCollection()` both validates the declaration
|
|
884
|
+
* and lets the public registry loader register the selected subtype.
|
|
885
|
+
*/
|
|
886
|
+
runtimeStiTargets(tools) {
|
|
887
|
+
const targets = {};
|
|
888
|
+
const classes = ObjectRegistry.getAllClasses();
|
|
889
|
+
for (const tool of tools) {
|
|
890
|
+
const separator = tool.name.indexOf("_");
|
|
891
|
+
if (separator === -1 || tool.name.slice(separator + 1) !== "create") continue;
|
|
892
|
+
const objectPrefix = tool.name.slice(0, separator);
|
|
893
|
+
const matched = Array.from(classes.entries()).find(([key, info]) => (info.name || key).toLowerCase() === objectPrefix);
|
|
894
|
+
if (!matched) continue;
|
|
895
|
+
const [key, classInfo] = matched;
|
|
896
|
+
const variants = this.getStiVariants(classInfo.name || key);
|
|
897
|
+
if (variants.length === 0) continue;
|
|
898
|
+
targets[objectPrefix] = Object.fromEntries(variants.map((variant) => [variant.discriminator, variant.discriminator]));
|
|
899
|
+
}
|
|
900
|
+
return targets;
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
706
903
|
* Generate modular MCP server structure
|
|
707
904
|
*
|
|
708
905
|
* Creates separate files for tools, handlers, configuration, and main entry point.
|
|
@@ -721,15 +918,17 @@ var MCPGenerator = class {
|
|
|
721
918
|
const configPath = resolve(outputDir, "config.ts");
|
|
722
919
|
await writeFile(configPath, this.generateConfigFile(serverName, serverVersion, debug), "utf-8");
|
|
723
920
|
console.log(`✅ Generated config: ${configPath}`);
|
|
921
|
+
const generatedTools = await this.generateTools();
|
|
724
922
|
const toolsPath = resolve(toolsDir, "index.ts");
|
|
725
|
-
await writeFile(toolsPath,
|
|
923
|
+
await writeFile(toolsPath, this.generateToolsFile(generatedTools), "utf-8");
|
|
726
924
|
console.log(`✅ Generated tools: ${toolsPath}`);
|
|
727
925
|
const handlersPath = resolve(handlersDir, "index.ts");
|
|
728
|
-
const tenantScopedObjects = await this.tenantScopedObjectNames(
|
|
926
|
+
const tenantScopedObjects = await this.tenantScopedObjectNames(generatedTools);
|
|
927
|
+
const hasTenantScopedTools = tenantScopedObjects.length > 0 || await this.hasTenantScopedTools(generatedTools);
|
|
729
928
|
await writeFile(handlersPath, await this.generateHandlersFile(tenantScopedObjects), "utf-8");
|
|
730
929
|
console.log(`✅ Generated handlers: ${handlersPath}`);
|
|
731
930
|
const indexPath = resolve(outputDir, "index.js");
|
|
732
|
-
await writeFile(indexPath, this.generateModularIndex(), "utf-8");
|
|
931
|
+
await writeFile(indexPath, this.generateModularIndex(resolveMCPToolListCacheHint(this.config.cache?.toolsList, hasTenantScopedTools)), "utf-8");
|
|
733
932
|
console.log(`✅ Generated MCP server: ${indexPath}`);
|
|
734
933
|
}
|
|
735
934
|
/**
|
|
@@ -750,8 +949,7 @@ export const DEBUG = ${debug};
|
|
|
750
949
|
/**
|
|
751
950
|
* Generate tools definitions file for modular server
|
|
752
951
|
*/
|
|
753
|
-
|
|
754
|
-
const tools = await this.generateTools();
|
|
952
|
+
generateToolsFile(tools) {
|
|
755
953
|
return `/**
|
|
756
954
|
* MCP Tools Definitions
|
|
757
955
|
* Auto-generated from SMRT objects
|
|
@@ -761,14 +959,15 @@ export const tools: Array<{
|
|
|
761
959
|
name: string;
|
|
762
960
|
description: string;
|
|
763
961
|
inputSchema: any;
|
|
962
|
+
outputSchema: any;
|
|
764
963
|
}> = ${JSON.stringify(tools, null, 2)};
|
|
765
964
|
`;
|
|
766
965
|
}
|
|
767
966
|
/**
|
|
768
967
|
* Generate switch cases for tool execution
|
|
769
968
|
*/
|
|
770
|
-
async generateToolSwitchCases(indent = " ") {
|
|
771
|
-
const tools = await this.generateTools();
|
|
969
|
+
async generateToolSwitchCases(indent = " ", generatedTools) {
|
|
970
|
+
const tools = generatedTools ?? await this.generateTools();
|
|
772
971
|
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
773
972
|
return (await Promise.all(tools.map(async (tool) => {
|
|
774
973
|
const separator = tool.name.indexOf("_");
|
|
@@ -781,13 +980,17 @@ ${indent} const offset = args.offset ?? 0;
|
|
|
781
980
|
${indent} const where = args.where ?? {};
|
|
782
981
|
|
|
783
982
|
${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {
|
|
784
|
-
${indent} persistence: { type: '
|
|
983
|
+
${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },
|
|
785
984
|
${indent} ai: aiConfig
|
|
786
985
|
${indent} });
|
|
787
986
|
|
|
788
987
|
${indent} const items = await collection.list({ where, limit, offset });
|
|
789
988
|
${indent} const itemsPublic = items.map((item) => item.toPublicJSON(PUBLIC_JSON_OPTIONS));
|
|
790
|
-
${indent}
|
|
989
|
+
${indent} const structuredContent = {
|
|
990
|
+
${indent} data: itemsPublic,
|
|
991
|
+
${indent} meta: { total: await collection.count({ where }), limit, offset, count: items.length },
|
|
992
|
+
${indent} };
|
|
993
|
+
${indent} return successResult(structuredContent, JSON.stringify(itemsPublic));
|
|
791
994
|
${indent}}`;
|
|
792
995
|
case "get": return `${indent}case '${tool.name}': {
|
|
793
996
|
${indent} if (!args.id && !args.slug) {
|
|
@@ -795,7 +998,7 @@ ${indent} throw new Error('Either id or slug is required');
|
|
|
795
998
|
${indent} }
|
|
796
999
|
|
|
797
1000
|
${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {
|
|
798
|
-
${indent} persistence: { type: '
|
|
1001
|
+
${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },
|
|
799
1002
|
${indent} ai: aiConfig
|
|
800
1003
|
${indent} });
|
|
801
1004
|
|
|
@@ -806,18 +1009,15 @@ ${indent} if (!item) {
|
|
|
806
1009
|
${indent} throw new Error('Object not found');
|
|
807
1010
|
${indent} }
|
|
808
1011
|
|
|
809
|
-
${indent} return
|
|
1012
|
+
${indent} return successResult(item.toPublicJSON(PUBLIC_JSON_OPTIONS));
|
|
810
1013
|
${indent}}`;
|
|
811
1014
|
case "create": return `${indent}case '${tool.name}': {
|
|
812
|
-
${indent} const collection = await
|
|
813
|
-
${indent} persistence: { type: 'sql', url: process.env.DATABASE_URL || ':memory:' },
|
|
814
|
-
${indent} ai: aiConfig
|
|
815
|
-
${indent} });
|
|
1015
|
+
${indent} const { collection, objectName: targetObjectName } = await resolveCreateTarget('${objectName}', args, aiConfig);
|
|
816
1016
|
|
|
817
|
-
${indent} const newItem = await collection.create(applyWritablePolicy(
|
|
1017
|
+
${indent} const newItem = await collection.create(applyWritablePolicy(targetObjectName, args));
|
|
818
1018
|
${indent} await newItem.save();
|
|
819
1019
|
|
|
820
|
-
${indent} return
|
|
1020
|
+
${indent} return successResult(newItem.toPublicJSON(PUBLIC_JSON_OPTIONS));
|
|
821
1021
|
${indent}}`;
|
|
822
1022
|
case "update": return `${indent}case '${tool.name}': {
|
|
823
1023
|
${indent} const { id, ...updateData } = args;
|
|
@@ -826,7 +1026,7 @@ ${indent} throw new Error('ID is required for update');
|
|
|
826
1026
|
${indent} }
|
|
827
1027
|
|
|
828
1028
|
${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {
|
|
829
|
-
${indent} persistence: { type: '
|
|
1029
|
+
${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },
|
|
830
1030
|
${indent} ai: aiConfig
|
|
831
1031
|
${indent} });
|
|
832
1032
|
|
|
@@ -838,7 +1038,7 @@ ${indent} }
|
|
|
838
1038
|
${indent} Object.assign(existing, applyWritablePolicy('${capitalize(objectName)}', updateData));
|
|
839
1039
|
${indent} await existing.save();
|
|
840
1040
|
|
|
841
|
-
${indent} return
|
|
1041
|
+
${indent} return successResult(existing.toPublicJSON(PUBLIC_JSON_OPTIONS));
|
|
842
1042
|
${indent}}`;
|
|
843
1043
|
case "delete": return `${indent}case '${tool.name}': {
|
|
844
1044
|
${indent} if (!args.id) {
|
|
@@ -846,7 +1046,7 @@ ${indent} throw new Error('ID is required for delete');
|
|
|
846
1046
|
${indent} }
|
|
847
1047
|
|
|
848
1048
|
${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {
|
|
849
|
-
${indent} persistence: { type: '
|
|
1049
|
+
${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },
|
|
850
1050
|
${indent} ai: aiConfig
|
|
851
1051
|
${indent} });
|
|
852
1052
|
|
|
@@ -857,7 +1057,7 @@ ${indent} }
|
|
|
857
1057
|
|
|
858
1058
|
${indent} await toDelete.delete();
|
|
859
1059
|
|
|
860
|
-
${indent} return
|
|
1060
|
+
${indent} return successResult({ success: true, message: 'Object deleted successfully' });
|
|
861
1061
|
${indent}}`;
|
|
862
1062
|
default: {
|
|
863
1063
|
const matched = Array.from(ObjectRegistry.getAllClasses().entries()).find(([key, info]) => (info.name || key).toLowerCase() === objectName.toLowerCase());
|
|
@@ -878,7 +1078,7 @@ ${indent} throw new Error('Custom action ${action} is collection-scoped and d
|
|
|
878
1078
|
${indent} }
|
|
879
1079
|
|
|
880
1080
|
${indent} const collection = await ObjectRegistry.getCollection(${JSON.stringify(registeredName)}, {
|
|
881
|
-
${indent} persistence: { type: '
|
|
1081
|
+
${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },
|
|
882
1082
|
${indent} ai: aiConfig
|
|
883
1083
|
${indent} });
|
|
884
1084
|
|
|
@@ -900,14 +1100,15 @@ ${indent} const methodArgs = ${methodArgs.startsWith("[") ? methodArgs : `[${me
|
|
|
900
1100
|
${indent} const result = await actionMethod.call(target, ...methodArgs);
|
|
901
1101
|
${indent} const failure = normalizeCustomActionFailure(result);
|
|
902
1102
|
${indent} if (failure) {
|
|
903
|
-
${indent} return
|
|
904
|
-
${indent}
|
|
905
|
-
${indent}
|
|
906
|
-
${indent}
|
|
907
|
-
${indent}
|
|
1103
|
+
${indent} return errorResult(
|
|
1104
|
+
${indent} { error: failure },
|
|
1105
|
+
${indent} JSON.stringify({ error: failure }),
|
|
1106
|
+
${indent} { [SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY]: failure },
|
|
1107
|
+
${indent} );
|
|
908
1108
|
${indent} }
|
|
909
1109
|
|
|
910
|
-
${indent}
|
|
1110
|
+
${indent} const publicResult = toPublicResult(result);
|
|
1111
|
+
${indent} return successResult({ data: publicResult }, JSON.stringify(publicResult));
|
|
911
1112
|
${indent}}`;
|
|
912
1113
|
}
|
|
913
1114
|
}
|
|
@@ -917,7 +1118,9 @@ ${indent}}`;
|
|
|
917
1118
|
* Generate handlers file for modular server
|
|
918
1119
|
*/
|
|
919
1120
|
async generateHandlersFile(tenantScopedObjects = []) {
|
|
920
|
-
const
|
|
1121
|
+
const tools = await this.generateTools();
|
|
1122
|
+
const switchCases = await this.generateToolSwitchCases(" ", tools);
|
|
1123
|
+
const stiTargets = this.runtimeStiTargets(tools);
|
|
921
1124
|
const tenantScopedSet = Array.from(new Set(tenantScopedObjects.map((n) => n.toLowerCase())));
|
|
922
1125
|
const hasTenantScoped = tenantScopedSet.length > 0;
|
|
923
1126
|
return `/**
|
|
@@ -950,6 +1153,7 @@ const PUBLIC_JSON_OPTIONS = {
|
|
|
950
1153
|
.map((permission) => permission.trim())
|
|
951
1154
|
.filter(Boolean),
|
|
952
1155
|
};
|
|
1156
|
+
const STI_TARGETS: Record<string, Record<string, string>> = ${JSON.stringify(stiTargets)};
|
|
953
1157
|
|
|
954
1158
|
/**
|
|
955
1159
|
* Mass-assignment guard (#1540): strip framework/server-managed and
|
|
@@ -984,6 +1188,23 @@ function applyWritablePolicy(objectName: string, data: any): Record<string, any>
|
|
|
984
1188
|
return result;
|
|
985
1189
|
}
|
|
986
1190
|
|
|
1191
|
+
/** Resolve an advertised STI discriminator to its registered subtype collection. */
|
|
1192
|
+
async function resolveCreateTarget(baseObjectName: string, args: Record<string, any>, aiConfig: any) {
|
|
1193
|
+
let objectName = baseObjectName;
|
|
1194
|
+
const discriminator = args._meta_type;
|
|
1195
|
+
const targets = STI_TARGETS[baseObjectName];
|
|
1196
|
+
if (typeof discriminator === 'string' && targets) {
|
|
1197
|
+
const target = targets[discriminator];
|
|
1198
|
+
if (!target) throw new Error('Unknown STI discriminator: ' + discriminator);
|
|
1199
|
+
objectName = target;
|
|
1200
|
+
}
|
|
1201
|
+
const collection = await ObjectRegistry.getCollection(objectName, {
|
|
1202
|
+
persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },
|
|
1203
|
+
ai: aiConfig,
|
|
1204
|
+
});
|
|
1205
|
+
return { collection, objectName };
|
|
1206
|
+
}
|
|
1207
|
+
|
|
987
1208
|
/**
|
|
988
1209
|
* Sensitive-field-safe serialization for custom-action results (#1540).
|
|
989
1210
|
* Recurses through arrays and plain objects so nested SmrtObjects are stripped
|
|
@@ -1008,6 +1229,22 @@ function toPublicResult(value: any, seen: WeakSet<object> = new WeakSet()): any
|
|
|
1008
1229
|
return out;
|
|
1009
1230
|
}
|
|
1010
1231
|
|
|
1232
|
+
function successResult(structuredContent: any, text = JSON.stringify(structuredContent)) {
|
|
1233
|
+
return {
|
|
1234
|
+
content: [{ type: 'text', text }],
|
|
1235
|
+
structuredContent,
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
function errorResult(structuredContent: any, text: string, _meta?: Record<string, any>) {
|
|
1240
|
+
return {
|
|
1241
|
+
content: [{ type: 'text', text }],
|
|
1242
|
+
isError: true,
|
|
1243
|
+
structuredContent,
|
|
1244
|
+
...(_meta ? { _meta } : {}),
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1011
1248
|
/**
|
|
1012
1249
|
* Handle tool call request
|
|
1013
1250
|
*/
|
|
@@ -1043,15 +1280,10 @@ ${hasTenantScoped ? `
|
|
|
1043
1280
|
} catch (error) {
|
|
1044
1281
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1045
1282
|
|
|
1046
|
-
return
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
text: \`Error executing tool \${name}: \${errorMessage}\`,
|
|
1051
|
-
},
|
|
1052
|
-
],
|
|
1053
|
-
isError: true,
|
|
1054
|
-
};
|
|
1283
|
+
return errorResult(
|
|
1284
|
+
{ error: { message: errorMessage } },
|
|
1285
|
+
\`Error executing tool \${name}: \${errorMessage}\`,
|
|
1286
|
+
);
|
|
1055
1287
|
}
|
|
1056
1288
|
}
|
|
1057
1289
|
`;
|
|
@@ -1059,7 +1291,7 @@ ${hasTenantScoped ? `
|
|
|
1059
1291
|
/**
|
|
1060
1292
|
* Generate modular index file (main entry point)
|
|
1061
1293
|
*/
|
|
1062
|
-
generateModularIndex() {
|
|
1294
|
+
generateModularIndex(toolListCacheHint) {
|
|
1063
1295
|
return `#!/usr/bin/env node
|
|
1064
1296
|
/**
|
|
1065
1297
|
* Auto-generated MCP Server
|
|
@@ -1070,7 +1302,10 @@ ${hasTenantScoped ? `
|
|
|
1070
1302
|
|
|
1071
1303
|
import { Server } from '@modelcontextprotocol/server';
|
|
1072
1304
|
import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
|
1305
|
+
import { existsSync } from 'node:fs';
|
|
1306
|
+
import { resolve } from 'node:path';
|
|
1073
1307
|
import { pathToFileURL } from 'node:url';
|
|
1308
|
+
import { ObjectRegistry } from '@happyvertical/smrt-core';
|
|
1074
1309
|
import { loadConfig } from '@happyvertical/smrt-config';
|
|
1075
1310
|
import { getDatabase } from '@happyvertical/sql';
|
|
1076
1311
|
import { getAI } from '@happyvertical/ai';
|
|
@@ -1079,6 +1314,8 @@ import { SERVER_NAME, SERVER_VERSION, DEBUG } from './config.js';
|
|
|
1079
1314
|
import { tools } from './tools/index.js';
|
|
1080
1315
|
import { handleToolCall } from './handlers/index.js';
|
|
1081
1316
|
|
|
1317
|
+
const TOOL_LIST_CACHE_HINT = ${JSON.stringify(toolListCacheHint)};
|
|
1318
|
+
|
|
1082
1319
|
/**
|
|
1083
1320
|
* Main server startup function
|
|
1084
1321
|
*/
|
|
@@ -1088,6 +1325,17 @@ export async function createServer() {
|
|
|
1088
1325
|
console.error(\`[MCP] Available tools:\`, tools.map(t => t.name).join(', '));
|
|
1089
1326
|
}
|
|
1090
1327
|
|
|
1328
|
+
// Register the application package manifest before resolving generated
|
|
1329
|
+
// object names. Generated servers are commonly run from the application
|
|
1330
|
+
// package itself, which is not a node_modules dependency of its process.
|
|
1331
|
+
const localManifestPaths = [
|
|
1332
|
+
resolve(process.cwd(), 'dist', 'manifest.json'),
|
|
1333
|
+
resolve(process.cwd(), '.smrt', 'manifest.json'),
|
|
1334
|
+
].filter(existsSync);
|
|
1335
|
+
if (localManifestPaths.length > 0) {
|
|
1336
|
+
ObjectRegistry.loadAllManifests({ manifestPaths: localManifestPaths });
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1091
1339
|
// Load configuration from environment and .smrt.config files
|
|
1092
1340
|
const appConfig = await loadConfig();
|
|
1093
1341
|
const aiConfig = appConfig?.ai || {};
|
|
@@ -1102,6 +1350,9 @@ export async function createServer() {
|
|
|
1102
1350
|
capabilities: {
|
|
1103
1351
|
tools: {},
|
|
1104
1352
|
},
|
|
1353
|
+
cacheHints: {
|
|
1354
|
+
'tools/list': TOOL_LIST_CACHE_HINT,
|
|
1355
|
+
},
|
|
1105
1356
|
}
|
|
1106
1357
|
);
|
|
1107
1358
|
|
|
@@ -1112,10 +1363,11 @@ export async function createServer() {
|
|
|
1112
1363
|
}
|
|
1113
1364
|
|
|
1114
1365
|
return {
|
|
1115
|
-
tools: tools.map(tool => ({
|
|
1366
|
+
tools: [...tools].sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0).map(tool => ({
|
|
1116
1367
|
name: tool.name,
|
|
1117
1368
|
description: tool.description,
|
|
1118
1369
|
inputSchema: tool.inputSchema,
|
|
1370
|
+
outputSchema: tool.outputSchema,
|
|
1119
1371
|
})),
|
|
1120
1372
|
};
|
|
1121
1373
|
});
|
|
@@ -1163,6 +1415,6 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
|
1163
1415
|
}
|
|
1164
1416
|
};
|
|
1165
1417
|
//#endregion
|
|
1166
|
-
export { MCPGenerator };
|
|
1418
|
+
export { MCPGenerator, MCP_STABLE_CATALOG_TTL_MS, resolveMCPToolListCacheHint, sortMCPTools };
|
|
1167
1419
|
|
|
1168
1420
|
//# sourceMappingURL=mcp.js.map
|