tracelattice 1.3.2 → 1.3.3
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/README.md +25 -25
- package/dist/ServerConfig.d.ts +2 -23
- package/dist/ServerConfig.d.ts.map +1 -1
- package/dist/ServerConfig.js.map +1 -1
- package/dist/__tests__/eval/fixtures/scenarios.d.ts.map +1 -1
- package/dist/__tests__/helpers/factories.d.ts +13 -1
- package/dist/__tests__/helpers/factories.d.ts.map +1 -1
- package/dist/cache/DiscoveryCache.d.ts +1 -1
- package/dist/cache/DiscoveryCache.d.ts.map +1 -1
- package/dist/cache/DiscoveryCache.js.map +1 -1
- package/dist/cli.js +3483 -8
- package/dist/config/ConfigLoader.d.ts +2 -2
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +6 -4
- package/dist/config/ConfigLoader.js.map +1 -1
- package/dist/contracts/PersistenceBackend.d.ts.map +1 -0
- package/dist/contracts/features.d.ts +39 -0
- package/dist/contracts/features.d.ts.map +1 -0
- package/dist/contracts/features.js +15 -0
- package/dist/contracts/features.js.map +1 -0
- package/dist/contracts/ids.d.ts +58 -0
- package/dist/contracts/ids.d.ts.map +1 -0
- package/dist/contracts/ids.js +31 -0
- package/dist/contracts/ids.js.map +1 -0
- package/dist/contracts/interfaces.d.ts +6 -3
- package/dist/contracts/interfaces.d.ts.map +1 -1
- package/dist/contracts/strategy.d.ts +2 -2
- package/dist/contracts/strategy.d.ts.map +1 -1
- package/dist/contracts/suspension.d.ts +3 -2
- package/dist/contracts/suspension.d.ts.map +1 -1
- package/dist/contracts/transport.d.ts +25 -0
- package/dist/contracts/transport.d.ts.map +1 -0
- package/dist/core/HistoryManager.d.ts +2 -3
- package/dist/core/HistoryManager.d.ts.map +1 -1
- package/dist/core/HistoryManager.js.map +1 -1
- package/dist/core/IHistoryManager.d.ts +10 -0
- package/dist/core/IHistoryManager.d.ts.map +1 -1
- package/dist/core/IThoughtFormatter.d.ts +51 -0
- package/dist/core/IThoughtFormatter.d.ts.map +1 -0
- package/dist/core/IThoughtFormatter.js +1 -0
- package/dist/core/InputNormalizer.d.ts.map +1 -1
- package/dist/core/InputNormalizer.js +4 -3
- package/dist/core/InputNormalizer.js.map +1 -1
- package/dist/core/PersistenceBuffer.d.ts +1 -1
- package/dist/core/PersistenceBuffer.d.ts.map +1 -1
- package/dist/core/PersistenceBuffer.js.map +1 -1
- package/dist/core/ThoughtFormatter.d.ts +2 -1
- package/dist/core/ThoughtFormatter.d.ts.map +1 -1
- package/dist/core/ThoughtFormatter.js +3 -0
- package/dist/core/ThoughtFormatter.js.map +1 -1
- package/dist/core/ThoughtProcessor.d.ts +2 -2
- package/dist/core/ThoughtProcessor.d.ts.map +1 -1
- package/dist/core/ThoughtProcessor.js +8 -3
- package/dist/core/ThoughtProcessor.js.map +1 -1
- package/dist/core/compression/CompressionService.js +3 -3
- package/dist/core/compression/CompressionService.js.map +1 -1
- package/dist/core/compression/Summary.d.ts +4 -3
- package/dist/core/compression/Summary.d.ts.map +1 -1
- package/dist/core/graph/Edge.d.ts +11 -4
- package/dist/core/graph/Edge.d.ts.map +1 -1
- package/dist/core/graph/EdgeEmitter.js +5 -5
- package/dist/core/graph/EdgeEmitter.js.map +1 -1
- package/dist/core/reasoning/strategies/StrategyFactory.d.ts +1 -1
- package/dist/core/reasoning/strategies/StrategyFactory.d.ts.map +1 -1
- package/dist/core/reasoning/strategies/StrategyFactory.js.map +1 -1
- package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.d.ts.map +1 -1
- package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.js +5 -0
- package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.js.map +1 -1
- package/dist/core/reasoning.d.ts +8 -1
- package/dist/core/reasoning.d.ts.map +1 -1
- package/dist/core/step.d.ts +5 -0
- package/dist/core/step.d.ts.map +1 -1
- package/dist/core/thought.d.ts +4 -3
- package/dist/core/thought.d.ts.map +1 -1
- package/dist/core/tools/InMemorySuspensionStore.d.ts +3 -1
- package/dist/core/tools/InMemorySuspensionStore.d.ts.map +1 -1
- package/dist/core/tools/InMemorySuspensionStore.js +2 -2
- package/dist/core/tools/InMemorySuspensionStore.js.map +1 -1
- package/dist/di/Container.d.ts +6 -3
- package/dist/di/Container.d.ts.map +1 -1
- package/dist/di/Container.js.map +1 -1
- package/dist/di/ServiceRegistry.d.ts +3 -3
- package/dist/di/ServiceRegistry.d.ts.map +1 -1
- package/dist/errors.d.ts +36 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +49 -22
- package/dist/errors.js.map +1 -1
- package/dist/health/HealthChecker.d.ts +1 -1
- package/dist/health/HealthChecker.d.ts.map +1 -1
- package/dist/health/HealthChecker.js.map +1 -1
- package/dist/lib.d.ts +60 -2
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js.map +1 -1
- package/dist/persistence/FilePersistence.d.ts +2 -2
- package/dist/persistence/FilePersistence.d.ts.map +1 -1
- package/dist/persistence/FilePersistence.js.map +1 -1
- package/dist/persistence/MemoryPersistence.d.ts +1 -1
- package/dist/persistence/MemoryPersistence.d.ts.map +1 -1
- package/dist/persistence/MemoryPersistence.js.map +1 -1
- package/dist/persistence/PersistenceFactory.d.ts +1 -1
- package/dist/persistence/PersistenceFactory.d.ts.map +1 -1
- package/dist/persistence/PersistenceFactory.js.map +1 -1
- package/dist/persistence/SqlitePersistence.d.ts +1 -1
- package/dist/persistence/SqlitePersistence.d.ts.map +1 -1
- package/dist/persistence/SqlitePersistence.js.map +1 -1
- package/dist/pool/ConnectionPool.d.ts +11 -13
- package/dist/pool/ConnectionPool.d.ts.map +1 -1
- package/dist/pool/ConnectionPool.js.map +1 -1
- package/dist/pool/IConnectionPool.d.ts +100 -0
- package/dist/pool/IConnectionPool.d.ts.map +1 -0
- package/dist/pool/IConnectionPool.js +1 -0
- package/dist/registry/BaseRegistry.d.ts +1 -1
- package/dist/registry/BaseRegistry.d.ts.map +1 -1
- package/dist/registry/BaseRegistry.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js.map +1 -1
- package/dist/transport/BaseTransport.d.ts +3 -2
- package/dist/transport/BaseTransport.d.ts.map +1 -1
- package/dist/transport/BaseTransport.js +1 -1
- package/dist/transport/BaseTransport.js.map +1 -1
- package/dist/transport/HttpTransport.d.ts +4 -2
- package/dist/transport/HttpTransport.d.ts.map +1 -1
- package/dist/transport/HttpTransport.js +4 -1
- package/dist/transport/HttpTransport.js.map +1 -1
- package/dist/transport/SseTransport.d.ts +4 -2
- package/dist/transport/SseTransport.d.ts.map +1 -1
- package/dist/transport/SseTransport.js +3 -0
- package/dist/transport/SseTransport.js.map +1 -1
- package/dist/transport/StreamableHttpTransport.d.ts +4 -2
- package/dist/transport/StreamableHttpTransport.d.ts.map +1 -1
- package/dist/transport/StreamableHttpTransport.js +4 -1
- package/dist/transport/StreamableHttpTransport.js.map +1 -1
- package/dist/types/skill.d.ts +5 -0
- package/dist/types/skill.d.ts.map +1 -1
- package/dist/types/tool.d.ts +5 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/package.json +11 -11
- package/dist/__tests__/helpers/index.d.ts +0 -3
- package/dist/__tests__/helpers/index.d.ts.map +0 -1
- package/dist/contracts/index.d.ts +0 -14
- package/dist/contracts/index.d.ts.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -1
- package/dist/persistence/PersistenceBackend.d.ts.map +0 -1
- /package/dist/{persistence → contracts}/PersistenceBackend.d.ts +0 -0
- /package/dist/{persistence → contracts}/PersistenceBackend.js +0 -0
- /package/dist/contracts/{index.js → transport.js} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,1054 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import*as
|
|
2
|
+
import * as __rspack_external__lib_js_262c9725 from "./lib.js";
|
|
3
|
+
import * as __rspack_external_node_crypto_9ba42079 from "node:crypto";
|
|
4
|
+
import * as __rspack_external_node_http_2dc67212 from "node:http";
|
|
5
|
+
import * as __rspack_external_node_url_e96de089 from "node:url";
|
|
6
|
+
import * as __rspack_external_valibot from "valibot";
|
|
7
|
+
import { ValibotJsonSchemaAdapter } from "@tmcp/adapter-valibot";
|
|
8
|
+
import { StdioTransport } from "@tmcp/transport-stdio";
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
import { McpServer } from "tmcp";
|
|
12
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
|
+
var __webpack_modules__ = ({
|
|
14
|
+
937(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
15
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
16
|
+
Ag: () => (SessionNotFoundError),
|
|
17
|
+
m_: () => (PoolTerminatedError),
|
|
18
|
+
qA: () => (SessionNotActiveError),
|
|
19
|
+
u1: () => (getErrorMessage),
|
|
20
|
+
yM: () => (MaxSessionsReachedError)
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Custom error types for the TraceLattice server.
|
|
24
|
+
*
|
|
25
|
+
* This module defines a hierarchy of error classes for handling various
|
|
26
|
+
* error conditions that can occur in the sequential thinking server.
|
|
27
|
+
* All errors extend the base `SequentialThinkingError` class with
|
|
28
|
+
* specific error codes for programmatic handling.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* import { ToolNotFoundError, SkillDiscoveryError } from './errors.js';
|
|
33
|
+
*
|
|
34
|
+
* // Throw a tool not found error
|
|
35
|
+
* throw new ToolNotFoundError('my-tool');
|
|
36
|
+
*
|
|
37
|
+
* // Catch and handle specific errors
|
|
38
|
+
* try {
|
|
39
|
+
* await discoverSkills(dir);
|
|
40
|
+
* } catch (error) {
|
|
41
|
+
* if (error instanceof SkillDiscoveryError) {
|
|
42
|
+
* console.error(`Failed to discover skills: ${error.message}`);
|
|
43
|
+
* console.error(`Error code: ${error.code}`);
|
|
44
|
+
* }
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
* @module errors
|
|
48
|
+
*/ /**
|
|
49
|
+
* All known error codes as a const object for exhaustive switching.
|
|
50
|
+
*/ const ERROR_CODES = {
|
|
51
|
+
CONFIGURATION_ERROR: 'CONFIGURATION_ERROR',
|
|
52
|
+
TOOL_NOT_FOUND: 'TOOL_NOT_FOUND',
|
|
53
|
+
SKILL_NOT_FOUND: 'SKILL_NOT_FOUND',
|
|
54
|
+
INVALID_THOUGHT: 'INVALID_THOUGHT',
|
|
55
|
+
SKILL_DISCOVERY_FAILED: 'SKILL_DISCOVERY_FAILED',
|
|
56
|
+
HISTORY_LIMIT_EXCEEDED: 'HISTORY_LIMIT_EXCEEDED',
|
|
57
|
+
DUPLICATE_SKILL: 'DUPLICATE_SKILL',
|
|
58
|
+
INVALID_SKILL: 'INVALID_SKILL',
|
|
59
|
+
DUPLICATE_TOOL: 'DUPLICATE_TOOL',
|
|
60
|
+
INVALID_TOOL: 'INVALID_TOOL',
|
|
61
|
+
SESSION_NOT_ACTIVE: 'SESSION_NOT_ACTIVE',
|
|
62
|
+
SESSION_NOT_FOUND: 'SESSION_NOT_FOUND',
|
|
63
|
+
MAX_SESSIONS_REACHED: 'MAX_SESSIONS_REACHED',
|
|
64
|
+
POOL_TERMINATED: 'POOL_TERMINATED',
|
|
65
|
+
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
66
|
+
INVALID_EDGE: 'INVALID_EDGE',
|
|
67
|
+
CYCLE_DETECTED: 'CYCLE_DETECTED',
|
|
68
|
+
SUSPENSION_NOT_FOUND: 'SUSPENSION_NOT_FOUND',
|
|
69
|
+
SUSPENSION_EXPIRED: 'SUSPENSION_EXPIRED',
|
|
70
|
+
INVALID_TOOL_CALL: 'INVALID_TOOL_CALL',
|
|
71
|
+
INVALID_BACKTRACK: 'INVALID_BACKTRACK',
|
|
72
|
+
DUPLICATE_SUMMARY: 'DUPLICATE_SUMMARY'
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Base error class for all Sequential Thinking server errors.
|
|
76
|
+
*
|
|
77
|
+
* This error extends the native `Error` class and adds a `code` property
|
|
78
|
+
* for programmatic error identification and handling. All specific error
|
|
79
|
+
* types in the system extend this base class.
|
|
80
|
+
*
|
|
81
|
+
* @remarks
|
|
82
|
+
* **Error Codes:**
|
|
83
|
+
* - `TOOL_NOT_FOUND` - A requested tool was not found
|
|
84
|
+
* - `SKILL_NOT_FOUND` - A requested skill was not found
|
|
85
|
+
* - `INVALID_THOUGHT` - Thought validation failed
|
|
86
|
+
* - `SKILL_DISCOVERY_FAILED` - Skill discovery operation failed
|
|
87
|
+
* - `HISTORY_LIMIT_EXCEEDED` - History size limit was exceeded
|
|
88
|
+
* - `INVALID_EDGE` - An invalid edge operation was attempted
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* // Throw a custom sequential thinking error
|
|
93
|
+
* throw new SequentialThinkingError('Custom error message', 'CUSTOM_CODE');
|
|
94
|
+
*
|
|
95
|
+
* // Check if an error is a SequentialThinkingError
|
|
96
|
+
* if (error instanceof SequentialThinkingError) {
|
|
97
|
+
* console.error(`Error [${error.code}]: ${error.message}`);
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
100
|
+
*/ class SequentialThinkingError extends Error {
|
|
101
|
+
/** The error code for programmatic identification. */ code;
|
|
102
|
+
/**
|
|
103
|
+
* Creates a new SequentialThinkingError.
|
|
104
|
+
*
|
|
105
|
+
* @param message - Human-readable error message
|
|
106
|
+
* @param code - Error code for programmatic handling
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* const error = new SequentialThinkingError(
|
|
111
|
+
* 'Something went wrong',
|
|
112
|
+
* 'CUSTOM_ERROR'
|
|
113
|
+
* );
|
|
114
|
+
* console.log(error.code); // 'CUSTOM_ERROR'
|
|
115
|
+
* ```
|
|
116
|
+
*/ constructor(message, code){
|
|
117
|
+
super(message);
|
|
118
|
+
this.code = code;
|
|
119
|
+
this.name = 'SequentialThinkingError';
|
|
120
|
+
Error.captureStackTrace(this, this.constructor);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
class ConfigurationError extends SequentialThinkingError {
|
|
124
|
+
constructor(message){
|
|
125
|
+
super(message, ERROR_CODES.CONFIGURATION_ERROR);
|
|
126
|
+
this.name = 'ConfigurationError';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Error thrown when a requested tool is not found in the registry.
|
|
131
|
+
*
|
|
132
|
+
* This error is thrown when attempting to retrieve, update, or delete
|
|
133
|
+
* a tool that doesn't exist in the tool registry.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* const tool = registry.getTool('non-existent-tool');
|
|
138
|
+
* if (!tool) {
|
|
139
|
+
* throw new ToolNotFoundError('non-existent-tool');
|
|
140
|
+
* }
|
|
141
|
+
* ```
|
|
142
|
+
*/ class ToolNotFoundError extends SequentialThinkingError {
|
|
143
|
+
/**
|
|
144
|
+
* Creates a new ToolNotFoundError.
|
|
145
|
+
*
|
|
146
|
+
* @param toolName - The name of the tool that was not found
|
|
147
|
+
* @param action - Optional action being performed (e.g., 'remove', 'update')
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* throw new ToolNotFoundError('my-custom-tool');
|
|
152
|
+
* // Error: tool 'my-custom-tool' not found
|
|
153
|
+
*
|
|
154
|
+
* throw new ToolNotFoundError('my-custom-tool', 'remove');
|
|
155
|
+
* // Error: tool 'my-custom-tool' not found, cannot remove
|
|
156
|
+
* // Code: TOOL_NOT_FOUND
|
|
157
|
+
* ```
|
|
158
|
+
*/ constructor(toolName, action){
|
|
159
|
+
const message = action ? `Tool '${toolName}' not found, cannot ${action}` : `Tool '${toolName}' not found`;
|
|
160
|
+
super(message, ERROR_CODES.TOOL_NOT_FOUND);
|
|
161
|
+
this.name = 'ToolNotFoundError';
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Error thrown when a requested skill is not found in the registry.
|
|
166
|
+
*
|
|
167
|
+
* This error is thrown when attempting to retrieve, update, or delete
|
|
168
|
+
* a skill that doesn't exist in the skill registry.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```typescript
|
|
172
|
+
* const skill = registry.getSkill('non-existent-skill');
|
|
173
|
+
* if (!skill) {
|
|
174
|
+
* throw new SkillNotFoundError('non-existent-skill');
|
|
175
|
+
* }
|
|
176
|
+
* ```
|
|
177
|
+
*/ class SkillNotFoundError extends SequentialThinkingError {
|
|
178
|
+
/**
|
|
179
|
+
* Creates a new SkillNotFoundError.
|
|
180
|
+
*
|
|
181
|
+
* @param skillName - The name of the skill that was not found
|
|
182
|
+
* @param action - Optional action being performed (e.g., 'remove', 'update')
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```typescript
|
|
186
|
+
* throw new SkillNotFoundError('my-custom-skill');
|
|
187
|
+
* // Error: skill 'my-custom-skill' not found
|
|
188
|
+
*
|
|
189
|
+
* throw new SkillNotFoundError('my-custom-skill', 'remove');
|
|
190
|
+
* // Error: skill 'my-custom-skill' not found, cannot remove
|
|
191
|
+
* // Code: SKILL_NOT_FOUND
|
|
192
|
+
* ```
|
|
193
|
+
*/ constructor(skillName, action){
|
|
194
|
+
const message = action ? `Skill '${skillName}' not found, cannot ${action}` : `Skill '${skillName}' not found`;
|
|
195
|
+
super(message, ERROR_CODES.SKILL_NOT_FOUND);
|
|
196
|
+
this.name = 'SkillNotFoundError';
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Error thrown when thought validation fails.
|
|
201
|
+
*
|
|
202
|
+
* This error is thrown when a thought fails validation, typically due to
|
|
203
|
+
* invalid values, missing required fields, or constraint violations.
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```typescript
|
|
207
|
+
* // Validate thought number
|
|
208
|
+
* if (thought.thought_number < 1) {
|
|
209
|
+
* throw new InvalidThoughtError(thought.thought_number, 'thought_number must be >= 1');
|
|
210
|
+
* }
|
|
211
|
+
* ```
|
|
212
|
+
*/ class InvalidThoughtError extends SequentialThinkingError {
|
|
213
|
+
/**
|
|
214
|
+
* Creates a new InvalidThoughtError.
|
|
215
|
+
*
|
|
216
|
+
* @param thoughtNumber - The thought number that failed validation
|
|
217
|
+
* @param reason - Human-readable explanation of why validation failed
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```typescript
|
|
221
|
+
* throw new InvalidThoughtError(5, 'thought_number exceeds total_thoughts');
|
|
222
|
+
* // Error: Invalid thought 5: thought_number exceeds total_thoughts
|
|
223
|
+
* // Code: INVALID_THOUGHT
|
|
224
|
+
* ```
|
|
225
|
+
*/ constructor(thoughtNumber, reason){
|
|
226
|
+
super(`Invalid thought ${thoughtNumber}: ${reason}`, ERROR_CODES.INVALID_THOUGHT);
|
|
227
|
+
this.name = 'InvalidThoughtError';
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Error thrown when skill discovery fails.
|
|
232
|
+
*
|
|
233
|
+
* This error is thrown when the skill discovery process encounters an issue,
|
|
234
|
+
* such as filesystem errors, invalid skill files, or parsing failures.
|
|
235
|
+
*
|
|
236
|
+
* @remarks
|
|
237
|
+
* The original error that caused the discovery failure is preserved in the
|
|
238
|
+
* `cause` property for debugging purposes.
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```typescript
|
|
242
|
+
* try {
|
|
243
|
+
* await discoverSkills('./skills');
|
|
244
|
+
* } catch (error) {
|
|
245
|
+
* throw new SkillDiscoveryError('./skills', error as Error);
|
|
246
|
+
* }
|
|
247
|
+
* ```
|
|
248
|
+
*/ class SkillDiscoveryError extends SequentialThinkingError {
|
|
249
|
+
/** The underlying error that caused the discovery failure. */ cause;
|
|
250
|
+
/**
|
|
251
|
+
* Creates a new SkillDiscoveryError.
|
|
252
|
+
*
|
|
253
|
+
* @param directory - The directory where discovery failed
|
|
254
|
+
* @param cause - The underlying error that caused the failure
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```typescript
|
|
258
|
+
* try {
|
|
259
|
+
* const skills = await loadSkills('./invalid-directory');
|
|
260
|
+
* } catch (error) {
|
|
261
|
+
* throw new SkillDiscoveryError('./invalid-directory', error as Error);
|
|
262
|
+
* }
|
|
263
|
+
* ```
|
|
264
|
+
*/ constructor(directory, cause){
|
|
265
|
+
super(`Failed to discover skills in ${directory}: ${cause.message}`, ERROR_CODES.SKILL_DISCOVERY_FAILED);
|
|
266
|
+
this.name = 'SkillDiscoveryError';
|
|
267
|
+
this.cause = cause;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Error thrown when history size exceeds the configured limit.
|
|
272
|
+
*
|
|
273
|
+
* This error is thrown when an operation would cause the history size
|
|
274
|
+
* to exceed the maximum configured size limit.
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```typescript
|
|
278
|
+
* if (history.length >= maxSize) {
|
|
279
|
+
* throw new HistoryLimitExceededError(history.length, maxSize);
|
|
280
|
+
* }
|
|
281
|
+
* ```
|
|
282
|
+
*/ class HistoryLimitExceededError extends SequentialThinkingError {
|
|
283
|
+
/**
|
|
284
|
+
* Creates a new HistoryLimitExceededError.
|
|
285
|
+
*
|
|
286
|
+
* @param currentSize - The current history size
|
|
287
|
+
* @param maxSize - The maximum allowed size
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* ```typescript
|
|
291
|
+
* throw new HistoryLimitExceededError(1500, 1000);
|
|
292
|
+
* // Error: History size 1500 exceeds limit 1000
|
|
293
|
+
* // Code: HISTORY_LIMIT_EXCEEDED
|
|
294
|
+
* ```
|
|
295
|
+
*/ constructor(currentSize, maxSize){
|
|
296
|
+
super(`History size ${currentSize} exceeds limit ${maxSize}`, ERROR_CODES.HISTORY_LIMIT_EXCEEDED);
|
|
297
|
+
this.name = 'HistoryLimitExceededError';
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Error thrown when attempting to add a skill that already exists.
|
|
302
|
+
*
|
|
303
|
+
* This error is thrown when trying to register a skill with a name that
|
|
304
|
+
* is already present in the skill registry.
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* ```typescript
|
|
308
|
+
* if (registry.hasSkill(skill.name)) {
|
|
309
|
+
* throw new DuplicateSkillError(skill.name);
|
|
310
|
+
* }
|
|
311
|
+
* ```
|
|
312
|
+
*/ class DuplicateSkillError extends SequentialThinkingError {
|
|
313
|
+
/**
|
|
314
|
+
* Creates a new DuplicateSkillError.
|
|
315
|
+
*
|
|
316
|
+
* @param skillName - The name of the duplicate skill
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* ```typescript
|
|
320
|
+
* throw new DuplicateSkillError('my-skill');
|
|
321
|
+
* // Error: skill 'my-skill' already exists
|
|
322
|
+
* // Code: DUPLICATE_SKILL
|
|
323
|
+
* ```
|
|
324
|
+
*/ constructor(skillName){
|
|
325
|
+
super(`skill '${skillName}' already exists`, ERROR_CODES.DUPLICATE_SKILL);
|
|
326
|
+
this.name = 'DuplicateSkillError';
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Error thrown when a skill has invalid data.
|
|
331
|
+
*
|
|
332
|
+
* This error is thrown when a skill fails validation, typically due to
|
|
333
|
+
* missing required fields or invalid values.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```typescript
|
|
337
|
+
* if (!skill.name) {
|
|
338
|
+
* throw new InvalidSkillError('Skill must have a valid name');
|
|
339
|
+
* }
|
|
340
|
+
* ```
|
|
341
|
+
*/ class InvalidSkillError extends SequentialThinkingError {
|
|
342
|
+
/**
|
|
343
|
+
* Creates a new InvalidSkillError.
|
|
344
|
+
*
|
|
345
|
+
* @param reason - The reason for the validation failure
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* ```typescript
|
|
349
|
+
* throw new InvalidSkillError('Skill must have a valid name');
|
|
350
|
+
* // Error: Invalid skill: Skill must have a valid name
|
|
351
|
+
* // Code: INVALID_SKILL
|
|
352
|
+
* ```
|
|
353
|
+
*/ constructor(reason){
|
|
354
|
+
super(`Invalid skill: ${reason}`, ERROR_CODES.INVALID_SKILL);
|
|
355
|
+
this.name = 'InvalidSkillError';
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Error thrown when attempting to add a tool that already exists.
|
|
360
|
+
*
|
|
361
|
+
* This error is thrown when trying to register a tool with a name that
|
|
362
|
+
* is already present in the tool registry.
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```typescript
|
|
366
|
+
* if (registry.hasTool(tool.name)) {
|
|
367
|
+
* throw new DuplicateToolError(tool.name);
|
|
368
|
+
* }
|
|
369
|
+
* ```
|
|
370
|
+
*/ class DuplicateToolError extends SequentialThinkingError {
|
|
371
|
+
/**
|
|
372
|
+
* Creates a new DuplicateToolError.
|
|
373
|
+
*
|
|
374
|
+
* @param toolName - The name of the duplicate tool
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* ```typescript
|
|
378
|
+
* throw new DuplicateToolError('my-tool');
|
|
379
|
+
* // Error: tool 'my-tool' already exists
|
|
380
|
+
* // Code: DUPLICATE_TOOL
|
|
381
|
+
* ```
|
|
382
|
+
*/ constructor(toolName){
|
|
383
|
+
super(`tool '${toolName}' already exists`, ERROR_CODES.DUPLICATE_TOOL);
|
|
384
|
+
this.name = 'DuplicateToolError';
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Error thrown when a tool has invalid data.
|
|
389
|
+
*
|
|
390
|
+
* This error is thrown when a tool fails validation, typically due to
|
|
391
|
+
* missing required fields or invalid values.
|
|
392
|
+
*
|
|
393
|
+
* @example
|
|
394
|
+
* ```typescript
|
|
395
|
+
* if (!tool.name) {
|
|
396
|
+
* throw new InvalidToolError('Tool must have a valid name');
|
|
397
|
+
* }
|
|
398
|
+
* ```
|
|
399
|
+
*/ class InvalidToolError extends SequentialThinkingError {
|
|
400
|
+
/**
|
|
401
|
+
* Creates a new InvalidToolError.
|
|
402
|
+
*
|
|
403
|
+
* @param reason - The reason for the validation failure
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```typescript
|
|
407
|
+
* throw new InvalidToolError('Tool must have a valid name');
|
|
408
|
+
* // Error: Invalid tool: Tool must have a valid name
|
|
409
|
+
* // Code: INVALID_TOOL
|
|
410
|
+
* ```
|
|
411
|
+
*/ constructor(reason){
|
|
412
|
+
super(`Invalid tool: ${reason}`, ERROR_CODES.INVALID_TOOL);
|
|
413
|
+
this.name = 'InvalidToolError';
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Error thrown when attempting to process a session that is not active.
|
|
418
|
+
*
|
|
419
|
+
* This error is thrown when trying to use a session that has been closed
|
|
420
|
+
* or deactivated.
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* ```typescript
|
|
424
|
+
* if (!session.isActive) {
|
|
425
|
+
* throw new SessionNotActiveError(sessionId);
|
|
426
|
+
* }
|
|
427
|
+
* ```
|
|
428
|
+
*/ class SessionNotActiveError extends SequentialThinkingError {
|
|
429
|
+
/**
|
|
430
|
+
* Creates a new SessionNotActiveError.
|
|
431
|
+
*
|
|
432
|
+
* @param sessionId - The ID of the inactive session
|
|
433
|
+
*
|
|
434
|
+
* @example
|
|
435
|
+
* ```typescript
|
|
436
|
+
* throw new SessionNotActiveError('session-123');
|
|
437
|
+
* // Error: Session 'session-123' is not active
|
|
438
|
+
* // Code: SESSION_NOT_ACTIVE
|
|
439
|
+
* ```
|
|
440
|
+
*/ constructor(sessionId){
|
|
441
|
+
super(`Session '${sessionId}' is not active`, ERROR_CODES.SESSION_NOT_ACTIVE);
|
|
442
|
+
this.name = 'SessionNotActiveError';
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Error thrown when a requested session is not found in the pool.
|
|
447
|
+
*
|
|
448
|
+
* This error is thrown when attempting to retrieve, process, or close
|
|
449
|
+
* a session that doesn't exist in the session pool.
|
|
450
|
+
*
|
|
451
|
+
* @example
|
|
452
|
+
* ```typescript
|
|
453
|
+
* const session = pool.getSession('non-existent-session');
|
|
454
|
+
* if (!session) {
|
|
455
|
+
* throw new SessionNotFoundError('non-existent-session');
|
|
456
|
+
* }
|
|
457
|
+
* ```
|
|
458
|
+
*/ class SessionNotFoundError extends SequentialThinkingError {
|
|
459
|
+
/**
|
|
460
|
+
* Creates a new SessionNotFoundError.
|
|
461
|
+
*
|
|
462
|
+
* @param sessionId - The ID of the session that was not found
|
|
463
|
+
*
|
|
464
|
+
* @example
|
|
465
|
+
* ```typescript
|
|
466
|
+
* throw new SessionNotFoundError('session-123');
|
|
467
|
+
* // Error: Session not found: session-123
|
|
468
|
+
* // Code: SESSION_NOT_FOUND
|
|
469
|
+
* ```
|
|
470
|
+
*/ constructor(sessionId){
|
|
471
|
+
super(`Session not found: ${sessionId}`, ERROR_CODES.SESSION_NOT_FOUND);
|
|
472
|
+
this.name = 'SessionNotFoundError';
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Error thrown when the maximum number of sessions has been reached.
|
|
477
|
+
*
|
|
478
|
+
* This error is thrown when trying to create a new session when the
|
|
479
|
+
* pool has reached its configured maximum session limit.
|
|
480
|
+
*
|
|
481
|
+
* @example
|
|
482
|
+
* ```typescript
|
|
483
|
+
* if (pool.sessionCount >= pool.maxSessions) {
|
|
484
|
+
* throw new MaxSessionsReachedError(pool.maxSessions);
|
|
485
|
+
* }
|
|
486
|
+
* ```
|
|
487
|
+
*/ class MaxSessionsReachedError extends SequentialThinkingError {
|
|
488
|
+
/**
|
|
489
|
+
* Creates a new MaxSessionsReachedError.
|
|
490
|
+
*
|
|
491
|
+
* @param maxSessions - The maximum number of sessions allowed
|
|
492
|
+
*
|
|
493
|
+
* @example
|
|
494
|
+
* ```typescript
|
|
495
|
+
* throw new MaxSessionsReachedError(100);
|
|
496
|
+
* // Error: Max sessions (100) reached. Wait for a session to close or increase maxSessions.
|
|
497
|
+
* // Code: MAX_SESSIONS_REACHED
|
|
498
|
+
* ```
|
|
499
|
+
*/ constructor(maxSessions){
|
|
500
|
+
super(`Max sessions (${maxSessions}) reached. Wait for a session to close or increase maxSessions.`, ERROR_CODES.MAX_SESSIONS_REACHED);
|
|
501
|
+
this.name = 'MaxSessionsReachedError';
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Error thrown when attempting to use a terminated connection pool.
|
|
506
|
+
*
|
|
507
|
+
* This error is thrown when trying to create sessions or process requests
|
|
508
|
+
* after the connection pool has been terminated.
|
|
509
|
+
*
|
|
510
|
+
* @example
|
|
511
|
+
* ```typescript
|
|
512
|
+
* if (pool.isTerminated) {
|
|
513
|
+
* throw new PoolTerminatedError();
|
|
514
|
+
* }
|
|
515
|
+
* ```
|
|
516
|
+
*/ class PoolTerminatedError extends SequentialThinkingError {
|
|
517
|
+
/**
|
|
518
|
+
* Creates a new PoolTerminatedError.
|
|
519
|
+
*
|
|
520
|
+
* @example
|
|
521
|
+
* ```typescript
|
|
522
|
+
* throw new PoolTerminatedError();
|
|
523
|
+
* // Error: ConnectionPool has been terminated
|
|
524
|
+
* // Code: POOL_TERMINATED
|
|
525
|
+
* ```
|
|
526
|
+
*/ constructor(){
|
|
527
|
+
super('ConnectionPool has been terminated', ERROR_CODES.POOL_TERMINATED);
|
|
528
|
+
this.name = 'PoolTerminatedError';
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Error thrown when input validation fails due to invalid or malicious data.
|
|
533
|
+
*
|
|
534
|
+
* This error is thrown when user input fails security or format validation,
|
|
535
|
+
* such as path traversal attempts or invalid identifier formats.
|
|
536
|
+
*
|
|
537
|
+
* @example
|
|
538
|
+
* ```typescript
|
|
539
|
+
* if (!BRANCH_ID_PATTERN.test(branchId)) {
|
|
540
|
+
* throw new ValidationError('branchId', 'Invalid format');
|
|
541
|
+
* }
|
|
542
|
+
* ```
|
|
543
|
+
*/ class ValidationError extends SequentialThinkingError {
|
|
544
|
+
/** The field that failed validation. */ field;
|
|
545
|
+
constructor(field, reason){
|
|
546
|
+
super(`Validation failed for '${field}': ${reason}`, ERROR_CODES.VALIDATION_ERROR);
|
|
547
|
+
this.name = 'ValidationError';
|
|
548
|
+
this.field = field;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Error thrown when an invalid edge operation is attempted.
|
|
553
|
+
*
|
|
554
|
+
* This error is thrown when attempting to add an edge that violates
|
|
555
|
+
* structural invariants of the thought DAG, such as a self-edge
|
|
556
|
+
* (where `from` and `to` reference the same thought).
|
|
557
|
+
*
|
|
558
|
+
* @example
|
|
559
|
+
* ```typescript
|
|
560
|
+
* if (edge.from === edge.to) {
|
|
561
|
+
* throw new InvalidEdgeError(
|
|
562
|
+
* `Self-edge not allowed: from and to are the same (${edge.from})`
|
|
563
|
+
* );
|
|
564
|
+
* }
|
|
565
|
+
* ```
|
|
566
|
+
*/ class InvalidEdgeError extends SequentialThinkingError {
|
|
567
|
+
/**
|
|
568
|
+
* Creates a new InvalidEdgeError.
|
|
569
|
+
*
|
|
570
|
+
* @param message - Human-readable explanation of the invalid edge
|
|
571
|
+
*
|
|
572
|
+
* @example
|
|
573
|
+
* ```typescript
|
|
574
|
+
* throw new InvalidEdgeError('Self-edge not allowed: from and to are the same (t1)');
|
|
575
|
+
* // Code: INVALID_EDGE
|
|
576
|
+
* ```
|
|
577
|
+
*/ constructor(message){
|
|
578
|
+
super(message, ERROR_CODES.INVALID_EDGE);
|
|
579
|
+
this.name = 'InvalidEdgeError';
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Error thrown when a cycle is detected during graph traversal.
|
|
584
|
+
*
|
|
585
|
+
* This error is thrown by graph algorithms (such as topological sort)
|
|
586
|
+
* when the thought DAG contains a cycle, violating the acyclic invariant.
|
|
587
|
+
*
|
|
588
|
+
* @example
|
|
589
|
+
* ```typescript
|
|
590
|
+
* try {
|
|
591
|
+
* const order = graphView.topological(sessionId);
|
|
592
|
+
* } catch (error) {
|
|
593
|
+
* if (error instanceof CycleDetectedError) {
|
|
594
|
+
* console.error('Cycle in thought graph:', error.message);
|
|
595
|
+
* }
|
|
596
|
+
* }
|
|
597
|
+
* ```
|
|
598
|
+
*/ class CycleDetectedError extends SequentialThinkingError {
|
|
599
|
+
/**
|
|
600
|
+
* Creates a new CycleDetectedError.
|
|
601
|
+
*
|
|
602
|
+
* @param message - Human-readable explanation of the cycle
|
|
603
|
+
*
|
|
604
|
+
* @example
|
|
605
|
+
* ```typescript
|
|
606
|
+
* throw new CycleDetectedError('Cycle detected in session s1');
|
|
607
|
+
* // Code: CYCLE_DETECTED
|
|
608
|
+
* ```
|
|
609
|
+
*/ constructor(message){
|
|
610
|
+
super(message, ERROR_CODES.CYCLE_DETECTED);
|
|
611
|
+
this.name = 'CycleDetectedError';
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Error thrown when a suspension record is not found.
|
|
616
|
+
*
|
|
617
|
+
* This error is thrown when attempting to resume a tool interleave
|
|
618
|
+
* suspension that does not exist in the suspension store.
|
|
619
|
+
*/ class SuspensionNotFoundError extends SequentialThinkingError {
|
|
620
|
+
constructor(message){
|
|
621
|
+
super(message, ERROR_CODES.SUSPENSION_NOT_FOUND);
|
|
622
|
+
this.name = 'SuspensionNotFoundError';
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Error thrown when a suspension record has expired.
|
|
627
|
+
*
|
|
628
|
+
* This error is thrown when attempting to resume a tool interleave
|
|
629
|
+
* suspension whose TTL has elapsed.
|
|
630
|
+
*/ class SuspensionExpiredError extends SequentialThinkingError {
|
|
631
|
+
constructor(message){
|
|
632
|
+
super(message, ERROR_CODES.SUSPENSION_EXPIRED);
|
|
633
|
+
this.name = 'SuspensionExpiredError';
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Error thrown when a tool call payload is invalid.
|
|
638
|
+
*
|
|
639
|
+
* This error is thrown when a tool interleave invocation has malformed
|
|
640
|
+
* arguments, missing identifiers, or otherwise fails validation.
|
|
641
|
+
*/ class InvalidToolCallError extends SequentialThinkingError {
|
|
642
|
+
constructor(message){
|
|
643
|
+
super(message, ERROR_CODES.INVALID_TOOL_CALL);
|
|
644
|
+
this.name = 'InvalidToolCallError';
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Error thrown when a backtrack operation is invalid.
|
|
649
|
+
*
|
|
650
|
+
* This error is thrown when an attempt to backtrack the reasoning
|
|
651
|
+
* chain references an unreachable thought or violates DAG invariants.
|
|
652
|
+
*/ class InvalidBacktrackError extends SequentialThinkingError {
|
|
653
|
+
constructor(message){
|
|
654
|
+
super(message, ERROR_CODES.INVALID_BACKTRACK);
|
|
655
|
+
this.name = 'InvalidBacktrackError';
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Type guard to check if an error has a specific error code.
|
|
660
|
+
*/ function isErrorCode(err, code) {
|
|
661
|
+
return err instanceof SequentialThinkingError && err.code === code;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Extract a human-readable message from an unknown error value.
|
|
665
|
+
*
|
|
666
|
+
* Standardizes the common `error instanceof Error ? error.message : String(error)`
|
|
667
|
+
* pattern used in catch blocks across the codebase.
|
|
668
|
+
*
|
|
669
|
+
* @param error - The unknown error value to extract a message from
|
|
670
|
+
* @returns The error message string
|
|
671
|
+
*
|
|
672
|
+
* @example
|
|
673
|
+
* ```typescript
|
|
674
|
+
* try {
|
|
675
|
+
* await doSomething();
|
|
676
|
+
* } catch (error) {
|
|
677
|
+
* logger.error('Failed', { error: getErrorMessage(error) });
|
|
678
|
+
* }
|
|
679
|
+
* ```
|
|
680
|
+
*/ function getErrorMessage(error) {
|
|
681
|
+
return error instanceof Error ? error.message : String(error);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
},
|
|
686
|
+
789(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
687
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
688
|
+
createConnectionPool: () => (createConnectionPool)
|
|
689
|
+
});
|
|
690
|
+
/* import */ var _errors_js__rspack_import_0 = __webpack_require__(937);
|
|
691
|
+
/**
|
|
692
|
+
* Connection Pool for managing concurrent user sessions.
|
|
693
|
+
*
|
|
694
|
+
* This module provides session management for multi-user scenarios,
|
|
695
|
+
* allowing multiple concurrent clients to each have isolated state.
|
|
696
|
+
*
|
|
697
|
+
* @example
|
|
698
|
+
* ```typescript
|
|
699
|
+
* const pool = new ConnectionPool({
|
|
700
|
+
* maxSessions: 100,
|
|
701
|
+
* sessionTimeout: 300000 // 5 minutes
|
|
702
|
+
* });
|
|
703
|
+
*
|
|
704
|
+
* const sessionId = await pool.createSession();
|
|
705
|
+
* await pool.process(sessionId, thought);
|
|
706
|
+
* await pool.closeSession(sessionId);
|
|
707
|
+
* ```
|
|
708
|
+
*/
|
|
709
|
+
/**
|
|
710
|
+
* Represents a user session with its own server instance.
|
|
711
|
+
*/ class Session {
|
|
712
|
+
_server;
|
|
713
|
+
_id;
|
|
714
|
+
_createdAt;
|
|
715
|
+
_lastActivityAt;
|
|
716
|
+
_isActiveValue;
|
|
717
|
+
_timeout;
|
|
718
|
+
_cleanupTimer = null;
|
|
719
|
+
_logger;
|
|
720
|
+
constructor(id, server, timeout, logger){
|
|
721
|
+
this._server = server;
|
|
722
|
+
this._id = id;
|
|
723
|
+
this._createdAt = Date.now();
|
|
724
|
+
this._lastActivityAt = this._createdAt;
|
|
725
|
+
this._isActiveValue = true;
|
|
726
|
+
this._timeout = timeout;
|
|
727
|
+
this._logger = logger;
|
|
728
|
+
// Start session timeout timer
|
|
729
|
+
this._startTimeout();
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Check if the session is active.
|
|
733
|
+
*/ get isActive() {
|
|
734
|
+
return this._isActiveValue;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Process a thought through this session's server instance.
|
|
738
|
+
*/ async process(input) {
|
|
739
|
+
if (!this.isActive) {
|
|
740
|
+
throw new _errors_js__rspack_import_0/* .SessionNotActiveError */.qA(this._id);
|
|
741
|
+
}
|
|
742
|
+
// Update last activity
|
|
743
|
+
this._lastActivityAt = Date.now();
|
|
744
|
+
// Reset timeout timer
|
|
745
|
+
this._resetTimeout();
|
|
746
|
+
// Process the thought
|
|
747
|
+
return this._server.processThought(input);
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Get session information.
|
|
751
|
+
*/ getInfo() {
|
|
752
|
+
return {
|
|
753
|
+
id: this._id,
|
|
754
|
+
server: this._server,
|
|
755
|
+
createdAt: this._createdAt,
|
|
756
|
+
lastActivityAt: this._lastActivityAt,
|
|
757
|
+
isActive: this.isActive
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Check if the session has timed out.
|
|
762
|
+
*/ isTimedOut() {
|
|
763
|
+
return Date.now() - this._lastActivityAt > this._timeout;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Close the session and stop the server.
|
|
767
|
+
*/ async close() {
|
|
768
|
+
this._isActiveValue = false;
|
|
769
|
+
// Stop timeout timer
|
|
770
|
+
if (this._cleanupTimer) {
|
|
771
|
+
clearTimeout(this._cleanupTimer);
|
|
772
|
+
this._cleanupTimer = null;
|
|
773
|
+
}
|
|
774
|
+
// Stop the server
|
|
775
|
+
this._server.stop();
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Start the session timeout timer.
|
|
779
|
+
*/ _startTimeout() {
|
|
780
|
+
if (this._cleanupTimer) {
|
|
781
|
+
clearTimeout(this._cleanupTimer);
|
|
782
|
+
}
|
|
783
|
+
this._cleanupTimer = setTimeout(()=>{
|
|
784
|
+
if (this.isTimedOut()) {
|
|
785
|
+
this._logger.warn(`Session ${this._id} timed out, closing`);
|
|
786
|
+
this.close().catch((err)=>{
|
|
787
|
+
this._logger.error(`Error closing timed out session ${this._id}:`, err);
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
}, this._timeout);
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Reset the timeout timer after activity.
|
|
794
|
+
*/ _resetTimeout() {
|
|
795
|
+
this._startTimeout();
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* ConnectionPool manages multiple concurrent user sessions.
|
|
800
|
+
*
|
|
801
|
+
* Each session has its own server instance with isolated state,
|
|
802
|
+
* allowing multiple users to interact with the system simultaneously.
|
|
803
|
+
*/ class ConnectionPool {
|
|
804
|
+
_sessions = new Map();
|
|
805
|
+
_createSessionLock = null;
|
|
806
|
+
_maxSessions;
|
|
807
|
+
_sessionTimeout;
|
|
808
|
+
_autoCleanup;
|
|
809
|
+
_cleanupInterval;
|
|
810
|
+
_cleanupTimerId = null;
|
|
811
|
+
_terminated = false;
|
|
812
|
+
_logger;
|
|
813
|
+
_serverFactory;
|
|
814
|
+
constructor(options = {}){
|
|
815
|
+
this._maxSessions = options.maxSessions ?? 100;
|
|
816
|
+
this._sessionTimeout = options.sessionTimeout ?? 300000; // 5 minutes
|
|
817
|
+
this._autoCleanup = options.autoCleanup ?? true;
|
|
818
|
+
this._cleanupInterval = options.cleanupInterval ?? 60000; // 1 minute
|
|
819
|
+
this._serverFactory = options.serverFactory ?? null;
|
|
820
|
+
this._logger = options.logger ?? this._createNoopLogger();
|
|
821
|
+
if (this._autoCleanup) {
|
|
822
|
+
this._startCleanup();
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Create a no-op logger when none is provided.
|
|
827
|
+
*/ _createNoopLogger() {
|
|
828
|
+
return {
|
|
829
|
+
info: ()=>{},
|
|
830
|
+
warn: ()=>{},
|
|
831
|
+
error: ()=>{},
|
|
832
|
+
debug: ()=>{},
|
|
833
|
+
setLevel: ()=>{},
|
|
834
|
+
getLevel: ()=>'info'
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Create a new session.
|
|
839
|
+
*
|
|
840
|
+
* @returns The session ID
|
|
841
|
+
* @throws Error if max sessions reached
|
|
842
|
+
*/ async createSession() {
|
|
843
|
+
while(this._createSessionLock){
|
|
844
|
+
await this._createSessionLock;
|
|
845
|
+
}
|
|
846
|
+
if (this._terminated) {
|
|
847
|
+
throw new _errors_js__rspack_import_0/* .PoolTerminatedError */.m_();
|
|
848
|
+
}
|
|
849
|
+
if (this._sessions.size >= this._maxSessions) {
|
|
850
|
+
throw new _errors_js__rspack_import_0/* .MaxSessionsReachedError */.yM(this._maxSessions);
|
|
851
|
+
}
|
|
852
|
+
if (!this._serverFactory) {
|
|
853
|
+
throw new Error('ConnectionPool requires a serverFactory option to create sessions');
|
|
854
|
+
}
|
|
855
|
+
let resolveLock;
|
|
856
|
+
this._createSessionLock = new Promise((resolve)=>{
|
|
857
|
+
resolveLock = resolve;
|
|
858
|
+
});
|
|
859
|
+
try {
|
|
860
|
+
// Generate unique session ID
|
|
861
|
+
const sessionId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
862
|
+
// Create a new server instance for this session
|
|
863
|
+
const server = await this._serverFactory();
|
|
864
|
+
// Create session
|
|
865
|
+
const session = new Session(sessionId, server, this._sessionTimeout, this._logger);
|
|
866
|
+
this._sessions.set(sessionId, session);
|
|
867
|
+
this._logger.info(`Created session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`);
|
|
868
|
+
return sessionId;
|
|
869
|
+
} finally{
|
|
870
|
+
resolveLock();
|
|
871
|
+
this._createSessionLock = null;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Process a thought in the specified session.
|
|
876
|
+
*
|
|
877
|
+
* @param sessionId - The session ID
|
|
878
|
+
* @param input - The thought data to process
|
|
879
|
+
* @returns Promise with the processing result
|
|
880
|
+
* @throws Error if session not found
|
|
881
|
+
*/ async process(sessionId, input) {
|
|
882
|
+
const session = this._sessions.get(sessionId);
|
|
883
|
+
if (!session) {
|
|
884
|
+
throw new _errors_js__rspack_import_0/* .SessionNotFoundError */.Ag(sessionId);
|
|
885
|
+
}
|
|
886
|
+
return session.process(input);
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Close a session and release resources.
|
|
890
|
+
*
|
|
891
|
+
* @param sessionId - The session ID to close
|
|
892
|
+
* @throws Error if session not found
|
|
893
|
+
*/ async closeSession(sessionId) {
|
|
894
|
+
const session = this._sessions.get(sessionId);
|
|
895
|
+
if (!session) {
|
|
896
|
+
throw new _errors_js__rspack_import_0/* .SessionNotFoundError */.Ag(sessionId);
|
|
897
|
+
}
|
|
898
|
+
await session.close();
|
|
899
|
+
this._sessions.delete(sessionId);
|
|
900
|
+
this._logger.info(`Closed session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`);
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Get information about a session.
|
|
904
|
+
*
|
|
905
|
+
* @param sessionId - The session ID
|
|
906
|
+
* @returns Session info or undefined if not found
|
|
907
|
+
*/ getSessionInfo(sessionId) {
|
|
908
|
+
return this._sessions.get(sessionId)?.getInfo();
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Get all active sessions.
|
|
912
|
+
*
|
|
913
|
+
* @returns Array of session information
|
|
914
|
+
*/ getActiveSessions() {
|
|
915
|
+
return Array.from(this._sessions.values()).filter((s)=>s.isActive).map((s)=>s.getInfo());
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Get connection pool statistics.
|
|
919
|
+
*/ getStats() {
|
|
920
|
+
const activeSessions = this.getActiveSessions();
|
|
921
|
+
return {
|
|
922
|
+
totalSessions: this._sessions.size,
|
|
923
|
+
activeSessions: activeSessions.length,
|
|
924
|
+
maxSessions: this._maxSessions,
|
|
925
|
+
cleanupEnabled: this._autoCleanup,
|
|
926
|
+
sessionTimeout: this._sessionTimeout
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Start the automatic cleanup timer.
|
|
931
|
+
*/ _startCleanup() {
|
|
932
|
+
if (this._cleanupTimerId !== null) {
|
|
933
|
+
clearInterval(this._cleanupTimerId);
|
|
934
|
+
}
|
|
935
|
+
this._cleanupTimerId = setInterval(()=>{
|
|
936
|
+
this._cleanupTimedOutSessions();
|
|
937
|
+
}, this._cleanupInterval);
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Remove timed-out sessions.
|
|
941
|
+
*/ _cleanupTimedOutSessions() {
|
|
942
|
+
let cleaned = 0;
|
|
943
|
+
for (const [sessionId, session] of this._sessions.entries()){
|
|
944
|
+
if (session.isTimedOut()) {
|
|
945
|
+
session.close().catch((err)=>{
|
|
946
|
+
this._logger.error(`Error closing timed out session ${sessionId}:`, err);
|
|
947
|
+
});
|
|
948
|
+
this._sessions.delete(sessionId);
|
|
949
|
+
cleaned++;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
if (cleaned > 0) {
|
|
953
|
+
this._logger.info(`Cleaned ${cleaned} timed-out sessions (${this._sessions.size}/${this._maxSessions} active sessions)`);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Close all sessions and stop the cleanup timer.
|
|
958
|
+
*/ async terminate() {
|
|
959
|
+
if (this._terminated) {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
this._terminated = true;
|
|
963
|
+
// Stop cleanup timer
|
|
964
|
+
if (this._cleanupTimerId !== null) {
|
|
965
|
+
clearInterval(this._cleanupTimerId);
|
|
966
|
+
this._cleanupTimerId = null;
|
|
967
|
+
}
|
|
968
|
+
// Close all sessions
|
|
969
|
+
const closePromises = Array.from(this._sessions.values()).map((session)=>session.close().catch((err)=>{
|
|
970
|
+
this._logger.error(`Error closing session ${session.getInfo().id}:`, err);
|
|
971
|
+
}));
|
|
972
|
+
await Promise.all(closePromises);
|
|
973
|
+
this._sessions.clear();
|
|
974
|
+
this._logger.info('ConnectionPool terminated');
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Dispose of the connection pool, releasing all resources.
|
|
978
|
+
* Implements the IDisposable interface.
|
|
979
|
+
* Delegates to terminate() for backward compatibility.
|
|
980
|
+
*/ async dispose() {
|
|
981
|
+
await this.terminate();
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Check if the connection pool is active.
|
|
985
|
+
*/ isRunning() {
|
|
986
|
+
return !this._terminated;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Create a connection pool with the given options.
|
|
991
|
+
*
|
|
992
|
+
* @param options - Connection pool configuration
|
|
993
|
+
* @returns A configured connection pool
|
|
994
|
+
*
|
|
995
|
+
* @example
|
|
996
|
+
* ```typescript
|
|
997
|
+
* const pool = createConnectionPool({
|
|
998
|
+
* maxSessions: 50,
|
|
999
|
+
* sessionTimeout: 300000
|
|
1000
|
+
* });
|
|
1001
|
+
* ```
|
|
1002
|
+
*/ function createConnectionPool(options) {
|
|
1003
|
+
return new ConnectionPool(options);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
},
|
|
1008
|
+
613(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
1009
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
1010
|
+
Uv: () => (JsonRpcRequestSchema),
|
|
1011
|
+
ZK: () => (SequentialThinkingSchema),
|
|
1012
|
+
iV: () => (SEQUENTIAL_THINKING_TOOL)
|
|
1013
|
+
});
|
|
1014
|
+
/* import */ var valibot__rspack_import_0 = __webpack_require__(821);
|
|
1015
|
+
/**
|
|
1016
|
+
* Valibot validation schemas for the sequential thinking MCP tool.
|
|
1017
|
+
*
|
|
1018
|
+
* This module defines the validation schemas used for the sequential thinking tool,
|
|
1019
|
+
* including schemas for tool recommendations, skill recommendations, step recommendations,
|
|
1020
|
+
* and the main sequential thinking input. All schemas use Valibot for runtime validation
|
|
1021
|
+
* and provide detailed descriptions for MCP protocol compatibility.
|
|
1022
|
+
*
|
|
1023
|
+
* @remarks
|
|
1024
|
+
* **Schema Overview:**
|
|
1025
|
+
* - `ToolRecommendationSchema` - Validates tool recommendation objects with confidence scores
|
|
1026
|
+
* - `SkillRecommendationSchema` - Validates skill recommendation objects
|
|
1027
|
+
* - `StepRecommendationSchema` - Validates step coordination structures
|
|
1028
|
+
* - `SequentialThinkingSchema` - Main schema for thought input validation
|
|
1029
|
+
* - Reasoning enhancement fields: thought_type, quality_score, confidence, hypothesis_id, etc.
|
|
1030
|
+
*
|
|
1031
|
+
* @example
|
|
1032
|
+
* ```typescript
|
|
1033
|
+
* import { SequentialThinkingSchema } from './schema.js';
|
|
1034
|
+
* import { safeParse } from 'valibot';
|
|
1035
|
+
*
|
|
1036
|
+
* const result = safeParse(SequentialThinkingSchema, inputData);
|
|
1037
|
+
* if (result.success) {
|
|
1038
|
+
* const thought = result.output;
|
|
1039
|
+
* // Process the valid thought
|
|
1040
|
+
* } else {
|
|
1041
|
+
* console.error('Validation failed:', result.issues);
|
|
1042
|
+
* }
|
|
1043
|
+
* ```
|
|
1044
|
+
* @module schema
|
|
1045
|
+
*/
|
|
1046
|
+
/**
|
|
1047
|
+
* Detailed description for the sequential thinking tool.
|
|
1048
|
+
*
|
|
1049
|
+
* This description is shown to LLMs when they consider using this tool.
|
|
1050
|
+
* It explains when to use the tool, its features, parameters, and best practices.
|
|
1051
|
+
*/ const TOOL_DESCRIPTION = `A detailed tool for dynamic and reflective problem-solving through thoughts.
|
|
3
1052
|
This tool helps analyze problems through a flexible thinking process that can adapt and evolve.
|
|
4
1053
|
Each thought can build on, question, or revise previous insights as understanding deepens.
|
|
5
1054
|
|
|
@@ -102,13 +1151,2439 @@ You should:
|
|
|
102
1151
|
20. Self-assess quality and confidence to track reasoning reliability
|
|
103
1152
|
21. Use merge_from_thoughts to combine insights from multiple reasoning branches
|
|
104
1153
|
22. Use session_id to isolate independent reasoning chains from each other
|
|
105
|
-
23. Use reset_state: true when starting a completely new analysis to avoid statistical contamination from previous chains`,o=i.object({tool_name:i.pipe(i.string(),i.description("Name of the tool being recommended")),confidence:i.pipe(i.number(),i.minValue(0),i.maxValue(1),i.description("0-1 indicating confidence in recommendation")),rationale:i.pipe(i.string(),i.description("Why this tool is recommended")),priority:i.optional(i.pipe(i.number(),i.description("Order in the recommendation sequence (default: 999)"))),suggested_inputs:i.optional(i.pipe(i.record(i.string(),i.unknown()),i.description("Optional suggested parameters"))),alternatives:i.optional(i.pipe(i.array(i.string()),i.description("Alternative tools that could be used")))}),r=i.object({skill_name:i.pipe(i.string(),i.description("Name of the skill being recommended")),confidence:i.optional(i.pipe(i.number(),i.minValue(0),i.maxValue(1),i.description("0-1 indicating confidence in recommendation (default: 0.5)"))),rationale:i.optional(i.pipe(i.string(),i.description("Why this skill is recommended (default: empty string)"))),priority:i.optional(i.pipe(i.number(),i.description("Order in the recommendation sequence (default: 999)"))),alternatives:i.optional(i.pipe(i.array(i.string()),i.description("Alternative skills that could be used"))),allowed_tools:i.optional(i.pipe(i.array(i.string()),i.description("Tools this skill is allowed to use (from skill frontmatter)"))),user_invocable:i.optional(i.pipe(i.boolean(),i.description("Whether this skill can be user-invoked")))}),a=i.object({step_description:i.pipe(i.string(),i.description("What needs to be done")),recommended_tools:i.pipe(i.array(o),i.description("Tools recommended for this step")),recommended_skills:i.optional(i.pipe(i.array(r),i.description("Skills recommended for this step"))),expected_outcome:i.pipe(i.string(),i.description("What to expect from this step")),next_step_conditions:i.optional(i.pipe(i.array(i.string()),i.description("Conditions to consider for the next step")))}),l=i.object({tool_name:i.pipe(i.string(),i.description("Name of the tool being recommended")),rationale:i.optional(i.pipe(i.string(),i.description("Why this tool is recommended (default: empty string)"))),confidence:i.optional(i.pipe(i.number(),i.minValue(0),i.maxValue(1),i.description("0-1 indicating confidence in recommendation (default: 0.5)"))),priority:i.optional(i.pipe(i.number(),i.description("Order in the recommendation sequence (default: 999)"))),suggested_inputs:i.optional(i.pipe(i.record(i.string(),i.unknown()),i.description("Optional suggested parameters"))),alternatives:i.optional(i.pipe(i.array(i.string()),i.description("Alternative tools that could be used")))}),c=i.object({step_description:i.pipe(i.string(),i.description("What needs to be done")),recommended_tools:i.pipe(i.array(l),i.description("Tools recommended for this step")),recommended_skills:i.optional(i.pipe(i.array(r),i.description("Skills recommended for this step"))),expected_outcome:i.optional(i.pipe(i.string(),i.description("What to expect from this step (default: empty string)"))),next_step_conditions:i.optional(i.pipe(i.array(i.string()),i.description("Conditions to consider for the next step")))}),h=i.object({available_mcp_tools:i.optional(i.pipe(i.array(i.string()),i.description('Array of MCP tool names available for use (e.g., ["mcp-omnisearch", "mcp-turso-cloud"])'))),available_skills:i.optional(i.pipe(i.array(i.string()),i.description('Array of skill names available for use (e.g., ["commit", "review-pr", "pdf"])'))),thought:i.pipe(i.string(),i.description("Your current thinking step")),id:i.optional(i.pipe(i.string(),i.minLength(1),i.maxLength(30),i.description("Unique identifier for this thought. Auto-generated if not provided."))),next_thought_needed:i.optional(i.pipe(i.boolean(),i.description("Whether another thought step is needed (defaults to true if not provided)"))),thought_number:i.pipe(i.number(),i.minValue(1),i.description("Current thought number")),total_thoughts:i.pipe(i.number(),i.minValue(1),i.description("Estimated total thoughts needed")),is_revision:i.optional(i.pipe(i.boolean(),i.description("Whether this revises previous thinking"))),revises_thought:i.optional(i.pipe(i.number(),i.minValue(1),i.description("Which thought is being reconsidered"))),branch_from_thought:i.optional(i.pipe(i.number(),i.minValue(1),i.description("Branching point thought number"))),branch_id:i.optional(i.pipe(i.string(),i.regex(/^[a-zA-Z0-9_-]+$/,"Branch ID must contain only letters, numbers, hyphens, and underscores"),i.minLength(1),i.maxLength(50),i.description("Branch identifier (alphanumeric, hyphens, underscores only, max 50 chars)"))),needs_more_thoughts:i.optional(i.pipe(i.boolean(),i.description("If more thoughts are needed"))),current_step:i.optional(i.pipe(a,i.description("Current step recommendation"))),previous_steps:i.optional(i.pipe(i.array(c),i.description("Steps already recommended (lenient schema - allows partial data with defaults)"))),remaining_steps:i.optional(i.pipe(i.array(i.string()),i.description("High-level descriptions of upcoming steps"))),thought_type:i.optional(i.pipe(i.picklist(["regular","hypothesis","verification","critique","synthesis","meta","tool_call","tool_observation","assumption","decomposition","backtrack"]),i.description("Classified purpose: regular (default), hypothesis, verification, critique, synthesis, meta, tool_call (requires toolInterleave), tool_observation (requires toolInterleave), assumption (requires newThoughtTypes), decomposition (requires newThoughtTypes), backtrack (requires newThoughtTypes)"))),quality_score:i.optional(i.pipe(i.number(),i.minValue(0),i.maxValue(1),i.description("Self-assessed quality score (0-1)"))),confidence:i.optional(i.pipe(i.number(),i.minValue(0),i.maxValue(1),i.description("Explicit confidence in correctness (0-1)"))),hypothesis_id:i.optional(i.pipe(i.string(),i.regex(/^[a-zA-Z0-9_-]+$/,"Hypothesis ID must contain only letters, numbers, hyphens, and underscores"),i.minLength(1),i.maxLength(50),i.description("Identifier linking hypothesis to verification thoughts"))),verification_target:i.optional(i.pipe(i.number(),i.minValue(1),i.description("Thought number being verified or critiqued"))),synthesis_sources:i.optional(i.pipe(i.array(i.pipe(i.number(),i.minValue(1))),i.description("Thought numbers being synthesized"))),merge_from_thoughts:i.optional(i.pipe(i.array(i.pipe(i.number(),i.minValue(1))),i.description("Thought numbers from other branches being merged (DAG)"))),merge_branch_ids:i.optional(i.pipe(i.array(i.pipe(i.string(),i.regex(/^[a-zA-Z0-9_-]+$/),i.maxLength(50))),i.description("Branch IDs being merged into current context"))),meta_observation:i.optional(i.pipe(i.string(),i.description("Metacognitive observation about reasoning process"))),reasoning_depth:i.optional(i.pipe(i.picklist(["shallow","moderate","deep"]),i.description("Effort signal: how deep reasoning should go"))),session_id:i.optional(i.pipe(i.string(),i.regex(/^[a-zA-Z0-9_-]+$/,"Session ID must contain only letters, numbers, hyphens, and underscores"),i.minLength(1),i.maxLength(100),i.description("Optional session identifier for state isolation. When provided, thought history, branches, and statistics are scoped to this session. Omitting preserves global behavior."))),reset_state:i.optional(i.pipe(i.boolean(),i.description("When true, clears all state for the target session before processing this thought. The thought is then processed as the first in a fresh session."))),tool_name:i.optional(i.pipe(i.string(),i.minLength(1),i.description("Name of the tool being invoked (for tool_call thoughts)"))),tool_arguments:i.optional(i.pipe(i.record(i.string(),i.unknown()),i.description("Arguments passed to the tool (for tool_call thoughts)"))),tool_result:i.optional(i.pipe(i.unknown(),i.description("Result returned by the tool (for tool_observation thoughts)"))),continuation_token:i.optional(i.pipe(i.string(),i.minLength(1),i.description("Token for resuming long-running tool invocations"))),decomposition_children:i.optional(i.pipe(i.array(i.string()),i.description("Child thought IDs produced by decomposition"))),backtrack_target:i.optional(i.pipe(i.number(),i.integer(),i.minValue(1),i.description("Thought number to backtrack to. When the parent thought has thought_type=backtrack, this thought is logically retracted: it remains in history but is excluded from quality signals and reasoning stats."))),register_branch_id:i.optional(i.pipe(i.string(),i.regex(/^[a-zA-Z0-9_-]+$/,"register_branch_id must contain only letters, numbers, hyphens, and underscores"),i.minLength(1),i.maxLength(50),i.description("Pre-declares a branch ID for this session before any thoughts reference it. Useful so that subsequent thoughts using merge_branch_ids can target a branch that has not yet received any thoughts.")))}),p={name:"sequentialthinking_tools",description:n,inputSchema:{}},d=i.object({jsonrpc:i.pipe(i.string(),i.literal("2.0"),i.description('JSON-RPC protocol version (must be "2.0")')),method:i.pipe(i.string(),i.minLength(1),i.description("Method name to invoke")),params:i.optional(i.pipe(i.union([i.object({}),i.array(i.unknown())]),i.description("Method parameters (object or array)"))),id:i.optional(i.pipe(i.union([i.string(),i.number(),i.null()]),i.description("Request ID (omit for notifications)")))}),u=i.union([i.literal("sequence"),i.literal("branch"),i.literal("merge"),i.literal("verifies"),i.literal("critiques"),i.literal("derives_from"),i.literal("tool_invocation"),i.literal("revises")]);i.object({id:i.pipe(i.string(),i.minLength(1),i.maxLength(30)),from:i.pipe(i.string(),i.minLength(1),i.maxLength(30)),to:i.pipe(i.string(),i.minLength(1),i.maxLength(30)),kind:u,sessionId:i.pipe(i.string(),i.minLength(1)),createdAt:i.number(),metadata:i.optional(i.record(i.string(),i.unknown()))})},681(e,t,s){s.d(t,{j:()=>r}),s(561);let i=/^[a-zA-Z0-9_-]+$/;class n{_level="info";info(e,t){}warn(e,t){}error(e,t){}debug(e,t){}setLevel(e){this._level=e}getLevel(){return this._level}}let o=new Set(["session","sessionId","client","clientId"]);class r{_port;_host;_corsOrigin;_enableCors;_rateLimitEnabled;_maxRequestsPerMinute;_allowedHosts;_rateLimitMap=new Map;_rateLimitCleanupIntervalId=null;_wasHostExplicitlySet;_isShuttingDown=!1;_logger;_healthChecker;constructor(e={}){this._port=e.port??9108,this._host=e.host??"127.0.0.1",this._wasHostExplicitlySet=void 0!==e.host,this._corsOrigin=e.corsOrigin??"*",this._enableCors=e.enableCors??!0,this._rateLimitEnabled=e.enableRateLimit??!0,this._maxRequestsPerMinute=e.maxRequestsPerMinute??100,this._allowedHosts=this._buildAllowedHosts(e.allowedHosts),this._isShuttingDown=!1,this._logger=e.logger??new n,this._healthChecker=e.healthChecker??null,this._rateLimitEnabled&&this._startRateLimitCleanup()}get serverUrl(){let e=this._wasHostExplicitlySet||"127.0.0.1"!==this._host?this._host:"localhost";return`http://${e}:${this._port}`}validateSessionId(e){return!(e.length>100)&&i.test(e)}sanitizeQueryParams(e){let t={};for(let[s,i]of e.searchParams.entries())o.has(s)&&(t[s]=i);return t}checkRateLimit(e){if(!this._rateLimitEnabled)return!1;let t=Date.now();this._cleanupExpiredRateLimitEntries(t);let s=this._rateLimitMap.get(e);return!s||t>s.resetTime?(this._rateLimitMap.set(e,{count:1,resetTime:t+6e4}),!1):s.count>=this._maxRequestsPerMinute||(s.count++,!1)}_cleanupExpiredRateLimitEntries(e=Date.now()){for(let[t,s]of this._rateLimitMap.entries())s.resetTime<=e&&this._rateLimitMap.delete(t)}_startRateLimitCleanup(){null!==this._rateLimitCleanupIntervalId&&clearInterval(this._rateLimitCleanupIntervalId),this._rateLimitCleanupIntervalId=setInterval(()=>{this._cleanupExpiredRateLimitEntries()},6e4)}_stopRateLimitCleanup(){null!==this._rateLimitCleanupIntervalId&&(clearInterval(this._rateLimitCleanupIntervalId),this._rateLimitCleanupIntervalId=null)}getClientIp(e){let t=e.headers["x-forwarded-for"];return t&&"string"==typeof t?t.split(",")[0].trim():e.socket.remoteAddress||"unknown"}validateCorsOrigin(e){if("*"===this._corsOrigin)return!0;let t=e.headers.origin;if(!t||this._corsOrigin===t)return!0;if(this._corsOrigin.includes("*")){let e=this._corsOrigin.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,"[a-zA-Z0-9.-]*");return RegExp(`^${e}$`).test(t)}return!1}setCorsHeaders(e){this._enableCors&&(e.setHeader("Access-Control-Allow-Origin",this._corsOrigin),e.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),e.setHeader("Access-Control-Allow-Headers","Content-Type"))}validateHostHeader(e){let t=e.headers.host;if(!t)return!0;let s=t.split(":")[0].trim().toLowerCase();return!!s&&(0===this._allowedHosts.size||this._allowedHosts.has(s))}_buildAllowedHosts(e){if(e&&e.length>0)return new Set(e.map(e=>e.toLowerCase().trim()).filter(Boolean));let t=this._host.toLowerCase(),s=["localhost","127.0.0.1","::1"];return new Set(s.includes(t)||"0.0.0.0"===t||"::"===t?s:[t])}log(e,t,s){"info"===e?this._logger.info(t,s):"warn"===e?this._logger.warn(t,s):this._logger.error(t,s)}isShuttingDown(){return this._isShuttingDown}handleHealthEndpoint(e,t){let s={status:"healthy",...t};this._healthChecker&&(s.liveness=this._healthChecker.checkLiveness()),e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify(s))}async handleReadinessEndpoint(e){if(this._healthChecker){let t=await this._healthChecker.checkReadiness(),s="ok"===t.status?200:503;e.writeHead(s,{"Content-Type":"application/json"}),e.end(JSON.stringify(t))}else e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify({status:"ok",timestamp:new Date().toISOString(),components:{}}))}handleMetricsEndpoint(e,t){if(!t){e.writeHead(404,{"Content-Type":"text/plain"}),e.end("Not Found");return}e.writeHead(200,{"Content-Type":"text/plain; version=0.0.4; charset=utf-8"}),e.end(t())}}},504(e,t,s){s.d(t,{SseTransport:()=>l});var i=s(316),n=s(61),o=s(821),r=s(555),a=s(681);class l extends a.j{_server;_path;_clients=new Set;_clientSessionMap=new Map;_messageQueue=new Map;_metrics;_connectionPool;constructor(e={}){super(e),this._path=e.path??"/sse",this._metrics=e.metrics,this._connectionPool=e.connectionPool,this._updateActiveConnectionsMetric(),this._server=(0,i.createServer)((e,t)=>this._handleRequest(e,t))}async connect(e){return this._mcpServer=e,new Promise(e=>{this._server.listen(this._port,this._host,()=>{this.log("info",`SSE transport listening on http://${this._host}:${this._port}`),e()})})}_mcpServer=null;async _handleRequest(e,t){var s,i,o,r,a;let l=Date.now(),c=e.url||"/",h=e.method||"GET";if(null==(s=this._metrics)||s.counter("http_requests_total",1,{transport:"sse",method:h,path:c},"Total HTTP requests"),t.once("finish",()=>{var e;let t=(Date.now()-l)/1e3;null==(e=this._metrics)||e.histogram("http_request_duration_seconds",t,{transport:"sse",path:c})}),!this.validateHostHeader(e)){null==(i=this._metrics)||i.counter("http_request_errors_total",1,{transport:"sse",error_type:"forbidden"},"Total HTTP request errors"),t.writeHead(403,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Forbidden - invalid host header"}));return}let p=new n.URL(e.url||"",`http://${e.headers.host}`),d=this.getClientIp(e);if(this.checkRateLimit(d)){null==(o=this._metrics)||o.counter("http_request_errors_total",1,{transport:"sse",error_type:"rate_limit"},"Total HTTP request errors"),t.writeHead(429,{"Content-Type":"application/json","Retry-After":"60"}),t.end(JSON.stringify({error:"Too many requests"}));return}if(!this.validateCorsOrigin(e)){null==(r=this._metrics)||r.counter("http_request_errors_total",1,{transport:"sse",error_type:"forbidden"},"Total HTTP request errors"),t.writeHead(403,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Forbidden - invalid origin"}));return}this.setCorsHeaders(t);let u=this.sanitizeQueryParams(p);if(u.session||u.sessionId){let e=u.session??u.sessionId;if(!this.validateSessionId(e)){null==(a=this._metrics)||a.counter("http_request_errors_total",1,{transport:"sse",error_type:"validation"},"Total HTTP request errors"),t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Invalid session ID format"}));return}}if(this._enableCors&&"OPTIONS"===e.method){t.writeHead(204),t.end();return}p.pathname===this._path&&"GET"===e.method?await this._handleSseConnection(e,t,u):p.pathname===`${this._path}/message`&&"POST"===e.method?await this._handleMessage(e,t,u):"/health"===p.pathname?this._handleHealthCheck(t):"/ready"===p.pathname?await this._handleReadinessCheck(t):(t.writeHead(404,{"Content-Type":"text/plain"}),t.end("Not Found"))}_handleHealthCheck(e){let t={status:"healthy",clients:this._clients.size};this._connectionPool&&(t.pool=this._connectionPool.getStats()),this._healthChecker&&(t.liveness=this._healthChecker.checkLiveness()),e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify(t))}async _handleReadinessCheck(e){if(this._healthChecker){let t=await this._healthChecker.checkReadiness(),s="ok"===t.status?200:503;e.writeHead(s,{"Content-Type":"application/json"}),e.end(JSON.stringify(t))}else e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify({status:"ok",timestamp:new Date().toISOString(),components:{}}))}async _handleSseConnection(e,t,s){let i;if(t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"}),this._connectionPool){let e=s.session??s.sessionId;if(e&&this._connectionPool.getSessionInfo(e))i=e;else try{i=await this._connectionPool.createSession()}catch(e){t.write(`event: error
|
|
106
|
-
|
|
1154
|
+
23. Use reset_state: true when starting a completely new analysis to avoid statistical contamination from previous chains`;
|
|
1155
|
+
/**
|
|
1156
|
+
* Valibot schema for validating tool recommendation objects.
|
|
1157
|
+
*
|
|
1158
|
+
* Validates that a tool recommendation has:
|
|
1159
|
+
* - A tool name (string)
|
|
1160
|
+
* - A confidence score between 0 and 1
|
|
1161
|
+
* - A rationale explaining the recommendation
|
|
1162
|
+
* - A priority number for ordering
|
|
1163
|
+
* - Optional suggested input parameters
|
|
1164
|
+
* - Optional alternative tools
|
|
1165
|
+
*
|
|
1166
|
+
* @example
|
|
1167
|
+
* ```typescript
|
|
1168
|
+
* import { safeParse } from 'valibot';
|
|
1169
|
+
* import { ToolRecommendationSchema } from './schema.js';
|
|
1170
|
+
*
|
|
1171
|
+
* const result = safeParse(ToolRecommendationSchema, {
|
|
1172
|
+
* tool_name: 'mcp__tavily-mcp__tavily-search',
|
|
1173
|
+
* confidence: 0.9,
|
|
1174
|
+
* rationale: 'Best for web search',
|
|
1175
|
+
* priority: 1
|
|
1176
|
+
* });
|
|
1177
|
+
* ```
|
|
1178
|
+
*/ const ToolRecommendationSchema = valibot__rspack_import_0.object({
|
|
1179
|
+
tool_name: valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.description('Name of the tool being recommended')),
|
|
1180
|
+
confidence: valibot__rspack_import_0.pipe(valibot__rspack_import_0.number(), valibot__rspack_import_0.minValue(0), valibot__rspack_import_0.maxValue(1), valibot__rspack_import_0.description('0-1 indicating confidence in recommendation')),
|
|
1181
|
+
rationale: valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.description('Why this tool is recommended')),
|
|
1182
|
+
priority: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.number(), valibot__rspack_import_0.description('Order in the recommendation sequence (default: 999)'))),
|
|
1183
|
+
suggested_inputs: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.record(valibot__rspack_import_0.string(), valibot__rspack_import_0.unknown()), valibot__rspack_import_0.description('Optional suggested parameters'))),
|
|
1184
|
+
alternatives: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(valibot__rspack_import_0.string()), valibot__rspack_import_0.description('Alternative tools that could be used')))
|
|
1185
|
+
});
|
|
1186
|
+
/**
|
|
1187
|
+
* Valibot schema for validating skill recommendation objects.
|
|
1188
|
+
*
|
|
1189
|
+
* Validates that a skill recommendation has:
|
|
1190
|
+
* - A skill name (string)
|
|
1191
|
+
* - A confidence score between 0 and 1
|
|
1192
|
+
* - A rationale explaining the recommendation
|
|
1193
|
+
* - A priority number for ordering
|
|
1194
|
+
* - Optional alternative skills
|
|
1195
|
+
* - Optional allowed tools list
|
|
1196
|
+
* - Optional user invocable flag
|
|
1197
|
+
*
|
|
1198
|
+
* @example
|
|
1199
|
+
* ```typescript
|
|
1200
|
+
* import { safeParse } from 'valibot';
|
|
1201
|
+
* import { SkillRecommendationSchema } from './schema.js';
|
|
1202
|
+
*
|
|
1203
|
+
* const result = safeParse(SkillRecommendationSchema, {
|
|
1204
|
+
* skill_name: 'commit',
|
|
1205
|
+
* confidence: 0.95,
|
|
1206
|
+
* rationale: 'Handles git commit workflow',
|
|
1207
|
+
* priority: 1,
|
|
1208
|
+
* user_invocable: true
|
|
1209
|
+
* });
|
|
1210
|
+
* ```
|
|
1211
|
+
*/ const SkillRecommendationSchema = valibot__rspack_import_0.object({
|
|
1212
|
+
skill_name: valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.description('Name of the skill being recommended')),
|
|
1213
|
+
confidence: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.number(), valibot__rspack_import_0.minValue(0), valibot__rspack_import_0.maxValue(1), valibot__rspack_import_0.description('0-1 indicating confidence in recommendation (default: 0.5)'))),
|
|
1214
|
+
rationale: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.description('Why this skill is recommended (default: empty string)'))),
|
|
1215
|
+
priority: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.number(), valibot__rspack_import_0.description('Order in the recommendation sequence (default: 999)'))),
|
|
1216
|
+
alternatives: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(valibot__rspack_import_0.string()), valibot__rspack_import_0.description('Alternative skills that could be used'))),
|
|
1217
|
+
allowed_tools: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(valibot__rspack_import_0.string()), valibot__rspack_import_0.description('Tools this skill is allowed to use (from skill frontmatter)'))),
|
|
1218
|
+
user_invocable: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.boolean(), valibot__rspack_import_0.description('Whether this skill can be user-invoked')))
|
|
1219
|
+
});
|
|
1220
|
+
/**
|
|
1221
|
+
* Valibot schema for validating step recommendation objects.
|
|
1222
|
+
*
|
|
1223
|
+
* Validates that a step recommendation has:
|
|
1224
|
+
* - A step description
|
|
1225
|
+
* - An array of recommended tools
|
|
1226
|
+
* - An optional array of recommended skills
|
|
1227
|
+
* - An expected outcome
|
|
1228
|
+
* - Optional conditions for the next step
|
|
1229
|
+
*
|
|
1230
|
+
* @example
|
|
1231
|
+
* ```typescript
|
|
1232
|
+
* import { safeParse } from 'valibot';
|
|
1233
|
+
* import { StepRecommendationSchema } from './schema.js';
|
|
1234
|
+
*
|
|
1235
|
+
* const result = safeParse(StepRecommendationSchema, {
|
|
1236
|
+
* step_description: 'Search for TypeScript files',
|
|
1237
|
+
* recommended_tools: [{ ... }],
|
|
1238
|
+
* expected_outcome: 'List of all TypeScript files'
|
|
1239
|
+
* });
|
|
1240
|
+
* ```
|
|
1241
|
+
*/ const StepRecommendationSchema = valibot__rspack_import_0.object({
|
|
1242
|
+
step_description: valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.description('What needs to be done')),
|
|
1243
|
+
recommended_tools: valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(ToolRecommendationSchema), valibot__rspack_import_0.description('Tools recommended for this step')),
|
|
1244
|
+
recommended_skills: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(SkillRecommendationSchema), valibot__rspack_import_0.description('Skills recommended for this step'))),
|
|
1245
|
+
expected_outcome: valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.description('What to expect from this step')),
|
|
1246
|
+
next_step_conditions: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(valibot__rspack_import_0.string()), valibot__rspack_import_0.description('Conditions to consider for the next step')))
|
|
1247
|
+
});
|
|
1248
|
+
/**
|
|
1249
|
+
* Valibot schema for validating partial tool recommendation objects.
|
|
1250
|
+
*
|
|
1251
|
+
* This is a lenient version of ToolRecommendationSchema used for previous_steps,
|
|
1252
|
+
* where LLMs naturally provide partial/skeletal data. Only tool_name and rationale
|
|
1253
|
+
* are required, while confidence and priority are optional with default values.
|
|
1254
|
+
*
|
|
1255
|
+
* Validates that a partial tool recommendation has:
|
|
1256
|
+
* - A tool name (required)
|
|
1257
|
+
* - A rationale explaining the recommendation (required)
|
|
1258
|
+
* - An optional confidence score (defaults to 0.5)
|
|
1259
|
+
* - An optional priority number (defaults to 999)
|
|
1260
|
+
* - Optional suggested input parameters
|
|
1261
|
+
* - Optional alternative tools
|
|
1262
|
+
*
|
|
1263
|
+
* @remarks
|
|
1264
|
+
* **Design Rationale:**
|
|
1265
|
+
* LLMs tend to provide complete data for current_step but only partial data
|
|
1266
|
+
* for previous_steps (historical context). This schema accommodates that natural
|
|
1267
|
+
* LLM behavior while maintaining data integrity through sensible defaults.
|
|
1268
|
+
*
|
|
1269
|
+
* @example
|
|
1270
|
+
* ```typescript
|
|
1271
|
+
* import { safeParse } from 'valibot';
|
|
1272
|
+
* import { PartialToolRecommendationSchema } from './schema.js';
|
|
1273
|
+
*
|
|
1274
|
+
* // Minimal valid input (LLM often generates this for previous_steps)
|
|
1275
|
+
* const result = safeParse(PartialToolRecommendationSchema, {
|
|
1276
|
+
* tool_name: 'Read',
|
|
1277
|
+
* rationale: 'Read the file'
|
|
1278
|
+
* });
|
|
1279
|
+
* // confidence and priority will be filled in by the normalizer
|
|
1280
|
+
* ```
|
|
1281
|
+
*/ const PartialToolRecommendationSchema = valibot__rspack_import_0.object({
|
|
1282
|
+
tool_name: valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.description('Name of the tool being recommended')),
|
|
1283
|
+
rationale: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.description('Why this tool is recommended (default: empty string)'))),
|
|
1284
|
+
confidence: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.number(), valibot__rspack_import_0.minValue(0), valibot__rspack_import_0.maxValue(1), valibot__rspack_import_0.description('0-1 indicating confidence in recommendation (default: 0.5)'))),
|
|
1285
|
+
priority: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.number(), valibot__rspack_import_0.description('Order in the recommendation sequence (default: 999)'))),
|
|
1286
|
+
suggested_inputs: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.record(valibot__rspack_import_0.string(), valibot__rspack_import_0.unknown()), valibot__rspack_import_0.description('Optional suggested parameters'))),
|
|
1287
|
+
alternatives: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(valibot__rspack_import_0.string()), valibot__rspack_import_0.description('Alternative tools that could be used')))
|
|
1288
|
+
});
|
|
1289
|
+
/**
|
|
1290
|
+
* Valibot schema for validating partial step recommendation objects.
|
|
1291
|
+
*
|
|
1292
|
+
* This is a lenient version of StepRecommendationSchema used for previous_steps,
|
|
1293
|
+
* where LLMs naturally provide partial/skeletal data. Only step_description is
|
|
1294
|
+
* strictly required, while expected_outcome and tool recommendation fields are
|
|
1295
|
+
* optional with default values.
|
|
1296
|
+
*
|
|
1297
|
+
* Validates that a partial step recommendation has:
|
|
1298
|
+
* - A step description (required)
|
|
1299
|
+
* - An array of recommended tools (with optional confidence/priority)
|
|
1300
|
+
* - An optional array of recommended skills
|
|
1301
|
+
* - An optional expected outcome (defaults to empty string)
|
|
1302
|
+
* - Optional conditions for the next step
|
|
1303
|
+
*
|
|
1304
|
+
* @remarks
|
|
1305
|
+
* **Design Rationale:**
|
|
1306
|
+
* LLMs provide complete, detailed data for current_step but only brief summaries
|
|
1307
|
+
* for previous_steps. This schema allows the natural LLM behavior while the
|
|
1308
|
+
* InputNormalizer fills in sensible defaults for missing fields.
|
|
1309
|
+
*
|
|
1310
|
+
* @example
|
|
1311
|
+
* ```typescript
|
|
1312
|
+
* import { safeParse } from 'valibot';
|
|
1313
|
+
* import { PartialStepRecommendationSchema } from './schema.js';
|
|
1314
|
+
*
|
|
1315
|
+
* // Minimal valid input (LLM often generates this for previous_steps)
|
|
1316
|
+
* const result = safeParse(PartialStepRecommendationSchema, {
|
|
1317
|
+
* step_description: 'Read the file',
|
|
1318
|
+
* recommended_tools: [{
|
|
1319
|
+
* tool_name: 'Read',
|
|
1320
|
+
* rationale: 'Read the file'
|
|
1321
|
+
* }]
|
|
1322
|
+
* });
|
|
1323
|
+
* // confidence, priority, and expected_outcome will be filled in by normalizer
|
|
1324
|
+
* ```
|
|
1325
|
+
*/ const PartialStepRecommendationSchema = valibot__rspack_import_0.object({
|
|
1326
|
+
step_description: valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.description('What needs to be done')),
|
|
1327
|
+
recommended_tools: valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(PartialToolRecommendationSchema), valibot__rspack_import_0.description('Tools recommended for this step')),
|
|
1328
|
+
recommended_skills: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(SkillRecommendationSchema), valibot__rspack_import_0.description('Skills recommended for this step'))),
|
|
1329
|
+
expected_outcome: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.description('What to expect from this step (default: empty string)'))),
|
|
1330
|
+
next_step_conditions: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(valibot__rspack_import_0.string()), valibot__rspack_import_0.description('Conditions to consider for the next step')))
|
|
1331
|
+
});
|
|
1332
|
+
/**
|
|
1333
|
+
* Main Valibot schema for validating sequential thinking tool input.
|
|
1334
|
+
*
|
|
1335
|
+
* This is the primary schema used for the sequential thinking MCP tool.
|
|
1336
|
+
* It validates all thought data including:
|
|
1337
|
+
* - Optional available tools and skills arrays
|
|
1338
|
+
* - The thought content (required)
|
|
1339
|
+
* - Thought numbering (thought_number, total_thoughts)
|
|
1340
|
+
* - Revision and branching metadata
|
|
1341
|
+
* - Current, previous, and remaining step recommendations
|
|
1342
|
+
*
|
|
1343
|
+
* @remarks
|
|
1344
|
+
* **Validation Rules:**
|
|
1345
|
+
* - `thought_number` must be >= 1
|
|
1346
|
+
* - `total_thoughts` must be >= 1
|
|
1347
|
+
* - `branch_id` must be 1-50 characters, alphanumeric/hyphens/underscores only
|
|
1348
|
+
* - `confidence` values must be between 0 and 1
|
|
1349
|
+
* - `thought_type` must be one of: regular, hypothesis, verification, critique, synthesis, meta
|
|
1350
|
+
* - `quality_score` and `confidence` must be between 0 and 1
|
|
1351
|
+
* - `hypothesis_id` must be 1-50 characters, alphanumeric/hyphens/underscores only
|
|
1352
|
+
*
|
|
1353
|
+
* @example
|
|
1354
|
+
* ```typescript
|
|
1355
|
+
* import { safeParse } from 'valibot';
|
|
1356
|
+
* import { SequentialThinkingSchema } from './schema.js';
|
|
1357
|
+
*
|
|
1358
|
+
* const result = safeParse(SequentialThinkingSchema, {
|
|
1359
|
+
* thought: 'I need to analyze the problem',
|
|
1360
|
+
* thought_number: 1,
|
|
1361
|
+
* total_thoughts: 5,
|
|
1362
|
+
* next_thought_needed: true,
|
|
1363
|
+
* available_mcp_tools: ['Read', 'Write', 'Grep']
|
|
1364
|
+
* });
|
|
1365
|
+
*
|
|
1366
|
+
* if (result.success) {
|
|
1367
|
+
* console.log('Valid thought:', result.output);
|
|
1368
|
+
* } else {
|
|
1369
|
+
* console.error('Validation errors:', result.issues);
|
|
1370
|
+
* }
|
|
1371
|
+
* ```
|
|
1372
|
+
*/ const SequentialThinkingSchema = valibot__rspack_import_0.object({
|
|
1373
|
+
available_mcp_tools: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(valibot__rspack_import_0.string()), valibot__rspack_import_0.description('Array of MCP tool names available for use (e.g., ["mcp-omnisearch", "mcp-turso-cloud"])'))),
|
|
1374
|
+
available_skills: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(valibot__rspack_import_0.string()), valibot__rspack_import_0.description('Array of skill names available for use (e.g., ["commit", "review-pr", "pdf"])'))),
|
|
1375
|
+
thought: valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.description('Your current thinking step')),
|
|
1376
|
+
id: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.minLength(1), valibot__rspack_import_0.maxLength(30), valibot__rspack_import_0.description('Unique identifier for this thought. Auto-generated if not provided.'))),
|
|
1377
|
+
next_thought_needed: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.boolean(), valibot__rspack_import_0.description('Whether another thought step is needed (defaults to true if not provided)'))),
|
|
1378
|
+
thought_number: valibot__rspack_import_0.pipe(valibot__rspack_import_0.number(), valibot__rspack_import_0.minValue(1), valibot__rspack_import_0.description('Current thought number')),
|
|
1379
|
+
total_thoughts: valibot__rspack_import_0.pipe(valibot__rspack_import_0.number(), valibot__rspack_import_0.minValue(1), valibot__rspack_import_0.description('Estimated total thoughts needed')),
|
|
1380
|
+
is_revision: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.boolean(), valibot__rspack_import_0.description('Whether this revises previous thinking'))),
|
|
1381
|
+
revises_thought: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.number(), valibot__rspack_import_0.minValue(1), valibot__rspack_import_0.description('Which thought is being reconsidered'))),
|
|
1382
|
+
branch_from_thought: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.number(), valibot__rspack_import_0.minValue(1), valibot__rspack_import_0.description('Branching point thought number'))),
|
|
1383
|
+
branch_id: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.regex(/^[a-zA-Z0-9_-]+$/, 'Branch ID must contain only letters, numbers, hyphens, and underscores'), valibot__rspack_import_0.minLength(1), valibot__rspack_import_0.maxLength(50), valibot__rspack_import_0.description('Branch identifier (alphanumeric, hyphens, underscores only, max 50 chars)'))),
|
|
1384
|
+
needs_more_thoughts: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.boolean(), valibot__rspack_import_0.description('If more thoughts are needed'))),
|
|
1385
|
+
current_step: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(StepRecommendationSchema, valibot__rspack_import_0.description('Current step recommendation'))),
|
|
1386
|
+
previous_steps: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(PartialStepRecommendationSchema), valibot__rspack_import_0.description('Steps already recommended (lenient schema - allows partial data with defaults)'))),
|
|
1387
|
+
remaining_steps: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(valibot__rspack_import_0.string()), valibot__rspack_import_0.description('High-level descriptions of upcoming steps'))),
|
|
1388
|
+
thought_type: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.picklist([
|
|
1389
|
+
'regular',
|
|
1390
|
+
'hypothesis',
|
|
1391
|
+
'verification',
|
|
1392
|
+
'critique',
|
|
1393
|
+
'synthesis',
|
|
1394
|
+
'meta',
|
|
1395
|
+
'tool_call',
|
|
1396
|
+
'tool_observation',
|
|
1397
|
+
'assumption',
|
|
1398
|
+
'decomposition',
|
|
1399
|
+
'backtrack'
|
|
1400
|
+
]), valibot__rspack_import_0.description('Classified purpose: regular (default), hypothesis, verification, critique, synthesis, meta, tool_call (requires toolInterleave), tool_observation (requires toolInterleave), assumption (requires newThoughtTypes), decomposition (requires newThoughtTypes), backtrack (requires newThoughtTypes)'))),
|
|
1401
|
+
quality_score: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.number(), valibot__rspack_import_0.minValue(0), valibot__rspack_import_0.maxValue(1), valibot__rspack_import_0.description('Self-assessed quality score (0-1)'))),
|
|
1402
|
+
confidence: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.number(), valibot__rspack_import_0.minValue(0), valibot__rspack_import_0.maxValue(1), valibot__rspack_import_0.description('Explicit confidence in correctness (0-1)'))),
|
|
1403
|
+
hypothesis_id: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.regex(/^[a-zA-Z0-9_-]+$/, 'Hypothesis ID must contain only letters, numbers, hyphens, and underscores'), valibot__rspack_import_0.minLength(1), valibot__rspack_import_0.maxLength(50), valibot__rspack_import_0.description('Identifier linking hypothesis to verification thoughts'))),
|
|
1404
|
+
verification_target: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.number(), valibot__rspack_import_0.minValue(1), valibot__rspack_import_0.description('Thought number being verified or critiqued'))),
|
|
1405
|
+
synthesis_sources: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(valibot__rspack_import_0.pipe(valibot__rspack_import_0.number(), valibot__rspack_import_0.minValue(1))), valibot__rspack_import_0.description('Thought numbers being synthesized'))),
|
|
1406
|
+
merge_from_thoughts: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(valibot__rspack_import_0.pipe(valibot__rspack_import_0.number(), valibot__rspack_import_0.minValue(1))), valibot__rspack_import_0.description('Thought numbers from other branches being merged (DAG)'))),
|
|
1407
|
+
merge_branch_ids: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.regex(/^[a-zA-Z0-9_-]+$/), valibot__rspack_import_0.maxLength(50))), valibot__rspack_import_0.description('Branch IDs being merged into current context'))),
|
|
1408
|
+
meta_observation: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.description('Metacognitive observation about reasoning process'))),
|
|
1409
|
+
reasoning_depth: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.picklist([
|
|
1410
|
+
'shallow',
|
|
1411
|
+
'moderate',
|
|
1412
|
+
'deep'
|
|
1413
|
+
]), valibot__rspack_import_0.description('Effort signal: how deep reasoning should go'))),
|
|
1414
|
+
session_id: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.regex(/^[a-zA-Z0-9_-]+$/, 'Session ID must contain only letters, numbers, hyphens, and underscores'), valibot__rspack_import_0.minLength(1), valibot__rspack_import_0.maxLength(100), valibot__rspack_import_0.description('Optional session identifier for state isolation. When provided, thought history, branches, and statistics are scoped to this session. Omitting preserves global behavior.'))),
|
|
1415
|
+
reset_state: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.boolean(), valibot__rspack_import_0.description('When true, clears all state for the target session before processing this thought. The thought is then processed as the first in a fresh session.'))),
|
|
1416
|
+
tool_name: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.minLength(1), valibot__rspack_import_0.description('Name of the tool being invoked (for tool_call thoughts)'))),
|
|
1417
|
+
tool_arguments: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.record(valibot__rspack_import_0.string(), valibot__rspack_import_0.unknown()), valibot__rspack_import_0.description('Arguments passed to the tool (for tool_call thoughts)'))),
|
|
1418
|
+
tool_result: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.unknown(), valibot__rspack_import_0.description('Result returned by the tool (for tool_observation thoughts)'))),
|
|
1419
|
+
continuation_token: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.minLength(1), valibot__rspack_import_0.description('Token for resuming long-running tool invocations'))),
|
|
1420
|
+
decomposition_children: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.array(valibot__rspack_import_0.string()), valibot__rspack_import_0.description('Child thought IDs produced by decomposition'))),
|
|
1421
|
+
backtrack_target: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.number(), valibot__rspack_import_0.integer(), valibot__rspack_import_0.minValue(1), valibot__rspack_import_0.description('Thought number to backtrack to. When the parent thought has thought_type=backtrack, this thought is logically retracted: it remains in history but is excluded from quality signals and reasoning stats.'))),
|
|
1422
|
+
register_branch_id: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.regex(/^[a-zA-Z0-9_-]+$/, 'register_branch_id must contain only letters, numbers, hyphens, and underscores'), valibot__rspack_import_0.minLength(1), valibot__rspack_import_0.maxLength(50), valibot__rspack_import_0.description('Pre-declares a branch ID for this session before any thoughts reference it. Useful so that subsequent thoughts using merge_branch_ids can target a branch that has not yet received any thoughts.')))
|
|
1423
|
+
});
|
|
1424
|
+
/**
|
|
1425
|
+
* The sequential thinking tool definition for MCP registration.
|
|
1426
|
+
*
|
|
1427
|
+
* This object defines the tool that is registered with the MCP server.
|
|
1428
|
+
* The inputSchema is left empty as the schema is handled by tmcp
|
|
1429
|
+
* when registering the tool using the Valibot adapter.
|
|
1430
|
+
*
|
|
1431
|
+
* @example
|
|
1432
|
+
* ```typescript
|
|
1433
|
+
* import { SEQUENTIAL_THINKING_TOOL } from './schema.js';
|
|
1434
|
+
* import { McpServer } from 'tmcp';
|
|
1435
|
+
*
|
|
1436
|
+
* const server = new McpServer({ name: 'my-server', version: '1.0.0' });
|
|
1437
|
+
* server.tool({
|
|
1438
|
+
* name: SEQUENTIAL_THINKING_TOOL.name,
|
|
1439
|
+
* description: SEQUENTIAL_THINKING_TOOL.description,
|
|
1440
|
+
* schema: SequentialThinkingSchema
|
|
1441
|
+
* }, handler);
|
|
1442
|
+
* ```
|
|
1443
|
+
*/ const SEQUENTIAL_THINKING_TOOL = {
|
|
1444
|
+
name: 'sequentialthinking_tools',
|
|
1445
|
+
description: TOOL_DESCRIPTION,
|
|
1446
|
+
inputSchema: {}
|
|
1447
|
+
};
|
|
1448
|
+
/**
|
|
1449
|
+
* Valibot schema for validating JSON-RPC 2.0 request messages.
|
|
1450
|
+
*
|
|
1451
|
+
* Validates that a JSON-RPC request has:
|
|
1452
|
+
* - A jsonrpc version (must be "2.0")
|
|
1453
|
+
* - A method name (string)
|
|
1454
|
+
* - Optional params (object or array)
|
|
1455
|
+
* - Optional id (string, number, or null for notifications)
|
|
1456
|
+
*
|
|
1457
|
+
* @example
|
|
1458
|
+
* ```typescript
|
|
1459
|
+
* import { safeParse } from 'valibot';
|
|
1460
|
+
* import { JsonRpcRequestSchema } from './schema.js';
|
|
1461
|
+
*
|
|
1462
|
+
* const result = safeParse(JsonRpcRequestSchema, {
|
|
1463
|
+
* jsonrpc: '2.0',
|
|
1464
|
+
* method: 'tools/list',
|
|
1465
|
+
* id: 1
|
|
1466
|
+
* });
|
|
1467
|
+
* ```
|
|
1468
|
+
*/ const JsonRpcRequestSchema = valibot__rspack_import_0.object({
|
|
1469
|
+
jsonrpc: valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.literal('2.0'), valibot__rspack_import_0.description('JSON-RPC protocol version (must be "2.0")')),
|
|
1470
|
+
method: valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.minLength(1), valibot__rspack_import_0.description('Method name to invoke')),
|
|
1471
|
+
params: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.union([
|
|
1472
|
+
valibot__rspack_import_0.object({}),
|
|
1473
|
+
valibot__rspack_import_0.array(valibot__rspack_import_0.unknown())
|
|
1474
|
+
]), valibot__rspack_import_0.description('Method parameters (object or array)'))),
|
|
1475
|
+
id: valibot__rspack_import_0.optional(valibot__rspack_import_0.pipe(valibot__rspack_import_0.union([
|
|
1476
|
+
valibot__rspack_import_0.string(),
|
|
1477
|
+
valibot__rspack_import_0.number(),
|
|
1478
|
+
valibot__rspack_import_0["null"]()
|
|
1479
|
+
]), valibot__rspack_import_0.description('Request ID (omit for notifications)')))
|
|
1480
|
+
});
|
|
1481
|
+
/**
|
|
1482
|
+
* Schema for {@link EdgeKind} — the semantic relationship between two thoughts.
|
|
1483
|
+
*/ const EdgeKindSchema = valibot__rspack_import_0.union([
|
|
1484
|
+
valibot__rspack_import_0.literal('sequence'),
|
|
1485
|
+
valibot__rspack_import_0.literal('branch'),
|
|
1486
|
+
valibot__rspack_import_0.literal('merge'),
|
|
1487
|
+
valibot__rspack_import_0.literal('verifies'),
|
|
1488
|
+
valibot__rspack_import_0.literal('critiques'),
|
|
1489
|
+
valibot__rspack_import_0.literal('derives_from'),
|
|
1490
|
+
valibot__rspack_import_0.literal('tool_invocation'),
|
|
1491
|
+
valibot__rspack_import_0.literal('revises')
|
|
1492
|
+
]);
|
|
1493
|
+
/**
|
|
1494
|
+
* Schema for {@link Edge} — a directed edge in the thought DAG.
|
|
1495
|
+
*/ const EdgeSchema = valibot__rspack_import_0.object({
|
|
1496
|
+
id: valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.minLength(1), valibot__rspack_import_0.maxLength(30)),
|
|
1497
|
+
from: valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.minLength(1), valibot__rspack_import_0.maxLength(30)),
|
|
1498
|
+
to: valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.minLength(1), valibot__rspack_import_0.maxLength(30)),
|
|
1499
|
+
kind: EdgeKindSchema,
|
|
1500
|
+
sessionId: valibot__rspack_import_0.pipe(valibot__rspack_import_0.string(), valibot__rspack_import_0.minLength(1)),
|
|
1501
|
+
createdAt: valibot__rspack_import_0.number(),
|
|
1502
|
+
metadata: valibot__rspack_import_0.optional(valibot__rspack_import_0.record(valibot__rspack_import_0.string(), valibot__rspack_import_0.unknown()))
|
|
1503
|
+
});
|
|
1504
|
+
|
|
1505
|
+
|
|
1506
|
+
},
|
|
1507
|
+
419(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
1508
|
+
|
|
1509
|
+
// EXPORTS
|
|
1510
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
1511
|
+
j: () => (/* binding */ BaseTransport)
|
|
1512
|
+
});
|
|
1513
|
+
|
|
1514
|
+
// EXTERNAL MODULE: external "node:crypto"
|
|
1515
|
+
var external_node_crypto_ = __webpack_require__(561);
|
|
1516
|
+
;// CONCATENATED MODULE: ./src/core/ids.ts
|
|
1517
|
+
/**
|
|
1518
|
+
* Unique identifier generation utilities for thoughts and edges.
|
|
1519
|
+
*
|
|
1520
|
+
* @module core/ids
|
|
1521
|
+
*/
|
|
1522
|
+
/**
|
|
1523
|
+
* Generate a unique lexicographically-sortable identifier.
|
|
1524
|
+
*
|
|
1525
|
+
* Format: 8-char base36 timestamp + 20-char hex random (10 random bytes).
|
|
1526
|
+
* Result is up to 28 characters, sortable by creation time.
|
|
1527
|
+
*
|
|
1528
|
+
* @returns A unique string identifier.
|
|
1529
|
+
*
|
|
1530
|
+
* @example
|
|
1531
|
+
* ```typescript
|
|
1532
|
+
* const id = generateUlid(); // '01h2k3m400a1b2c3d4e5f6...'
|
|
1533
|
+
* ```
|
|
1534
|
+
*/ function generateUlid() {
|
|
1535
|
+
const timestamp = Date.now().toString(36).padStart(8, '0');
|
|
1536
|
+
const random = crypto.randomBytes(10).toString('hex');
|
|
1537
|
+
return `${timestamp}${random}`;
|
|
1538
|
+
}
|
|
1539
|
+
/**
|
|
1540
|
+
* Valid session ID pattern: alphanumeric, hyphens, underscores.
|
|
1541
|
+
*
|
|
1542
|
+
* Length is enforced separately via {@link MAX_SESSION_ID_LENGTH}.
|
|
1543
|
+
* Same character set as branch IDs.
|
|
1544
|
+
*/ const SESSION_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
1545
|
+
/**
|
|
1546
|
+
* Maximum session ID length (in characters).
|
|
1547
|
+
*
|
|
1548
|
+
* Allows compound identifiers (e.g. `user-123_task-abc`).
|
|
1549
|
+
*/ const MAX_SESSION_ID_LENGTH = 100;
|
|
1550
|
+
|
|
1551
|
+
;// CONCATENATED MODULE: ./src/transport/BaseTransport.ts
|
|
1552
|
+
/**
|
|
1553
|
+
* Base transport implementation.
|
|
1554
|
+
*
|
|
1555
|
+
* This class provides shared functionality for all transport implementations,
|
|
1556
|
+
* including session validation, rate limiting, CORS handling, and IP extraction.
|
|
1557
|
+
*
|
|
1558
|
+
* @remarks
|
|
1559
|
+
* **Security Features:**
|
|
1560
|
+
* - Session ID validation (alphanumeric, max 64 chars)
|
|
1561
|
+
* - Query parameter sanitization (whitelist allowed keys)
|
|
1562
|
+
* - Rate limiting per IP (configurable, default 100 req/min)
|
|
1563
|
+
* - CORS origin validation
|
|
1564
|
+
*
|
|
1565
|
+
* **Rate Limiting:**
|
|
1566
|
+
* - Tracks requests per IP address within a time window
|
|
1567
|
+
* - Returns 429 Too Many Requests when limit exceeded
|
|
1568
|
+
* - Can be disabled via `enableRateLimit: false`
|
|
1569
|
+
*/
|
|
1570
|
+
/**
|
|
1571
|
+
* No-op logger that does nothing. Used when no logger is provided.
|
|
1572
|
+
*/ class NoopLogger {
|
|
1573
|
+
_level = 'info';
|
|
1574
|
+
info(_message, _meta) {}
|
|
1575
|
+
warn(_message, _meta) {}
|
|
1576
|
+
error(_message, _meta) {}
|
|
1577
|
+
debug(_message, _meta) {}
|
|
1578
|
+
setLevel(level) {
|
|
1579
|
+
this._level = level;
|
|
1580
|
+
}
|
|
1581
|
+
getLevel() {
|
|
1582
|
+
return this._level;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Allowed query parameter names (whitelist for security).
|
|
1587
|
+
*/ const ALLOWED_QUERY_PARAMS = new Set([
|
|
1588
|
+
'session',
|
|
1589
|
+
'sessionId',
|
|
1590
|
+
'client',
|
|
1591
|
+
'clientId'
|
|
1592
|
+
]);
|
|
1593
|
+
/**
|
|
1594
|
+
* Rate limit settings (requests per minute per IP).
|
|
1595
|
+
*/ const RATE_LIMIT_REQUESTS = 100;
|
|
1596
|
+
const RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minute
|
|
1597
|
+
class BaseTransport {
|
|
1598
|
+
_port;
|
|
1599
|
+
_host;
|
|
1600
|
+
_corsOrigin;
|
|
1601
|
+
_enableCors;
|
|
1602
|
+
_rateLimitEnabled;
|
|
1603
|
+
_maxRequestsPerMinute;
|
|
1604
|
+
_allowedHosts;
|
|
1605
|
+
_rateLimitMap = new Map();
|
|
1606
|
+
_rateLimitCleanupIntervalId = null;
|
|
1607
|
+
_wasHostExplicitlySet;
|
|
1608
|
+
/** Shutdown state for graceful shutdown. */ _isShuttingDown = false;
|
|
1609
|
+
_logger;
|
|
1610
|
+
_healthChecker;
|
|
1611
|
+
constructor(options = {}){
|
|
1612
|
+
this._port = options.port ?? 9108;
|
|
1613
|
+
this._host = options.host ?? '127.0.0.1';
|
|
1614
|
+
this._wasHostExplicitlySet = options.host !== undefined;
|
|
1615
|
+
this._corsOrigin = options.corsOrigin ?? '*';
|
|
1616
|
+
this._enableCors = options.enableCors ?? true;
|
|
1617
|
+
this._rateLimitEnabled = options.enableRateLimit ?? true;
|
|
1618
|
+
this._maxRequestsPerMinute = options.maxRequestsPerMinute ?? RATE_LIMIT_REQUESTS;
|
|
1619
|
+
this._allowedHosts = this._buildAllowedHosts(options.allowedHosts);
|
|
1620
|
+
this._isShuttingDown = false;
|
|
1621
|
+
this._logger = options.logger ?? new NoopLogger();
|
|
1622
|
+
this._healthChecker = options.healthChecker ?? null;
|
|
1623
|
+
if (this._rateLimitEnabled) {
|
|
1624
|
+
this._startRateLimitCleanup();
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Get the server URL with localhost substitution for default host.
|
|
1629
|
+
*/ get serverUrl() {
|
|
1630
|
+
const host = !this._wasHostExplicitlySet && this._host === '127.0.0.1' ? 'localhost' : this._host;
|
|
1631
|
+
return `http://${host}:${this._port}`;
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Validate session ID format.
|
|
1635
|
+
*
|
|
1636
|
+
* @param sessionId - The session ID to validate
|
|
1637
|
+
* @returns true if valid, false otherwise
|
|
1638
|
+
*/ validateSessionId(sessionId) {
|
|
1639
|
+
if (sessionId.length > MAX_SESSION_ID_LENGTH) {
|
|
1640
|
+
return false;
|
|
1641
|
+
}
|
|
1642
|
+
return SESSION_ID_PATTERN.test(sessionId);
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* Sanitize query parameters by removing any not in whitelist.
|
|
1646
|
+
*
|
|
1647
|
+
* @param url - The URL object containing query parameters
|
|
1648
|
+
* @returns A sanitized record of allowed query parameters
|
|
1649
|
+
*/ sanitizeQueryParams(url) {
|
|
1650
|
+
const sanitized = {};
|
|
1651
|
+
for (const [key, value] of url.searchParams.entries()){
|
|
1652
|
+
if (ALLOWED_QUERY_PARAMS.has(key)) {
|
|
1653
|
+
sanitized[key] = value;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
return sanitized;
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Check rate limit for a given IP address.
|
|
1660
|
+
*
|
|
1661
|
+
* @param ip - The IP address to check
|
|
1662
|
+
* @returns true if rate limit exceeded, false otherwise
|
|
1663
|
+
*/ checkRateLimit(ip) {
|
|
1664
|
+
if (!this._rateLimitEnabled) {
|
|
1665
|
+
return false;
|
|
1666
|
+
}
|
|
1667
|
+
const now = Date.now();
|
|
1668
|
+
this._cleanupExpiredRateLimitEntries(now);
|
|
1669
|
+
const record = this._rateLimitMap.get(ip);
|
|
1670
|
+
if (!record || now > record.resetTime) {
|
|
1671
|
+
this._rateLimitMap.set(ip, {
|
|
1672
|
+
count: 1,
|
|
1673
|
+
resetTime: now + RATE_LIMIT_WINDOW_MS
|
|
1674
|
+
});
|
|
1675
|
+
return false;
|
|
1676
|
+
}
|
|
1677
|
+
if (record.count >= this._maxRequestsPerMinute) {
|
|
1678
|
+
return true; // Rate limit exceeded
|
|
1679
|
+
}
|
|
1680
|
+
record.count++;
|
|
1681
|
+
return false;
|
|
1682
|
+
}
|
|
1683
|
+
_cleanupExpiredRateLimitEntries(now = Date.now()) {
|
|
1684
|
+
for (const [ip, record] of this._rateLimitMap.entries()){
|
|
1685
|
+
if (record.resetTime <= now) {
|
|
1686
|
+
this._rateLimitMap.delete(ip);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
_startRateLimitCleanup() {
|
|
1691
|
+
if (this._rateLimitCleanupIntervalId !== null) {
|
|
1692
|
+
clearInterval(this._rateLimitCleanupIntervalId);
|
|
1693
|
+
}
|
|
1694
|
+
this._rateLimitCleanupIntervalId = setInterval(()=>{
|
|
1695
|
+
this._cleanupExpiredRateLimitEntries();
|
|
1696
|
+
}, RATE_LIMIT_WINDOW_MS);
|
|
1697
|
+
}
|
|
1698
|
+
_stopRateLimitCleanup() {
|
|
1699
|
+
if (this._rateLimitCleanupIntervalId !== null) {
|
|
1700
|
+
clearInterval(this._rateLimitCleanupIntervalId);
|
|
1701
|
+
this._rateLimitCleanupIntervalId = null;
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* Get client IP address from request.
|
|
1706
|
+
*
|
|
1707
|
+
* @param req - The incoming request
|
|
1708
|
+
* @returns The client IP address
|
|
1709
|
+
*/ getClientIp(req) {
|
|
1710
|
+
const forwardedFor = req.headers['x-forwarded-for'];
|
|
1711
|
+
if (forwardedFor && typeof forwardedFor === 'string') {
|
|
1712
|
+
return forwardedFor.split(',')[0].trim();
|
|
1713
|
+
}
|
|
1714
|
+
const remoteAddress = req.socket.remoteAddress;
|
|
1715
|
+
return remoteAddress || 'unknown';
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Validate CORS origin from request headers.
|
|
1719
|
+
*
|
|
1720
|
+
* @param req - The incoming request
|
|
1721
|
+
* @returns true if origin is valid, false otherwise
|
|
1722
|
+
*/ validateCorsOrigin(req) {
|
|
1723
|
+
if (this._corsOrigin === '*') {
|
|
1724
|
+
return true;
|
|
1725
|
+
}
|
|
1726
|
+
const origin = req.headers.origin;
|
|
1727
|
+
if (!origin) {
|
|
1728
|
+
return true; // No origin header is acceptable
|
|
1729
|
+
}
|
|
1730
|
+
// Exact match
|
|
1731
|
+
if (this._corsOrigin === origin) {
|
|
1732
|
+
return true;
|
|
1733
|
+
}
|
|
1734
|
+
// Check if configured origin is a wildcard pattern
|
|
1735
|
+
if (this._corsOrigin.includes('*')) {
|
|
1736
|
+
// Escape all regex metacharacters EXCEPT *,
|
|
1737
|
+
// then replace * with a hostname-safe pattern (alphanumeric, hyphens, dots)
|
|
1738
|
+
const escaped = this._corsOrigin.replace(/[.+?^${}()|[\]\\]/g, '\\$&') // escape metacharacters (not *)
|
|
1739
|
+
.replace(/\*/g, '[a-zA-Z0-9.-]*'); // * matches valid hostname chars only
|
|
1740
|
+
const regex = new RegExp(`^${escaped}$`);
|
|
1741
|
+
return regex.test(origin);
|
|
1742
|
+
}
|
|
1743
|
+
return false;
|
|
1744
|
+
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Set CORS headers on response.
|
|
1747
|
+
*
|
|
1748
|
+
* @param res - The server response
|
|
1749
|
+
*/ setCorsHeaders(res) {
|
|
1750
|
+
if (this._enableCors) {
|
|
1751
|
+
res.setHeader('Access-Control-Allow-Origin', this._corsOrigin);
|
|
1752
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
1753
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
validateHostHeader(req) {
|
|
1757
|
+
const rawHost = req.headers.host;
|
|
1758
|
+
if (!rawHost) {
|
|
1759
|
+
return true;
|
|
1760
|
+
}
|
|
1761
|
+
const hostWithoutPort = rawHost.split(':')[0].trim().toLowerCase();
|
|
1762
|
+
if (!hostWithoutPort) {
|
|
1763
|
+
return false;
|
|
1764
|
+
}
|
|
1765
|
+
if (this._allowedHosts.size === 0) {
|
|
1766
|
+
return true;
|
|
1767
|
+
}
|
|
1768
|
+
return this._allowedHosts.has(hostWithoutPort);
|
|
1769
|
+
}
|
|
1770
|
+
_buildAllowedHosts(configuredHosts) {
|
|
1771
|
+
if (configuredHosts && configuredHosts.length > 0) {
|
|
1772
|
+
return new Set(configuredHosts.map((host)=>host.toLowerCase().trim()).filter(Boolean));
|
|
1773
|
+
}
|
|
1774
|
+
const boundHost = this._host.toLowerCase();
|
|
1775
|
+
const localHosts = [
|
|
1776
|
+
'localhost',
|
|
1777
|
+
'127.0.0.1',
|
|
1778
|
+
'::1'
|
|
1779
|
+
];
|
|
1780
|
+
if (localHosts.includes(boundHost)) {
|
|
1781
|
+
return new Set(localHosts);
|
|
1782
|
+
}
|
|
1783
|
+
if (boundHost === '0.0.0.0' || boundHost === '::') {
|
|
1784
|
+
return new Set(localHosts);
|
|
1785
|
+
}
|
|
1786
|
+
return new Set([
|
|
1787
|
+
boundHost
|
|
1788
|
+
]);
|
|
1789
|
+
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Log a message using the configured logger.
|
|
1792
|
+
*
|
|
1793
|
+
* @param level - Log level
|
|
1794
|
+
* @param message - Message to log
|
|
1795
|
+
* @param meta - Optional metadata
|
|
1796
|
+
*/ log(level, message, meta) {
|
|
1797
|
+
if (level === 'info') {
|
|
1798
|
+
this._logger.info(message, meta);
|
|
1799
|
+
} else if (level === 'warn') {
|
|
1800
|
+
this._logger.warn(message, meta);
|
|
1801
|
+
} else {
|
|
1802
|
+
this._logger.error(message, meta);
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Check if transport is shutting down.
|
|
1807
|
+
* @returns true if in shutdown phase
|
|
1808
|
+
*/ get isShuttingDown() {
|
|
1809
|
+
return this._isShuttingDown;
|
|
1810
|
+
}
|
|
1811
|
+
/**
|
|
1812
|
+
* Handle GET /health endpoint — liveness check.
|
|
1813
|
+
*
|
|
1814
|
+
* Builds a standard health response with optional liveness data from the health checker.
|
|
1815
|
+
* Transports can pass extra data (e.g. client counts, session info).
|
|
1816
|
+
*
|
|
1817
|
+
* @param res - The server response
|
|
1818
|
+
* @param extraData - Optional additional health metadata
|
|
1819
|
+
*/ handleHealthEndpoint(res, extraData) {
|
|
1820
|
+
const healthData = {
|
|
1821
|
+
status: 'healthy',
|
|
1822
|
+
...extraData
|
|
1823
|
+
};
|
|
1824
|
+
if (this._healthChecker) {
|
|
1825
|
+
const liveness = this._healthChecker.checkLiveness();
|
|
1826
|
+
healthData.liveness = liveness;
|
|
1827
|
+
}
|
|
1828
|
+
res.writeHead(200, {
|
|
1829
|
+
'Content-Type': 'application/json'
|
|
1830
|
+
});
|
|
1831
|
+
res.end(JSON.stringify(healthData));
|
|
1832
|
+
}
|
|
1833
|
+
/**
|
|
1834
|
+
* Handle GET /ready endpoint — readiness check.
|
|
1835
|
+
*
|
|
1836
|
+
* Delegates to the health checker if available, otherwise returns a default OK response.
|
|
1837
|
+
*
|
|
1838
|
+
* @param res - The server response
|
|
1839
|
+
*/ async handleReadinessEndpoint(res) {
|
|
1840
|
+
if (this._healthChecker) {
|
|
1841
|
+
const readiness = await this._healthChecker.checkReadiness();
|
|
1842
|
+
const statusCode = readiness.status === 'ok' ? 200 : 503;
|
|
1843
|
+
res.writeHead(statusCode, {
|
|
1844
|
+
'Content-Type': 'application/json'
|
|
1845
|
+
});
|
|
1846
|
+
res.end(JSON.stringify(readiness));
|
|
1847
|
+
} else {
|
|
1848
|
+
res.writeHead(200, {
|
|
1849
|
+
'Content-Type': 'application/json'
|
|
1850
|
+
});
|
|
1851
|
+
res.end(JSON.stringify({
|
|
1852
|
+
status: 'ok',
|
|
1853
|
+
timestamp: new Date().toISOString(),
|
|
1854
|
+
components: {}
|
|
1855
|
+
}));
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Handle GET /metrics endpoint — Prometheus metrics.
|
|
1860
|
+
*
|
|
1861
|
+
* Returns 404 if no metrics provider is configured.
|
|
1862
|
+
*
|
|
1863
|
+
* @param res - The server response
|
|
1864
|
+
* @param metricsProvider - Function that returns Prometheus-format metrics text
|
|
1865
|
+
*/ handleMetricsEndpoint(res, metricsProvider) {
|
|
1866
|
+
if (!metricsProvider) {
|
|
1867
|
+
res.writeHead(404, {
|
|
1868
|
+
'Content-Type': 'text/plain'
|
|
1869
|
+
});
|
|
1870
|
+
res.end('Not Found');
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
res.writeHead(200, {
|
|
1874
|
+
'Content-Type': 'text/plain; version=0.0.4; charset=utf-8'
|
|
1875
|
+
});
|
|
1876
|
+
res.end(metricsProvider());
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
|
|
1881
|
+
},
|
|
1882
|
+
390(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
1883
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
1884
|
+
SseTransport: () => (SseTransport)
|
|
1885
|
+
});
|
|
1886
|
+
/* import */ var node_http__rspack_import_0 = __webpack_require__(316);
|
|
1887
|
+
/* import */ var node_url__rspack_import_1 = __webpack_require__(61);
|
|
1888
|
+
/* import */ var valibot__rspack_import_2 = __webpack_require__(821);
|
|
1889
|
+
/* import */ var _schema_js__rspack_import_3 = __webpack_require__(613);
|
|
1890
|
+
/* import */ var _BaseTransport_js__rspack_import_4 = __webpack_require__(419);
|
|
1891
|
+
/**
|
|
1892
|
+
* SSE (Server-Sent Events) Transport implementation.
|
|
1893
|
+
*
|
|
1894
|
+
* This transport allows multiple concurrent connections over HTTP using Server-Sent Events,
|
|
1895
|
+
* enabling multi-user scenarios and horizontal scaling.
|
|
1896
|
+
*
|
|
1897
|
+
* When a ConnectionPool is provided, each SSE client gets an isolated session with its own
|
|
1898
|
+
* thought history. Without a pool, all clients share a single server instance (backward compatible).
|
|
1899
|
+
*
|
|
1900
|
+
* @example
|
|
1901
|
+
* ```typescript
|
|
1902
|
+
* const transport = new SseTransport({
|
|
1903
|
+
* port: 3000,
|
|
1904
|
+
* host: 'localhost'
|
|
1905
|
+
* });
|
|
1906
|
+
* await transport.connect(server);
|
|
1907
|
+
* ```
|
|
1908
|
+
*/
|
|
1909
|
+
|
|
1910
|
+
|
|
1911
|
+
|
|
1912
|
+
|
|
1913
|
+
/**
|
|
1914
|
+
* SSE Transport for MCP server over HTTP.
|
|
1915
|
+
*
|
|
1916
|
+
* This transport uses Server-Sent Events (SSE) to communicate with clients,
|
|
1917
|
+
* allowing multiple concurrent connections and web-based clients.
|
|
1918
|
+
*
|
|
1919
|
+
* @remarks
|
|
1920
|
+
* **Security Features:**
|
|
1921
|
+
* - Session ID validation (alphanumeric, max 64 chars)
|
|
1922
|
+
* - Query parameter sanitization (whitelist allowed keys)
|
|
1923
|
+
* - Rate limiting per IP (configurable, default 100 req/min)
|
|
1924
|
+
* - CORS origin validation
|
|
1925
|
+
*
|
|
1926
|
+
* **Rate Limiting:**
|
|
1927
|
+
* - Tracks requests per IP address within a time window
|
|
1928
|
+
* - Returns 429 Too Many Requests when limit exceeded
|
|
1929
|
+
* - Can be disabled via `enableRateLimit: false`
|
|
1930
|
+
*/ class SseTransport extends _BaseTransport_js__rspack_import_4/* .BaseTransport */.j {
|
|
1931
|
+
get kind() {
|
|
1932
|
+
return 'sse';
|
|
1933
|
+
}
|
|
1934
|
+
_server;
|
|
1935
|
+
_path;
|
|
1936
|
+
_clients = new Set();
|
|
1937
|
+
_clientSessionMap = new Map();
|
|
1938
|
+
_messageQueue = new Map();
|
|
1939
|
+
_metrics;
|
|
1940
|
+
_connectionPool;
|
|
1941
|
+
constructor(options = {}){
|
|
1942
|
+
super(options);
|
|
1943
|
+
this._path = options.path ?? '/sse';
|
|
1944
|
+
this._metrics = options.metrics;
|
|
1945
|
+
this._connectionPool = options.connectionPool;
|
|
1946
|
+
this._updateActiveConnectionsMetric();
|
|
1947
|
+
this._server = (0,node_http__rspack_import_0.createServer)((req, res)=>this._handleRequest(req, res));
|
|
1948
|
+
}
|
|
1949
|
+
/**
|
|
1950
|
+
* Connect MCP server to this transport.
|
|
1951
|
+
*
|
|
1952
|
+
* @param mcpServer - The MCP server instance
|
|
1953
|
+
*/ async connect(mcpServer) {
|
|
1954
|
+
this._mcpServer = mcpServer;
|
|
1955
|
+
return new Promise((resolve)=>{
|
|
1956
|
+
this._server.listen(this._port, this._host, ()=>{
|
|
1957
|
+
this.log('info', `SSE transport listening on http://${this._host}:${this._port}`);
|
|
1958
|
+
resolve();
|
|
1959
|
+
});
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
_mcpServer = null;
|
|
1963
|
+
/**
|
|
1964
|
+
* Handle incoming HTTP requests
|
|
1965
|
+
*/ async _handleRequest(req, res) {
|
|
1966
|
+
const startTime = Date.now();
|
|
1967
|
+
const requestPath = req.url || '/';
|
|
1968
|
+
const requestMethod = req.method || 'GET';
|
|
1969
|
+
this._metrics?.counter('http_requests_total', 1, {
|
|
1970
|
+
transport: 'sse',
|
|
1971
|
+
method: requestMethod,
|
|
1972
|
+
path: requestPath
|
|
1973
|
+
}, 'Total HTTP requests');
|
|
1974
|
+
res.once('finish', ()=>{
|
|
1975
|
+
const durationSeconds = (Date.now() - startTime) / 1000;
|
|
1976
|
+
this._metrics?.histogram('http_request_duration_seconds', durationSeconds, {
|
|
1977
|
+
transport: 'sse',
|
|
1978
|
+
path: requestPath
|
|
1979
|
+
});
|
|
1980
|
+
});
|
|
1981
|
+
if (!this.validateHostHeader(req)) {
|
|
1982
|
+
this._metrics?.counter('http_request_errors_total', 1, {
|
|
1983
|
+
transport: 'sse',
|
|
1984
|
+
error_type: 'forbidden'
|
|
1985
|
+
}, 'Total HTTP request errors');
|
|
1986
|
+
res.writeHead(403, {
|
|
1987
|
+
'Content-Type': 'application/json'
|
|
1988
|
+
});
|
|
1989
|
+
res.end(JSON.stringify({
|
|
1990
|
+
error: 'Forbidden - invalid host header'
|
|
1991
|
+
}));
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
const url = new node_url__rspack_import_1.URL(req.url || '', `http://${req.headers.host}`);
|
|
1995
|
+
// Check rate limit first
|
|
1996
|
+
const clientIp = this.getClientIp(req);
|
|
1997
|
+
if (this.checkRateLimit(clientIp)) {
|
|
1998
|
+
this._metrics?.counter('http_request_errors_total', 1, {
|
|
1999
|
+
transport: 'sse',
|
|
2000
|
+
error_type: 'rate_limit'
|
|
2001
|
+
}, 'Total HTTP request errors');
|
|
2002
|
+
res.writeHead(429, {
|
|
2003
|
+
'Content-Type': 'application/json',
|
|
2004
|
+
'Retry-After': '60'
|
|
2005
|
+
});
|
|
2006
|
+
res.end(JSON.stringify({
|
|
2007
|
+
error: 'Too many requests'
|
|
2008
|
+
}));
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
// Validate CORS origin
|
|
2012
|
+
if (!this.validateCorsOrigin(req)) {
|
|
2013
|
+
this._metrics?.counter('http_request_errors_total', 1, {
|
|
2014
|
+
transport: 'sse',
|
|
2015
|
+
error_type: 'forbidden'
|
|
2016
|
+
}, 'Total HTTP request errors');
|
|
2017
|
+
res.writeHead(403, {
|
|
2018
|
+
'Content-Type': 'application/json'
|
|
2019
|
+
});
|
|
2020
|
+
res.end(JSON.stringify({
|
|
2021
|
+
error: 'Forbidden - invalid origin'
|
|
2022
|
+
}));
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
// Set CORS headers
|
|
2026
|
+
this.setCorsHeaders(res);
|
|
2027
|
+
// Sanitize query parameters
|
|
2028
|
+
const sanitizedParams = this.sanitizeQueryParams(url);
|
|
2029
|
+
// Validate session ID if present
|
|
2030
|
+
if (sanitizedParams.session || sanitizedParams.sessionId) {
|
|
2031
|
+
const sessionId = sanitizedParams.session ?? sanitizedParams.sessionId;
|
|
2032
|
+
if (!this.validateSessionId(sessionId)) {
|
|
2033
|
+
this._metrics?.counter('http_request_errors_total', 1, {
|
|
2034
|
+
transport: 'sse',
|
|
2035
|
+
error_type: 'validation'
|
|
2036
|
+
}, 'Total HTTP request errors');
|
|
2037
|
+
res.writeHead(400, {
|
|
2038
|
+
'Content-Type': 'application/json'
|
|
2039
|
+
});
|
|
2040
|
+
res.end(JSON.stringify({
|
|
2041
|
+
error: 'Invalid session ID format'
|
|
2042
|
+
}));
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
// Handle CORS preflight
|
|
2047
|
+
if (this._enableCors && req.method === 'OPTIONS') {
|
|
2048
|
+
res.writeHead(204);
|
|
2049
|
+
res.end();
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
2052
|
+
// Handle SSE endpoint
|
|
2053
|
+
if (url.pathname === this._path && req.method === 'GET') {
|
|
2054
|
+
await this._handleSseConnection(req, res, sanitizedParams);
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
// Handle message endpoint (for receiving messages from clients)
|
|
2058
|
+
if (url.pathname === `${this._path}/message` && req.method === 'POST') {
|
|
2059
|
+
await this._handleMessage(req, res, sanitizedParams);
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
// Handle health check (liveness)
|
|
2063
|
+
if (url.pathname === '/health') {
|
|
2064
|
+
this._handleHealthCheck(res);
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
// Handle readiness check
|
|
2068
|
+
if (url.pathname === '/ready') {
|
|
2069
|
+
await this._handleReadinessCheck(res);
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
// 404 for unknown paths
|
|
2073
|
+
res.writeHead(404, {
|
|
2074
|
+
'Content-Type': 'text/plain'
|
|
2075
|
+
});
|
|
2076
|
+
res.end('Not Found');
|
|
2077
|
+
}
|
|
2078
|
+
/**
|
|
2079
|
+
* Handle health check (liveness) endpoint
|
|
2080
|
+
*/ _handleHealthCheck(res) {
|
|
2081
|
+
const healthData = {
|
|
2082
|
+
status: 'healthy',
|
|
2083
|
+
clients: this._clients.size
|
|
2084
|
+
};
|
|
2085
|
+
if (this._connectionPool) {
|
|
2086
|
+
const poolStats = this._connectionPool.getStats();
|
|
2087
|
+
healthData.pool = poolStats;
|
|
2088
|
+
}
|
|
2089
|
+
if (this._healthChecker) {
|
|
2090
|
+
const liveness = this._healthChecker.checkLiveness();
|
|
2091
|
+
healthData.liveness = liveness;
|
|
2092
|
+
}
|
|
2093
|
+
res.writeHead(200, {
|
|
2094
|
+
'Content-Type': 'application/json'
|
|
2095
|
+
});
|
|
2096
|
+
res.end(JSON.stringify(healthData));
|
|
2097
|
+
}
|
|
2098
|
+
/**
|
|
2099
|
+
* Handle readiness check endpoint
|
|
2100
|
+
*/ async _handleReadinessCheck(res) {
|
|
2101
|
+
if (this._healthChecker) {
|
|
2102
|
+
const readiness = await this._healthChecker.checkReadiness();
|
|
2103
|
+
const statusCode = readiness.status === 'ok' ? 200 : 503;
|
|
2104
|
+
res.writeHead(statusCode, {
|
|
2105
|
+
'Content-Type': 'application/json'
|
|
2106
|
+
});
|
|
2107
|
+
res.end(JSON.stringify(readiness));
|
|
2108
|
+
} else {
|
|
2109
|
+
res.writeHead(200, {
|
|
2110
|
+
'Content-Type': 'application/json'
|
|
2111
|
+
});
|
|
2112
|
+
res.end(JSON.stringify({
|
|
2113
|
+
status: 'ok',
|
|
2114
|
+
timestamp: new Date().toISOString(),
|
|
2115
|
+
components: {}
|
|
2116
|
+
}));
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Handle new SSE connection
|
|
2121
|
+
*/ async _handleSseConnection(req, res, params) {
|
|
2122
|
+
// Set SSE headers
|
|
2123
|
+
res.writeHead(200, {
|
|
2124
|
+
'Content-Type': 'text/event-stream',
|
|
2125
|
+
'Cache-Control': 'no-cache',
|
|
2126
|
+
Connection: 'keep-alive'
|
|
2127
|
+
});
|
|
2128
|
+
// Resolve session ID when pool is active
|
|
2129
|
+
let sessionId;
|
|
2130
|
+
if (this._connectionPool) {
|
|
2131
|
+
const requestedSession = params.session ?? params.sessionId;
|
|
2132
|
+
if (requestedSession && this._connectionPool.getSessionInfo(requestedSession)) {
|
|
2133
|
+
sessionId = requestedSession;
|
|
2134
|
+
} else {
|
|
2135
|
+
try {
|
|
2136
|
+
sessionId = await this._connectionPool.createSession();
|
|
2137
|
+
} catch (error) {
|
|
2138
|
+
res.write(`event: error\n`);
|
|
2139
|
+
res.write(`data: ${JSON.stringify({
|
|
2140
|
+
error: error instanceof Error ? error.message : 'Failed to create session'
|
|
2141
|
+
})}\n\n`);
|
|
2142
|
+
res.end();
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
this._clientSessionMap.set(res, sessionId);
|
|
2147
|
+
this._updatePoolMetrics();
|
|
2148
|
+
}
|
|
2149
|
+
// Send initial connection event
|
|
2150
|
+
const connectedPayload = {
|
|
2151
|
+
timestamp: Date.now()
|
|
2152
|
+
};
|
|
2153
|
+
if (sessionId) {
|
|
2154
|
+
connectedPayload.sessionId = sessionId;
|
|
2155
|
+
}
|
|
2156
|
+
this._sendSseEvent(res, 'connected', connectedPayload);
|
|
2157
|
+
// Add to clients
|
|
2158
|
+
this._clients.add(res);
|
|
2159
|
+
this._updateActiveConnectionsMetric();
|
|
2160
|
+
// Handle client disconnect
|
|
2161
|
+
req.on('close', ()=>{
|
|
2162
|
+
this._clients.delete(res);
|
|
2163
|
+
this._clientSessionMap.delete(res);
|
|
2164
|
+
this._updateActiveConnectionsMetric();
|
|
2165
|
+
});
|
|
2166
|
+
// Send any queued messages
|
|
2167
|
+
const clientId = this._generateClientId();
|
|
2168
|
+
const queued = this._messageQueue.get(clientId);
|
|
2169
|
+
if (queued) {
|
|
2170
|
+
for (const message of queued){
|
|
2171
|
+
this._sendSseEvent(res, 'message', message);
|
|
2172
|
+
}
|
|
2173
|
+
this._messageQueue.delete(clientId);
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* Handle incoming message from client
|
|
2178
|
+
*/ async _handleMessage(req, res, _params) {
|
|
2179
|
+
let body = '';
|
|
2180
|
+
for await (const chunk of req){
|
|
2181
|
+
body += chunk.toString();
|
|
2182
|
+
}
|
|
2183
|
+
try {
|
|
2184
|
+
const jsonRpcRequest = JSON.parse(body);
|
|
2185
|
+
const parseResult = (0,valibot__rspack_import_2.safeParse)(_schema_js__rspack_import_3/* .JsonRpcRequestSchema */.Uv, jsonRpcRequest);
|
|
2186
|
+
if (!parseResult.success) {
|
|
2187
|
+
this._metrics?.counter('http_request_errors_total', 1, {
|
|
2188
|
+
transport: 'sse',
|
|
2189
|
+
error_type: 'validation'
|
|
2190
|
+
}, 'Total HTTP request errors');
|
|
2191
|
+
res.writeHead(200, {
|
|
2192
|
+
'Content-Type': 'application/json'
|
|
2193
|
+
});
|
|
2194
|
+
res.end(JSON.stringify({
|
|
2195
|
+
jsonrpc: '2.0',
|
|
2196
|
+
id: jsonRpcRequest?.id ?? null,
|
|
2197
|
+
error: {
|
|
2198
|
+
code: -32600,
|
|
2199
|
+
message: 'Invalid Request',
|
|
2200
|
+
data: parseResult.issues
|
|
2201
|
+
}
|
|
2202
|
+
}));
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2205
|
+
// Process message through MCP server
|
|
2206
|
+
if (this._mcpServer) {
|
|
2207
|
+
const response = await this._mcpServer.receive(jsonRpcRequest, {
|
|
2208
|
+
sessionInfo: {}
|
|
2209
|
+
});
|
|
2210
|
+
res.writeHead(200, {
|
|
2211
|
+
'Content-Type': 'application/json'
|
|
2212
|
+
});
|
|
2213
|
+
if (response) {
|
|
2214
|
+
res.end(JSON.stringify(response));
|
|
2215
|
+
} else {
|
|
2216
|
+
res.end(JSON.stringify({
|
|
2217
|
+
jsonrpc: '2.0',
|
|
2218
|
+
id: jsonRpcRequest?.id ?? null,
|
|
2219
|
+
result: null
|
|
2220
|
+
}));
|
|
2221
|
+
}
|
|
2222
|
+
} else {
|
|
2223
|
+
this._metrics?.counter('http_request_errors_total', 1, {
|
|
2224
|
+
transport: 'sse',
|
|
2225
|
+
error_type: 'server_not_ready'
|
|
2226
|
+
}, 'Total HTTP request errors');
|
|
2227
|
+
res.writeHead(503, {
|
|
2228
|
+
'Content-Type': 'application/json'
|
|
2229
|
+
});
|
|
2230
|
+
res.end(JSON.stringify({
|
|
2231
|
+
error: 'Server not ready'
|
|
2232
|
+
}));
|
|
2233
|
+
}
|
|
2234
|
+
} catch {
|
|
2235
|
+
this._metrics?.counter('http_request_errors_total', 1, {
|
|
2236
|
+
transport: 'sse',
|
|
2237
|
+
error_type: 'parse_error'
|
|
2238
|
+
}, 'Total HTTP request errors');
|
|
2239
|
+
res.writeHead(400, {
|
|
2240
|
+
'Content-Type': 'application/json'
|
|
2241
|
+
});
|
|
2242
|
+
res.end(JSON.stringify({
|
|
2243
|
+
error: 'Invalid JSON'
|
|
2244
|
+
}));
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
/**
|
|
2248
|
+
* Send an SSE event to a specific client
|
|
2249
|
+
*/ _sendSseEvent(res, event, data) {
|
|
2250
|
+
try {
|
|
2251
|
+
res.write(`event: ${event}\n`);
|
|
2252
|
+
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
2253
|
+
} catch {
|
|
2254
|
+
// Client disconnected
|
|
2255
|
+
this._clients.delete(res);
|
|
2256
|
+
this._updateActiveConnectionsMetric();
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
_updateActiveConnectionsMetric() {
|
|
2260
|
+
this._metrics?.gauge('sse_active_connections', this._clients.size, {}, 'Current active SSE connections');
|
|
2261
|
+
}
|
|
2262
|
+
_updatePoolMetrics() {
|
|
2263
|
+
if (!this._connectionPool || !this._metrics) {
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
const stats = this._connectionPool.getStats();
|
|
2267
|
+
this._metrics.gauge('sse_pool_active_sessions', stats.activeSessions, {}, 'Active sessions in connection pool');
|
|
2268
|
+
this._metrics.gauge('sse_pool_total_sessions', stats.totalSessions, {}, 'Total sessions in connection pool');
|
|
2269
|
+
this._metrics.gauge('sse_pool_max_sessions', stats.maxSessions, {}, 'Maximum sessions in connection pool');
|
|
2270
|
+
}
|
|
2271
|
+
/**
|
|
2272
|
+
* Broadcast a message to all connected clients
|
|
2273
|
+
*/ broadcast(event, data) {
|
|
2274
|
+
for (const client of this._clients){
|
|
2275
|
+
this._sendSseEvent(client, event, data);
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Generate a unique client ID
|
|
2280
|
+
*/ _generateClientId() {
|
|
2281
|
+
return `client_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
2282
|
+
}
|
|
2283
|
+
/**
|
|
2284
|
+
* Get number of connected clients
|
|
2285
|
+
*/ get clientCount() {
|
|
2286
|
+
return this._clients.size;
|
|
2287
|
+
}
|
|
2288
|
+
/**
|
|
2289
|
+
* Get the connection pool, if one was configured.
|
|
2290
|
+
*/ get connectionPool() {
|
|
2291
|
+
return this._connectionPool;
|
|
2292
|
+
}
|
|
2293
|
+
/**
|
|
2294
|
+
* Stop the transport server with graceful shutdown.
|
|
2295
|
+
*
|
|
2296
|
+
* @param timeout - Maximum time to wait for requests to drain (not used for SSE)
|
|
2297
|
+
* @returns Promise that resolves when shutdown is complete
|
|
2298
|
+
*/ async stop(_timeout) {
|
|
2299
|
+
this._isShuttingDown = true;
|
|
2300
|
+
this._stopRateLimitCleanup();
|
|
2301
|
+
// Terminate connection pool if present
|
|
2302
|
+
if (this._connectionPool) {
|
|
2303
|
+
await this._connectionPool.terminate();
|
|
2304
|
+
}
|
|
2305
|
+
return new Promise((resolve)=>{
|
|
2306
|
+
// Close all client connections
|
|
2307
|
+
for (const client of this._clients){
|
|
2308
|
+
try {
|
|
2309
|
+
client.end();
|
|
2310
|
+
} catch {
|
|
2311
|
+
// Ignore errors
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
this._clients.clear();
|
|
2315
|
+
this._clientSessionMap.clear();
|
|
2316
|
+
this._updateActiveConnectionsMetric();
|
|
2317
|
+
// Close server
|
|
2318
|
+
this._server.close(()=>{
|
|
2319
|
+
this.log('info', 'SSE transport stopped');
|
|
2320
|
+
resolve();
|
|
2321
|
+
});
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
/**
|
|
2326
|
+
* Create an SSE transport with given options.
|
|
2327
|
+
*
|
|
2328
|
+
* @param options - Transport configuration
|
|
2329
|
+
* @returns A configured SSE transport
|
|
2330
|
+
*
|
|
2331
|
+
* @example
|
|
2332
|
+
* ```typescript
|
|
2333
|
+
* const transport = createSseTransport({ port: 3000 });
|
|
2334
|
+
* await transport.connect(mcpServer);
|
|
2335
|
+
* ```
|
|
2336
|
+
*/ function createSseTransport(options = {}) {
|
|
2337
|
+
return new SseTransport(options);
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
|
|
2341
|
+
},
|
|
2342
|
+
34(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
2343
|
+
|
|
2344
|
+
// EXPORTS
|
|
2345
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
2346
|
+
StreamableHttpTransport: () => (/* binding */ StreamableHttpTransport)
|
|
2347
|
+
});
|
|
2348
|
+
|
|
2349
|
+
// UNUSED EXPORTS: createStreamableHttpTransport
|
|
2350
|
+
|
|
2351
|
+
// EXTERNAL MODULE: external "node:crypto"
|
|
2352
|
+
var external_node_crypto_ = __webpack_require__(561);
|
|
2353
|
+
// EXTERNAL MODULE: external "node:http"
|
|
2354
|
+
var external_node_http_ = __webpack_require__(316);
|
|
2355
|
+
// EXTERNAL MODULE: external "valibot"
|
|
2356
|
+
var external_valibot_ = __webpack_require__(821);
|
|
2357
|
+
// EXTERNAL MODULE: ./src/errors.ts
|
|
2358
|
+
var errors = __webpack_require__(937);
|
|
2359
|
+
// EXTERNAL MODULE: ./src/schema.ts
|
|
2360
|
+
var schema = __webpack_require__(613);
|
|
2361
|
+
// EXTERNAL MODULE: ./src/transport/BaseTransport.ts + 1 modules
|
|
2362
|
+
var BaseTransport = __webpack_require__(419);
|
|
2363
|
+
;// CONCATENATED MODULE: ./src/transport/HttpHelpers.ts
|
|
2364
|
+
/**
|
|
2365
|
+
* Shared HTTP helper utilities for MCP transport implementations.
|
|
2366
|
+
*
|
|
2367
|
+
* Centralizes JSON-RPC response formatting, request body reading,
|
|
2368
|
+
* and common HTTP response patterns to eliminate duplication across
|
|
2369
|
+
* HttpTransport, StreamableHttpTransport, and SseTransport.
|
|
2370
|
+
*
|
|
2371
|
+
* @module transport/HttpHelpers
|
|
2372
|
+
*/ /**
|
|
2373
|
+
* Send a JSON-RPC 2.0 error response.
|
|
2374
|
+
*
|
|
2375
|
+
* Standardizes error response formatting across all transport implementations.
|
|
2376
|
+
* All JSON-RPC errors use the standard `{ jsonrpc, id, error }` shape.
|
|
2377
|
+
*
|
|
2378
|
+
* @param res - The server response to write to
|
|
2379
|
+
* @param statusCode - HTTP status code (e.g. 400, 403, 429, 500)
|
|
2380
|
+
* @param code - JSON-RPC error code (e.g. -32700, -32600, -32603)
|
|
2381
|
+
* @param message - Human-readable error message
|
|
2382
|
+
* @param id - Optional JSON-RPC request ID (defaults to null)
|
|
2383
|
+
* @param data - Optional additional error data
|
|
2384
|
+
*/ function sendJsonRpcError(res, statusCode, code, message, id = null, data) {
|
|
2385
|
+
const body = {
|
|
2386
|
+
jsonrpc: '2.0',
|
|
2387
|
+
id,
|
|
2388
|
+
error: {
|
|
2389
|
+
code,
|
|
2390
|
+
message
|
|
2391
|
+
}
|
|
2392
|
+
};
|
|
2393
|
+
if (data !== undefined) {
|
|
2394
|
+
body.error.data = data;
|
|
2395
|
+
}
|
|
2396
|
+
res.writeHead(statusCode, {
|
|
2397
|
+
'Content-Type': 'application/json'
|
|
2398
|
+
});
|
|
2399
|
+
res.end(JSON.stringify(body));
|
|
2400
|
+
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Send a JSON-RPC 2.0 success response.
|
|
2403
|
+
*
|
|
2404
|
+
* @param res - The server response to write to
|
|
2405
|
+
* @param response - The JSON-RPC response object to send
|
|
2406
|
+
* @param statusCode - HTTP status code (default: 200)
|
|
2407
|
+
* @param headers - Optional additional response headers
|
|
2408
|
+
*/ function sendJsonRpcResponse(res, response, statusCode = 200, headers = {}) {
|
|
2409
|
+
const defaultHeaders = {
|
|
2410
|
+
'Content-Type': 'application/json'
|
|
2411
|
+
};
|
|
2412
|
+
res.writeHead(statusCode, {
|
|
2413
|
+
...defaultHeaders,
|
|
2414
|
+
...headers
|
|
2415
|
+
});
|
|
2416
|
+
res.end(JSON.stringify(response));
|
|
2417
|
+
}
|
|
2418
|
+
/**
|
|
2419
|
+
* Send a CORS preflight (OPTIONS) response.
|
|
2420
|
+
*
|
|
2421
|
+
* @param res - The server response to write to
|
|
2422
|
+
* @param extraAllowHeaders - Optional extra Access-Control-Allow-Headers values
|
|
2423
|
+
*/ function sendCorsPreflight(res, extraAllowHeaders) {
|
|
2424
|
+
if (extraAllowHeaders && extraAllowHeaders.length > 0) {
|
|
2425
|
+
res.setHeader('Access-Control-Allow-Headers', `Content-Type, ${extraAllowHeaders.join(', ')}`);
|
|
2426
|
+
}
|
|
2427
|
+
res.writeHead(204);
|
|
2428
|
+
res.end();
|
|
2429
|
+
}
|
|
2430
|
+
/**
|
|
2431
|
+
* Read the full request body with optional size limit enforcement.
|
|
2432
|
+
*
|
|
2433
|
+
* Streams the request body chunks, tracking total size.
|
|
2434
|
+
* If the body exceeds `maxBodySize`, reading stops and `null` is returned
|
|
2435
|
+
* to indicate the payload is too large.
|
|
2436
|
+
*
|
|
2437
|
+
* @param req - The incoming HTTP request
|
|
2438
|
+
* @param maxBodySize - Maximum allowed body size in bytes (0 = unlimited)
|
|
2439
|
+
* @returns The body string, or `null` if the body exceeded the size limit
|
|
2440
|
+
*/ async function readRequestBody(req, maxBodySize) {
|
|
2441
|
+
let body = '';
|
|
2442
|
+
let bodySize = 0;
|
|
2443
|
+
for await (const chunk of req){
|
|
2444
|
+
const chunkStr = typeof chunk === 'string' ? chunk : chunk.toString();
|
|
2445
|
+
bodySize += chunkStr.length;
|
|
2446
|
+
if (maxBodySize > 0 && bodySize > maxBodySize) {
|
|
2447
|
+
return null;
|
|
2448
|
+
}
|
|
2449
|
+
body += chunkStr;
|
|
2450
|
+
}
|
|
2451
|
+
return body;
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
;// CONCATENATED MODULE: ./src/transport/StreamableHttpTransport.ts
|
|
2455
|
+
/**
|
|
2456
|
+
* Streamable HTTP Transport implementation (MCP spec recommended transport).
|
|
2457
|
+
*
|
|
2458
|
+
* This transport implements the MCP Streamable HTTP specification, which replaces
|
|
2459
|
+
* the deprecated SSE transport as the recommended HTTP-based transport since March 2025.
|
|
2460
|
+
*
|
|
2461
|
+
* Key features:
|
|
2462
|
+
* - POST /mcp for JSON-RPC requests (main MCP endpoint)
|
|
2463
|
+
* - GET /mcp for optional SSE server-to-client notifications
|
|
2464
|
+
* - Session management via Mcp-Session-Id header
|
|
2465
|
+
* - Supports both stateful (session-based) and stateless (per-request) modes
|
|
2466
|
+
* - Health endpoints (/health, /ready)
|
|
2467
|
+
*
|
|
2468
|
+
* @example
|
|
2469
|
+
* ```typescript
|
|
2470
|
+
* const transport = new StreamableHttpTransport({
|
|
2471
|
+
* port: 3000,
|
|
2472
|
+
* host: 'localhost',
|
|
2473
|
+
* stateful: true,
|
|
2474
|
+
* });
|
|
2475
|
+
* await transport.connect(server);
|
|
2476
|
+
* ```
|
|
2477
|
+
*/
|
|
2478
|
+
|
|
2479
|
+
|
|
2480
|
+
|
|
2481
|
+
|
|
2482
|
+
|
|
2483
|
+
|
|
2484
|
+
/**
|
|
2485
|
+
* Streamable HTTP Transport for MCP server.
|
|
2486
|
+
*
|
|
2487
|
+
* This transport implements the MCP Streamable HTTP specification,
|
|
2488
|
+
* providing JSON-RPC over HTTP with optional session management
|
|
2489
|
+
* and server-to-client SSE notification streams.
|
|
2490
|
+
*
|
|
2491
|
+
* @remarks
|
|
2492
|
+
* **Security Features (inherited from BaseTransport):**
|
|
2493
|
+
* - Session ID validation (alphanumeric, max 64 chars)
|
|
2494
|
+
* - Query parameter sanitization (whitelist allowed keys)
|
|
2495
|
+
* - Rate limiting per IP (configurable, default 100 req/min)
|
|
2496
|
+
* - CORS origin validation
|
|
2497
|
+
* - Host header validation
|
|
2498
|
+
*
|
|
2499
|
+
* **MCP Streamable HTTP Spec Compliance:**
|
|
2500
|
+
* - POST /mcp — JSON-RPC method calls
|
|
2501
|
+
* - GET /mcp — SSE notification stream (server-to-client)
|
|
2502
|
+
* - Mcp-Session-Id header for session management
|
|
2503
|
+
* - Content-Type: application/json for JSON-RPC responses
|
|
2504
|
+
* - Content-Type: text/event-stream for SSE notification streams
|
|
2505
|
+
*
|
|
2506
|
+
* **HTTP Status Code Mapping:**
|
|
2507
|
+
* - 200: Success (JSON-RPC response or SSE stream)
|
|
2508
|
+
* - 202: Accepted (JSON-RPC notification, no response body)
|
|
2509
|
+
* - 204: CORS Preflight (empty body)
|
|
2510
|
+
* - 400: Bad Request (invalid JSON, invalid session ID)
|
|
2511
|
+
* - 403: Forbidden (invalid CORS, invalid host)
|
|
2512
|
+
* - 404: Not Found
|
|
2513
|
+
* - 405: Method Not Allowed
|
|
2514
|
+
* - 413: Payload Too Large
|
|
2515
|
+
* - 429: Too Many Requests
|
|
2516
|
+
* - 500: Internal Server Error
|
|
2517
|
+
* - 503: Server Not Ready / Shutting Down
|
|
2518
|
+
*/ class StreamableHttpTransport extends BaseTransport/* .BaseTransport */.j {
|
|
2519
|
+
get kind() {
|
|
2520
|
+
return 'streamable-http';
|
|
2521
|
+
}
|
|
2522
|
+
_server = null;
|
|
2523
|
+
_mcpServer = null;
|
|
2524
|
+
_path;
|
|
2525
|
+
_stateful;
|
|
2526
|
+
_sessionIdGenerator;
|
|
2527
|
+
_sessions = new Map();
|
|
2528
|
+
_requestCount = 0;
|
|
2529
|
+
_activeRequests = 0;
|
|
2530
|
+
_bodySizeLimitEnabled;
|
|
2531
|
+
_maxBodySize;
|
|
2532
|
+
_requestTimeout;
|
|
2533
|
+
_metrics;
|
|
2534
|
+
_metricsProvider;
|
|
2535
|
+
constructor(options = {}){
|
|
2536
|
+
super(options);
|
|
2537
|
+
this._path = options.path ?? '/mcp';
|
|
2538
|
+
this._stateful = options.stateful ?? true;
|
|
2539
|
+
this._sessionIdGenerator = options.sessionIdGenerator ?? (()=>(0,external_node_crypto_.randomUUID)());
|
|
2540
|
+
this._bodySizeLimitEnabled = options.enableBodySizeLimit ?? true;
|
|
2541
|
+
this._maxBodySize = options.maxBodySize ?? 10 * 1024 * 1024;
|
|
2542
|
+
this._requestTimeout = options.requestTimeout ?? 30000;
|
|
2543
|
+
this._metrics = options.metrics;
|
|
2544
|
+
this._metricsProvider = options.metricsProvider ?? null;
|
|
2545
|
+
}
|
|
2546
|
+
/**
|
|
2547
|
+
* Get number of active sessions (stateful) or active requests (stateless).
|
|
2548
|
+
*/ get clientCount() {
|
|
2549
|
+
return this._stateful ? this._sessions.size : this._activeRequests;
|
|
2550
|
+
}
|
|
2551
|
+
/**
|
|
2552
|
+
* Get the total number of requests handled.
|
|
2553
|
+
*/ get requestCount() {
|
|
2554
|
+
return this._requestCount;
|
|
2555
|
+
}
|
|
2556
|
+
/**
|
|
2557
|
+
* Connects MCP server to this transport and starts listening.
|
|
2558
|
+
*/ async connect(mcpServer) {
|
|
2559
|
+
this._mcpServer = mcpServer;
|
|
2560
|
+
this._server = (0,external_node_http_.createServer)((req, res)=>this._handleRequest(req, res));
|
|
2561
|
+
return new Promise((resolve)=>{
|
|
2562
|
+
this._server.listen(this._port, this._host, ()=>{
|
|
2563
|
+
this.log('info', `Streamable HTTP transport listening on http://${this._host}:${this._port}`);
|
|
2564
|
+
resolve();
|
|
2565
|
+
});
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2568
|
+
/**
|
|
2569
|
+
* Route and handle incoming HTTP requests.
|
|
2570
|
+
*/ async _handleRequest(req, res) {
|
|
2571
|
+
const startTime = Date.now();
|
|
2572
|
+
this._metrics?.counter('streamable_http_requests_total', 1, {}, 'Total Streamable HTTP transport requests');
|
|
2573
|
+
res.once('finish', ()=>{
|
|
2574
|
+
const durationSeconds = (Date.now() - startTime) / 1000;
|
|
2575
|
+
this._metrics?.histogram('streamable_http_request_duration_seconds', durationSeconds, {});
|
|
2576
|
+
});
|
|
2577
|
+
// Host validation
|
|
2578
|
+
if (!this.validateHostHeader(req)) {
|
|
2579
|
+
this._sendJsonRpcError(res, 403, -32000, 'Forbidden - invalid host header');
|
|
2580
|
+
return;
|
|
2581
|
+
}
|
|
2582
|
+
// Shutdown check
|
|
2583
|
+
if (this.isShuttingDown) {
|
|
2584
|
+
this._sendJsonRpcError(res, 503, -32603, 'Server is shutting down');
|
|
2585
|
+
return;
|
|
2586
|
+
}
|
|
2587
|
+
// Rate limiting
|
|
2588
|
+
const clientIp = this.getClientIp(req);
|
|
2589
|
+
if (this.checkRateLimit(clientIp)) {
|
|
2590
|
+
res.setHeader('Retry-After', '60');
|
|
2591
|
+
this._sendJsonRpcError(res, 429, -32000, 'Too many requests');
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2594
|
+
// CORS validation
|
|
2595
|
+
if (!this.validateCorsOrigin(req)) {
|
|
2596
|
+
this._sendJsonRpcError(res, 403, -32000, 'Forbidden - invalid origin');
|
|
2597
|
+
return;
|
|
2598
|
+
}
|
|
2599
|
+
this.setCorsHeaders(res);
|
|
2600
|
+
// CORS preflight
|
|
2601
|
+
if (req.method === 'OPTIONS') {
|
|
2602
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Mcp-Session-Id');
|
|
2603
|
+
res.writeHead(204);
|
|
2604
|
+
res.end();
|
|
2605
|
+
return;
|
|
2606
|
+
}
|
|
2607
|
+
// Parse URL path
|
|
2608
|
+
const urlPath = req.url?.split('?')[0] ?? '/';
|
|
2609
|
+
// Metrics endpoint
|
|
2610
|
+
if (req.method === 'GET' && urlPath === '/metrics') {
|
|
2611
|
+
this._handleMetrics(res);
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
// Health check (liveness)
|
|
2615
|
+
if (req.method === 'GET' && urlPath === '/health') {
|
|
2616
|
+
this._handleHealthCheck(res);
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
// Readiness check
|
|
2620
|
+
if (req.method === 'GET' && urlPath === '/ready') {
|
|
2621
|
+
await this._handleReadinessCheck(res);
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
// MCP endpoint routing
|
|
2625
|
+
if (urlPath === this._path) {
|
|
2626
|
+
if (req.method === 'POST') {
|
|
2627
|
+
await this._handleMcpPost(req, res);
|
|
2628
|
+
return;
|
|
2629
|
+
}
|
|
2630
|
+
if (req.method === 'GET') {
|
|
2631
|
+
this._handleMcpGet(req, res);
|
|
2632
|
+
return;
|
|
2633
|
+
}
|
|
2634
|
+
res.writeHead(405, {
|
|
2635
|
+
'Content-Type': 'application/json',
|
|
2636
|
+
Allow: 'GET, POST'
|
|
2637
|
+
});
|
|
2638
|
+
res.end(JSON.stringify({
|
|
2639
|
+
jsonrpc: '2.0',
|
|
2640
|
+
id: null,
|
|
2641
|
+
error: {
|
|
2642
|
+
code: -32601,
|
|
2643
|
+
message: 'Method not allowed'
|
|
2644
|
+
}
|
|
2645
|
+
}));
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2648
|
+
// 404 for unknown paths
|
|
2649
|
+
this._sendJsonRpcError(res, 404, -32601, 'Not Found');
|
|
2650
|
+
}
|
|
2651
|
+
/**
|
|
2652
|
+
* Send a JSON-RPC error response.
|
|
2653
|
+
*/ _sendJsonRpcError(res, statusCode, code, message, id = null, extra) {
|
|
2654
|
+
const error = {
|
|
2655
|
+
code,
|
|
2656
|
+
message
|
|
2657
|
+
};
|
|
2658
|
+
if (extra) {
|
|
2659
|
+
Object.assign(error, extra);
|
|
2660
|
+
}
|
|
2661
|
+
res.writeHead(statusCode, {
|
|
2662
|
+
'Content-Type': 'application/json'
|
|
2663
|
+
});
|
|
2664
|
+
res.end(JSON.stringify({
|
|
2665
|
+
jsonrpc: '2.0',
|
|
2666
|
+
id,
|
|
2667
|
+
error
|
|
2668
|
+
}));
|
|
2669
|
+
}
|
|
2670
|
+
/**
|
|
2671
|
+
* Handle POST /mcp — JSON-RPC method calls.
|
|
2672
|
+
*
|
|
2673
|
+
* Per the MCP Streamable HTTP spec:
|
|
2674
|
+
* - Accepts JSON-RPC request bodies
|
|
2675
|
+
* - Returns Mcp-Session-Id header for new sessions (stateful mode)
|
|
2676
|
+
* - Validates Mcp-Session-Id for existing sessions (stateful mode)
|
|
2677
|
+
* - Content-Type: application/json for responses
|
|
2678
|
+
*/ async _handleMcpPost(req, res) {
|
|
2679
|
+
this._requestCount++;
|
|
2680
|
+
this._activeRequests++;
|
|
2681
|
+
const timeout = setTimeout(()=>{
|
|
2682
|
+
this._activeRequests--;
|
|
2683
|
+
this._sendJsonRpcError(res, 500, -32603, 'Request timeout');
|
|
2684
|
+
}, this._requestTimeout);
|
|
2685
|
+
try {
|
|
2686
|
+
// Read request body with size limit
|
|
2687
|
+
const body = await readRequestBody(req, this._bodySizeLimitEnabled ? this._maxBodySize : 0);
|
|
2688
|
+
if (body === null) {
|
|
2689
|
+
clearTimeout(timeout);
|
|
2690
|
+
this._activeRequests--;
|
|
2691
|
+
this._sendJsonRpcError(res, 413, -32000, 'Request body too large');
|
|
2692
|
+
return;
|
|
2693
|
+
}
|
|
2694
|
+
// Parse JSON
|
|
2695
|
+
let jsonRpcRequest;
|
|
2696
|
+
try {
|
|
2697
|
+
jsonRpcRequest = JSON.parse(body);
|
|
2698
|
+
} catch {
|
|
2699
|
+
clearTimeout(timeout);
|
|
2700
|
+
this._activeRequests--;
|
|
2701
|
+
this._sendJsonRpcError(res, 200, -32700, 'Parse error');
|
|
2702
|
+
return;
|
|
2703
|
+
}
|
|
2704
|
+
// Validate JSON-RPC schema
|
|
2705
|
+
const parseResult = (0,external_valibot_.safeParse)(schema/* .JsonRpcRequestSchema */.Uv, jsonRpcRequest);
|
|
2706
|
+
if (!parseResult.success) {
|
|
2707
|
+
clearTimeout(timeout);
|
|
2708
|
+
this._activeRequests--;
|
|
2709
|
+
this._sendJsonRpcError(res, 200, -32600, 'Invalid Request', jsonRpcRequest?.id ?? null, {
|
|
2710
|
+
data: parseResult.issues
|
|
2711
|
+
});
|
|
2712
|
+
return;
|
|
2713
|
+
}
|
|
2714
|
+
// Session management (stateful mode)
|
|
2715
|
+
let sessionId;
|
|
2716
|
+
if (this._stateful) {
|
|
2717
|
+
const sessionResult = this._resolveSession(req, res);
|
|
2718
|
+
if (sessionResult === false) {
|
|
2719
|
+
clearTimeout(timeout);
|
|
2720
|
+
this._activeRequests--;
|
|
2721
|
+
return;
|
|
2722
|
+
}
|
|
2723
|
+
sessionId = sessionResult;
|
|
2724
|
+
}
|
|
2725
|
+
// Check if MCP server is ready
|
|
2726
|
+
if (!this._mcpServer) {
|
|
2727
|
+
clearTimeout(timeout);
|
|
2728
|
+
this._activeRequests--;
|
|
2729
|
+
this._sendJsonRpcError(res, 503, -32603, 'Server not ready', jsonRpcRequest?.id ?? null);
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
// Process JSON-RPC request through MCP server
|
|
2733
|
+
const response = await this._mcpServer.receive(jsonRpcRequest, {
|
|
2734
|
+
sessionInfo: {}
|
|
2735
|
+
});
|
|
2736
|
+
clearTimeout(timeout);
|
|
2737
|
+
this._activeRequests--;
|
|
2738
|
+
// Send response with session header if applicable
|
|
2739
|
+
const responseHeaders = {
|
|
2740
|
+
'Content-Type': 'application/json'
|
|
2741
|
+
};
|
|
2742
|
+
if (sessionId) responseHeaders['Mcp-Session-Id'] = sessionId;
|
|
2743
|
+
if (response) {
|
|
2744
|
+
res.writeHead(200, responseHeaders);
|
|
2745
|
+
res.end(JSON.stringify(response));
|
|
2746
|
+
} else {
|
|
2747
|
+
if (sessionId) res.setHeader('Mcp-Session-Id', sessionId);
|
|
2748
|
+
res.writeHead(202);
|
|
2749
|
+
res.end();
|
|
2750
|
+
}
|
|
2751
|
+
} catch (error) {
|
|
2752
|
+
clearTimeout(timeout);
|
|
2753
|
+
this._activeRequests--;
|
|
2754
|
+
this._sendJsonRpcError(res, 200, -32603, 'Internal error', null, {
|
|
2755
|
+
data: (0,errors/* .getErrorMessage */.u1)(error)
|
|
2756
|
+
});
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
/**
|
|
2760
|
+
* Handle GET /mcp — Optional SSE notification stream.
|
|
2761
|
+
*
|
|
2762
|
+
* Per the MCP Streamable HTTP spec, clients may open a GET request
|
|
2763
|
+
* to receive server-initiated notifications as SSE events.
|
|
2764
|
+
* Requires a valid Mcp-Session-Id in stateful mode.
|
|
2765
|
+
*/ _handleMcpGet(req, res) {
|
|
2766
|
+
if (!this._stateful) {
|
|
2767
|
+
// SSE notification streams require stateful mode
|
|
2768
|
+
res.writeHead(405, {
|
|
2769
|
+
'Content-Type': 'application/json',
|
|
2770
|
+
Allow: 'POST'
|
|
2771
|
+
});
|
|
2772
|
+
res.end(JSON.stringify({
|
|
2773
|
+
jsonrpc: '2.0',
|
|
2774
|
+
id: null,
|
|
2775
|
+
error: {
|
|
2776
|
+
code: -32601,
|
|
2777
|
+
message: 'GET not supported in stateless mode'
|
|
2778
|
+
}
|
|
2779
|
+
}));
|
|
2780
|
+
return;
|
|
2781
|
+
}
|
|
2782
|
+
// Require Mcp-Session-Id for GET requests
|
|
2783
|
+
const sessionId = this._getSessionIdFromHeader(req);
|
|
2784
|
+
if (!sessionId) {
|
|
2785
|
+
res.writeHead(400, {
|
|
2786
|
+
'Content-Type': 'application/json'
|
|
2787
|
+
});
|
|
2788
|
+
res.end(JSON.stringify({
|
|
2789
|
+
jsonrpc: '2.0',
|
|
2790
|
+
id: null,
|
|
2791
|
+
error: {
|
|
2792
|
+
code: -32600,
|
|
2793
|
+
message: 'Missing Mcp-Session-Id header'
|
|
2794
|
+
}
|
|
2795
|
+
}));
|
|
2796
|
+
return;
|
|
2797
|
+
}
|
|
2798
|
+
const session = this._sessions.get(sessionId);
|
|
2799
|
+
if (!session) {
|
|
2800
|
+
res.writeHead(404, {
|
|
2801
|
+
'Content-Type': 'application/json'
|
|
2802
|
+
});
|
|
2803
|
+
res.end(JSON.stringify({
|
|
2804
|
+
jsonrpc: '2.0',
|
|
2805
|
+
id: null,
|
|
2806
|
+
error: {
|
|
2807
|
+
code: -32001,
|
|
2808
|
+
message: 'Session not found'
|
|
2809
|
+
}
|
|
2810
|
+
}));
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
// Set SSE headers
|
|
2814
|
+
res.writeHead(200, {
|
|
2815
|
+
'Content-Type': 'text/event-stream',
|
|
2816
|
+
'Cache-Control': 'no-cache',
|
|
2817
|
+
Connection: 'keep-alive',
|
|
2818
|
+
'Mcp-Session-Id': sessionId
|
|
2819
|
+
});
|
|
2820
|
+
// Send initial connected event
|
|
2821
|
+
this._sendSseEvent(res, 'connected', {
|
|
2822
|
+
sessionId,
|
|
2823
|
+
timestamp: Date.now()
|
|
2824
|
+
});
|
|
2825
|
+
// Track this notification stream
|
|
2826
|
+
session.notificationStreams.add(res);
|
|
2827
|
+
session.lastActivityAt = Date.now();
|
|
2828
|
+
this._updateSessionMetrics();
|
|
2829
|
+
// Handle client disconnect
|
|
2830
|
+
req.on('close', ()=>{
|
|
2831
|
+
session.notificationStreams.delete(res);
|
|
2832
|
+
this._updateSessionMetrics();
|
|
2833
|
+
});
|
|
2834
|
+
}
|
|
2835
|
+
/**
|
|
2836
|
+
* Resolve or create a session for a stateful request.
|
|
2837
|
+
*
|
|
2838
|
+
* @returns Session ID string on success, or `false` if the response was already sent (error).
|
|
2839
|
+
*/ _resolveSession(req, res) {
|
|
2840
|
+
const headerSessionId = this._getSessionIdFromHeader(req);
|
|
2841
|
+
if (headerSessionId) {
|
|
2842
|
+
// Validate format
|
|
2843
|
+
if (!this.validateSessionId(headerSessionId)) {
|
|
2844
|
+
res.writeHead(400, {
|
|
2845
|
+
'Content-Type': 'application/json'
|
|
2846
|
+
});
|
|
2847
|
+
res.end(JSON.stringify({
|
|
2848
|
+
jsonrpc: '2.0',
|
|
2849
|
+
id: null,
|
|
2850
|
+
error: {
|
|
2851
|
+
code: -32600,
|
|
2852
|
+
message: 'Invalid Mcp-Session-Id format'
|
|
2853
|
+
}
|
|
2854
|
+
}));
|
|
2855
|
+
return false;
|
|
2856
|
+
}
|
|
2857
|
+
// Check if session exists
|
|
2858
|
+
const session = this._sessions.get(headerSessionId);
|
|
2859
|
+
if (session) {
|
|
2860
|
+
session.lastActivityAt = Date.now();
|
|
2861
|
+
return headerSessionId;
|
|
2862
|
+
}
|
|
2863
|
+
// Unknown session ID — per spec, return 404
|
|
2864
|
+
res.writeHead(404, {
|
|
2865
|
+
'Content-Type': 'application/json'
|
|
2866
|
+
});
|
|
2867
|
+
res.end(JSON.stringify({
|
|
2868
|
+
jsonrpc: '2.0',
|
|
2869
|
+
id: null,
|
|
2870
|
+
error: {
|
|
2871
|
+
code: -32001,
|
|
2872
|
+
message: 'Session not found'
|
|
2873
|
+
}
|
|
2874
|
+
}));
|
|
2875
|
+
return false;
|
|
2876
|
+
}
|
|
2877
|
+
// No session header — create new session
|
|
2878
|
+
const newSessionId = this._sessionIdGenerator();
|
|
2879
|
+
const sessionState = {
|
|
2880
|
+
id: newSessionId,
|
|
2881
|
+
createdAt: Date.now(),
|
|
2882
|
+
lastActivityAt: Date.now(),
|
|
2883
|
+
notificationStreams: new Set()
|
|
2884
|
+
};
|
|
2885
|
+
this._sessions.set(newSessionId, sessionState);
|
|
2886
|
+
this.log('info', `New session created: ${newSessionId}`);
|
|
2887
|
+
this._updateSessionMetrics();
|
|
2888
|
+
return newSessionId;
|
|
2889
|
+
}
|
|
2890
|
+
/**
|
|
2891
|
+
* Extract Mcp-Session-Id from request headers.
|
|
2892
|
+
*/ _getSessionIdFromHeader(req) {
|
|
2893
|
+
const value = req.headers['mcp-session-id'];
|
|
2894
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
2895
|
+
return value;
|
|
2896
|
+
}
|
|
2897
|
+
return undefined;
|
|
2898
|
+
}
|
|
2899
|
+
/**
|
|
2900
|
+
* Send an SSE event to a specific client response stream.
|
|
2901
|
+
*/ _sendSseEvent(res, event, data) {
|
|
2902
|
+
try {
|
|
2903
|
+
res.write(`event: ${event}\n`);
|
|
2904
|
+
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
2905
|
+
} catch {
|
|
2906
|
+
// Client disconnected — ignore
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
/**
|
|
2910
|
+
* Broadcast a notification to all SSE streams in a given session.
|
|
2911
|
+
*
|
|
2912
|
+
* @param sessionId - Target session ID
|
|
2913
|
+
* @param event - SSE event name
|
|
2914
|
+
* @param data - Event payload
|
|
2915
|
+
*/ broadcastToSession(sessionId, event, data) {
|
|
2916
|
+
const session = this._sessions.get(sessionId);
|
|
2917
|
+
if (!session) {
|
|
2918
|
+
return;
|
|
2919
|
+
}
|
|
2920
|
+
for (const stream of session.notificationStreams){
|
|
2921
|
+
this._sendSseEvent(stream, event, data);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
/**
|
|
2925
|
+
* Handle GET /metrics endpoint.
|
|
2926
|
+
*/ _handleMetrics(res) {
|
|
2927
|
+
if (!this._metricsProvider) {
|
|
2928
|
+
res.writeHead(404, {
|
|
2929
|
+
'Content-Type': 'text/plain'
|
|
2930
|
+
});
|
|
2931
|
+
res.end('Not Found');
|
|
2932
|
+
return;
|
|
2933
|
+
}
|
|
2934
|
+
res.writeHead(200, {
|
|
2935
|
+
'Content-Type': 'text/plain; version=0.0.4; charset=utf-8'
|
|
2936
|
+
});
|
|
2937
|
+
res.end(this._metricsProvider());
|
|
2938
|
+
}
|
|
2939
|
+
/**
|
|
2940
|
+
* Handle GET /health — Liveness check.
|
|
2941
|
+
*/ _handleHealthCheck(res) {
|
|
2942
|
+
const healthData = {
|
|
2943
|
+
status: 'healthy',
|
|
2944
|
+
requests: this._requestCount,
|
|
2945
|
+
sessions: this._sessions.size,
|
|
2946
|
+
transport: 'streamable-http'
|
|
2947
|
+
};
|
|
2948
|
+
if (this._healthChecker) {
|
|
2949
|
+
const liveness = this._healthChecker.checkLiveness();
|
|
2950
|
+
healthData.liveness = liveness;
|
|
2951
|
+
}
|
|
2952
|
+
res.writeHead(200, {
|
|
2953
|
+
'Content-Type': 'application/json'
|
|
2954
|
+
});
|
|
2955
|
+
res.end(JSON.stringify(healthData));
|
|
2956
|
+
}
|
|
2957
|
+
/**
|
|
2958
|
+
* Handle GET /ready — Readiness check.
|
|
2959
|
+
*/ async _handleReadinessCheck(res) {
|
|
2960
|
+
if (this._healthChecker) {
|
|
2961
|
+
const readiness = await this._healthChecker.checkReadiness();
|
|
2962
|
+
const statusCode = readiness.status === 'ok' ? 200 : 503;
|
|
2963
|
+
res.writeHead(statusCode, {
|
|
2964
|
+
'Content-Type': 'application/json'
|
|
2965
|
+
});
|
|
2966
|
+
res.end(JSON.stringify(readiness));
|
|
2967
|
+
} else {
|
|
2968
|
+
res.writeHead(200, {
|
|
2969
|
+
'Content-Type': 'application/json'
|
|
2970
|
+
});
|
|
2971
|
+
res.end(JSON.stringify({
|
|
2972
|
+
status: 'ok',
|
|
2973
|
+
timestamp: new Date().toISOString(),
|
|
2974
|
+
components: {}
|
|
2975
|
+
}));
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Update session-related metrics.
|
|
2980
|
+
*/ _updateSessionMetrics() {
|
|
2981
|
+
this._metrics?.gauge('streamable_http_active_sessions', this._sessions.size, {}, 'Active Streamable HTTP sessions');
|
|
2982
|
+
let totalStreams = 0;
|
|
2983
|
+
for (const session of this._sessions.values()){
|
|
2984
|
+
totalStreams += session.notificationStreams.size;
|
|
2985
|
+
}
|
|
2986
|
+
this._metrics?.gauge('streamable_http_notification_streams', totalStreams, {}, 'Active SSE notification streams');
|
|
2987
|
+
}
|
|
2988
|
+
/**
|
|
2989
|
+
* Stop transport server with graceful shutdown.
|
|
2990
|
+
*
|
|
2991
|
+
* @param timeout - Maximum time to wait for in-flight requests (default: 30s)
|
|
2992
|
+
*/ async stop(timeout) {
|
|
2993
|
+
this._isShuttingDown = true;
|
|
2994
|
+
this._stopRateLimitCleanup();
|
|
2995
|
+
const shutdownTimeout = timeout ?? 30000;
|
|
2996
|
+
// Close all SSE notification streams
|
|
2997
|
+
for (const session of this._sessions.values()){
|
|
2998
|
+
for (const stream of session.notificationStreams){
|
|
2999
|
+
try {
|
|
3000
|
+
stream.end();
|
|
3001
|
+
} catch {
|
|
3002
|
+
// Ignore errors
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
session.notificationStreams.clear();
|
|
3006
|
+
}
|
|
3007
|
+
this._sessions.clear();
|
|
3008
|
+
return new Promise((resolve)=>{
|
|
3009
|
+
if (!this._server) {
|
|
3010
|
+
this.log('info', 'Streamable HTTP transport stopped (no server)');
|
|
3011
|
+
resolve();
|
|
3012
|
+
return;
|
|
3013
|
+
}
|
|
3014
|
+
// Force close after timeout
|
|
3015
|
+
const forceClose = setTimeout(()=>{
|
|
3016
|
+
this.log('warn', 'Streamable HTTP transport force-closing after timeout');
|
|
3017
|
+
resolve();
|
|
3018
|
+
}, shutdownTimeout);
|
|
3019
|
+
this._server.close(()=>{
|
|
3020
|
+
clearTimeout(forceClose);
|
|
3021
|
+
this.log('info', 'Streamable HTTP transport stopped');
|
|
3022
|
+
resolve();
|
|
3023
|
+
});
|
|
3024
|
+
});
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
/**
|
|
3028
|
+
* Create a Streamable HTTP transport with given options.
|
|
3029
|
+
*
|
|
3030
|
+
* @param options - Transport configuration
|
|
3031
|
+
* @returns A configured Streamable HTTP transport
|
|
3032
|
+
*
|
|
3033
|
+
* @example
|
|
3034
|
+
* ```typescript
|
|
3035
|
+
* const transport = createStreamableHttpTransport({ port: 3000, stateful: true });
|
|
3036
|
+
* await transport.connect(server);
|
|
3037
|
+
* ```
|
|
3038
|
+
*/ function createStreamableHttpTransport(options = {}) {
|
|
3039
|
+
return new StreamableHttpTransport(options);
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
|
|
3043
|
+
},
|
|
3044
|
+
65(module) {
|
|
3045
|
+
|
|
3046
|
+
module.exports = __rspack_external__lib_js_262c9725;
|
|
3047
|
+
|
|
3048
|
+
|
|
3049
|
+
},
|
|
3050
|
+
561(module) {
|
|
3051
|
+
|
|
3052
|
+
module.exports = __rspack_external_node_crypto_9ba42079;
|
|
3053
|
+
|
|
3054
|
+
|
|
3055
|
+
},
|
|
3056
|
+
316(module) {
|
|
3057
|
+
|
|
3058
|
+
module.exports = __rspack_external_node_http_2dc67212;
|
|
3059
|
+
|
|
3060
|
+
|
|
3061
|
+
},
|
|
3062
|
+
61(module) {
|
|
3063
|
+
|
|
3064
|
+
module.exports = __rspack_external_node_url_e96de089;
|
|
3065
|
+
|
|
3066
|
+
|
|
3067
|
+
},
|
|
3068
|
+
821(module) {
|
|
3069
|
+
|
|
3070
|
+
module.exports = __rspack_external_valibot;
|
|
3071
|
+
|
|
3072
|
+
|
|
3073
|
+
},
|
|
3074
|
+
|
|
3075
|
+
});
|
|
3076
|
+
// The module cache
|
|
3077
|
+
var __webpack_module_cache__ = {};
|
|
3078
|
+
|
|
3079
|
+
// The require function
|
|
3080
|
+
function __webpack_require__(moduleId) {
|
|
3081
|
+
|
|
3082
|
+
// Check if module is in cache
|
|
3083
|
+
var cachedModule = __webpack_module_cache__[moduleId];
|
|
3084
|
+
if (cachedModule !== undefined) {
|
|
3085
|
+
return cachedModule.exports;
|
|
3086
|
+
}
|
|
3087
|
+
// Create a new module (and put it into the cache)
|
|
3088
|
+
var module = (__webpack_module_cache__[moduleId] = {
|
|
3089
|
+
exports: {}
|
|
3090
|
+
});
|
|
3091
|
+
// Execute the module function
|
|
3092
|
+
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
|
3093
|
+
|
|
3094
|
+
// Return the exports of the module
|
|
3095
|
+
return module.exports;
|
|
3096
|
+
|
|
3097
|
+
}
|
|
3098
|
+
|
|
3099
|
+
// webpack/runtime/define_property_getters
|
|
3100
|
+
(() => {
|
|
3101
|
+
__webpack_require__.d = (exports, definition) => {
|
|
3102
|
+
for(var key in definition) {
|
|
3103
|
+
if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
|
|
3104
|
+
Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
};
|
|
3108
|
+
})();
|
|
3109
|
+
// webpack/runtime/has_own_property
|
|
3110
|
+
(() => {
|
|
3111
|
+
__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
|
|
3112
|
+
})();
|
|
3113
|
+
var __webpack_exports__ = {};
|
|
3114
|
+
// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk.
|
|
3115
|
+
(() => {
|
|
3116
|
+
|
|
3117
|
+
;// CONCATENATED MODULE: external "@tmcp/adapter-valibot"
|
|
3118
|
+
|
|
3119
|
+
;// CONCATENATED MODULE: external "@tmcp/transport-stdio"
|
|
3120
|
+
|
|
3121
|
+
;// CONCATENATED MODULE: external "node:fs"
|
|
3122
|
+
|
|
3123
|
+
;// CONCATENATED MODULE: external "node:path"
|
|
3124
|
+
|
|
3125
|
+
// EXTERNAL MODULE: external "node:url"
|
|
3126
|
+
var external_node_url_ = __webpack_require__(61);
|
|
3127
|
+
;// CONCATENATED MODULE: external "tmcp"
|
|
3128
|
+
|
|
3129
|
+
// EXTERNAL MODULE: external "./lib.js"
|
|
3130
|
+
var external_lib_js_ = __webpack_require__(65);
|
|
3131
|
+
;// CONCATENATED MODULE: external "node:async_hooks"
|
|
3132
|
+
|
|
3133
|
+
;// CONCATENATED MODULE: ./src/context/RequestContext.ts
|
|
3134
|
+
/**
|
|
3135
|
+
* Request context management using AsyncLocalStorage for correlation IDs.
|
|
3136
|
+
*
|
|
3137
|
+
* Provides zero-cost request ID propagation across async boundaries
|
|
3138
|
+
* via Node.js AsyncLocalStorage.
|
|
3139
|
+
*
|
|
3140
|
+
* @module context
|
|
3141
|
+
*/
|
|
3142
|
+
/**
|
|
3143
|
+
* AsyncLocalStorage instance for request context.
|
|
3144
|
+
* Stores requestId that propagates across async boundaries.
|
|
3145
|
+
*/ const store = new AsyncLocalStorage();
|
|
3146
|
+
/**
|
|
3147
|
+
* Get the current request ID from context.
|
|
3148
|
+
*
|
|
3149
|
+
* Returns undefined if called outside of a runWithContext() call.
|
|
3150
|
+
*
|
|
3151
|
+
* @returns The current request ID or undefined
|
|
3152
|
+
*/ function getRequestId() {
|
|
3153
|
+
return store.getStore()?.requestId;
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
;// CONCATENATED MODULE: ./src/logger/StructuredLogger.ts
|
|
3157
|
+
/**
|
|
3158
|
+
* Lightweight structured logging without external dependencies.
|
|
3159
|
+
*
|
|
3160
|
+
* This module provides a structured logging implementation that writes to stderr
|
|
3161
|
+
* for MCP server compatibility. It supports multiple log levels, pretty printing,
|
|
3162
|
+
* and hierarchical child loggers with inherited context.
|
|
3163
|
+
*
|
|
3164
|
+
* @module logger
|
|
3165
|
+
*/
|
|
3166
|
+
/**
|
|
3167
|
+
* Structured logger with level filtering and context support.
|
|
3168
|
+
*
|
|
3169
|
+
* This logger provides structured logging capabilities with configurable
|
|
3170
|
+
* output formats, log levels, and hierarchical context. All output is
|
|
3171
|
+
* written to stderr for compatibility with MCP servers.
|
|
3172
|
+
*
|
|
3173
|
+
* @remarks
|
|
3174
|
+
* **Log Level Priority** (lowest to highest):
|
|
3175
|
+
* - `debug` (0) - Detailed debugging information
|
|
3176
|
+
* - `info` (1) - General informational messages
|
|
3177
|
+
* - `warn` (2) - Warning messages for potential issues
|
|
3178
|
+
* - `error` (3) - Error messages for failures
|
|
3179
|
+
*
|
|
3180
|
+
* Only messages at or above the configured level will be output.
|
|
3181
|
+
*
|
|
3182
|
+
* **Output Formats:**
|
|
3183
|
+
* - Pretty (default): `[timestamp] [LEVEL] [context] message {meta}`
|
|
3184
|
+
* - JSON: `{"level":"info","message":"...","timestamp":"...","context":"...","meta":{...}}`
|
|
3185
|
+
*
|
|
3186
|
+
* @example
|
|
3187
|
+
* ```typescript
|
|
3188
|
+
* // Create a logger
|
|
3189
|
+
* const logger = new StructuredLogger({
|
|
3190
|
+
* level: 'info',
|
|
3191
|
+
* context: 'SequentialThinking',
|
|
3192
|
+
* pretty: true
|
|
3193
|
+
* });
|
|
3194
|
+
*
|
|
3195
|
+
* // Log messages
|
|
3196
|
+
* logger.debug('Detailed debug info', { userId: '123' });
|
|
3197
|
+
* logger.info('Server started', { port: 3000 });
|
|
3198
|
+
* logger.warn('High memory usage', { usage: '85%' });
|
|
3199
|
+
* logger.error('Connection failed', { error: 'ECONNREFUSED' });
|
|
3200
|
+
*
|
|
3201
|
+
* // Create a child logger with extended context
|
|
3202
|
+
* const childLogger = logger.createChild('Database');
|
|
3203
|
+
* childLogger.info('Query executed', { rows: 42 });
|
|
3204
|
+
* // Output: [timestamp] [INFO] [SequentialThinking:Database] Query executed {"rows":42}
|
|
3205
|
+
* ```
|
|
3206
|
+
*/ class StructuredLogger {
|
|
3207
|
+
/** Current minimum log level. */ _level;
|
|
3208
|
+
/** Default context for log messages. */ _context;
|
|
3209
|
+
/** Whether pretty printing is enabled. */ _pretty;
|
|
3210
|
+
/**
|
|
3211
|
+
* Log level priority ordering for filtering.
|
|
3212
|
+
* Higher numbers = higher severity.
|
|
3213
|
+
* @private
|
|
3214
|
+
*/ static LEVEL_PRIORITY = {
|
|
3215
|
+
debug: 0,
|
|
3216
|
+
info: 1,
|
|
3217
|
+
warn: 2,
|
|
3218
|
+
error: 3
|
|
3219
|
+
};
|
|
3220
|
+
/**
|
|
3221
|
+
* Creates a new StructuredLogger instance.
|
|
3222
|
+
*
|
|
3223
|
+
* @param options - Configuration options for the logger
|
|
3224
|
+
*
|
|
3225
|
+
* @example
|
|
3226
|
+
* ```typescript
|
|
3227
|
+
* // Default configuration
|
|
3228
|
+
* const logger1 = new StructuredLogger();
|
|
3229
|
+
*
|
|
3230
|
+
* // Custom configuration
|
|
3231
|
+
* const logger2 = new StructuredLogger({
|
|
3232
|
+
* level: 'debug',
|
|
3233
|
+
* context: 'MyApp',
|
|
3234
|
+
* pretty: false // JSON output
|
|
3235
|
+
* });
|
|
3236
|
+
* ```
|
|
3237
|
+
*/ constructor(options = {}){
|
|
3238
|
+
this._level = options.level ?? 'info';
|
|
3239
|
+
this._context = options.context ?? 'SequentialThinking';
|
|
3240
|
+
this._pretty = options.pretty ?? true;
|
|
3241
|
+
}
|
|
3242
|
+
/**
|
|
3243
|
+
* Determines whether a message at the given level should be logged.
|
|
3244
|
+
* @param level - The log level to check
|
|
3245
|
+
* @returns true if the level meets the threshold, false otherwise
|
|
3246
|
+
* @private
|
|
3247
|
+
*/ shouldLog(level) {
|
|
3248
|
+
return StructuredLogger.LEVEL_PRIORITY[level] >= StructuredLogger.LEVEL_PRIORITY[this._level];
|
|
3249
|
+
}
|
|
3250
|
+
/**
|
|
3251
|
+
* Formats a log entry for output.
|
|
3252
|
+
* @param entry - The log entry to format
|
|
3253
|
+
* @returns Formatted string representation
|
|
3254
|
+
* @private
|
|
3255
|
+
*/ format(entry) {
|
|
3256
|
+
if (this._pretty) {
|
|
3257
|
+
const metaStr = entry.meta ? ` ${JSON.stringify(entry.meta)}` : '';
|
|
3258
|
+
const requestIdStr = entry.requestId ? ` [${entry.requestId}]` : '';
|
|
3259
|
+
return `[${entry.timestamp}] [${entry.level.toUpperCase()}]${entry.context ? ` [${entry.context}]` : ''}${requestIdStr} ${entry.message}${metaStr}`;
|
|
3260
|
+
}
|
|
3261
|
+
return JSON.stringify(entry);
|
|
3262
|
+
}
|
|
3263
|
+
/**
|
|
3264
|
+
* Internal logging method that handles level filtering and output.
|
|
3265
|
+
* @param level - The log level for this message
|
|
3266
|
+
* @param message - The message to log
|
|
3267
|
+
* @param meta - Optional structured metadata
|
|
3268
|
+
* @private
|
|
3269
|
+
*/ log(level, message, meta) {
|
|
3270
|
+
if (!this.shouldLog(level)) return;
|
|
3271
|
+
const requestId = getRequestId();
|
|
3272
|
+
const entry = {
|
|
3273
|
+
level,
|
|
3274
|
+
message,
|
|
3275
|
+
timestamp: new Date().toISOString(),
|
|
3276
|
+
context: this._context,
|
|
3277
|
+
meta,
|
|
3278
|
+
...requestId ? {
|
|
3279
|
+
requestId
|
|
3280
|
+
} : {}
|
|
3281
|
+
};
|
|
3282
|
+
const formatted = this.format(entry);
|
|
3283
|
+
// Write to stderr for MCP server compatibility
|
|
3284
|
+
console.error(formatted);
|
|
3285
|
+
}
|
|
3286
|
+
/**
|
|
3287
|
+
* Log a debug message.
|
|
3288
|
+
*
|
|
3289
|
+
* Debug messages contain detailed information typically used for
|
|
3290
|
+
* troubleshooting and development. Only output when log level is 'debug'.
|
|
3291
|
+
*
|
|
3292
|
+
* @param message - The message to log
|
|
3293
|
+
* @param meta - Optional structured metadata
|
|
3294
|
+
*
|
|
3295
|
+
* @example
|
|
3296
|
+
* ```typescript
|
|
3297
|
+
* logger.debug('Processing request', { path: '/api/users', method: 'GET' });
|
|
3298
|
+
* ```
|
|
3299
|
+
*/ debug(message, meta) {
|
|
3300
|
+
this.log('debug', message, meta);
|
|
3301
|
+
}
|
|
3302
|
+
/**
|
|
3303
|
+
* Log an info message.
|
|
3304
|
+
*
|
|
3305
|
+
* Info messages contain general informational messages about normal operation.
|
|
3306
|
+
* Output when log level is 'info' or lower.
|
|
3307
|
+
*
|
|
3308
|
+
* @param message - The message to log
|
|
3309
|
+
* @param meta - Optional structured metadata
|
|
3310
|
+
*
|
|
3311
|
+
* @example
|
|
3312
|
+
* ```typescript
|
|
3313
|
+
* logger.info('Server started', { port: 3000, env: 'production' });
|
|
3314
|
+
* ```
|
|
3315
|
+
*/ info(message, meta) {
|
|
3316
|
+
this.log('info', message, meta);
|
|
3317
|
+
}
|
|
3318
|
+
/**
|
|
3319
|
+
* Log a warning message.
|
|
3320
|
+
*
|
|
3321
|
+
* Warning messages indicate potential issues that don't prevent operation
|
|
3322
|
+
* but may require attention. Output when log level is 'warn' or lower.
|
|
3323
|
+
*
|
|
3324
|
+
* @param message - The message to log
|
|
3325
|
+
* @param meta - Optional structured metadata
|
|
3326
|
+
*
|
|
3327
|
+
* @example
|
|
3328
|
+
* ```typescript
|
|
3329
|
+
* logger.warn('High memory usage detected', { usage: '85%', threshold: '80%' });
|
|
3330
|
+
* ```
|
|
3331
|
+
*/ warn(message, meta) {
|
|
3332
|
+
this.log('warn', message, meta);
|
|
3333
|
+
}
|
|
3334
|
+
/**
|
|
3335
|
+
* Log an error message.
|
|
3336
|
+
*
|
|
3337
|
+
* Error messages indicate failures or error conditions. Always output
|
|
3338
|
+
* regardless of log level setting.
|
|
3339
|
+
*
|
|
3340
|
+
* @param message - The message to log
|
|
3341
|
+
* @param meta - Optional structured metadata
|
|
3342
|
+
*
|
|
3343
|
+
* @example
|
|
3344
|
+
* ```typescript
|
|
3345
|
+
* logger.error('Database connection failed', { error: err.message, code: err.code });
|
|
3346
|
+
* ```
|
|
3347
|
+
*/ error(message, meta) {
|
|
3348
|
+
this.log('error', message, meta);
|
|
3349
|
+
}
|
|
3350
|
+
/**
|
|
3351
|
+
* Creates a child logger with inherited settings and extended context.
|
|
3352
|
+
*
|
|
3353
|
+
* Child loggers inherit the parent's log level and pretty print setting,
|
|
3354
|
+
* but have their context appended to the parent's context for hierarchical logging.
|
|
3355
|
+
*
|
|
3356
|
+
* @param context - Additional context to append to the parent's context
|
|
3357
|
+
* @returns A new logger instance with extended context
|
|
3358
|
+
*
|
|
3359
|
+
* @example
|
|
3360
|
+
* ```typescript
|
|
3361
|
+
* const parentLogger = new StructuredLogger({ context: 'App' });
|
|
3362
|
+
* const dbLogger = parentLogger.createChild('Database');
|
|
3363
|
+
* const queryLogger = dbLogger.createChild('Query');
|
|
3364
|
+
*
|
|
3365
|
+
* parentLogger.info('Starting up');
|
|
3366
|
+
* // Output: [timestamp] [INFO] [App] Starting up
|
|
3367
|
+
*
|
|
3368
|
+
* dbLogger.info('Connected');
|
|
3369
|
+
* // Output: [timestamp] [INFO] [App:Database] Connected
|
|
3370
|
+
*
|
|
3371
|
+
* queryLogger.info('Executed in 5ms');
|
|
3372
|
+
* // Output: [timestamp] [INFO] [App:Database:Query] Executed in 5ms
|
|
3373
|
+
* ```
|
|
3374
|
+
*/ createChild(context) {
|
|
3375
|
+
return new StructuredLogger({
|
|
3376
|
+
level: this._level,
|
|
3377
|
+
context: `${this._context}:${context}`,
|
|
3378
|
+
pretty: this._pretty
|
|
3379
|
+
});
|
|
3380
|
+
}
|
|
3381
|
+
/**
|
|
3382
|
+
* Sets the minimum log level.
|
|
3383
|
+
*
|
|
3384
|
+
* Only messages at or above this level will be output.
|
|
3385
|
+
*
|
|
3386
|
+
* @param level - The new minimum log level
|
|
3387
|
+
*
|
|
3388
|
+
* @example
|
|
3389
|
+
* ```typescript
|
|
3390
|
+
* logger.setLevel('debug'); // Enable all logging
|
|
3391
|
+
* logger.setLevel('error'); // Only show errors
|
|
3392
|
+
* ```
|
|
3393
|
+
*/ setLevel(level) {
|
|
3394
|
+
this._level = level;
|
|
3395
|
+
}
|
|
3396
|
+
/**
|
|
3397
|
+
* Gets the current minimum log level.
|
|
3398
|
+
*
|
|
3399
|
+
* @returns The current log level
|
|
3400
|
+
*
|
|
3401
|
+
* @example
|
|
3402
|
+
* ```typescript
|
|
3403
|
+
* const currentLevel = logger.getLevel();
|
|
3404
|
+
* console.log(`Current level: ${currentLevel}`);
|
|
3405
|
+
* ```
|
|
3406
|
+
*/ getLevel() {
|
|
3407
|
+
return this._level;
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
// EXTERNAL MODULE: ./src/errors.ts
|
|
3412
|
+
var errors = __webpack_require__(937);
|
|
3413
|
+
// EXTERNAL MODULE: ./src/schema.ts
|
|
3414
|
+
var schema = __webpack_require__(613);
|
|
3415
|
+
;// CONCATENATED MODULE: ./src/cli.ts
|
|
3416
|
+
//#!/usr/bin/env bun
|
|
3417
|
+
// CLI entry point for tracelattice MCP server.
|
|
3418
|
+
// This file handles CLI argument parsing, transport selection, and signal handlers.
|
|
3419
|
+
// For library usage, import from './lib.js' or './index.js' instead.
|
|
3420
|
+
|
|
3421
|
+
|
|
3422
|
+
|
|
3423
|
+
|
|
3424
|
+
|
|
3425
|
+
|
|
3426
|
+
|
|
3427
|
+
|
|
3428
|
+
|
|
107
3429
|
|
|
108
|
-
|
|
109
|
-
|
|
3430
|
+
// Get version from package.json
|
|
3431
|
+
const cli_filename = (0,external_node_url_.fileURLToPath)(import.meta.url);
|
|
3432
|
+
const cli_dirname = dirname(cli_filename);
|
|
3433
|
+
const package_json = JSON.parse(readFileSync(join(cli_dirname, '../package.json'), 'utf-8'));
|
|
3434
|
+
const { name: cli_name, version } = package_json;
|
|
3435
|
+
// Handle CLI arguments
|
|
3436
|
+
const args = process.argv.slice(2);
|
|
3437
|
+
const shouldShowVersion = args.includes('--version') || args.includes('-v');
|
|
3438
|
+
if (shouldShowVersion) {
|
|
3439
|
+
console.log(`${cli_name} v${version}`);
|
|
3440
|
+
process.exit(0);
|
|
3441
|
+
}
|
|
3442
|
+
async function main() {
|
|
3443
|
+
const adapter = new ValibotJsonSchemaAdapter();
|
|
3444
|
+
const server = new McpServer({
|
|
3445
|
+
name: cli_name,
|
|
3446
|
+
version,
|
|
3447
|
+
description: 'Semantic Sequential Thinking MCP Server'
|
|
3448
|
+
}, {
|
|
3449
|
+
adapter,
|
|
3450
|
+
capabilities: {
|
|
3451
|
+
tools: {
|
|
3452
|
+
listChanged: true
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
});
|
|
3456
|
+
const thinkingServer = await (0,external_lib_js_.initializeServer)();
|
|
3457
|
+
server.tool({
|
|
3458
|
+
name: 'sequentialthinking_tools',
|
|
3459
|
+
description: schema/* .SEQUENTIAL_THINKING_TOOL.description */.iV.description,
|
|
3460
|
+
schema: schema/* .SequentialThinkingSchema */.ZK
|
|
3461
|
+
}, async (input)=>{
|
|
3462
|
+
return thinkingServer.processThought(input);
|
|
3463
|
+
});
|
|
3464
|
+
const transportType = process.env.TRANSPORT_TYPE || 'stdio';
|
|
3465
|
+
if (transportType === 'sse') {
|
|
3466
|
+
await startSseTransport(server, thinkingServer);
|
|
3467
|
+
} else if (transportType === 'streamable-http') {
|
|
3468
|
+
await startStreamableHttpTransport(server, thinkingServer);
|
|
3469
|
+
} else {
|
|
3470
|
+
await startStdioTransport(server, thinkingServer);
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
/**
|
|
3474
|
+
* Start SSE transport for multi-user support
|
|
3475
|
+
*/ async function startSseTransport(server, thinkingServer) {
|
|
3476
|
+
const { SseTransport } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 390));
|
|
3477
|
+
const { createConnectionPool } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 789));
|
|
3478
|
+
const port = parseInt(process.env.SSE_PORT || '3000', 10);
|
|
3479
|
+
const host = process.env.SSE_HOST || 'localhost';
|
|
3480
|
+
const transportMetrics = thinkingServer.getContainer().resolve('Metrics');
|
|
3481
|
+
const enablePool = process.env.SSE_ENABLE_POOL !== 'false';
|
|
3482
|
+
const maxSessions = parseInt(process.env.SSE_MAX_SESSIONS || '100', 10);
|
|
3483
|
+
const sessionTimeout = parseInt(process.env.SSE_SESSION_TIMEOUT || '300000', 10);
|
|
3484
|
+
const connectionPool = enablePool ? createConnectionPool({
|
|
3485
|
+
maxSessions,
|
|
3486
|
+
sessionTimeout,
|
|
3487
|
+
logger: thinkingServer['_logger'],
|
|
3488
|
+
serverFactory: async ()=>{
|
|
3489
|
+
const { createServer: createThinkingServer } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 65));
|
|
3490
|
+
const sessionServer = await createThinkingServer({
|
|
3491
|
+
autoDiscover: true
|
|
3492
|
+
});
|
|
3493
|
+
return sessionServer;
|
|
3494
|
+
}
|
|
3495
|
+
}) : undefined;
|
|
3496
|
+
const sseTransport = new SseTransport({
|
|
3497
|
+
port,
|
|
3498
|
+
host,
|
|
3499
|
+
corsOrigin: process.env.CORS_ORIGIN || '*',
|
|
3500
|
+
enableCors: process.env.ENABLE_CORS !== 'false',
|
|
3501
|
+
allowedHosts: process.env.ALLOWED_HOSTS?.split(',').map((hostValue)=>hostValue.trim()),
|
|
3502
|
+
metrics: transportMetrics,
|
|
3503
|
+
connectionPool
|
|
3504
|
+
});
|
|
3505
|
+
// Connect the SSE transport
|
|
3506
|
+
await sseTransport.connect(server);
|
|
3507
|
+
const shutdown = async ()=>{
|
|
3508
|
+
await sseTransport.stop();
|
|
3509
|
+
await thinkingServer.stop();
|
|
3510
|
+
};
|
|
3511
|
+
registerShutdownHandlers(shutdown);
|
|
3512
|
+
thinkingServer['_logger'].info(`Sequential Thinking MCP Server running on SSE transport at http://${host}:${port}`);
|
|
3513
|
+
}
|
|
3514
|
+
/**
|
|
3515
|
+
* Start Streamable HTTP transport (MCP spec recommended)
|
|
3516
|
+
*/ async function startStreamableHttpTransport(server, thinkingServer) {
|
|
3517
|
+
const { StreamableHttpTransport } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 34));
|
|
3518
|
+
const port = parseInt(process.env.STREAMABLE_HTTP_PORT || process.env.SSE_PORT || '3000', 10);
|
|
3519
|
+
const host = process.env.STREAMABLE_HTTP_HOST || process.env.SSE_HOST || 'localhost';
|
|
3520
|
+
const transportMetrics = thinkingServer.getContainer().resolve('Metrics');
|
|
3521
|
+
const stateful = process.env.STREAMABLE_HTTP_STATEFUL !== 'false';
|
|
3522
|
+
const streamableTransport = new StreamableHttpTransport({
|
|
3523
|
+
port,
|
|
3524
|
+
host,
|
|
3525
|
+
corsOrigin: process.env.CORS_ORIGIN || '*',
|
|
3526
|
+
enableCors: process.env.ENABLE_CORS !== 'false',
|
|
3527
|
+
allowedHosts: process.env.ALLOWED_HOSTS?.split(',').map((hostValue)=>hostValue.trim()),
|
|
3528
|
+
metrics: transportMetrics,
|
|
3529
|
+
stateful
|
|
3530
|
+
});
|
|
3531
|
+
// Connect the Streamable HTTP transport
|
|
3532
|
+
await streamableTransport.connect(server);
|
|
3533
|
+
const shutdown = async ()=>{
|
|
3534
|
+
await streamableTransport.stop();
|
|
3535
|
+
await thinkingServer.stop();
|
|
3536
|
+
};
|
|
3537
|
+
registerShutdownHandlers(shutdown);
|
|
3538
|
+
thinkingServer['_logger'].info(`Sequential Thinking MCP Server running on Streamable HTTP transport at http://${host}:${port}`);
|
|
3539
|
+
}
|
|
3540
|
+
/**
|
|
3541
|
+
* Start stdio transport (default, single-user)
|
|
3542
|
+
*/ async function startStdioTransport(server, thinkingServer) {
|
|
3543
|
+
const transport = new StdioTransport(server);
|
|
3544
|
+
transport.listen();
|
|
3545
|
+
const shutdown = async ()=>{
|
|
3546
|
+
const forceExit = setTimeout(()=>{
|
|
3547
|
+
thinkingServer['_logger'].error('Graceful shutdown timed out after 30s - forcing exit');
|
|
3548
|
+
process.exit(1);
|
|
3549
|
+
}, 30000).unref(); // 30s timeout, don't keep process alive
|
|
3550
|
+
try {
|
|
3551
|
+
await thinkingServer.stop();
|
|
3552
|
+
clearTimeout(forceExit);
|
|
3553
|
+
process.exit(0);
|
|
3554
|
+
} catch (error) {
|
|
3555
|
+
thinkingServer['_logger'].error('Error during shutdown', {
|
|
3556
|
+
error: (0,errors/* .getErrorMessage */.u1)(error)
|
|
3557
|
+
});
|
|
3558
|
+
process.exit(1);
|
|
3559
|
+
}
|
|
3560
|
+
};
|
|
3561
|
+
// Register signal handlers ONCE (fixes double-registration bug)
|
|
3562
|
+
process.once('SIGINT', ()=>void shutdown());
|
|
3563
|
+
process.once('SIGTERM', ()=>void shutdown());
|
|
3564
|
+
thinkingServer['_logger'].info('Sequential Thinking MCP Server running on stdio');
|
|
3565
|
+
}
|
|
3566
|
+
/**
|
|
3567
|
+
* Register shutdown signal handlers for a common pattern
|
|
3568
|
+
*/ function registerShutdownHandlers(shutdown) {
|
|
3569
|
+
process.once('SIGINT', ()=>{
|
|
3570
|
+
shutdown().then(()=>process.exit(0)).catch(()=>process.exit(1));
|
|
3571
|
+
});
|
|
3572
|
+
process.once('SIGTERM', ()=>{
|
|
3573
|
+
shutdown().then(()=>process.exit(0)).catch(()=>process.exit(1));
|
|
3574
|
+
});
|
|
3575
|
+
}
|
|
3576
|
+
main().catch((error)=>{
|
|
3577
|
+
const logger = new StructuredLogger({
|
|
3578
|
+
level: 'error',
|
|
3579
|
+
context: 'SequentialThinking',
|
|
3580
|
+
pretty: true
|
|
3581
|
+
});
|
|
3582
|
+
logger.error('Fatal error running server', {
|
|
3583
|
+
error: (0,errors/* .getErrorMessage */.u1)(error)
|
|
3584
|
+
});
|
|
3585
|
+
process.exit(1);
|
|
3586
|
+
});
|
|
110
3587
|
|
|
111
|
-
|
|
112
|
-
`),e.write(`data: ${JSON.stringify(s)}
|
|
3588
|
+
})();
|
|
113
3589
|
|
|
114
|
-
`)}catch{}}broadcastToSession(e,t,s){let i=this._sessions.get(e);if(i)for(let e of i.notificationStreams)this._sendSseEvent(e,t,s)}_handleMetrics(e){if(!this._metricsProvider){e.writeHead(404,{"Content-Type":"text/plain"}),e.end("Not Found");return}e.writeHead(200,{"Content-Type":"text/plain; version=0.0.4; charset=utf-8"}),e.end(this._metricsProvider())}_handleHealthCheck(e){let t={status:"healthy",requests:this._requestCount,sessions:this._sessions.size,transport:"streamable-http"};this._healthChecker&&(t.liveness=this._healthChecker.checkLiveness()),e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify(t))}async _handleReadinessCheck(e){if(this._healthChecker){let t=await this._healthChecker.checkReadiness(),s="ok"===t.status?200:503;e.writeHead(s,{"Content-Type":"application/json"}),e.end(JSON.stringify(t))}else e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify({status:"ok",timestamp:new Date().toISOString(),components:{}}))}_updateSessionMetrics(){var e,t;null==(e=this._metrics)||e.gauge("streamable_http_active_sessions",this._sessions.size,{},"Active Streamable HTTP sessions");let s=0;for(let e of this._sessions.values())s+=e.notificationStreams.size;null==(t=this._metrics)||t.gauge("streamable_http_notification_streams",s,{},"Active SSE notification streams")}async stop(e){this._isShuttingDown=!0,this._stopRateLimitCleanup();let t=e??3e4;for(let e of this._sessions.values()){for(let t of e.notificationStreams)try{t.end()}catch{}e.notificationStreams.clear()}return this._sessions.clear(),new Promise(e=>{if(!this._server){this.log("info","Streamable HTTP transport stopped (no server)"),e();return}let s=setTimeout(()=>{this.log("warn","Streamable HTTP transport force-closing after timeout"),e()},t);this._server.close(()=>{clearTimeout(s),this.log("info","Streamable HTTP transport stopped"),e()})})}}},65(t){t.exports=e},561(e){e.exports=t},316(e){e.exports=s},61(e){e.exports=i},821(e){e.exports=n}},u={};function _(e){var t=u[e];if(void 0!==t)return t.exports;var s=u[e]={exports:{}};return d[e](s,s.exports,_),s.exports}_.d=(e,t)=>{for(var s in t)_.o(t,s)&&!_.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:t[s]})},_.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e=_(61),t=_(65);let s=new p;class i{_level;_context;_pretty;static LEVEL_PRIORITY={debug:0,info:1,warn:2,error:3};constructor(e={}){this._level=e.level??"info",this._context=e.context??"SequentialThinking",this._pretty=e.pretty??!0}shouldLog(e){return i.LEVEL_PRIORITY[e]>=i.LEVEL_PRIORITY[this._level]}format(e){if(this._pretty){let t=e.meta?` ${JSON.stringify(e.meta)}`:"",s=e.requestId?` [${e.requestId}]`:"";return`[${e.timestamp}] [${e.level.toUpperCase()}]${e.context?` [${e.context}]`:""}${s} ${e.message}${t}`}return JSON.stringify(e)}log(e,t,i){var n;if(!this.shouldLog(e))return;let o=null==(n=s.getStore())?void 0:n.requestId,r={level:e,message:t,timestamp:new Date().toISOString(),context:this._context,meta:i,...o?{requestId:o}:{}};console.error(this.format(r))}debug(e,t){this.log("debug",e,t)}info(e,t){this.log("info",e,t)}warn(e,t){this.log("warn",e,t)}error(e,t){this.log("error",e,t)}createChild(e){return new i({level:this._level,context:`${this._context}:${e}`,pretty:this._pretty})}setLevel(e){this._level=e}getLevel(){return this._level}}var n=_(787),d=_(555);let{name:u,version:m}=JSON.parse(a(c(l((0,e.fileURLToPath)(import.meta.url)),"../package.json"),"utf-8")),g=process.argv.slice(2);async function f(e,t){var s;let{SseTransport:i}=await Promise.resolve().then(_.bind(_,504)),{createConnectionPool:n}=await Promise.resolve().then(_.bind(_,527)),o=parseInt(process.env.SSE_PORT||"3000",10),r=process.env.SSE_HOST||"localhost",a=t.getContainer().resolve("Metrics"),l="false"!==process.env.SSE_ENABLE_POOL,c=parseInt(process.env.SSE_MAX_SESSIONS||"100",10),h=parseInt(process.env.SSE_SESSION_TIMEOUT||"300000",10),p=l?n({maxSessions:c,sessionTimeout:h,logger:t._logger,serverFactory:async()=>{let{createServer:e}=await Promise.resolve().then(_.bind(_,65));return await e({autoDiscover:!0})}}):void 0,d=new i({port:o,host:r,corsOrigin:process.env.CORS_ORIGIN||"*",enableCors:"false"!==process.env.ENABLE_CORS,allowedHosts:null==(s=process.env.ALLOWED_HOSTS)?void 0:s.split(",").map(e=>e.trim()),metrics:a,connectionPool:p});await d.connect(e),S(async()=>{await d.stop(),await t.stop()}),t._logger.info(`Sequential Thinking MCP Server running on SSE transport at http://${r}:${o}`)}async function v(e,t){var s;let{StreamableHttpTransport:i}=await Promise.resolve().then(_.bind(_,756)),n=parseInt(process.env.STREAMABLE_HTTP_PORT||process.env.SSE_PORT||"3000",10),o=process.env.STREAMABLE_HTTP_HOST||process.env.SSE_HOST||"localhost",r=t.getContainer().resolve("Metrics"),a="false"!==process.env.STREAMABLE_HTTP_STATEFUL,l=new i({port:n,host:o,corsOrigin:process.env.CORS_ORIGIN||"*",enableCors:"false"!==process.env.ENABLE_CORS,allowedHosts:null==(s=process.env.ALLOWED_HOSTS)?void 0:s.split(",").map(e=>e.trim()),metrics:r,stateful:a});await l.connect(e),S(async()=>{await l.stop(),await t.stop()}),t._logger.info(`Sequential Thinking MCP Server running on Streamable HTTP transport at http://${o}:${n}`)}async function y(e,t){new r(e).listen();let s=async()=>{let e=setTimeout(()=>{t._logger.error("Graceful shutdown timed out after 30s - forcing exit"),process.exit(1)},3e4).unref();try{await t.stop(),clearTimeout(e),process.exit(0)}catch(e){t._logger.error("Error during shutdown",{error:(0,n.u1)(e)}),process.exit(1)}};process.once("SIGINT",()=>void s()),process.once("SIGTERM",()=>void s()),t._logger.info("Sequential Thinking MCP Server running on stdio")}function S(e){process.once("SIGINT",()=>{e().then(()=>process.exit(0)).catch(()=>process.exit(1))}),process.once("SIGTERM",()=>{e().then(()=>process.exit(0)).catch(()=>process.exit(1))})}(g.includes("--version")||g.includes("-v"))&&(console.log(`${u} v${m}`),process.exit(0)),(async function(){let e=new h({name:u,version:m,description:"Semantic Sequential Thinking MCP Server"},{adapter:new o,capabilities:{tools:{listChanged:!0}}}),s=await (0,t.initializeServer)();e.tool({name:"sequentialthinking_tools",description:d.iV.description,schema:d.ZK},async e=>s.processThought(e));let i=process.env.TRANSPORT_TYPE||"stdio";"sse"===i?await f(e,s):"streamable-http"===i?await v(e,s):await y(e,s)})().catch(e=>{new i({level:"error",context:"SequentialThinking",pretty:!0}).error("Fatal error running server",{error:(0,n.u1)(e)}),process.exit(1)})})();
|