@nitrostack/core 1.0.16 → 1.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +688 -157
- 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 +450 -44
- 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 +166 -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 +9 -6
- package/dist/core/widget-bundle-paths.d.ts +0 -5
- package/dist/core/widget-bundle-paths.d.ts.map +0 -1
- package/dist/core/widget-bundle-paths.js +0 -25
- package/dist/core/widget-bundle-paths.js.map +0 -1
- package/dist/core/widget-html-mime.d.ts +0 -7
- package/dist/core/widget-html-mime.d.ts.map +0 -1
- package/dist/core/widget-html-mime.js +0 -7
- package/dist/core/widget-html-mime.js.map +0 -1
- package/dist/core/widget-mime.d.ts +0 -29
- package/dist/core/widget-mime.d.ts.map +0 -1
- package/dist/core/widget-mime.js +0 -41
- package/dist/core/widget-mime.js.map +0 -1
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
|
// ----------------------------------------------------------------
|
|
@@ -900,7 +1284,9 @@ export class NitroStackServer {
|
|
|
900
1284
|
if (!resource) {
|
|
901
1285
|
throw new ResourceNotFoundError(uri);
|
|
902
1286
|
}
|
|
903
|
-
const context = this.createContext(
|
|
1287
|
+
const context = this.createContext({
|
|
1288
|
+
extra: { sessionId: sessionId() },
|
|
1289
|
+
});
|
|
904
1290
|
try {
|
|
905
1291
|
const content = await resource.fetch(context, uri);
|
|
906
1292
|
let responseContent;
|
|
@@ -994,7 +1380,9 @@ export class NitroStackServer {
|
|
|
994
1380
|
if (!prompt) {
|
|
995
1381
|
throw new PromptNotFoundError(name);
|
|
996
1382
|
}
|
|
997
|
-
const context = this.createContext(
|
|
1383
|
+
const context = this.createContext({
|
|
1384
|
+
extra: { sessionId: sessionId() },
|
|
1385
|
+
});
|
|
998
1386
|
try {
|
|
999
1387
|
const result = await prompt.execute(args || {}, context);
|
|
1000
1388
|
this.stats.promptExecutions++;
|
|
@@ -1087,7 +1475,8 @@ export class NitroStackServer {
|
|
|
1087
1475
|
await this.configureTransportForProtocol(httpTransport);
|
|
1088
1476
|
// Set up tools callback and server config for documentation page
|
|
1089
1477
|
httpTransport.setToolsCallback(async () => {
|
|
1090
|
-
const
|
|
1478
|
+
const rawTools = await this.runToolPipeline();
|
|
1479
|
+
const tools = await Promise.all(rawTools.map((tool) => tool.toMcpTool()));
|
|
1091
1480
|
return tools;
|
|
1092
1481
|
});
|
|
1093
1482
|
httpTransport.setServerConfig({
|
|
@@ -1172,6 +1561,7 @@ export class NitroStackServer {
|
|
|
1172
1561
|
await (await this.getModernAdapter()).serveStdio();
|
|
1173
1562
|
}
|
|
1174
1563
|
else {
|
|
1564
|
+
this.legacyStdioSessionId ??= uuidv4();
|
|
1175
1565
|
const stdioTransport = new StdioServerTransport();
|
|
1176
1566
|
await this.mcpServer.connect(stdioTransport);
|
|
1177
1567
|
}
|
|
@@ -1198,7 +1588,8 @@ export class NitroStackServer {
|
|
|
1198
1588
|
await this.configureTransportForProtocol(transport);
|
|
1199
1589
|
// Set up tools callback and server config for documentation page
|
|
1200
1590
|
transport.setToolsCallback(async () => {
|
|
1201
|
-
const
|
|
1591
|
+
const rawTools = await this.runToolPipeline();
|
|
1592
|
+
const tools = await Promise.all(rawTools.map((tool) => tool.toMcpTool()));
|
|
1202
1593
|
return tools;
|
|
1203
1594
|
});
|
|
1204
1595
|
transport.setServerConfig({
|
|
@@ -1225,6 +1616,7 @@ export class NitroStackServer {
|
|
|
1225
1616
|
await (await this.getModernAdapter()).serveStdio();
|
|
1226
1617
|
}
|
|
1227
1618
|
else {
|
|
1619
|
+
this.legacyStdioSessionId ??= uuidv4();
|
|
1228
1620
|
const transport = new StdioServerTransport();
|
|
1229
1621
|
await this.mcpServer.connect(transport);
|
|
1230
1622
|
}
|
|
@@ -1459,6 +1851,20 @@ export class NitroStackServer {
|
|
|
1459
1851
|
}
|
|
1460
1852
|
// Destroy task manager (stops cleanup interval)
|
|
1461
1853
|
this.taskManager.destroy();
|
|
1854
|
+
// Destroy session visibility store (stops cleanup interval)
|
|
1855
|
+
this.sessionVisibilityStore.destroy();
|
|
1856
|
+
// Dispose transforms (terminates sandbox worker threads)
|
|
1857
|
+
for (const transform of this.transforms) {
|
|
1858
|
+
try {
|
|
1859
|
+
await transform.dispose?.();
|
|
1860
|
+
}
|
|
1861
|
+
catch (error) {
|
|
1862
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1863
|
+
this.logger.error(`Error disposing transform ${transform.name}`, { error: errorMessage });
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
// Dispose default spillover store (stops sweep interval)
|
|
1867
|
+
await this.defaultSpilloverStore.dispose();
|
|
1462
1868
|
// Close the modern protocol adapter (aborts in-flight modern exchanges).
|
|
1463
1869
|
if (this.modernAdapter) {
|
|
1464
1870
|
await this.modernAdapter.close();
|