@nitrostack/core 1.0.17 → 1.1.0-beta.1
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/core/app-decorator.d.ts +5 -0
- package/dist/core/app-decorator.d.ts.map +1 -1
- package/dist/core/app-decorator.js +2 -1
- package/dist/core/app-decorator.js.map +1 -1
- package/dist/core/builders.d.ts.map +1 -1
- package/dist/core/builders.js +2 -0
- package/dist/core/builders.js.map +1 -1
- package/dist/core/decorators.d.ts +11 -0
- package/dist/core/decorators.d.ts.map +1 -1
- package/dist/core/decorators.js.map +1 -1
- package/dist/core/health/health-checks.resource.d.ts +4 -3
- package/dist/core/health/health-checks.resource.d.ts.map +1 -1
- package/dist/core/health/health-checks.resource.js +35 -8
- package/dist/core/health/health-checks.resource.js.map +1 -1
- package/dist/core/health/health.interface.d.ts +40 -0
- package/dist/core/health/health.interface.d.ts.map +1 -0
- package/dist/core/health/health.interface.js +2 -0
- package/dist/core/health/health.interface.js.map +1 -0
- package/dist/core/index.d.ts +11 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +9 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/interceptors/data-spillover.interceptor.d.ts +30 -0
- package/dist/core/interceptors/data-spillover.interceptor.d.ts.map +1 -0
- package/dist/core/interceptors/data-spillover.interceptor.js +174 -0
- package/dist/core/interceptors/data-spillover.interceptor.js.map +1 -0
- package/dist/core/interceptors/interceptor.decorator.d.ts +17 -8
- package/dist/core/interceptors/interceptor.decorator.d.ts.map +1 -1
- package/dist/core/interceptors/interceptor.decorator.js +36 -11
- package/dist/core/interceptors/interceptor.decorator.js.map +1 -1
- package/dist/core/interceptors/interceptor.interface.d.ts +4 -0
- package/dist/core/interceptors/interceptor.interface.d.ts.map +1 -1
- package/dist/core/interceptors/spillover/fs-spillover.store.d.ts +54 -0
- package/dist/core/interceptors/spillover/fs-spillover.store.d.ts.map +1 -0
- package/dist/core/interceptors/spillover/fs-spillover.store.js +302 -0
- package/dist/core/interceptors/spillover/fs-spillover.store.js.map +1 -0
- package/dist/core/interceptors/spillover/index.d.ts +6 -0
- package/dist/core/interceptors/spillover/index.d.ts.map +1 -0
- package/dist/core/interceptors/spillover/index.js +6 -0
- package/dist/core/interceptors/spillover/index.js.map +1 -0
- package/dist/core/interceptors/spillover/memory-spillover.store.d.ts +25 -0
- package/dist/core/interceptors/spillover/memory-spillover.store.d.ts.map +1 -0
- package/dist/core/interceptors/spillover/memory-spillover.store.js +115 -0
- package/dist/core/interceptors/spillover/memory-spillover.store.js.map +1 -0
- package/dist/core/interceptors/spillover/preview-generator.d.ts +7 -0
- package/dist/core/interceptors/spillover/preview-generator.d.ts.map +1 -0
- package/dist/core/interceptors/spillover/preview-generator.js +79 -0
- package/dist/core/interceptors/spillover/preview-generator.js.map +1 -0
- package/dist/core/interceptors/spillover/spillover-store.interface.d.ts +20 -0
- package/dist/core/interceptors/spillover/spillover-store.interface.d.ts.map +1 -0
- package/dist/core/interceptors/spillover/spillover-store.interface.js +2 -0
- package/dist/core/interceptors/spillover/spillover-store.interface.js.map +1 -0
- package/dist/core/interceptors/spillover/types.d.ts +71 -0
- package/dist/core/interceptors/spillover/types.d.ts.map +1 -0
- package/dist/core/interceptors/spillover/types.js +12 -0
- package/dist/core/interceptors/spillover/types.js.map +1 -0
- package/dist/core/protocol/adapter.d.ts +7 -1
- package/dist/core/protocol/adapter.d.ts.map +1 -1
- package/dist/core/protocol/modern-v2.adapter.d.ts +95 -2
- package/dist/core/protocol/modern-v2.adapter.d.ts.map +1 -1
- package/dist/core/protocol/modern-v2.adapter.js +703 -165
- package/dist/core/protocol/modern-v2.adapter.js.map +1 -1
- package/dist/core/server.d.ts +104 -4
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +465 -52
- package/dist/core/server.js.map +1 -1
- package/dist/core/task.d.ts.map +1 -1
- package/dist/core/task.js +8 -7
- package/dist/core/task.js.map +1 -1
- package/dist/core/tool.d.ts +12 -3
- package/dist/core/tool.d.ts.map +1 -1
- package/dist/core/tool.js +21 -3
- package/dist/core/tool.js.map +1 -1
- package/dist/core/transforms/catalog.transform.d.ts +40 -0
- package/dist/core/transforms/catalog.transform.d.ts.map +1 -0
- package/dist/core/transforms/catalog.transform.js +52 -0
- package/dist/core/transforms/catalog.transform.js.map +1 -0
- package/dist/core/transforms/code-mode/code-mode.transform.d.ts +75 -0
- package/dist/core/transforms/code-mode/code-mode.transform.d.ts.map +1 -0
- package/dist/core/transforms/code-mode/code-mode.transform.js +226 -0
- package/dist/core/transforms/code-mode/code-mode.transform.js.map +1 -0
- package/dist/core/transforms/code-mode/destructive-guard.d.ts +7 -0
- package/dist/core/transforms/code-mode/destructive-guard.d.ts.map +1 -0
- package/dist/core/transforms/code-mode/destructive-guard.js +18 -0
- package/dist/core/transforms/code-mode/destructive-guard.js.map +1 -0
- package/dist/core/transforms/code-mode/guest-evaluator.d.ts +17 -0
- package/dist/core/transforms/code-mode/guest-evaluator.d.ts.map +1 -0
- package/dist/core/transforms/code-mode/guest-evaluator.js +277 -0
- package/dist/core/transforms/code-mode/guest-evaluator.js.map +1 -0
- package/dist/core/transforms/code-mode/index.d.ts +11 -0
- package/dist/core/transforms/code-mode/index.d.ts.map +1 -0
- package/dist/core/transforms/code-mode/index.js +11 -0
- package/dist/core/transforms/code-mode/index.js.map +1 -0
- package/dist/core/transforms/code-mode/ipc-messages.d.ts +31 -0
- package/dist/core/transforms/code-mode/ipc-messages.d.ts.map +1 -0
- package/dist/core/transforms/code-mode/ipc-messages.js +2 -0
- package/dist/core/transforms/code-mode/ipc-messages.js.map +1 -0
- package/dist/core/transforms/code-mode/legible-error.d.ts +8 -0
- package/dist/core/transforms/code-mode/legible-error.d.ts.map +1 -0
- package/dist/core/transforms/code-mode/legible-error.js +17 -0
- package/dist/core/transforms/code-mode/legible-error.js.map +1 -0
- package/dist/core/transforms/code-mode/quickjs-wasm.provider.d.ts +25 -0
- package/dist/core/transforms/code-mode/quickjs-wasm.provider.d.ts.map +1 -0
- package/dist/core/transforms/code-mode/quickjs-wasm.provider.js +31 -0
- package/dist/core/transforms/code-mode/quickjs-wasm.provider.js.map +1 -0
- package/dist/core/transforms/code-mode/sandbox.interface.d.ts +10 -0
- package/dist/core/transforms/code-mode/sandbox.interface.d.ts.map +1 -0
- package/dist/core/transforms/code-mode/sandbox.interface.js +2 -0
- package/dist/core/transforms/code-mode/sandbox.interface.js.map +1 -0
- package/dist/core/transforms/code-mode/sandbox.worker.d.ts +2 -0
- package/dist/core/transforms/code-mode/sandbox.worker.d.ts.map +1 -0
- package/dist/core/transforms/code-mode/sandbox.worker.js +69 -0
- package/dist/core/transforms/code-mode/sandbox.worker.js.map +1 -0
- package/dist/core/transforms/code-mode/synthetic-tools.d.ts +23 -0
- package/dist/core/transforms/code-mode/synthetic-tools.d.ts.map +1 -0
- package/dist/core/transforms/code-mode/synthetic-tools.js +112 -0
- package/dist/core/transforms/code-mode/synthetic-tools.js.map +1 -0
- package/dist/core/transforms/code-mode/types.d.ts +32 -0
- package/dist/core/transforms/code-mode/types.d.ts.map +1 -0
- package/dist/core/transforms/code-mode/types.js +2 -0
- package/dist/core/transforms/code-mode/types.js.map +1 -0
- package/dist/core/transforms/code-mode/worker-pool.d.ts +71 -0
- package/dist/core/transforms/code-mode/worker-pool.d.ts.map +1 -0
- package/dist/core/transforms/code-mode/worker-pool.js +435 -0
- package/dist/core/transforms/code-mode/worker-pool.js.map +1 -0
- package/dist/core/transforms/index.d.ts +6 -0
- package/dist/core/transforms/index.d.ts.map +1 -0
- package/dist/core/transforms/index.js +6 -0
- package/dist/core/transforms/index.js.map +1 -0
- package/dist/core/transforms/search/base-search.transform.d.ts +59 -0
- package/dist/core/transforms/search/base-search.transform.d.ts.map +1 -0
- package/dist/core/transforms/search/base-search.transform.js +169 -0
- package/dist/core/transforms/search/base-search.transform.js.map +1 -0
- package/dist/core/transforms/search/bm25-search.transform.d.ts +27 -0
- package/dist/core/transforms/search/bm25-search.transform.d.ts.map +1 -0
- package/dist/core/transforms/search/bm25-search.transform.js +37 -0
- package/dist/core/transforms/search/bm25-search.transform.js.map +1 -0
- package/dist/core/transforms/search/bm25.engine.d.ts +46 -0
- package/dist/core/transforms/search/bm25.engine.d.ts.map +1 -0
- package/dist/core/transforms/search/bm25.engine.js +160 -0
- package/dist/core/transforms/search/bm25.engine.js.map +1 -0
- package/dist/core/transforms/search/index.d.ts +9 -0
- package/dist/core/transforms/search/index.d.ts.map +1 -0
- package/dist/core/transforms/search/index.js +9 -0
- package/dist/core/transforms/search/index.js.map +1 -0
- package/dist/core/transforms/search/regex-match.worker.d.ts +2 -0
- package/dist/core/transforms/search/regex-match.worker.d.ts.map +1 -0
- package/dist/core/transforms/search/regex-match.worker.js +19 -0
- package/dist/core/transforms/search/regex-match.worker.js.map +1 -0
- package/dist/core/transforms/search/regex-search.transform.d.ts +41 -0
- package/dist/core/transforms/search/regex-search.transform.d.ts.map +1 -0
- package/dist/core/transforms/search/regex-search.transform.js +237 -0
- package/dist/core/transforms/search/regex-search.transform.js.map +1 -0
- package/dist/core/transforms/search/synthetic-tools.d.ts +11 -0
- package/dist/core/transforms/search/synthetic-tools.d.ts.map +1 -0
- package/dist/core/transforms/search/synthetic-tools.js +76 -0
- package/dist/core/transforms/search/synthetic-tools.js.map +1 -0
- package/dist/core/transforms/search/tokenizer.d.ts +11 -0
- package/dist/core/transforms/search/tokenizer.d.ts.map +1 -0
- package/dist/core/transforms/search/tokenizer.js +38 -0
- package/dist/core/transforms/search/tokenizer.js.map +1 -0
- package/dist/core/transforms/search/tool-serializer.d.ts +6 -0
- package/dist/core/transforms/search/tool-serializer.d.ts.map +1 -0
- package/dist/core/transforms/search/tool-serializer.js +41 -0
- package/dist/core/transforms/search/tool-serializer.js.map +1 -0
- package/dist/core/transforms/search/types.d.ts +36 -0
- package/dist/core/transforms/search/types.d.ts.map +1 -0
- package/dist/core/transforms/search/types.js +2 -0
- package/dist/core/transforms/search/types.js.map +1 -0
- package/dist/core/transforms/tool-cache-key.d.ts +15 -0
- package/dist/core/transforms/tool-cache-key.d.ts.map +1 -0
- package/dist/core/transforms/tool-cache-key.js +61 -0
- package/dist/core/transforms/tool-cache-key.js.map +1 -0
- package/dist/core/transforms/transform.interface.d.ts +34 -0
- package/dist/core/transforms/transform.interface.d.ts.map +1 -0
- package/dist/core/transforms/transform.interface.js +2 -0
- package/dist/core/transforms/transform.interface.js.map +1 -0
- package/dist/core/transforms/validate-tool-arguments.d.ts +10 -0
- package/dist/core/transforms/validate-tool-arguments.d.ts.map +1 -0
- package/dist/core/transforms/validate-tool-arguments.js +62 -0
- package/dist/core/transforms/validate-tool-arguments.js.map +1 -0
- package/dist/core/transforms/visibility/index.d.ts +3 -0
- package/dist/core/transforms/visibility/index.d.ts.map +1 -0
- package/dist/core/transforms/visibility/index.js +3 -0
- package/dist/core/transforms/visibility/index.js.map +1 -0
- package/dist/core/transforms/visibility/session-store.d.ts +132 -0
- package/dist/core/transforms/visibility/session-store.d.ts.map +1 -0
- package/dist/core/transforms/visibility/session-store.js +330 -0
- package/dist/core/transforms/visibility/session-store.js.map +1 -0
- package/dist/core/transforms/visibility/visibility.transform.d.ts +42 -0
- package/dist/core/transforms/visibility/visibility.transform.d.ts.map +1 -0
- package/dist/core/transforms/visibility/visibility.transform.js +109 -0
- package/dist/core/transforms/visibility/visibility.transform.js.map +1 -0
- package/dist/core/types.d.ts +60 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +3 -0
- package/dist/testing/index.js.map +1 -1
- package/package.json +8 -2
package/dist/core/server.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourceTemplatesRequestSchema, SubscribeRequestSchema, UnsubscribeRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
-
import { ResourceTemplate, createResource } from './resource.js';
|
|
5
|
+
import { Resource, ResourceTemplate, createResource } from './resource.js';
|
|
6
6
|
import { buildResourceReadContentsMeta } from './widget-mcp-meta.js';
|
|
7
7
|
import { getWidgetMimeType, isMcpAppMode, isOpenAiMode, getAppMode } from './app-mode.js';
|
|
8
8
|
import { createLogger } from './logger.js';
|
|
@@ -16,6 +16,9 @@ import { triggerLifecycleHook } from './lifecycle.js';
|
|
|
16
16
|
import { resolveProtocolEra, needsModernEngine, protocolVersionForEra, } from './protocol/version.js';
|
|
17
17
|
import { isInputRequired } from './protocol/features/mrtr.js';
|
|
18
18
|
import { extractBearerToken } from '../auth/token-validation.js';
|
|
19
|
+
import { SessionVisibilityStore } from './transforms/visibility/session-store.js';
|
|
20
|
+
import { MemorySpilloverStore } from './interceptors/spillover/memory-spillover.store.js';
|
|
21
|
+
import { FsSpilloverStore } from './interceptors/spillover/fs-spillover.store.js';
|
|
19
22
|
/** Optional Streamable HTTP session limits from environment variables. */
|
|
20
23
|
function getStreamableHttpEnvOptions() {
|
|
21
24
|
const maxSessions = process.env.MCP_MAX_SESSIONS
|
|
@@ -29,15 +32,50 @@ function getStreamableHttpEnvOptions() {
|
|
|
29
32
|
...(sessionTimeout !== undefined && !Number.isNaN(sessionTimeout) ? { sessionTimeout } : {}),
|
|
30
33
|
};
|
|
31
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Visibility and spillover key.
|
|
37
|
+
*
|
|
38
|
+
* The subject is the verified `extra.auth.subject` only. An unsigned bearer
|
|
39
|
+
* payload must not select a session. No transport session means no key.
|
|
40
|
+
*
|
|
41
|
+
* Anonymous and authenticated callers use disjoint prefixes, and both parts are
|
|
42
|
+
* encoded, so a client-supplied `mcp-session-id` cannot equal another
|
|
43
|
+
* principal's key.
|
|
44
|
+
*/
|
|
45
|
+
export function sessionIsolationKey(transportSessionId, verifiedSubject) {
|
|
46
|
+
if (!transportSessionId)
|
|
47
|
+
return undefined;
|
|
48
|
+
const session = encodeURIComponent(transportSessionId);
|
|
49
|
+
if (!verifiedSubject)
|
|
50
|
+
return `anon:${session}`;
|
|
51
|
+
return `user:${encodeURIComponent(verifiedSubject)}:${session}`;
|
|
52
|
+
}
|
|
53
|
+
function createSpilloverStore(config) {
|
|
54
|
+
const spillover = config.spillover;
|
|
55
|
+
if (spillover?.driver === 'filesystem') {
|
|
56
|
+
return new FsSpilloverStore({
|
|
57
|
+
storageDir: spillover.storageDir,
|
|
58
|
+
maxSizeBytes: spillover.maxSizeBytes,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return new MemorySpilloverStore(spillover?.maxSizeBytes !== undefined ? { maxSizeBytes: spillover.maxSizeBytes } : {});
|
|
62
|
+
}
|
|
32
63
|
/**
|
|
33
64
|
* NitroStackServer - Main server class
|
|
34
65
|
*/
|
|
35
66
|
export class NitroStackServer {
|
|
36
67
|
mcpServer;
|
|
68
|
+
/** Process-local session for the legacy stdio peer. Not taken from the client. */
|
|
69
|
+
legacyStdioSessionId;
|
|
37
70
|
tools = new Map();
|
|
71
|
+
sessionVisibilityStore;
|
|
72
|
+
pendingListChangedSessions = new Set();
|
|
73
|
+
notificationScheduled = false;
|
|
38
74
|
resources = new Map();
|
|
39
75
|
resourceTemplates = new Map();
|
|
40
76
|
templateResources = new Map();
|
|
77
|
+
defaultSpilloverStore;
|
|
78
|
+
startTime = Date.now();
|
|
41
79
|
prompts = new Map();
|
|
42
80
|
modules = [];
|
|
43
81
|
config;
|
|
@@ -72,12 +110,23 @@ export class NitroStackServer {
|
|
|
72
110
|
protocolEra;
|
|
73
111
|
/** Lazily constructed modern (2026-07-28) adapter (only on modern/auto). */
|
|
74
112
|
modernAdapter;
|
|
113
|
+
/** Registered MCP catalog transforms in pipeline order */
|
|
114
|
+
transforms = [];
|
|
75
115
|
constructor(config) {
|
|
76
116
|
// Default config if not provided (e.g., when instantiated by DI container)
|
|
77
117
|
this.config = config || {
|
|
78
118
|
name: 'nitrostack-server',
|
|
79
119
|
version: '1.0.0',
|
|
80
120
|
};
|
|
121
|
+
this.defaultSpilloverStore = createSpilloverStore(this.config);
|
|
122
|
+
this.transforms = this.config.transforms ? [...this.config.transforms] : [];
|
|
123
|
+
const existingVisibilityTransform = this.transforms.find((t) => t.name === 'visibility');
|
|
124
|
+
if (existingVisibilityTransform?.store) {
|
|
125
|
+
this.sessionVisibilityStore = existingVisibilityTransform.store;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
this.sessionVisibilityStore = new SessionVisibilityStore();
|
|
129
|
+
}
|
|
81
130
|
// Register itself in DI container so modules can inject the server.
|
|
82
131
|
// NOTE: DIContainer is a process-wide singleton, so this assumes a single
|
|
83
132
|
// NitroStackServer instance per process. Multiple instances would overwrite
|
|
@@ -90,6 +139,7 @@ export class NitroStackServer {
|
|
|
90
139
|
serviceName: this.config.name,
|
|
91
140
|
enableConsole: false, // CRITICAL: Console disabled for MCP compatibility
|
|
92
141
|
});
|
|
142
|
+
this.sessionVisibilityStore.setLogger(this.logger);
|
|
93
143
|
// Initialize task manager for MCP Tasks support
|
|
94
144
|
this.taskManager = new TaskManager({
|
|
95
145
|
logger: this.logger,
|
|
@@ -112,6 +162,23 @@ export class NitroStackServer {
|
|
|
112
162
|
version: this.config.version,
|
|
113
163
|
}, NitroStackServer.mcpServerOptions);
|
|
114
164
|
this.setupHandlersOn(this.mcpServer);
|
|
165
|
+
this.registerSpilloverResourceTemplate();
|
|
166
|
+
for (const transform of this.transforms) {
|
|
167
|
+
this.noteVisibilityBoundary(transform);
|
|
168
|
+
transform.onRegister?.(this);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Modern HTTP must present a session id this process minted before
|
|
173
|
+
* tools/list or tools/call. Visibility is not applied to a missing header.
|
|
174
|
+
*/
|
|
175
|
+
noteVisibilityBoundary(transform) {
|
|
176
|
+
if (transform.name !== 'visibility')
|
|
177
|
+
return;
|
|
178
|
+
if (this.protocolEra !== 'auto' && this.protocolEra !== 'modern')
|
|
179
|
+
return;
|
|
180
|
+
this.logger.warn('VisibilityTransform requires a server-issued session on the modern HTTP path. ' +
|
|
181
|
+
'tools/list and tools/call without a session issued by this process are rejected.');
|
|
115
182
|
}
|
|
116
183
|
/**
|
|
117
184
|
* Build the read-only registry view the modern protocol adapter consumes.
|
|
@@ -122,12 +189,18 @@ export class NitroStackServer {
|
|
|
122
189
|
config: this.config,
|
|
123
190
|
logger: this.logger,
|
|
124
191
|
getTools: () => this.tools,
|
|
192
|
+
getTransformedTools: async (ctx) => {
|
|
193
|
+
const tools = await this.runToolPipeline(ctx);
|
|
194
|
+
return new Map(tools.map((t) => [t.name, t]));
|
|
195
|
+
},
|
|
196
|
+
resolveTool: (name, ctx) => this.resolveTool(name, ctx),
|
|
125
197
|
getResources: () => this.resources,
|
|
126
198
|
getResourceTemplates: () => this.resourceTemplates,
|
|
127
199
|
getTemplateResources: () => this.templateResources,
|
|
128
200
|
getPrompts: () => this.prompts,
|
|
129
201
|
getTaskManager: () => this.taskManager,
|
|
130
202
|
createExecutionContext: (options) => this.createContext(options),
|
|
203
|
+
hasSessionVisibility: () => this.transforms.some((transform) => transform.name === 'visibility'),
|
|
131
204
|
};
|
|
132
205
|
}
|
|
133
206
|
/**
|
|
@@ -307,6 +380,98 @@ export class NitroStackServer {
|
|
|
307
380
|
}
|
|
308
381
|
return this;
|
|
309
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* Register a tool with the server (alias for tool()).
|
|
385
|
+
*/
|
|
386
|
+
registerTool(tool) {
|
|
387
|
+
return this.tool(tool);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Get a registered tool by name.
|
|
391
|
+
*/
|
|
392
|
+
getTool(name) {
|
|
393
|
+
return this.tools.get(name);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Get all registered raw tools, keyed by name.
|
|
397
|
+
*/
|
|
398
|
+
getTools() {
|
|
399
|
+
return new Map(this.tools);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Register a transform into the MCP tool catalog pipeline.
|
|
403
|
+
*/
|
|
404
|
+
addTransform(transform) {
|
|
405
|
+
this.transforms.push(transform);
|
|
406
|
+
this.noteVisibilityBoundary(transform);
|
|
407
|
+
if (transform.name === 'visibility' && transform.store) {
|
|
408
|
+
this.sessionVisibilityStore = transform.store;
|
|
409
|
+
}
|
|
410
|
+
transform.onRegister?.(this);
|
|
411
|
+
return this;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Get all registered transforms in the pipeline.
|
|
415
|
+
*/
|
|
416
|
+
getTransforms() {
|
|
417
|
+
return [...this.transforms];
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Returns live telemetry descriptors for all active transforms in pipeline order.
|
|
421
|
+
*/
|
|
422
|
+
getTransformTelemetry() {
|
|
423
|
+
return this.transforms.map((transform, index) => {
|
|
424
|
+
const customTelemetry = typeof transform.getTelemetry === 'function'
|
|
425
|
+
? transform.getTelemetry()
|
|
426
|
+
: {};
|
|
427
|
+
return {
|
|
428
|
+
name: transform.name,
|
|
429
|
+
type: transform.constructor.name,
|
|
430
|
+
order: index,
|
|
431
|
+
details: customTelemetry,
|
|
432
|
+
};
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Get server uptime in seconds.
|
|
437
|
+
*/
|
|
438
|
+
getUptimeSeconds() {
|
|
439
|
+
return Math.floor((Date.now() - this.startTime) / 1000);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Runs all registered transforms sequentially on the current tools catalog.
|
|
443
|
+
*/
|
|
444
|
+
async runToolPipeline(context) {
|
|
445
|
+
let tools = Array.from(this.tools.values());
|
|
446
|
+
for (const transform of this.transforms) {
|
|
447
|
+
if (transform.transformTools) {
|
|
448
|
+
try {
|
|
449
|
+
tools = await transform.transformTools(tools, context);
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
this.logger.error(`Transform '${transform.name}' failed during transformTools`, error instanceof Error ? error : new Error(String(error)));
|
|
453
|
+
throw error;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return tools;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Resolves a tool by traversing the transforms in onion-order.
|
|
461
|
+
*/
|
|
462
|
+
async resolveTool(name, context) {
|
|
463
|
+
const dispatch = async (i, toolName, ctx) => {
|
|
464
|
+
if (i >= this.transforms.length) {
|
|
465
|
+
return this.tools.get(toolName);
|
|
466
|
+
}
|
|
467
|
+
const transform = this.transforms[i];
|
|
468
|
+
if (transform.resolveTool) {
|
|
469
|
+
return transform.resolveTool(toolName, (nextName, nextCtx) => dispatch(i + 1, nextName, nextCtx ?? ctx), ctx);
|
|
470
|
+
}
|
|
471
|
+
return dispatch(i + 1, toolName, ctx);
|
|
472
|
+
};
|
|
473
|
+
return dispatch(0, name, context);
|
|
474
|
+
}
|
|
310
475
|
/**
|
|
311
476
|
* Register a component as an MCP resource
|
|
312
477
|
*/
|
|
@@ -400,6 +565,70 @@ export class NitroStackServer {
|
|
|
400
565
|
this.logger.info(`Resource template registered: ${template.uriTemplate}`);
|
|
401
566
|
return this;
|
|
402
567
|
}
|
|
568
|
+
/**
|
|
569
|
+
* Initializes the built-in spillover resource template.
|
|
570
|
+
* Called during server initialization.
|
|
571
|
+
*/
|
|
572
|
+
registerSpilloverResourceTemplate() {
|
|
573
|
+
const templateUri = 'resource://data-spillover/{id}';
|
|
574
|
+
const spilloverResource = new Resource({
|
|
575
|
+
uri: templateUri,
|
|
576
|
+
name: 'Data Spillover Storage',
|
|
577
|
+
title: 'Spillover Dataset Storage',
|
|
578
|
+
description: 'Parameterized retrieval endpoint for large tool output spillover payloads',
|
|
579
|
+
mimeType: 'application/json',
|
|
580
|
+
handler: async (uri, context) => {
|
|
581
|
+
// Extract ID from URI (e.g. resource://data-spillover/spill-12345)
|
|
582
|
+
const id = uri.split('/').pop() || uri.replace('resource://data-spillover/', '');
|
|
583
|
+
const record = await this.defaultSpilloverStore.get(id);
|
|
584
|
+
if (!record) {
|
|
585
|
+
throw new ResourceNotFoundError(uri);
|
|
586
|
+
}
|
|
587
|
+
const visibilityOn = this.transforms.some((transform) => transform.name === 'visibility');
|
|
588
|
+
// With visibility installed, a row that has no session is not readable.
|
|
589
|
+
// A row written for a session is readable only by that isolation key.
|
|
590
|
+
if (visibilityOn && !record.sessionId) {
|
|
591
|
+
throw new ResourceNotFoundError(uri);
|
|
592
|
+
}
|
|
593
|
+
if (record.sessionId && record.sessionId !== context.sessionId) {
|
|
594
|
+
throw new ResourceNotFoundError(uri);
|
|
595
|
+
}
|
|
596
|
+
if (record.mimeType === 'application/json') {
|
|
597
|
+
return {
|
|
598
|
+
type: 'json',
|
|
599
|
+
data: JSON.parse(record.data),
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
if (record.mimeType === 'application/octet-stream') {
|
|
603
|
+
return {
|
|
604
|
+
type: 'binary',
|
|
605
|
+
data: Buffer.from(record.data, 'base64'),
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
return {
|
|
609
|
+
type: 'text',
|
|
610
|
+
data: record.data,
|
|
611
|
+
};
|
|
612
|
+
},
|
|
613
|
+
});
|
|
614
|
+
this.resource(spilloverResource);
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Get the default spillover store used for payload offloading.
|
|
618
|
+
*/
|
|
619
|
+
getSpilloverStore() {
|
|
620
|
+
return this.defaultSpilloverStore;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Set the default spillover store used for payload offloading.
|
|
624
|
+
*/
|
|
625
|
+
setSpilloverStore(store) {
|
|
626
|
+
if (this.defaultSpilloverStore && this.defaultSpilloverStore !== store) {
|
|
627
|
+
this.defaultSpilloverStore.dispose().catch(() => { });
|
|
628
|
+
}
|
|
629
|
+
this.defaultSpilloverStore = store;
|
|
630
|
+
return this;
|
|
631
|
+
}
|
|
403
632
|
/**
|
|
404
633
|
* Notify clients that the list of resources has changed
|
|
405
634
|
*/
|
|
@@ -436,20 +665,91 @@ export class NitroStackServer {
|
|
|
436
665
|
}
|
|
437
666
|
}
|
|
438
667
|
/**
|
|
439
|
-
*
|
|
668
|
+
* Get the session visibility store
|
|
440
669
|
*/
|
|
441
|
-
|
|
442
|
-
this.
|
|
670
|
+
getSessionVisibilityStore() {
|
|
671
|
+
return this.sessionVisibilityStore;
|
|
672
|
+
}
|
|
673
|
+
/** True when a VisibilityTransform is in the pipeline. */
|
|
674
|
+
hasSessionVisibility() {
|
|
675
|
+
return this.transforms.some((transform) => transform.name === 'visibility');
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Dispatches notifications/tools/list_changed.
|
|
679
|
+
* If sessionId is provided, targets the specific session; if omitted, broadcasts globally.
|
|
680
|
+
* Coalesced via queueMicrotask to avoid duplicate notifications in a single tick.
|
|
681
|
+
*/
|
|
682
|
+
notifyToolsListChanged(sessionId) {
|
|
683
|
+
if (sessionId) {
|
|
684
|
+
this.pendingListChangedSessions.add(sessionId);
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
this.pendingListChangedSessions.add('*'); // Global broadcast flag
|
|
688
|
+
}
|
|
689
|
+
if (!this.notificationScheduled) {
|
|
690
|
+
this.notificationScheduled = true;
|
|
691
|
+
queueMicrotask(() => this.flushListChangedNotifications());
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
async flushListChangedNotifications() {
|
|
695
|
+
this.notificationScheduled = false;
|
|
696
|
+
const targetSessions = new Set(this.pendingListChangedSessions);
|
|
697
|
+
this.pendingListChangedSessions.clear();
|
|
698
|
+
const isGlobal = targetSessions.has('*');
|
|
699
|
+
// 1. Modern Protocol Adapter (2026-07-28)
|
|
700
|
+
if (this.modernAdapter) {
|
|
701
|
+
const modernTargets = isGlobal ? [undefined] : [...targetSessions];
|
|
702
|
+
for (const target of modernTargets) {
|
|
703
|
+
try {
|
|
704
|
+
this.modernAdapter.notifyToolsListChanged(target);
|
|
705
|
+
}
|
|
706
|
+
catch (err) {
|
|
707
|
+
this.logger.error('Failed to dispatch modern notifyToolsListChanged', { error: String(err) });
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
// 2. Legacy HTTP+SSE Sessions (2025-06-18)
|
|
712
|
+
if (isGlobal) {
|
|
713
|
+
for (const [sid, session] of this.legacySdkSseSessions.entries()) {
|
|
714
|
+
this.sendSessionNotification(session.server, 'notifications/tools/list_changed', sid);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
for (const sid of targetSessions) {
|
|
719
|
+
const session = this.legacySdkSseSessions.get(sid);
|
|
720
|
+
if (session) {
|
|
721
|
+
this.sendSessionNotification(session.server, 'notifications/tools/list_changed', sid);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
// 3. Legacy stdio (the standalone McpServer instance). Its single peer has a
|
|
726
|
+
// process-local session; a change for any other session is not its concern.
|
|
727
|
+
const stdioTargeted = this.legacyStdioSessionId !== undefined && targetSessions.has(this.legacyStdioSessionId);
|
|
728
|
+
if (isGlobal || stdioTargeted) {
|
|
729
|
+
try {
|
|
730
|
+
const serverWithNotify = this.mcpServer;
|
|
731
|
+
if (serverWithNotify.notification) {
|
|
732
|
+
await serverWithNotify.notification({ method: 'notifications/tools/list_changed' });
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
catch {
|
|
736
|
+
/* ignore if stdio client is disconnected */
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
sendSessionNotification(server, method, sessionId) {
|
|
443
741
|
try {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
742
|
+
if (typeof server?.notification === 'function') {
|
|
743
|
+
server.notification({ method }).catch((err) => {
|
|
744
|
+
this.logger.debug('Failed to deliver session list_changed notification', {
|
|
745
|
+
sessionId,
|
|
746
|
+
error: err instanceof Error ? err.message : String(err),
|
|
747
|
+
});
|
|
748
|
+
});
|
|
448
749
|
}
|
|
449
750
|
}
|
|
450
|
-
catch (
|
|
451
|
-
|
|
452
|
-
this.logger.error('Error sending tools list changed notification', { error: errorMessage });
|
|
751
|
+
catch (err) {
|
|
752
|
+
this.logger.debug('Error dispatching session notification', { sessionId, error: String(err) });
|
|
453
753
|
}
|
|
454
754
|
}
|
|
455
755
|
/**
|
|
@@ -553,7 +853,7 @@ export class NitroStackServer {
|
|
|
553
853
|
/**
|
|
554
854
|
* Create execution context
|
|
555
855
|
*/
|
|
556
|
-
createContext(options) {
|
|
856
|
+
createContext(options, sessionContext) {
|
|
557
857
|
const metadata = options?.metadata || {};
|
|
558
858
|
let auth = options?.extra?.auth;
|
|
559
859
|
if (!auth) {
|
|
@@ -584,25 +884,105 @@ export class NitroStackServer {
|
|
|
584
884
|
};
|
|
585
885
|
}
|
|
586
886
|
}
|
|
887
|
+
const extra = options?.extra;
|
|
888
|
+
// Transport session wins. extra is applied below, but must not replace this
|
|
889
|
+
// value — a client-supplied extra.sessionId would otherwise read another session.
|
|
890
|
+
const transportSessionId = sessionContext?.sessionId || extra?.sessionId;
|
|
891
|
+
// Verified subject only. The unsigned bearer decode above fills `auth` for
|
|
892
|
+
// existing consumers and must not choose the isolation key.
|
|
893
|
+
const verifiedSubject = typeof extra?.auth?.subject === 'string' ? extra.auth.subject : undefined;
|
|
894
|
+
const sessionId = sessionIsolationKey(transportSessionId, verifiedSubject);
|
|
895
|
+
const enableTools = async (names) => {
|
|
896
|
+
if (!sessionId) {
|
|
897
|
+
this.logger.warn('ctx.enableTools called without an active sessionId; no-op');
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
// Validation: Warn on unregistered tools
|
|
901
|
+
for (const name of names) {
|
|
902
|
+
if (!this.tools.has(name)) {
|
|
903
|
+
this.logger.warn(`ctx.enableTools: tool '${name}' is not registered in the catalog`);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
if (verifiedSubject) {
|
|
907
|
+
this.sessionVisibilityStore.enableSubject(verifiedSubject, names);
|
|
908
|
+
}
|
|
909
|
+
this.sessionVisibilityStore.enableTools(sessionId, names);
|
|
910
|
+
if (transportSessionId)
|
|
911
|
+
this.notifyToolsListChanged(transportSessionId);
|
|
912
|
+
};
|
|
913
|
+
const disableTools = async (names) => {
|
|
914
|
+
if (!sessionId) {
|
|
915
|
+
this.logger.warn('ctx.disableTools called without an active sessionId; no-op');
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
this.sessionVisibilityStore.disableTools(sessionId, names);
|
|
919
|
+
if (verifiedSubject) {
|
|
920
|
+
this.sessionVisibilityStore.disableSubject(verifiedSubject, names);
|
|
921
|
+
}
|
|
922
|
+
if (transportSessionId)
|
|
923
|
+
this.notifyToolsListChanged(transportSessionId);
|
|
924
|
+
};
|
|
925
|
+
const getVisibleTools = () => {
|
|
926
|
+
if (!sessionId)
|
|
927
|
+
return undefined;
|
|
928
|
+
const session = this.sessionVisibilityStore.getSession(sessionId);
|
|
929
|
+
const subjectRestricted = verifiedSubject
|
|
930
|
+
? this.sessionVisibilityStore.hasSubjectDenies(verifiedSubject)
|
|
931
|
+
: false;
|
|
932
|
+
if (!session && !this.sessionVisibilityStore.hasRevocations(sessionId) && !subjectRestricted) {
|
|
933
|
+
return undefined;
|
|
934
|
+
}
|
|
935
|
+
const allowed = new Set();
|
|
936
|
+
for (const [name, tool] of this.tools.entries()) {
|
|
937
|
+
if (verifiedSubject && this.sessionVisibilityStore.hasSubjectDisabled(verifiedSubject, name)) {
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
if (this.sessionVisibilityStore.hasDisabled(sessionId, name))
|
|
941
|
+
continue;
|
|
942
|
+
if (session?.enabledTools.has(name) || tool.visibility !== 'hidden') {
|
|
943
|
+
allowed.add(name);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
return allowed;
|
|
947
|
+
};
|
|
587
948
|
return {
|
|
949
|
+
// Additive 2026-07-28 fields (protocolVersion, requestState, inputResponses,
|
|
950
|
+
// trace, clientInfo, clientCapabilities, auth) supplied by the modern adapter.
|
|
951
|
+
...(extra || {}),
|
|
588
952
|
logger: this.logger,
|
|
589
953
|
requestId: uuidv4(),
|
|
590
|
-
toolName: options?.toolName,
|
|
954
|
+
toolName: options?.toolName ?? extra?.toolName,
|
|
591
955
|
metadata,
|
|
592
|
-
auth,
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
956
|
+
auth: extra?.auth ?? auth,
|
|
957
|
+
sessionId,
|
|
958
|
+
verifiedSubject,
|
|
959
|
+
enableTools,
|
|
960
|
+
disableTools,
|
|
961
|
+
getVisibleTools,
|
|
596
962
|
};
|
|
597
963
|
}
|
|
964
|
+
/**
|
|
965
|
+
* Build an execution context (alias for createContext()).
|
|
966
|
+
*/
|
|
967
|
+
createExecutionContext(options, sessionContext) {
|
|
968
|
+
return this.createContext(options, sessionContext);
|
|
969
|
+
}
|
|
598
970
|
/**
|
|
599
971
|
* Register MCP protocol handlers on the given server instance (main or per legacy SSE session).
|
|
600
972
|
*/
|
|
601
973
|
setupHandlersOn(mcp, sessionContext) {
|
|
974
|
+
// The standalone server only ever serves the stdio peer, whose session starts at connect.
|
|
975
|
+
const sessionId = () => sessionContext?.sessionId ?? (mcp === this.mcpServer ? this.legacyStdioSessionId : undefined);
|
|
602
976
|
// List tools
|
|
603
977
|
mcp.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
604
978
|
this.logger.debug('Listing tools');
|
|
605
|
-
const
|
|
979
|
+
const context = this.createContext({
|
|
980
|
+
extra: {
|
|
981
|
+
sessionId: sessionId(),
|
|
982
|
+
},
|
|
983
|
+
});
|
|
984
|
+
const rawTools = await this.runToolPipeline(context);
|
|
985
|
+
const tools = await Promise.all(rawTools.map((tool) => tool.toMcpTool()));
|
|
606
986
|
return {
|
|
607
987
|
tools,
|
|
608
988
|
};
|
|
@@ -610,28 +990,7 @@ export class NitroStackServer {
|
|
|
610
990
|
// Call tool
|
|
611
991
|
mcp.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
612
992
|
const { name, arguments: args } = request.params;
|
|
613
|
-
const tool = this.tools.get(name);
|
|
614
|
-
if (!tool) {
|
|
615
|
-
throw new ToolExecutionError(name, new Error('Tool not found'));
|
|
616
|
-
}
|
|
617
|
-
// ----------------------------------------------------------------
|
|
618
|
-
// MCP Tasks: detect task-augmented requests
|
|
619
|
-
// The client sends `task: { ttl?: number }` in params to request
|
|
620
|
-
// async task execution.
|
|
621
|
-
// ----------------------------------------------------------------
|
|
622
993
|
const requestParams = request.params;
|
|
623
|
-
const taskParam = requestParams['task'];
|
|
624
|
-
const isTaskAugmented = taskParam !== undefined;
|
|
625
|
-
// Enforce tool-level task support negotiation
|
|
626
|
-
if (isTaskAugmented && tool.taskSupport === 'forbidden') {
|
|
627
|
-
throw {
|
|
628
|
-
code: -32601,
|
|
629
|
-
message: `Tool '${name}' does not support task augmentation`,
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
if (!isTaskAugmented && tool.taskSupport === 'required') {
|
|
633
|
-
throw new TaskAugmentationRequiredError();
|
|
634
|
-
}
|
|
635
994
|
// Extract _meta from request params (MCP spec) and from arguments
|
|
636
995
|
// (legacy Studio clients); params._meta takes precedence.
|
|
637
996
|
const argsRecord = (args || {});
|
|
@@ -654,8 +1013,33 @@ export class NitroStackServer {
|
|
|
654
1013
|
}
|
|
655
1014
|
const context = this.createContext({
|
|
656
1015
|
metadata: combinedMeta,
|
|
657
|
-
toolName: name
|
|
1016
|
+
toolName: name,
|
|
1017
|
+
extra: {
|
|
1018
|
+
sessionId: sessionId(),
|
|
1019
|
+
},
|
|
658
1020
|
});
|
|
1021
|
+
// Resolve tool through the pipeline with context
|
|
1022
|
+
const tool = await this.resolveTool(name, context);
|
|
1023
|
+
if (!tool) {
|
|
1024
|
+
throw new ToolExecutionError(name, new Error(`Tool '${name}' not found`));
|
|
1025
|
+
}
|
|
1026
|
+
// ----------------------------------------------------------------
|
|
1027
|
+
// MCP Tasks: detect task-augmented requests
|
|
1028
|
+
// The client sends `task: { ttl?: number }` in params to request
|
|
1029
|
+
// async task execution.
|
|
1030
|
+
// ----------------------------------------------------------------
|
|
1031
|
+
const taskParam = requestParams['task'];
|
|
1032
|
+
const isTaskAugmented = taskParam !== undefined;
|
|
1033
|
+
// Enforce tool-level task support negotiation
|
|
1034
|
+
if (isTaskAugmented && tool.taskSupport === 'forbidden') {
|
|
1035
|
+
throw {
|
|
1036
|
+
code: -32601,
|
|
1037
|
+
message: `Tool '${name}' does not support task augmentation`,
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
if (!isTaskAugmented && tool.taskSupport === 'required') {
|
|
1041
|
+
throw new TaskAugmentationRequiredError();
|
|
1042
|
+
}
|
|
659
1043
|
// ----------------------------------------------------------------
|
|
660
1044
|
// Task-augmented path: create task, run async, return immediately
|
|
661
1045
|
// ----------------------------------------------------------------
|
|
@@ -699,15 +1083,22 @@ export class NitroStackServer {
|
|
|
699
1083
|
};
|
|
700
1084
|
}
|
|
701
1085
|
this.stats.toolCalls++;
|
|
1086
|
+
const isCallToolResult = result !== null &&
|
|
1087
|
+
typeof result === 'object' &&
|
|
1088
|
+
Array.isArray(result.content) &&
|
|
1089
|
+
result.content.length > 0 &&
|
|
1090
|
+
typeof result.content[0]?.type === 'string';
|
|
702
1091
|
// Check if tool has a UI component
|
|
703
|
-
const response =
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
1092
|
+
const response = isCallToolResult
|
|
1093
|
+
? result
|
|
1094
|
+
: {
|
|
1095
|
+
content: [
|
|
1096
|
+
{
|
|
1097
|
+
type: 'text',
|
|
1098
|
+
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
|
|
1099
|
+
},
|
|
1100
|
+
],
|
|
1101
|
+
};
|
|
711
1102
|
// Add structuredContent and _meta if component is attached
|
|
712
1103
|
if (tool.hasComponent()) {
|
|
713
1104
|
const component = tool.getComponent();
|
|
@@ -900,7 +1291,9 @@ export class NitroStackServer {
|
|
|
900
1291
|
if (!resource) {
|
|
901
1292
|
throw new ResourceNotFoundError(uri);
|
|
902
1293
|
}
|
|
903
|
-
const context = this.createContext(
|
|
1294
|
+
const context = this.createContext({
|
|
1295
|
+
extra: { sessionId: sessionId() },
|
|
1296
|
+
});
|
|
904
1297
|
try {
|
|
905
1298
|
const content = await resource.fetch(context, uri);
|
|
906
1299
|
let responseContent;
|
|
@@ -994,7 +1387,9 @@ export class NitroStackServer {
|
|
|
994
1387
|
if (!prompt) {
|
|
995
1388
|
throw new PromptNotFoundError(name);
|
|
996
1389
|
}
|
|
997
|
-
const context = this.createContext(
|
|
1390
|
+
const context = this.createContext({
|
|
1391
|
+
extra: { sessionId: sessionId() },
|
|
1392
|
+
});
|
|
998
1393
|
try {
|
|
999
1394
|
const result = await prompt.execute(args || {}, context);
|
|
1000
1395
|
this.stats.promptExecutions++;
|
|
@@ -1087,7 +1482,8 @@ export class NitroStackServer {
|
|
|
1087
1482
|
await this.configureTransportForProtocol(httpTransport);
|
|
1088
1483
|
// Set up tools callback and server config for documentation page
|
|
1089
1484
|
httpTransport.setToolsCallback(async () => {
|
|
1090
|
-
const
|
|
1485
|
+
const rawTools = await this.runToolPipeline();
|
|
1486
|
+
const tools = await Promise.all(rawTools.map((tool) => tool.toMcpTool()));
|
|
1091
1487
|
return tools;
|
|
1092
1488
|
});
|
|
1093
1489
|
httpTransport.setServerConfig({
|
|
@@ -1172,6 +1568,7 @@ export class NitroStackServer {
|
|
|
1172
1568
|
await (await this.getModernAdapter()).serveStdio();
|
|
1173
1569
|
}
|
|
1174
1570
|
else {
|
|
1571
|
+
this.legacyStdioSessionId ??= uuidv4();
|
|
1175
1572
|
const stdioTransport = new StdioServerTransport();
|
|
1176
1573
|
await this.mcpServer.connect(stdioTransport);
|
|
1177
1574
|
}
|
|
@@ -1198,7 +1595,8 @@ export class NitroStackServer {
|
|
|
1198
1595
|
await this.configureTransportForProtocol(transport);
|
|
1199
1596
|
// Set up tools callback and server config for documentation page
|
|
1200
1597
|
transport.setToolsCallback(async () => {
|
|
1201
|
-
const
|
|
1598
|
+
const rawTools = await this.runToolPipeline();
|
|
1599
|
+
const tools = await Promise.all(rawTools.map((tool) => tool.toMcpTool()));
|
|
1202
1600
|
return tools;
|
|
1203
1601
|
});
|
|
1204
1602
|
transport.setServerConfig({
|
|
@@ -1225,6 +1623,7 @@ export class NitroStackServer {
|
|
|
1225
1623
|
await (await this.getModernAdapter()).serveStdio();
|
|
1226
1624
|
}
|
|
1227
1625
|
else {
|
|
1626
|
+
this.legacyStdioSessionId ??= uuidv4();
|
|
1228
1627
|
const transport = new StdioServerTransport();
|
|
1229
1628
|
await this.mcpServer.connect(transport);
|
|
1230
1629
|
}
|
|
@@ -1459,6 +1858,20 @@ export class NitroStackServer {
|
|
|
1459
1858
|
}
|
|
1460
1859
|
// Destroy task manager (stops cleanup interval)
|
|
1461
1860
|
this.taskManager.destroy();
|
|
1861
|
+
// Destroy session visibility store (stops cleanup interval)
|
|
1862
|
+
this.sessionVisibilityStore.destroy();
|
|
1863
|
+
// Dispose transforms (terminates sandbox worker threads)
|
|
1864
|
+
for (const transform of this.transforms) {
|
|
1865
|
+
try {
|
|
1866
|
+
await transform.dispose?.();
|
|
1867
|
+
}
|
|
1868
|
+
catch (error) {
|
|
1869
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1870
|
+
this.logger.error(`Error disposing transform ${transform.name}`, { error: errorMessage });
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
// Dispose default spillover store (stops sweep interval)
|
|
1874
|
+
await this.defaultSpilloverStore.dispose();
|
|
1462
1875
|
// Close the modern protocol adapter (aborts in-flight modern exchanges).
|
|
1463
1876
|
if (this.modernAdapter) {
|
|
1464
1877
|
await this.modernAdapter.close();
|