@nitrostack/core 1.0.10 → 1.0.12
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/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +17 -0
- package/dist/auth/middleware.js.map +1 -1
- package/dist/auth/token-validation.d.ts.map +1 -1
- package/dist/auth/token-validation.js +12 -2
- package/dist/auth/token-validation.js.map +1 -1
- package/dist/core/app-decorator.d.ts.map +1 -1
- package/dist/core/app-decorator.js +3 -16
- package/dist/core/app-decorator.js.map +1 -1
- package/dist/core/oauth-module.d.ts +76 -10
- package/dist/core/oauth-module.d.ts.map +1 -1
- package/dist/core/oauth-module.js +339 -110
- package/dist/core/oauth-module.js.map +1 -1
- package/dist/core/prompt.d.ts +0 -3
- package/dist/core/prompt.d.ts.map +1 -1
- package/dist/core/prompt.js +27 -1
- package/dist/core/prompt.js.map +1 -1
- package/dist/core/server.d.ts +13 -25
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +113 -96
- package/dist/core/server.js.map +1 -1
- package/dist/core/transports/streamable-http.d.ts +64 -61
- package/dist/core/transports/streamable-http.d.ts.map +1 -1
- package/dist/core/transports/streamable-http.js +1608 -823
- package/dist/core/transports/streamable-http.js.map +1 -1
- package/package.json +5 -5
package/dist/core/prompt.js
CHANGED
|
@@ -2,6 +2,32 @@ import { ValidationError } from './errors.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Prompt class provides a clean abstraction for defining and executing prompts
|
|
4
4
|
*/
|
|
5
|
+
// Enforces the PromptMessage contract (see types.ts): role in
|
|
6
|
+
// user|assistant|system and string content. Structured content blocks are not
|
|
7
|
+
// supported here by design; supporting them requires changing the PromptMessage
|
|
8
|
+
// type and the server-side MCP mapping together.
|
|
9
|
+
function validateMessageFormat(msg) {
|
|
10
|
+
if (!msg || typeof msg !== 'object') {
|
|
11
|
+
throw new ValidationError('Invalid prompt message format: message must be an object');
|
|
12
|
+
}
|
|
13
|
+
if (!msg.role || !['user', 'assistant', 'system'].includes(msg.role)) {
|
|
14
|
+
throw new ValidationError(`Invalid prompt message role: '${msg.role}'. Must be 'user', 'assistant', or 'system'`);
|
|
15
|
+
}
|
|
16
|
+
if (typeof msg.content !== 'string') {
|
|
17
|
+
throw new ValidationError('Invalid prompt message content: content must be a string');
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
role: msg.role,
|
|
21
|
+
content: msg.content,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function normalizePromptResponse(result) {
|
|
25
|
+
if (result === null || result === undefined) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
const arrayResult = Array.isArray(result) ? result : [result];
|
|
29
|
+
return arrayResult.map(validateMessageFormat);
|
|
30
|
+
}
|
|
5
31
|
export class Prompt {
|
|
6
32
|
definition;
|
|
7
33
|
constructor(definition) {
|
|
@@ -40,7 +66,7 @@ export class Prompt {
|
|
|
40
66
|
context.logger.info(`Executing prompt: ${this.name}`, { args: args });
|
|
41
67
|
try {
|
|
42
68
|
const messagesResult = await this.definition.handler(args, context);
|
|
43
|
-
const messages =
|
|
69
|
+
const messages = normalizePromptResponse(messagesResult);
|
|
44
70
|
context.logger.info(`Prompt executed successfully: ${this.name}`, {
|
|
45
71
|
messageCount: messages.length,
|
|
46
72
|
});
|
package/dist/core/prompt.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/core/prompt.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAY9C;;GAEG;AACH,MAAM,OAAO,MAAM;IACT,UAAU,CAAmB;IAErC,YAAY,UAA4B;QACtC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;IACrC,CAAC;IAED;;OAEG;IACH,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,IAAI,EAAE,CAAC;IACzC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO,CAAC,IAAyC,EAAE,OAAyB;QAChF,8BAA8B;QAC9B,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAE7B,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,IAA6B,EAAE,CAAC,CAAC;QAE/F,IAAI,CAAC;YACH,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACpE,MAAM,QAAQ,GAAG,
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/core/prompt.ts"],"names":[],"mappings":"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"}
|
package/dist/core/server.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import type { Express, Response } from 'express';
|
|
2
3
|
import { Tool } from './tool.js';
|
|
3
4
|
import { Resource, ResourceTemplate } from './resource.js';
|
|
4
5
|
import { Prompt } from './prompt.js';
|
|
@@ -10,8 +11,9 @@ import { TaskManager } from './task.js';
|
|
|
10
11
|
interface HttpTransport {
|
|
11
12
|
start(): Promise<void>;
|
|
12
13
|
close(): Promise<void>;
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
/** Provide the factory used to build a configured MCP server per /mcp session. */
|
|
15
|
+
setMcpServerFactory?(factory: () => McpServer): void;
|
|
16
|
+
setLegacySseHandler?(handler: (req: unknown, res: Response) => Promise<void>): void;
|
|
15
17
|
setToolsCallback?(callback: () => Promise<unknown[]>): void;
|
|
16
18
|
setServerConfig?(config: {
|
|
17
19
|
name: string;
|
|
@@ -21,28 +23,6 @@ interface HttpTransport {
|
|
|
21
23
|
/** StreamableHttpTransport exposes the Express app for extra routes (legacy SDK SSE). */
|
|
22
24
|
getApp?(): Express;
|
|
23
25
|
}
|
|
24
|
-
/**
|
|
25
|
-
* JSON-RPC request structure
|
|
26
|
-
*/
|
|
27
|
-
interface JsonRpcRequest {
|
|
28
|
-
jsonrpc: '2.0';
|
|
29
|
-
id?: string | number | null;
|
|
30
|
-
method?: string;
|
|
31
|
-
params?: Record<string, unknown>;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* JSON-RPC response structure
|
|
35
|
-
*/
|
|
36
|
-
interface JsonRpcResponse {
|
|
37
|
-
jsonrpc: '2.0';
|
|
38
|
-
id: string | number | null;
|
|
39
|
-
result?: unknown;
|
|
40
|
-
error?: {
|
|
41
|
-
code: number;
|
|
42
|
-
message: string;
|
|
43
|
-
data?: unknown;
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
26
|
/**
|
|
47
27
|
* NitroStackServer - Main server class
|
|
48
28
|
*/
|
|
@@ -71,10 +51,18 @@ export declare class NitroStackServer {
|
|
|
71
51
|
constructor(config?: McpServerConfig);
|
|
72
52
|
/** Shared MCP server constructor options (main + per legacy SSE session) */
|
|
73
53
|
private static readonly mcpServerOptions;
|
|
54
|
+
/**
|
|
55
|
+
* Build a fresh MCP server instance wired with all of this application's
|
|
56
|
+
* tool/resource/prompt handlers. Used for transports that require their own
|
|
57
|
+
* server instance (legacy SSE sessions, official Streamable HTTP sessions),
|
|
58
|
+
* since a single SDK server can only be connected to one transport at a time.
|
|
59
|
+
*/
|
|
60
|
+
createConfiguredMcpServer(): McpServer;
|
|
74
61
|
/**
|
|
75
62
|
* SDK-compatible legacy HTTP+SSE: GET /sse (SSEServerTransport) and POST /mcp/messages?sessionId=.
|
|
76
63
|
* Streamable HTTP stays on GET/POST /mcp.
|
|
77
64
|
*/
|
|
65
|
+
private startLegacySdkSseSession;
|
|
78
66
|
private attachLegacySdkSseRoutes;
|
|
79
67
|
private attachLegacySdkSseIfNeeded;
|
|
80
68
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/core/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/core/server.ts"],"names":[],"mappings":"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"}
|
package/dist/core/server.js
CHANGED
|
@@ -12,6 +12,19 @@ import { isModule, getModuleMetadata } from './module.js';
|
|
|
12
12
|
import { buildController } from './builders.js';
|
|
13
13
|
import { DIContainer } from './di/container.js';
|
|
14
14
|
import { TaskManager, TaskContext, TaskNotFoundError, TaskAlreadyTerminalError, TaskAugmentationRequiredError, } from './task.js';
|
|
15
|
+
/** Optional Streamable HTTP session limits from environment variables. */
|
|
16
|
+
function getStreamableHttpEnvOptions() {
|
|
17
|
+
const maxSessions = process.env.MCP_MAX_SESSIONS
|
|
18
|
+
? parseInt(process.env.MCP_MAX_SESSIONS, 10)
|
|
19
|
+
: undefined;
|
|
20
|
+
const sessionTimeout = process.env.MCP_SESSION_TIMEOUT_MS
|
|
21
|
+
? parseInt(process.env.MCP_SESSION_TIMEOUT_MS, 10)
|
|
22
|
+
: undefined;
|
|
23
|
+
return {
|
|
24
|
+
...(maxSessions !== undefined && !Number.isNaN(maxSessions) ? { maxSessions } : {}),
|
|
25
|
+
...(sessionTimeout !== undefined && !Number.isNaN(sessionTimeout) ? { sessionTimeout } : {}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
15
28
|
/**
|
|
16
29
|
* NitroStackServer - Main server class
|
|
17
30
|
*/
|
|
@@ -48,6 +61,12 @@ export class NitroStackServer {
|
|
|
48
61
|
name: 'nitrostack-server',
|
|
49
62
|
version: '1.0.0',
|
|
50
63
|
};
|
|
64
|
+
// Register itself in DI container so modules can inject the server.
|
|
65
|
+
// NOTE: DIContainer is a process-wide singleton, so this assumes a single
|
|
66
|
+
// NitroStackServer instance per process. Multiple instances would overwrite
|
|
67
|
+
// each other's registration here.
|
|
68
|
+
DIContainer.getInstance().registerValue(NitroStackServer, this);
|
|
69
|
+
DIContainer.getInstance().registerValue('NitroStackServer', this);
|
|
51
70
|
this.logger = createLogger({
|
|
52
71
|
level: this.config.logging?.level || 'info',
|
|
53
72
|
file: this.config.logging?.file,
|
|
@@ -90,10 +109,59 @@ export class NitroStackServer {
|
|
|
90
109
|
},
|
|
91
110
|
},
|
|
92
111
|
};
|
|
112
|
+
/**
|
|
113
|
+
* Build a fresh MCP server instance wired with all of this application's
|
|
114
|
+
* tool/resource/prompt handlers. Used for transports that require their own
|
|
115
|
+
* server instance (legacy SSE sessions, official Streamable HTTP sessions),
|
|
116
|
+
* since a single SDK server can only be connected to one transport at a time.
|
|
117
|
+
*/
|
|
118
|
+
createConfiguredMcpServer() {
|
|
119
|
+
const mcp = new McpServer({
|
|
120
|
+
name: this.config.name,
|
|
121
|
+
version: this.config.version,
|
|
122
|
+
}, NitroStackServer.mcpServerOptions);
|
|
123
|
+
this.setupHandlersOn(mcp);
|
|
124
|
+
return mcp;
|
|
125
|
+
}
|
|
93
126
|
/**
|
|
94
127
|
* SDK-compatible legacy HTTP+SSE: GET /sse (SSEServerTransport) and POST /mcp/messages?sessionId=.
|
|
95
128
|
* Streamable HTTP stays on GET/POST /mcp.
|
|
96
129
|
*/
|
|
130
|
+
async startLegacySdkSseSession(res, messagesPath) {
|
|
131
|
+
try {
|
|
132
|
+
const sessionMcp = this.createConfiguredMcpServer();
|
|
133
|
+
const transport = new SSEServerTransport(messagesPath, res);
|
|
134
|
+
const sessionId = transport.sessionId;
|
|
135
|
+
this.legacySdkSseSessions.set(sessionId, { server: sessionMcp, transport });
|
|
136
|
+
let closing = false;
|
|
137
|
+
transport.onclose = async () => {
|
|
138
|
+
if (closing) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
closing = true;
|
|
142
|
+
this.legacySdkSseSessions.delete(sessionId);
|
|
143
|
+
try {
|
|
144
|
+
await sessionMcp.close();
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// ignore
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
transport.onerror = (err) => {
|
|
151
|
+
this.logger.error('Legacy SDK SSE transport error', {
|
|
152
|
+
error: err instanceof Error ? err.message : String(err),
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
await sessionMcp.connect(transport);
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
159
|
+
this.logger.error('Failed to start legacy SDK SSE session', { error: message });
|
|
160
|
+
if (!res.headersSent) {
|
|
161
|
+
res.status(500).end('Failed to establish SSE connection');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
97
165
|
attachLegacySdkSseRoutes(app) {
|
|
98
166
|
if (this._legacySseRoutesAttached) {
|
|
99
167
|
return;
|
|
@@ -101,44 +169,8 @@ export class NitroStackServer {
|
|
|
101
169
|
this._legacySseRoutesAttached = true;
|
|
102
170
|
const LEGACY_SSE_PATH = '/sse';
|
|
103
171
|
const LEGACY_MESSAGES_PATH = '/mcp/messages';
|
|
104
|
-
app.get(LEGACY_SSE_PATH, async (
|
|
105
|
-
|
|
106
|
-
const sessionMcp = new McpServer({
|
|
107
|
-
name: this.config.name,
|
|
108
|
-
version: this.config.version,
|
|
109
|
-
}, NitroStackServer.mcpServerOptions);
|
|
110
|
-
this.setupHandlersOn(sessionMcp);
|
|
111
|
-
const transport = new SSEServerTransport(LEGACY_MESSAGES_PATH, res);
|
|
112
|
-
const sessionId = transport.sessionId;
|
|
113
|
-
this.legacySdkSseSessions.set(sessionId, { server: sessionMcp, transport });
|
|
114
|
-
let closing = false;
|
|
115
|
-
transport.onclose = async () => {
|
|
116
|
-
if (closing) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
closing = true;
|
|
120
|
-
this.legacySdkSseSessions.delete(sessionId);
|
|
121
|
-
try {
|
|
122
|
-
await sessionMcp.close();
|
|
123
|
-
}
|
|
124
|
-
catch {
|
|
125
|
-
// ignore
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
transport.onerror = (err) => {
|
|
129
|
-
this.logger.error('Legacy SDK SSE transport error', {
|
|
130
|
-
error: err instanceof Error ? err.message : String(err),
|
|
131
|
-
});
|
|
132
|
-
};
|
|
133
|
-
await sessionMcp.connect(transport);
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
137
|
-
this.logger.error('Failed to start legacy SDK SSE session', { error: message });
|
|
138
|
-
if (!res.headersSent) {
|
|
139
|
-
res.status(500).end('Failed to establish SSE connection');
|
|
140
|
-
}
|
|
141
|
-
}
|
|
172
|
+
app.get(LEGACY_SSE_PATH, async (_req, res) => {
|
|
173
|
+
await this.startLegacySdkSseSession(res, LEGACY_MESSAGES_PATH);
|
|
142
174
|
});
|
|
143
175
|
app.post(LEGACY_MESSAGES_PATH, async (req, res) => {
|
|
144
176
|
const sessionId = typeof req.query.sessionId === 'string' ? req.query.sessionId : undefined;
|
|
@@ -168,6 +200,12 @@ export class NitroStackServer {
|
|
|
168
200
|
if (typeof transport.getApp === 'function') {
|
|
169
201
|
this.attachLegacySdkSseRoutes(transport.getApp());
|
|
170
202
|
}
|
|
203
|
+
// Cursor opens GET /mcp without mcp-session-id; fall back to legacy SSE on that path.
|
|
204
|
+
if (typeof transport.setLegacySseHandler === 'function') {
|
|
205
|
+
transport.setLegacySseHandler(async (_req, res) => {
|
|
206
|
+
await this.startLegacySdkSseSession(res, '/mcp/messages');
|
|
207
|
+
});
|
|
208
|
+
}
|
|
171
209
|
}
|
|
172
210
|
/**
|
|
173
211
|
* Add a tool to the server
|
|
@@ -702,7 +740,7 @@ export class NitroStackServer {
|
|
|
702
740
|
text: JSON.stringify(content, null, 2),
|
|
703
741
|
};
|
|
704
742
|
}
|
|
705
|
-
const contentsMeta = buildResourceReadContentsMeta(resource.getWidgetReadMeta());
|
|
743
|
+
const contentsMeta = buildResourceReadContentsMeta(resource.getWidgetReadMeta?.());
|
|
706
744
|
if (contentsMeta) {
|
|
707
745
|
responseContent._meta = contentsMeta;
|
|
708
746
|
}
|
|
@@ -810,8 +848,8 @@ export class NitroStackServer {
|
|
|
810
848
|
// On Windows, NODE_ENV might not be passed correctly, so we're more lenient
|
|
811
849
|
const nodeEnv = process.env.NODE_ENV?.toLowerCase();
|
|
812
850
|
const isDevelopment = nodeEnv === 'development' || nodeEnv === 'dev' || !nodeEnv;
|
|
813
|
-
// Use explicit transport if set,
|
|
814
|
-
const transportType = explicitTransport ||
|
|
851
|
+
// Use explicit transport if set, otherwise infer from NODE_ENV
|
|
852
|
+
const transportType = explicitTransport || (isDevelopment ? 'stdio' : 'dual');
|
|
815
853
|
this._transportType = transportType;
|
|
816
854
|
this.logger.debug(`NitroStackServer.start(): NODE_ENV=${process.env.NODE_ENV}, MCP_TRANSPORT_TYPE=${explicitTransport}, transportType=${transportType}`);
|
|
817
855
|
// Call onModuleInit for all modules
|
|
@@ -821,20 +859,24 @@ export class NitroStackServer {
|
|
|
821
859
|
await moduleInstance.onModuleInit();
|
|
822
860
|
}
|
|
823
861
|
}
|
|
824
|
-
// If HTTP transport is needed (dual mode), set it up BEFORE calling module.start()
|
|
825
|
-
// This allows modules like OAuthModule to register endpoints on the HTTP server
|
|
826
|
-
if (transportType === 'dual') {
|
|
862
|
+
// If HTTP transport is needed (dual or http mode), set it up BEFORE calling module.start()
|
|
863
|
+
// This allows modules like OAuthModule to register endpoints/middleware on the HTTP server
|
|
864
|
+
if (transportType === 'dual' || transportType === 'http') {
|
|
827
865
|
const port = parseInt(process.env.PORT || '3000');
|
|
828
866
|
const host = process.env.HOST || 'localhost';
|
|
829
|
-
// Create
|
|
867
|
+
// Create HTTP transport first (do not start listening yet)
|
|
830
868
|
const { StreamableHttpTransport } = await import('./transports/streamable-http.js');
|
|
831
869
|
const httpTransport = new StreamableHttpTransport({
|
|
832
870
|
port: port,
|
|
833
871
|
host: host,
|
|
834
872
|
endpoint: '/mcp',
|
|
835
|
-
enableSessions:
|
|
873
|
+
enableSessions: transportType === 'http', // Sessions ONLY in pure http mode
|
|
836
874
|
enableCors: process.env.ENABLE_CORS !== 'false',
|
|
875
|
+
...getStreamableHttpEnvOptions(),
|
|
837
876
|
});
|
|
877
|
+
// Delegate /mcp protocol handling to the official SDK transport: each
|
|
878
|
+
// session gets its own configured MCP server built via this factory.
|
|
879
|
+
httpTransport.setMcpServerFactory(() => this.createConfiguredMcpServer());
|
|
838
880
|
// Set up tools callback and server config for documentation page
|
|
839
881
|
httpTransport.setToolsCallback(async () => {
|
|
840
882
|
const tools = await Promise.all(Array.from(this.tools.values()).map((tool) => tool.toMcpTool()));
|
|
@@ -845,13 +887,11 @@ export class NitroStackServer {
|
|
|
845
887
|
version: this.config.version,
|
|
846
888
|
description: this.config.description,
|
|
847
889
|
});
|
|
848
|
-
this.attachLegacySdkSseIfNeeded(httpTransport);
|
|
849
|
-
await httpTransport.start();
|
|
850
890
|
// Store HTTP transport reference BEFORE modules start
|
|
851
|
-
// This allows OAuthModule to register discovery endpoints
|
|
891
|
+
// This allows OAuthModule to register discovery endpoints and middleware
|
|
852
892
|
this._httpTransport = httpTransport;
|
|
853
893
|
}
|
|
854
|
-
// Call start for all modules (e.g., OAuthModule to register discovery endpoints)
|
|
894
|
+
// Call start for all modules (e.g., OAuthModule to register discovery endpoints and middleware)
|
|
855
895
|
// Now _httpTransport is available for OAuthModule to use
|
|
856
896
|
for (const moduleClass of this.modules) {
|
|
857
897
|
const moduleInstance = DIContainer.getInstance().resolve(moduleClass);
|
|
@@ -859,7 +899,12 @@ export class NitroStackServer {
|
|
|
859
899
|
await moduleInstance.start();
|
|
860
900
|
}
|
|
861
901
|
}
|
|
862
|
-
//
|
|
902
|
+
// Start HTTP listener and attach legacy SSE routes ONLY AFTER dynamic modules have finished starting
|
|
903
|
+
if ((transportType === 'dual' || transportType === 'http') && this._httpTransport) {
|
|
904
|
+
this.attachLegacySdkSseIfNeeded(this._httpTransport);
|
|
905
|
+
await this._httpTransport.start();
|
|
906
|
+
}
|
|
907
|
+
// Now complete the transport setup (connect MCP server) using the determined transportType
|
|
863
908
|
const port = parseInt(process.env.PORT || '3000');
|
|
864
909
|
const host = process.env.HOST || 'localhost';
|
|
865
910
|
await this.startWithTransport(transportType, {
|
|
@@ -888,57 +933,27 @@ export class NitroStackServer {
|
|
|
888
933
|
// DUAL transport: STDIO + HTTP SSE
|
|
889
934
|
// STDIO: For direct MCP connections (dev tools, Claude Desktop)
|
|
890
935
|
// HTTP SSE: For web-based clients and multiple concurrent connections
|
|
891
|
-
// 1.
|
|
936
|
+
// 1. Ensure the HTTP host is running (normally created by start()).
|
|
937
|
+
// The host owns /mcp and delegates protocol handling to the official SDK
|
|
938
|
+
// Streamable HTTP transport via the MCP server factory (one server per
|
|
939
|
+
// session), so no manual message forwarding is required here.
|
|
892
940
|
let httpTransport = this._httpTransport;
|
|
893
941
|
if (!httpTransport) {
|
|
894
942
|
const { StreamableHttpTransport } = await import('./transports/streamable-http.js');
|
|
895
|
-
|
|
943
|
+
const transport = new StreamableHttpTransport({
|
|
896
944
|
port: transportOptions?.port || 3000,
|
|
897
945
|
host: transportOptions?.host || 'localhost',
|
|
898
946
|
endpoint: transportOptions?.endpoint || '/mcp',
|
|
899
|
-
enableSessions: false, // Disable sessions for simpler backward compat
|
|
900
947
|
enableCors: transportOptions?.enableCors !== false, // Enable CORS by default for web clients
|
|
948
|
+
...getStreamableHttpEnvOptions(),
|
|
901
949
|
});
|
|
902
|
-
this.
|
|
903
|
-
|
|
950
|
+
transport.setMcpServerFactory(() => this.createConfiguredMcpServer());
|
|
951
|
+
this.attachLegacySdkSseIfNeeded(transport);
|
|
952
|
+
await transport.start();
|
|
953
|
+
httpTransport = transport;
|
|
904
954
|
this._httpTransport = httpTransport;
|
|
905
955
|
}
|
|
906
|
-
//
|
|
907
|
-
// Since we can't connect to two transports, manually forward HTTP messages
|
|
908
|
-
const transport = httpTransport;
|
|
909
|
-
transport.onmessage = async (message) => {
|
|
910
|
-
// Handle the message through the MCP server's internal handler
|
|
911
|
-
try {
|
|
912
|
-
// Access internal handlers - this is necessary for dual mode
|
|
913
|
-
const mcpServerInternal = this.mcpServer;
|
|
914
|
-
const handlers = mcpServerInternal._requestHandlers;
|
|
915
|
-
if (handlers && message && message.method && message.id !== undefined) {
|
|
916
|
-
const handler = handlers.get(message.method);
|
|
917
|
-
if (handler) {
|
|
918
|
-
const result = await handler(message);
|
|
919
|
-
// Send response back through HTTP transport
|
|
920
|
-
await transport.send({
|
|
921
|
-
jsonrpc: '2.0',
|
|
922
|
-
id: message.id,
|
|
923
|
-
result,
|
|
924
|
-
});
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
catch (error) {
|
|
929
|
-
const err = error;
|
|
930
|
-
// Send error response
|
|
931
|
-
await transport.send({
|
|
932
|
-
jsonrpc: '2.0',
|
|
933
|
-
id: message.id ?? null,
|
|
934
|
-
error: {
|
|
935
|
-
code: -32603,
|
|
936
|
-
message: err.message || 'Internal error',
|
|
937
|
-
},
|
|
938
|
-
});
|
|
939
|
-
}
|
|
940
|
-
};
|
|
941
|
-
// 2. Connect MCP server via STDIO for direct connections
|
|
956
|
+
// 2. Connect the primary MCP server via STDIO for direct connections.
|
|
942
957
|
const stdioTransport = new StdioServerTransport();
|
|
943
958
|
await this.mcpServer.connect(stdioTransport);
|
|
944
959
|
this.logger.info(`${this.config.name} started successfully (DUAL MODE)`);
|
|
@@ -957,9 +972,11 @@ export class NitroStackServer {
|
|
|
957
972
|
port: transportOptions?.port || 3000,
|
|
958
973
|
host: transportOptions?.host || 'localhost',
|
|
959
974
|
endpoint: transportOptions?.endpoint || '/mcp',
|
|
960
|
-
enableSessions: true,
|
|
961
975
|
enableCors: transportOptions?.enableCors || false,
|
|
976
|
+
...getStreamableHttpEnvOptions(),
|
|
962
977
|
});
|
|
978
|
+
// Delegate /mcp protocol handling to the official SDK transport.
|
|
979
|
+
transport.setMcpServerFactory(() => this.createConfiguredMcpServer());
|
|
963
980
|
// Set up tools callback and server config for documentation page
|
|
964
981
|
transport.setToolsCallback(async () => {
|
|
965
982
|
const tools = await Promise.all(Array.from(this.tools.values()).map((tool) => tool.toMcpTool()));
|
|
@@ -971,13 +988,13 @@ export class NitroStackServer {
|
|
|
971
988
|
description: this.config.description,
|
|
972
989
|
});
|
|
973
990
|
this.attachLegacySdkSseIfNeeded(transport);
|
|
974
|
-
// Start HTTP server
|
|
991
|
+
// Start HTTP server
|
|
975
992
|
await transport.start();
|
|
976
993
|
httpTransport = transport;
|
|
977
994
|
this._httpTransport = httpTransport;
|
|
978
995
|
}
|
|
979
|
-
//
|
|
980
|
-
|
|
996
|
+
// The HTTP host owns /mcp and manages its own per-session MCP servers via
|
|
997
|
+
// the factory; no direct mcpServer.connect() is needed here.
|
|
981
998
|
this.logger.info(`${this.config.name} started successfully (HTTP SSE transport)`);
|
|
982
999
|
this.logger.info(`✨ Mode: ${getAppMode().toUpperCase()} (via NITROSTACK_APP_MODE)`);
|
|
983
1000
|
this.logger.info(`🌐 Streamable HTTP: http://${transportOptions?.host || 'localhost'}:${transportOptions?.port || 3000}${transportOptions?.endpoint || '/mcp'}`);
|