@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
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
* failures were therefore invisible to callers — an optimistic-update layer
|
|
10
10
|
* (e.g. TanStack DB `onInsert`) could never observe the failure and roll back.
|
|
11
11
|
*
|
|
12
|
-
* `SmrtClientError` carries the HTTP `status` and
|
|
13
|
-
*
|
|
14
|
-
* messages. Both helpers are emitted with a
|
|
15
|
-
* collide with a collection key in the generated
|
|
12
|
+
* `SmrtClientError` carries the HTTP `status` and, for non-5xx responses, the
|
|
13
|
+
* parsed error `body`, so callers can branch on status and surface safe server
|
|
14
|
+
* messages. Server-side failures stay opaque. Both helpers are emitted with a
|
|
15
|
+
* `__smrt` prefix so they cannot collide with a collection key in the generated
|
|
16
|
+
* module.
|
|
16
17
|
*
|
|
17
18
|
* WIRE-SHAPE POLICY (#1797, ADR 0001): the server returns BARE JSON — a bare
|
|
18
19
|
* array for list, a bare object for get/create/update — with snake_case field
|
|
@@ -22,5 +23,5 @@
|
|
|
22
23
|
* (vite-plugin + prebuild) are written to match this shape. This same
|
|
23
24
|
* snake_case wire feeds the mobile DTO codegen; do not change the wire here.
|
|
24
25
|
*/
|
|
25
|
-
export declare const CLIENT_FETCH_RUNTIME = "class SmrtClientError extends Error {\n constructor(message, status, body) {\n super(message);\n this.name = 'SmrtClientError';\n this.status = status;\n this.body = body;\n }\n}\n\nasync function __smrtParseBody(response) {\n const contentType = response.headers.get('content-type') || '';\n if (contentType.includes('application/json')) {\n try {\n return await response.json();\n } catch {\n return undefined;\n }\n }\n try {\n return await response.text();\n } catch {\n return undefined;\n }\n}\n\n// Rejects on !response.ok with a typed SmrtClientError; otherwise resolves the\n// parsed JSON body exactly as the server sent it (bare array/object, snake_case\n// fields).\nasync function __smrtFetchJson(url, init) {\n const response = await fetch(url, init);\n
|
|
26
|
+
export declare const CLIENT_FETCH_RUNTIME = "class SmrtClientError extends Error {\n constructor(message, status, body, code) {\n super(message);\n this.name = 'SmrtClientError';\n this.status = status;\n if (body !== undefined) this.body = body;\n if (code) this.code = code;\n }\n}\n\nasync function __smrtParseBody(response) {\n const contentType = response.headers.get('content-type') || '';\n if (contentType.includes('application/json')) {\n try {\n return await response.json();\n } catch {\n return undefined;\n }\n }\n try {\n return await response.text();\n } catch {\n return undefined;\n }\n}\n\n// Generated REST routes retain the legacy { error: string } form while custom\n// action failures use { error: { ok: false, code, message, status? } }. Keep\n// the nested message human-readable and retain its machine-readable code.\nfunction __smrtErrorDetail(body) {\n if (!body || typeof body !== 'object') return {};\n const error = body.error;\n if (typeof error === 'string') return { message: error };\n if (error && typeof error === 'object') {\n return {\n message: typeof error.message === 'string' ? error.message : undefined,\n code: typeof error.code === 'string' ? error.code : undefined,\n };\n }\n return typeof body.message === 'string' ? { message: body.message } : {};\n}\n\n// Rejects on !response.ok with a typed SmrtClientError; otherwise resolves the\n// parsed JSON body exactly as the server sent it (bare array/object, snake_case\n// fields).\nasync function __smrtFetchJson(url, init) {\n const response = await fetch(url, init);\n if (!response.ok) {\n // Server-side failures may carry stack traces or upstream payloads. Do not\n // parse, interpolate, or expose them to browser callers.\n if (response.status >= 500) {\n throw new SmrtClientError(\n 'Request failed with status ' + response.status,\n response.status,\n );\n }\n const body = await __smrtParseBody(response);\n const detail = __smrtErrorDetail(body);\n throw new SmrtClientError(\n 'Request failed with status ' + response.status +\n (detail.message ? ': ' + detail.message : ''),\n response.status,\n body,\n detail.code,\n );\n }\n return __smrtParseBody(response);\n}\n\n// DELETE variant: rejects on !response.ok (so a failed delete is observable),\n// otherwise resolves true.\nasync function __smrtFetchOk(url, init) {\n const response = await fetch(url, init);\n if (!response.ok) {\n // Keep 5xx failures opaque for the same reason as __smrtFetchJson.\n if (response.status >= 500) {\n throw new SmrtClientError(\n 'Request failed with status ' + response.status,\n response.status,\n );\n }\n const body = await __smrtParseBody(response);\n const detail = __smrtErrorDetail(body);\n throw new SmrtClientError(\n 'Request failed with status ' + response.status +\n (detail.message ? ': ' + detail.message : ''),\n response.status,\n body,\n detail.code,\n );\n }\n return true;\n}";
|
|
26
27
|
//# sourceMappingURL=generated-client-runtime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated-client-runtime.d.ts","sourceRoot":"","sources":["../src/generated-client-runtime.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"generated-client-runtime.d.ts","sourceRoot":"","sources":["../src/generated-client-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,oBAAoB,sgGA4F/B,CAAC"}
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
* failures were therefore invisible to callers — an optimistic-update layer
|
|
11
11
|
* (e.g. TanStack DB `onInsert`) could never observe the failure and roll back.
|
|
12
12
|
*
|
|
13
|
-
* `SmrtClientError` carries the HTTP `status` and
|
|
14
|
-
*
|
|
15
|
-
* messages. Both helpers are emitted with a
|
|
16
|
-
* collide with a collection key in the generated
|
|
13
|
+
* `SmrtClientError` carries the HTTP `status` and, for non-5xx responses, the
|
|
14
|
+
* parsed error `body`, so callers can branch on status and surface safe server
|
|
15
|
+
* messages. Server-side failures stay opaque. Both helpers are emitted with a
|
|
16
|
+
* `__smrt` prefix so they cannot collide with a collection key in the generated
|
|
17
|
+
* module.
|
|
17
18
|
*
|
|
18
19
|
* WIRE-SHAPE POLICY (#1797, ADR 0001): the server returns BARE JSON — a bare
|
|
19
20
|
* array for list, a bare object for get/create/update — with snake_case field
|
|
@@ -24,11 +25,12 @@
|
|
|
24
25
|
* snake_case wire feeds the mobile DTO codegen; do not change the wire here.
|
|
25
26
|
*/
|
|
26
27
|
var CLIENT_FETCH_RUNTIME = `class SmrtClientError extends Error {
|
|
27
|
-
constructor(message, status, body) {
|
|
28
|
+
constructor(message, status, body, code) {
|
|
28
29
|
super(message);
|
|
29
30
|
this.name = 'SmrtClientError';
|
|
30
31
|
this.status = status;
|
|
31
|
-
this.body = body;
|
|
32
|
+
if (body !== undefined) this.body = body;
|
|
33
|
+
if (code) this.code = code;
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -48,22 +50,47 @@ async function __smrtParseBody(response) {
|
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
// Generated REST routes retain the legacy { error: string } form while custom
|
|
54
|
+
// action failures use { error: { ok: false, code, message, status? } }. Keep
|
|
55
|
+
// the nested message human-readable and retain its machine-readable code.
|
|
56
|
+
function __smrtErrorDetail(body) {
|
|
57
|
+
if (!body || typeof body !== 'object') return {};
|
|
58
|
+
const error = body.error;
|
|
59
|
+
if (typeof error === 'string') return { message: error };
|
|
60
|
+
if (error && typeof error === 'object') {
|
|
61
|
+
return {
|
|
62
|
+
message: typeof error.message === 'string' ? error.message : undefined,
|
|
63
|
+
code: typeof error.code === 'string' ? error.code : undefined,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return typeof body.message === 'string' ? { message: body.message } : {};
|
|
67
|
+
}
|
|
68
|
+
|
|
51
69
|
// Rejects on !response.ok with a typed SmrtClientError; otherwise resolves the
|
|
52
70
|
// parsed JSON body exactly as the server sent it (bare array/object, snake_case
|
|
53
71
|
// fields).
|
|
54
72
|
async function __smrtFetchJson(url, init) {
|
|
55
73
|
const response = await fetch(url, init);
|
|
56
|
-
const body = await __smrtParseBody(response);
|
|
57
74
|
if (!response.ok) {
|
|
58
|
-
|
|
59
|
-
|
|
75
|
+
// Server-side failures may carry stack traces or upstream payloads. Do not
|
|
76
|
+
// parse, interpolate, or expose them to browser callers.
|
|
77
|
+
if (response.status >= 500) {
|
|
78
|
+
throw new SmrtClientError(
|
|
79
|
+
'Request failed with status ' + response.status,
|
|
80
|
+
response.status,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const body = await __smrtParseBody(response);
|
|
84
|
+
const detail = __smrtErrorDetail(body);
|
|
60
85
|
throw new SmrtClientError(
|
|
61
|
-
'Request failed with status ' + response.status +
|
|
86
|
+
'Request failed with status ' + response.status +
|
|
87
|
+
(detail.message ? ': ' + detail.message : ''),
|
|
62
88
|
response.status,
|
|
63
89
|
body,
|
|
90
|
+
detail.code,
|
|
64
91
|
);
|
|
65
92
|
}
|
|
66
|
-
return
|
|
93
|
+
return __smrtParseBody(response);
|
|
67
94
|
}
|
|
68
95
|
|
|
69
96
|
// DELETE variant: rejects on !response.ok (so a failed delete is observable),
|
|
@@ -71,13 +98,21 @@ async function __smrtFetchJson(url, init) {
|
|
|
71
98
|
async function __smrtFetchOk(url, init) {
|
|
72
99
|
const response = await fetch(url, init);
|
|
73
100
|
if (!response.ok) {
|
|
101
|
+
// Keep 5xx failures opaque for the same reason as __smrtFetchJson.
|
|
102
|
+
if (response.status >= 500) {
|
|
103
|
+
throw new SmrtClientError(
|
|
104
|
+
'Request failed with status ' + response.status,
|
|
105
|
+
response.status,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
74
108
|
const body = await __smrtParseBody(response);
|
|
75
|
-
const detail =
|
|
76
|
-
body && typeof body === 'object' && body.error ? ': ' + body.error : '';
|
|
109
|
+
const detail = __smrtErrorDetail(body);
|
|
77
110
|
throw new SmrtClientError(
|
|
78
|
-
'Request failed with status ' + response.status +
|
|
111
|
+
'Request failed with status ' + response.status +
|
|
112
|
+
(detail.message ? ': ' + detail.message : ''),
|
|
79
113
|
response.status,
|
|
80
114
|
body,
|
|
115
|
+
detail.code,
|
|
81
116
|
);
|
|
82
117
|
}
|
|
83
118
|
return true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated-client-runtime.js","names":[],"sources":["../src/generated-client-runtime.ts"],"sourcesContent":["/**\n * Shared runtime source injected into every generated API client\n * (`smrtPlugin`'s `@happyvertical/smrt-virt-client` and `smrtConsumer`'s\n * `@smrt/client` fallback).\n *\n * The generated fetchers MUST reject on `!response.ok` (#1796): before this,\n * every fetcher resolved `r.json()` regardless of HTTP status, so a 500 (or any\n * error response) resolved successfully with the error payload. Mutation\n * failures were therefore invisible to callers — an optimistic-update layer\n * (e.g. TanStack DB `onInsert`) could never observe the failure and roll back.\n *\n * `SmrtClientError` carries the HTTP `status` and
|
|
1
|
+
{"version":3,"file":"generated-client-runtime.js","names":[],"sources":["../src/generated-client-runtime.ts"],"sourcesContent":["/**\n * Shared runtime source injected into every generated API client\n * (`smrtPlugin`'s `@happyvertical/smrt-virt-client` and `smrtConsumer`'s\n * `@smrt/client` fallback).\n *\n * The generated fetchers MUST reject on `!response.ok` (#1796): before this,\n * every fetcher resolved `r.json()` regardless of HTTP status, so a 500 (or any\n * error response) resolved successfully with the error payload. Mutation\n * failures were therefore invisible to callers — an optimistic-update layer\n * (e.g. TanStack DB `onInsert`) could never observe the failure and roll back.\n *\n * `SmrtClientError` carries the HTTP `status` and, for non-5xx responses, the\n * parsed error `body`, so callers can branch on status and surface safe server\n * messages. Server-side failures stay opaque. Both helpers are emitted with a\n * `__smrt` prefix so they cannot collide with a collection key in the generated\n * module.\n *\n * WIRE-SHAPE POLICY (#1797, ADR 0001): the server returns BARE JSON — a bare\n * array for list, a bare object for get/create/update — with snake_case field\n * names (`created_at`, `updated_at`) exactly as `SmrtObject.toJSON()` emits\n * them. These helpers pass that JSON through unchanged; they do NOT wrap it in\n * an envelope or camelCase the keys. The generated `.d.ts` declarations\n * (vite-plugin + prebuild) are written to match this shape. This same\n * snake_case wire feeds the mobile DTO codegen; do not change the wire here.\n */\nexport const CLIENT_FETCH_RUNTIME = `class SmrtClientError extends Error {\n constructor(message, status, body, code) {\n super(message);\n this.name = 'SmrtClientError';\n this.status = status;\n if (body !== undefined) this.body = body;\n if (code) this.code = code;\n }\n}\n\nasync function __smrtParseBody(response) {\n const contentType = response.headers.get('content-type') || '';\n if (contentType.includes('application/json')) {\n try {\n return await response.json();\n } catch {\n return undefined;\n }\n }\n try {\n return await response.text();\n } catch {\n return undefined;\n }\n}\n\n// Generated REST routes retain the legacy { error: string } form while custom\n// action failures use { error: { ok: false, code, message, status? } }. Keep\n// the nested message human-readable and retain its machine-readable code.\nfunction __smrtErrorDetail(body) {\n if (!body || typeof body !== 'object') return {};\n const error = body.error;\n if (typeof error === 'string') return { message: error };\n if (error && typeof error === 'object') {\n return {\n message: typeof error.message === 'string' ? error.message : undefined,\n code: typeof error.code === 'string' ? error.code : undefined,\n };\n }\n return typeof body.message === 'string' ? { message: body.message } : {};\n}\n\n// Rejects on !response.ok with a typed SmrtClientError; otherwise resolves the\n// parsed JSON body exactly as the server sent it (bare array/object, snake_case\n// fields).\nasync function __smrtFetchJson(url, init) {\n const response = await fetch(url, init);\n if (!response.ok) {\n // Server-side failures may carry stack traces or upstream payloads. Do not\n // parse, interpolate, or expose them to browser callers.\n if (response.status >= 500) {\n throw new SmrtClientError(\n 'Request failed with status ' + response.status,\n response.status,\n );\n }\n const body = await __smrtParseBody(response);\n const detail = __smrtErrorDetail(body);\n throw new SmrtClientError(\n 'Request failed with status ' + response.status +\n (detail.message ? ': ' + detail.message : ''),\n response.status,\n body,\n detail.code,\n );\n }\n return __smrtParseBody(response);\n}\n\n// DELETE variant: rejects on !response.ok (so a failed delete is observable),\n// otherwise resolves true.\nasync function __smrtFetchOk(url, init) {\n const response = await fetch(url, init);\n if (!response.ok) {\n // Keep 5xx failures opaque for the same reason as __smrtFetchJson.\n if (response.status >= 500) {\n throw new SmrtClientError(\n 'Request failed with status ' + response.status,\n response.status,\n );\n }\n const body = await __smrtParseBody(response);\n const detail = __smrtErrorDetail(body);\n throw new SmrtClientError(\n 'Request failed with status ' + response.status +\n (detail.message ? ': ' + detail.message : ''),\n response.status,\n body,\n detail.code,\n );\n }\n return true;\n}`;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,IAAa,uBAAuB"}
|
|
@@ -6,11 +6,12 @@ export { CLIGenerator, getCLIHandler, setupCLI } from './cli';
|
|
|
6
6
|
export { canonicalReadRepresentation, computeBodyEtag, computeTableVersionEtag, conditionalJsonResponse, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, PRIVATE_READ_CACHE_CONTROL, type ReadCacheControlOptions, resolveReadCacheControl, resolveTenantEtagDiscriminator, versionConditionalResponse, warnIfSharedCacheNeutralized, } from './conditional-get';
|
|
7
7
|
export { buildCustomActionInputSchema, buildCustomActionInvocationArgs, type CustomActionFailure, type CustomActionMetadata, type CustomActionScope, customActionParameterInputName, normalizeCustomActionFailure, type ResolveCustomActionMetadataOptions, resolveCustomActionMetadata, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, } from './custom-action';
|
|
8
8
|
export { buildChangeEventStream, type ChangeEventStreamOptions, changeEventSubscribersAtCapacity, DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, eventStreamCapacityExceededResponse, normalizeEventsMaxSubscribers, signalVisibleToTenant, tryReserveChangeEventSubscriberSlot, } from './events-route';
|
|
9
|
-
export type { MCPConfig, MCPContext, MCPRequest, MCPResponse, MCPTool, } from './mcp';
|
|
10
|
-
export { MCPGenerator } from './mcp';
|
|
9
|
+
export type { MCPConfig, MCPContext, MCPRequest, MCPResponse, MCPTool, MCPToolListCacheHint, MCPToolListCacheOptions, } from './mcp';
|
|
10
|
+
export { MCP_STABLE_CATALOG_TTL_MS, MCPGenerator, resolveMCPToolListCacheHint, sortMCPTools, } from './mcp';
|
|
11
11
|
export type { APIConfig, APIContext, RestServerConfig } from './rest';
|
|
12
12
|
export { APIGenerator, computeRuntimeWebManifestHash, createRestServer, startRestServer, } from './rest';
|
|
13
13
|
export type { OpenAPIConfig } from './swagger';
|
|
14
14
|
export { generateOpenAPISpec, setupSwaggerUI, } from './swagger';
|
|
15
15
|
export { runWithTenantGate, setTenantEntryPointRunner, type TenantEntryPointRunner, type TenantGateOptions, } from './tenant-gate';
|
|
16
|
+
export { normalizeTypedHttpError, type TypedHttpFailure, } from './typed-http-error';
|
|
16
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generators/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEnD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG9D,OAAO,EACL,2BAA2B,EAC3B,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,2BAA2B,EAC3B,oBAAoB,EACpB,0BAA0B,EAC1B,KAAK,uBAAuB,EAC5B,uBAAuB,EACvB,8BAA8B,EAC9B,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,4BAA4B,EAC5B,+BAA+B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,8BAA8B,EAC9B,4BAA4B,EAC5B,KAAK,kCAAkC,EACvC,2BAA2B,EAC3B,qCAAqC,GACtC,MAAM,iBAAiB,CAAC;AAKzB,OAAO,EACL,sBAAsB,EACtB,KAAK,wBAAwB,EAC7B,gCAAgC,EAChC,2BAA2B,EAC3B,8BAA8B,EAC9B,kCAAkC,EAClC,mCAAmC,EACnC,6BAA6B,EAC7B,qBAAqB,EACrB,mCAAmC,GACpC,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,SAAS,EACT,UAAU,EACV,UAAU,EACV,WAAW,EACX,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generators/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEnD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG9D,OAAO,EACL,2BAA2B,EAC3B,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,2BAA2B,EAC3B,oBAAoB,EACpB,0BAA0B,EAC1B,KAAK,uBAAuB,EAC5B,uBAAuB,EACvB,8BAA8B,EAC9B,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,4BAA4B,EAC5B,+BAA+B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,8BAA8B,EAC9B,4BAA4B,EAC5B,KAAK,kCAAkC,EACvC,2BAA2B,EAC3B,qCAAqC,GACtC,MAAM,iBAAiB,CAAC;AAKzB,OAAO,EACL,sBAAsB,EACtB,KAAK,wBAAwB,EAC7B,gCAAgC,EAChC,2BAA2B,EAC3B,8BAA8B,EAC9B,kCAAkC,EAClC,mCAAmC,EACnC,6BAA6B,EAC7B,qBAAqB,EACrB,mCAAmC,GACpC,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,SAAS,EACT,UAAU,EACV,UAAU,EACV,WAAW,EACX,OAAO,EACP,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,OAAO,CAAC;AAEf,OAAO,EACL,yBAAyB,EACzB,YAAY,EACZ,2BAA2B,EAC3B,YAAY,GACb,MAAM,OAAO,CAAC;AACf,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAEtE,OAAO,EACL,YAAY,EACZ,6BAA6B,EAC7B,gBAAgB,EAChB,eAAe,GAChB,MAAM,QAAQ,CAAC;AAChB,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,OAAO,EACL,mBAAmB,EACnB,cAAc,GACf,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,iBAAiB,EACjB,yBAAyB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,GACvB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,uBAAuB,EACvB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC"}
|
package/dist/generators/index.js
CHANGED
|
@@ -3,7 +3,8 @@ import { runWithTenantGate, setTenantEntryPointRunner } from "./tenant-gate.js";
|
|
|
3
3
|
import { CLIGenerator, getCLIHandler, setupCLI } from "./cli.js";
|
|
4
4
|
import { PRIVATE_READ_CACHE_CONTROL, canonicalReadRepresentation, computeBodyEtag, computeTableVersionEtag, conditionalJsonResponse, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, resolveReadCacheControl, resolveTenantEtagDiscriminator, versionConditionalResponse, warnIfSharedCacheNeutralized } from "./conditional-get.js";
|
|
5
5
|
import { DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, buildChangeEventStream, changeEventSubscribersAtCapacity, eventStreamCapacityExceededResponse, normalizeEventsMaxSubscribers, signalVisibleToTenant, tryReserveChangeEventSubscriberSlot } from "./events-route.js";
|
|
6
|
-
import { MCPGenerator } from "./mcp.js";
|
|
6
|
+
import { MCPGenerator, MCP_STABLE_CATALOG_TTL_MS, resolveMCPToolListCacheHint, sortMCPTools } from "./mcp.js";
|
|
7
|
+
import { normalizeTypedHttpError } from "./typed-http-error.js";
|
|
7
8
|
import { APIGenerator, computeRuntimeWebManifestHash, createRestServer, startRestServer } from "./rest.js";
|
|
8
9
|
import { generateOpenAPISpec, setupSwaggerUI } from "./swagger.js";
|
|
9
|
-
export { APIGenerator, CLIGenerator, DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, MCPGenerator, PRIVATE_READ_CACHE_CONTROL, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildChangeEventStream, buildCustomActionInputSchema, buildCustomActionInvocationArgs, canonicalReadRepresentation, changeEventSubscribersAtCapacity, computeBodyEtag, computeRuntimeWebManifestHash, computeTableVersionEtag, conditionalJsonResponse, createRestServer, customActionParameterInputName, eventStreamCapacityExceededResponse, generateOpenAPISpec, getCLIHandler, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, normalizeCustomActionFailure, normalizeEventsMaxSubscribers, resolveCustomActionMetadata, resolveReadCacheControl, resolveTenantEtagDiscriminator, runWithTenantGate, setTenantEntryPointRunner, setupCLI, setupSwaggerUI, signalVisibleToTenant, startRestServer, tryReserveChangeEventSubscriberSlot, versionConditionalResponse, warnIfSharedCacheNeutralized };
|
|
10
|
+
export { APIGenerator, CLIGenerator, DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, MCPGenerator, MCP_STABLE_CATALOG_TTL_MS, PRIVATE_READ_CACHE_CONTROL, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildChangeEventStream, buildCustomActionInputSchema, buildCustomActionInvocationArgs, canonicalReadRepresentation, changeEventSubscribersAtCapacity, computeBodyEtag, computeRuntimeWebManifestHash, computeTableVersionEtag, conditionalJsonResponse, createRestServer, customActionParameterInputName, eventStreamCapacityExceededResponse, generateOpenAPISpec, getCLIHandler, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, normalizeCustomActionFailure, normalizeEventsMaxSubscribers, normalizeTypedHttpError, resolveCustomActionMetadata, resolveMCPToolListCacheHint, resolveReadCacheControl, resolveTenantEtagDiscriminator, runWithTenantGate, setTenantEntryPointRunner, setupCLI, setupSwaggerUI, signalVisibleToTenant, sortMCPTools, startRestServer, tryReserveChangeEventSubscriberSlot, versionConditionalResponse, warnIfSharedCacheNeutralized };
|
|
@@ -18,7 +18,13 @@ export interface RuntimeOptions {
|
|
|
18
18
|
name: string;
|
|
19
19
|
description: string;
|
|
20
20
|
inputSchema: Record<string, unknown>;
|
|
21
|
+
outputSchema?: Record<string, unknown>;
|
|
21
22
|
}>;
|
|
23
|
+
/** Cache hint emitted for deploy-static tools/list results. */
|
|
24
|
+
toolListCacheHint?: {
|
|
25
|
+
ttlMs: number;
|
|
26
|
+
cacheScope: 'private' | 'public';
|
|
27
|
+
};
|
|
22
28
|
/** Internal invocation metadata; never exposed by the MCP tools/list result. */
|
|
23
29
|
customActions?: Record<string, {
|
|
24
30
|
scope: CustomActionScope;
|
|
@@ -37,6 +43,13 @@ export interface RuntimeOptions {
|
|
|
37
43
|
* `SMRT_MCP_ALLOW_CROSS_TENANT` env vars (this server has no auth principal).
|
|
38
44
|
*/
|
|
39
45
|
tenantScopedObjects?: string[];
|
|
46
|
+
/**
|
|
47
|
+
* Build-time approved STI discriminators, keyed by the lowercased MCP object
|
|
48
|
+
* prefix. The generated runtime uses this instead of searching an initially
|
|
49
|
+
* empty registry, then loads the approved qualified type through the public
|
|
50
|
+
* collection API.
|
|
51
|
+
*/
|
|
52
|
+
stiTargets?: Record<string, Record<string, string>>;
|
|
40
53
|
}
|
|
41
54
|
/**
|
|
42
55
|
* Generate runtime bootstrap code for MCP server
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-runtime-template.d.ts","sourceRoot":"","sources":["../../src/generators/mcp-runtime-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAStD,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,kCAAkC;IAClC,MAAM,CAAC,EAAE,SAAS,CAAC;IAEnB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,UAAU,CAAC;IAErB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,wDAAwD;IACxD,KAAK,CAAC,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"mcp-runtime-template.d.ts","sourceRoot":"","sources":["../../src/generators/mcp-runtime-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAStD,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,kCAAkC;IAClC,MAAM,CAAC,EAAE,SAAS,CAAC;IAEnB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,UAAU,CAAC;IAErB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,wDAAwD;IACxD,KAAK,CAAC,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACxC,CAAC,CAAC;IACH,+DAA+D;IAC/D,iBAAiB,CAAC,EAAE;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,SAAS,GAAG,QAAQ,CAAC;KAClC,CAAC;IACF,gFAAgF;IAChF,aAAa,CAAC,EAAE,MAAM,CACpB,MAAM,EACN;QACE,KAAK,EAAE,iBAAiB,CAAC;QACzB,QAAQ,EAAE,OAAO,CAAC;QAClB,sEAAsE;QACtE,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B,aAAa,EAAE,OAAO,CAAC;KACxB,CACF,CAAC;IAEF;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE/B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACrD;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM,CAme7E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,GAAE,MAA6B,GACxC,MAAM,CAER;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,MAAM,CASR;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,MAAM,CA0GR"}
|
|
@@ -12,7 +12,10 @@ function capitalize(str) {
|
|
|
12
12
|
* @returns TypeScript code for server entry point
|
|
13
13
|
*/
|
|
14
14
|
function generateRuntimeBootstrap(options = {}) {
|
|
15
|
-
const { name = "smrt-mcp-server", version = "1.0.0", description = "Auto-generated MCP server from SMRT objects", debug = false, tools = [], customActions = {}, tenantScopedObjects = [] } =
|
|
15
|
+
const { name = "smrt-mcp-server", version = "1.0.0", description = "Auto-generated MCP server from SMRT objects", debug = false, tools = [], customActions = {}, tenantScopedObjects = [], stiTargets = {}, toolListCacheHint = {
|
|
16
|
+
ttlMs: 864e5,
|
|
17
|
+
cacheScope: "private"
|
|
18
|
+
} } = options;
|
|
16
19
|
const toolsCode = tools.length > 0 ? JSON.stringify(tools, null, 2) : "[]";
|
|
17
20
|
const tenantScopedSet = Array.from(new Set(tenantScopedObjects.map((n) => n.toLowerCase())));
|
|
18
21
|
const hasTenantScoped = tenantScopedSet.length > 0;
|
|
@@ -28,13 +31,17 @@ ${indent} const offset = args.offset ?? 0;
|
|
|
28
31
|
${indent} const where = args.where ?? {};
|
|
29
32
|
|
|
30
33
|
${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {
|
|
31
|
-
${indent} persistence: { type: '
|
|
34
|
+
${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },
|
|
32
35
|
${indent} ai: aiConfig
|
|
33
36
|
${indent} });
|
|
34
37
|
|
|
35
38
|
${indent} const items = await collection.list({ where, limit, offset });
|
|
36
39
|
${indent} const itemsPublic = items.map((item) => item.toPublicJSON(PUBLIC_JSON_OPTIONS));
|
|
37
|
-
${indent}
|
|
40
|
+
${indent} const structuredContent = {
|
|
41
|
+
${indent} data: itemsPublic,
|
|
42
|
+
${indent} meta: { total: await collection.count({ where }), limit, offset, count: items.length },
|
|
43
|
+
${indent} };
|
|
44
|
+
${indent} return successResult(structuredContent, JSON.stringify(itemsPublic));
|
|
38
45
|
${indent}}`;
|
|
39
46
|
case "get": return `${indent}case '${tool.name}': {
|
|
40
47
|
${indent} if (!args.id && !args.slug) {
|
|
@@ -42,7 +49,7 @@ ${indent} throw new Error('Either id or slug is required');
|
|
|
42
49
|
${indent} }
|
|
43
50
|
|
|
44
51
|
${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {
|
|
45
|
-
${indent} persistence: { type: '
|
|
52
|
+
${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },
|
|
46
53
|
${indent} ai: aiConfig
|
|
47
54
|
${indent} });
|
|
48
55
|
|
|
@@ -53,18 +60,15 @@ ${indent} if (!item) {
|
|
|
53
60
|
${indent} throw new Error('Object not found');
|
|
54
61
|
${indent} }
|
|
55
62
|
|
|
56
|
-
${indent} return
|
|
63
|
+
${indent} return successResult(item.toPublicJSON(PUBLIC_JSON_OPTIONS));
|
|
57
64
|
${indent}}`;
|
|
58
65
|
case "create": return `${indent}case '${tool.name}': {
|
|
59
|
-
${indent} const collection = await
|
|
60
|
-
${indent} persistence: { type: 'sql', url: process.env.DATABASE_URL || ':memory:' },
|
|
61
|
-
${indent} ai: aiConfig
|
|
62
|
-
${indent} });
|
|
66
|
+
${indent} const { collection, objectName: targetObjectName } = await resolveCreateTarget('${objectName}', args, aiConfig);
|
|
63
67
|
|
|
64
|
-
${indent} const newItem = await collection.create(applyWritablePolicy(
|
|
68
|
+
${indent} const newItem = await collection.create(applyWritablePolicy(targetObjectName, args));
|
|
65
69
|
${indent} await newItem.save();
|
|
66
70
|
|
|
67
|
-
${indent} return
|
|
71
|
+
${indent} return successResult(newItem.toPublicJSON(PUBLIC_JSON_OPTIONS));
|
|
68
72
|
${indent}}`;
|
|
69
73
|
case "update": return `${indent}case '${tool.name}': {
|
|
70
74
|
${indent} const { id, ...updateData } = args;
|
|
@@ -73,7 +77,7 @@ ${indent} throw new Error('ID is required for update');
|
|
|
73
77
|
${indent} }
|
|
74
78
|
|
|
75
79
|
${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {
|
|
76
|
-
${indent} persistence: { type: '
|
|
80
|
+
${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },
|
|
77
81
|
${indent} ai: aiConfig
|
|
78
82
|
${indent} });
|
|
79
83
|
|
|
@@ -85,7 +89,7 @@ ${indent} }
|
|
|
85
89
|
${indent} Object.assign(existing, applyWritablePolicy('${capitalize(objectName)}', updateData));
|
|
86
90
|
${indent} await existing.save();
|
|
87
91
|
|
|
88
|
-
${indent} return
|
|
92
|
+
${indent} return successResult(existing.toPublicJSON(PUBLIC_JSON_OPTIONS));
|
|
89
93
|
${indent}}`;
|
|
90
94
|
case "delete": return `${indent}case '${tool.name}': {
|
|
91
95
|
${indent} if (!args.id) {
|
|
@@ -93,7 +97,7 @@ ${indent} throw new Error('ID is required for delete');
|
|
|
93
97
|
${indent} }
|
|
94
98
|
|
|
95
99
|
${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {
|
|
96
|
-
${indent} persistence: { type: '
|
|
100
|
+
${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },
|
|
97
101
|
${indent} ai: aiConfig
|
|
98
102
|
${indent} });
|
|
99
103
|
|
|
@@ -104,7 +108,7 @@ ${indent} }
|
|
|
104
108
|
|
|
105
109
|
${indent} await toDelete.delete();
|
|
106
110
|
|
|
107
|
-
${indent} return
|
|
111
|
+
${indent} return successResult({ success: true, message: 'Object deleted successfully' });
|
|
108
112
|
${indent}}`;
|
|
109
113
|
default: return `${indent}case '${tool.name}': {
|
|
110
114
|
${indent} const actionMeta = CUSTOM_ACTIONS['${tool.name}'] || { scope: 'item', isStatic: false, legacyOptions: true };
|
|
@@ -118,7 +122,7 @@ ${indent} throw new Error('Custom action ${action} is collection-scoped and d
|
|
|
118
122
|
${indent} }
|
|
119
123
|
|
|
120
124
|
${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {
|
|
121
|
-
${indent} persistence: { type: '
|
|
125
|
+
${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },
|
|
122
126
|
${indent} ai: aiConfig
|
|
123
127
|
${indent} });
|
|
124
128
|
|
|
@@ -147,14 +151,15 @@ ${indent} ]);
|
|
|
147
151
|
${indent} const result = await actionMethod.call(target, ...methodArgs);
|
|
148
152
|
${indent} const failure = normalizeCustomActionFailure(result);
|
|
149
153
|
${indent} if (failure) {
|
|
150
|
-
${indent} return
|
|
151
|
-
${indent}
|
|
152
|
-
${indent}
|
|
153
|
-
${indent}
|
|
154
|
-
${indent}
|
|
154
|
+
${indent} return errorResult(
|
|
155
|
+
${indent} { error: failure },
|
|
156
|
+
${indent} JSON.stringify({ error: failure }),
|
|
157
|
+
${indent} { [SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY]: failure },
|
|
158
|
+
${indent} );
|
|
155
159
|
${indent} }
|
|
156
160
|
|
|
157
|
-
${indent}
|
|
161
|
+
${indent} const publicResult = toPublicResult(result);
|
|
162
|
+
${indent} return successResult({ data: publicResult }, JSON.stringify(publicResult));
|
|
158
163
|
${indent}}`;
|
|
159
164
|
}
|
|
160
165
|
}).join("\n\n");
|
|
@@ -180,6 +185,8 @@ import {
|
|
|
180
185
|
Server,
|
|
181
186
|
} from '@modelcontextprotocol/server';
|
|
182
187
|
import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
|
188
|
+
import { existsSync } from 'node:fs';
|
|
189
|
+
import { resolve } from 'node:path';
|
|
183
190
|
import { pathToFileURL } from 'node:url';
|
|
184
191
|
import { ObjectRegistry } from '@happyvertical/smrt-core';
|
|
185
192
|
import { normalizeCustomActionFailure, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY } from '@happyvertical/smrt-core';
|
|
@@ -193,7 +200,9 @@ const DEBUG = ${debug};
|
|
|
193
200
|
|
|
194
201
|
// Static tool definitions (generated at build time)
|
|
195
202
|
const TOOLS = ${toolsCode};
|
|
203
|
+
const TOOL_LIST_CACHE_HINT = ${JSON.stringify(toolListCacheHint)};
|
|
196
204
|
const CUSTOM_ACTIONS = ${JSON.stringify(customActions)};
|
|
205
|
+
const STI_TARGETS: Record<string, Record<string, string>> = ${JSON.stringify(stiTargets)};
|
|
197
206
|
${hasTenantScoped ? `
|
|
198
207
|
// Fail-closed tenant context (#1554): tenant-scoped objects must run inside a
|
|
199
208
|
// tenant. This stdio server has no auth principal, so the tenant is taken from
|
|
@@ -243,6 +252,23 @@ function applyWritablePolicy(objectName: string, data: any): Record<string, any>
|
|
|
243
252
|
return result;
|
|
244
253
|
}
|
|
245
254
|
|
|
255
|
+
/** Resolve an advertised STI discriminator to its registered subtype collection. */
|
|
256
|
+
async function resolveCreateTarget(baseObjectName: string, args: Record<string, any>, aiConfig: any) {
|
|
257
|
+
let objectName = baseObjectName;
|
|
258
|
+
const discriminator = args._meta_type;
|
|
259
|
+
const targets = STI_TARGETS[baseObjectName];
|
|
260
|
+
if (typeof discriminator === 'string' && targets) {
|
|
261
|
+
const target = targets[discriminator];
|
|
262
|
+
if (!target) throw new Error('Unknown STI discriminator: ' + discriminator);
|
|
263
|
+
objectName = target;
|
|
264
|
+
}
|
|
265
|
+
const collection = await ObjectRegistry.getCollection(objectName, {
|
|
266
|
+
persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },
|
|
267
|
+
ai: aiConfig,
|
|
268
|
+
});
|
|
269
|
+
return { collection, objectName };
|
|
270
|
+
}
|
|
271
|
+
|
|
246
272
|
/**
|
|
247
273
|
* Sensitive-field-safe serialization for custom-action results (#1540).
|
|
248
274
|
* Recurses through arrays and plain objects so nested SmrtObjects are stripped
|
|
@@ -267,6 +293,22 @@ function toPublicResult(value: any, seen: WeakSet<object> = new WeakSet()): any
|
|
|
267
293
|
return out;
|
|
268
294
|
}
|
|
269
295
|
|
|
296
|
+
function successResult(structuredContent: any, text = JSON.stringify(structuredContent)) {
|
|
297
|
+
return {
|
|
298
|
+
content: [{ type: 'text', text }],
|
|
299
|
+
structuredContent,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function errorResult(structuredContent: any, text: string, _meta?: Record<string, any>) {
|
|
304
|
+
return {
|
|
305
|
+
content: [{ type: 'text', text }],
|
|
306
|
+
isError: true,
|
|
307
|
+
structuredContent,
|
|
308
|
+
...(_meta ? { _meta } : {}),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
270
312
|
/**
|
|
271
313
|
* Main server startup function
|
|
272
314
|
*/
|
|
@@ -282,6 +324,17 @@ ${hasTenantScoped ? `
|
|
|
282
324
|
// with no interceptor to enforce it.
|
|
283
325
|
enableTenancy();
|
|
284
326
|
` : ""}
|
|
327
|
+
// Register the application package manifest before resolving generated
|
|
328
|
+
// object names. Generated servers are commonly run from the application
|
|
329
|
+
// package itself, which is not a node_modules dependency of its process.
|
|
330
|
+
const localManifestPaths = [
|
|
331
|
+
resolve(process.cwd(), 'dist', 'manifest.json'),
|
|
332
|
+
resolve(process.cwd(), '.smrt', 'manifest.json'),
|
|
333
|
+
].filter(existsSync);
|
|
334
|
+
if (localManifestPaths.length > 0) {
|
|
335
|
+
ObjectRegistry.loadAllManifests({ manifestPaths: localManifestPaths });
|
|
336
|
+
}
|
|
337
|
+
|
|
285
338
|
// Load configuration from environment and .smrt.config files
|
|
286
339
|
const appConfig = await loadConfig();
|
|
287
340
|
const aiConfig = appConfig?.ai || {};
|
|
@@ -301,6 +354,9 @@ ${hasTenantScoped ? `
|
|
|
301
354
|
capabilities: {
|
|
302
355
|
tools: {},
|
|
303
356
|
},
|
|
357
|
+
cacheHints: {
|
|
358
|
+
'tools/list': TOOL_LIST_CACHE_HINT,
|
|
359
|
+
},
|
|
304
360
|
}
|
|
305
361
|
);
|
|
306
362
|
|
|
@@ -311,7 +367,7 @@ ${hasTenantScoped ? `
|
|
|
311
367
|
}
|
|
312
368
|
|
|
313
369
|
return {
|
|
314
|
-
tools: TOOLS,
|
|
370
|
+
tools: [...TOOLS].sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0),
|
|
315
371
|
};
|
|
316
372
|
});
|
|
317
373
|
|
|
@@ -355,15 +411,10 @@ ${hasTenantScoped ? `
|
|
|
355
411
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
356
412
|
console.error(\`[MCP] Tool execution failed: \${toolName}\`, error);
|
|
357
413
|
|
|
358
|
-
return
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
text: \`Error executing tool \${toolName}: \${errorMessage}\`,
|
|
363
|
-
},
|
|
364
|
-
],
|
|
365
|
-
isError: true,
|
|
366
|
-
};
|
|
414
|
+
return errorResult(
|
|
415
|
+
{ error: { message: errorMessage } },
|
|
416
|
+
\`Error executing tool \${toolName}: \${errorMessage}\`,
|
|
417
|
+
);
|
|
367
418
|
}
|
|
368
419
|
});
|
|
369
420
|
|