tracelattice 1.2.5
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/LICENSE +24 -0
- package/README.md +112 -0
- package/dist/ServerConfig.d.ts +229 -0
- package/dist/ServerConfig.d.ts.map +1 -0
- package/dist/ServerConfig.js +121 -0
- package/dist/ServerConfig.js.map +1 -0
- package/dist/__tests__/base-registry.test.d.ts +2 -0
- package/dist/__tests__/base-registry.test.d.ts.map +1 -0
- package/dist/__tests__/base-transport-cov.test.d.ts +2 -0
- package/dist/__tests__/base-transport-cov.test.d.ts.map +1 -0
- package/dist/__tests__/base-transport.test.d.ts +2 -0
- package/dist/__tests__/base-transport.test.d.ts.map +1 -0
- package/dist/__tests__/config-loader.test.d.ts +2 -0
- package/dist/__tests__/config-loader.test.d.ts.map +1 -0
- package/dist/__tests__/connection-pool-cov.test.d.ts +2 -0
- package/dist/__tests__/connection-pool-cov.test.d.ts.map +1 -0
- package/dist/__tests__/connection-pool.test.d.ts +2 -0
- package/dist/__tests__/connection-pool.test.d.ts.map +1 -0
- package/dist/__tests__/container.test.d.ts +2 -0
- package/dist/__tests__/container.test.d.ts.map +1 -0
- package/dist/__tests__/crud.test.d.ts +2 -0
- package/dist/__tests__/crud.test.d.ts.map +1 -0
- package/dist/__tests__/discovery-cache.test.d.ts +2 -0
- package/dist/__tests__/discovery-cache.test.d.ts.map +1 -0
- package/dist/__tests__/errors.test.d.ts +2 -0
- package/dist/__tests__/errors.test.d.ts.map +1 -0
- package/dist/__tests__/factories.test.d.ts +2 -0
- package/dist/__tests__/factories.test.d.ts.map +1 -0
- package/dist/__tests__/health-checker-cov.test.d.ts +2 -0
- package/dist/__tests__/health-checker-cov.test.d.ts.map +1 -0
- package/dist/__tests__/health-checker.test.d.ts +2 -0
- package/dist/__tests__/health-checker.test.d.ts.map +1 -0
- package/dist/__tests__/helpers/factories.d.ts +36 -0
- package/dist/__tests__/helpers/factories.d.ts.map +1 -0
- package/dist/__tests__/helpers/index.d.ts +3 -0
- package/dist/__tests__/helpers/index.d.ts.map +1 -0
- package/dist/__tests__/helpers/timers.d.ts +4 -0
- package/dist/__tests__/helpers/timers.d.ts.map +1 -0
- package/dist/__tests__/history-manager.test.d.ts +2 -0
- package/dist/__tests__/history-manager.test.d.ts.map +1 -0
- package/dist/__tests__/http-helpers-cov.test.d.ts +2 -0
- package/dist/__tests__/http-helpers-cov.test.d.ts.map +1 -0
- package/dist/__tests__/http-transport-cov.test.d.ts +2 -0
- package/dist/__tests__/http-transport-cov.test.d.ts.map +1 -0
- package/dist/__tests__/http-transport.test.d.ts +2 -0
- package/dist/__tests__/http-transport.test.d.ts.map +1 -0
- package/dist/__tests__/input-normalizer.test.d.ts +8 -0
- package/dist/__tests__/input-normalizer.test.d.ts.map +1 -0
- package/dist/__tests__/integration.test.d.ts +2 -0
- package/dist/__tests__/integration.test.d.ts.map +1 -0
- package/dist/__tests__/lib-server.test.d.ts +2 -0
- package/dist/__tests__/lib-server.test.d.ts.map +1 -0
- package/dist/__tests__/memory-persistence.test.d.ts +2 -0
- package/dist/__tests__/memory-persistence.test.d.ts.map +1 -0
- package/dist/__tests__/metrics-integration.test.d.ts +2 -0
- package/dist/__tests__/metrics-integration.test.d.ts.map +1 -0
- package/dist/__tests__/persistence.test.d.ts +2 -0
- package/dist/__tests__/persistence.test.d.ts.map +1 -0
- package/dist/__tests__/reasoning-integration.test.d.ts +11 -0
- package/dist/__tests__/reasoning-integration.test.d.ts.map +1 -0
- package/dist/__tests__/reasoning-types.test.d.ts +2 -0
- package/dist/__tests__/reasoning-types.test.d.ts.map +1 -0
- package/dist/__tests__/request-context.test.d.ts +2 -0
- package/dist/__tests__/request-context.test.d.ts.map +1 -0
- package/dist/__tests__/sanitize.test.d.ts +2 -0
- package/dist/__tests__/sanitize.test.d.ts.map +1 -0
- package/dist/__tests__/schema.test.d.ts +2 -0
- package/dist/__tests__/schema.test.d.ts.map +1 -0
- package/dist/__tests__/sequentialthinking-tools.test.d.ts +2 -0
- package/dist/__tests__/sequentialthinking-tools.test.d.ts.map +1 -0
- package/dist/__tests__/server-config.test.d.ts +2 -0
- package/dist/__tests__/server-config.test.d.ts.map +1 -0
- package/dist/__tests__/skill-discovery.test.d.ts +2 -0
- package/dist/__tests__/skill-discovery.test.d.ts.map +1 -0
- package/dist/__tests__/skill-registry.test.d.ts +2 -0
- package/dist/__tests__/skill-registry.test.d.ts.map +1 -0
- package/dist/__tests__/skill-watcher.test.d.ts +2 -0
- package/dist/__tests__/skill-watcher.test.d.ts.map +1 -0
- package/dist/__tests__/sqlite-persistence.test.d.ts +2 -0
- package/dist/__tests__/sqlite-persistence.test.d.ts.map +1 -0
- package/dist/__tests__/sse-transport-cov.test.d.ts +2 -0
- package/dist/__tests__/sse-transport-cov.test.d.ts.map +1 -0
- package/dist/__tests__/sse-transport.test.d.ts +2 -0
- package/dist/__tests__/sse-transport.test.d.ts.map +1 -0
- package/dist/__tests__/streamable-http-cov.test.d.ts +2 -0
- package/dist/__tests__/streamable-http-cov.test.d.ts.map +1 -0
- package/dist/__tests__/streamable-http-transport.test.d.ts +2 -0
- package/dist/__tests__/streamable-http-transport.test.d.ts.map +1 -0
- package/dist/__tests__/structured-logger.test.d.ts +2 -0
- package/dist/__tests__/structured-logger.test.d.ts.map +1 -0
- package/dist/__tests__/thought-evaluator.test.d.ts +2 -0
- package/dist/__tests__/thought-evaluator.test.d.ts.map +1 -0
- package/dist/__tests__/thought-formatter.test.d.ts +2 -0
- package/dist/__tests__/thought-formatter.test.d.ts.map +1 -0
- package/dist/__tests__/thought-processor.test.d.ts +8 -0
- package/dist/__tests__/thought-processor.test.d.ts.map +1 -0
- package/dist/__tests__/tool-registry-cov.test.d.ts +2 -0
- package/dist/__tests__/tool-registry-cov.test.d.ts.map +1 -0
- package/dist/__tests__/tool-registry.test.d.ts +2 -0
- package/dist/__tests__/tool-registry.test.d.ts.map +1 -0
- package/dist/__tests__/tool-watcher.test.d.ts +2 -0
- package/dist/__tests__/tool-watcher.test.d.ts.map +1 -0
- package/dist/__tests__/worker-manager-cov.test.d.ts +2 -0
- package/dist/__tests__/worker-manager-cov.test.d.ts.map +1 -0
- package/dist/__tests__/worker-manager.test.d.ts +2 -0
- package/dist/__tests__/worker-manager.test.d.ts.map +1 -0
- package/dist/cache/DiscoveryCache.d.ts +269 -0
- package/dist/cache/DiscoveryCache.d.ts.map +1 -0
- package/dist/cache/DiscoveryCache.js +100 -0
- package/dist/cache/DiscoveryCache.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +114 -0
- package/dist/cli.js.map +1 -0
- package/dist/cluster/WorkerManager.d.ts +166 -0
- package/dist/cluster/WorkerManager.d.ts.map +1 -0
- package/dist/cluster/WorkerManager.js +202 -0
- package/dist/cluster/WorkerManager.js.map +1 -0
- package/dist/cluster/worker.d.ts +11 -0
- package/dist/cluster/worker.d.ts.map +1 -0
- package/dist/cluster/worker.js +36 -0
- package/dist/cluster/worker.js.map +1 -0
- package/dist/config/ConfigLoader.d.ts +224 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +85 -0
- package/dist/config/ConfigLoader.js.map +1 -0
- package/dist/context/RequestContext.d.ts +61 -0
- package/dist/context/RequestContext.d.ts.map +1 -0
- package/dist/context/RequestContext.js +17 -0
- package/dist/context/RequestContext.js.map +1 -0
- package/dist/contracts/index.d.ts +10 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/interfaces.d.ts +107 -0
- package/dist/contracts/interfaces.d.ts.map +1 -0
- package/dist/contracts/interfaces.js +1 -0
- package/dist/core/HistoryManager.d.ts +514 -0
- package/dist/core/HistoryManager.d.ts.map +1 -0
- package/dist/core/HistoryManager.js +331 -0
- package/dist/core/HistoryManager.js.map +1 -0
- package/dist/core/IHistoryManager.d.ts +100 -0
- package/dist/core/IHistoryManager.d.ts.map +1 -0
- package/dist/core/IHistoryManager.js +1 -0
- package/dist/core/InputNormalizer.d.ts +139 -0
- package/dist/core/InputNormalizer.d.ts.map +1 -0
- package/dist/core/InputNormalizer.js +101 -0
- package/dist/core/InputNormalizer.js.map +1 -0
- package/dist/core/ThoughtEvaluator.d.ts +127 -0
- package/dist/core/ThoughtEvaluator.d.ts.map +1 -0
- package/dist/core/ThoughtEvaluator.js +346 -0
- package/dist/core/ThoughtEvaluator.js.map +1 -0
- package/dist/core/ThoughtFormatter.d.ts +133 -0
- package/dist/core/ThoughtFormatter.d.ts.map +1 -0
- package/dist/core/ThoughtFormatter.js +70 -0
- package/dist/core/ThoughtFormatter.js.map +1 -0
- package/dist/core/ThoughtProcessor.d.ts +218 -0
- package/dist/core/ThoughtProcessor.d.ts.map +1 -0
- package/dist/core/ThoughtProcessor.js +205 -0
- package/dist/core/ThoughtProcessor.js.map +1 -0
- package/dist/core/reasoning.d.ts +169 -0
- package/dist/core/reasoning.d.ts.map +1 -0
- package/dist/core/reasoning.js +1 -0
- package/dist/core/step.d.ts +45 -0
- package/dist/core/step.d.ts.map +1 -0
- package/dist/core/step.js +1 -0
- package/dist/core/thought.d.ts +190 -0
- package/dist/core/thought.d.ts.map +1 -0
- package/dist/core/thought.js +1 -0
- package/dist/di/Container.d.ts +226 -0
- package/dist/di/Container.d.ts.map +1 -0
- package/dist/di/Container.js +96 -0
- package/dist/di/Container.js.map +1 -0
- package/dist/di/ServiceRegistry.d.ts +32 -0
- package/dist/di/ServiceRegistry.d.ts.map +1 -0
- package/dist/di/ServiceRegistry.js +1 -0
- package/dist/errors.d.ts +482 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +108 -0
- package/dist/errors.js.map +1 -0
- package/dist/health/HealthChecker.d.ts +73 -0
- package/dist/health/HealthChecker.d.ts.map +1 -0
- package/dist/health/HealthChecker.js +69 -0
- package/dist/health/HealthChecker.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/lib.d.ts +205 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +219 -0
- package/dist/lib.js.map +1 -0
- package/dist/logger/NullLogger.d.ts +154 -0
- package/dist/logger/NullLogger.d.ts.map +1 -0
- package/dist/logger/NullLogger.js +24 -0
- package/dist/logger/NullLogger.js.map +1 -0
- package/dist/logger/StructuredLogger.d.ts +327 -0
- package/dist/logger/StructuredLogger.d.ts.map +1 -0
- package/dist/logger/StructuredLogger.js +72 -0
- package/dist/logger/StructuredLogger.js.map +1 -0
- package/dist/metrics/__tests__/metrics.test.d.ts +2 -0
- package/dist/metrics/__tests__/metrics.test.d.ts.map +1 -0
- package/dist/metrics/metrics.impl.d.ts +252 -0
- package/dist/metrics/metrics.impl.d.ts.map +1 -0
- package/dist/metrics/metrics.impl.js +197 -0
- package/dist/metrics/metrics.impl.js.map +1 -0
- package/dist/persistence/FilePersistence.d.ts +66 -0
- package/dist/persistence/FilePersistence.d.ts.map +1 -0
- package/dist/persistence/FilePersistence.js +132 -0
- package/dist/persistence/FilePersistence.js.map +1 -0
- package/dist/persistence/MemoryPersistence.d.ts +68 -0
- package/dist/persistence/MemoryPersistence.d.ts.map +1 -0
- package/dist/persistence/MemoryPersistence.js +51 -0
- package/dist/persistence/MemoryPersistence.js.map +1 -0
- package/dist/persistence/PersistenceBackend.d.ts +69 -0
- package/dist/persistence/PersistenceBackend.d.ts.map +1 -0
- package/dist/persistence/PersistenceBackend.js +1 -0
- package/dist/persistence/PersistenceFactory.d.ts +21 -0
- package/dist/persistence/PersistenceFactory.d.ts.map +1 -0
- package/dist/persistence/PersistenceFactory.js +25 -0
- package/dist/persistence/PersistenceFactory.js.map +1 -0
- package/dist/persistence/SqlitePersistence.d.ts +60 -0
- package/dist/persistence/SqlitePersistence.d.ts.map +1 -0
- package/dist/persistence/SqlitePersistence.js +136 -0
- package/dist/persistence/SqlitePersistence.js.map +1 -0
- package/dist/pool/ConnectionPool.d.ts +215 -0
- package/dist/pool/ConnectionPool.d.ts.map +1 -0
- package/dist/pool/ConnectionPool.js +187 -0
- package/dist/pool/ConnectionPool.js.map +1 -0
- package/dist/registry/BaseRegistry.d.ts +203 -0
- package/dist/registry/BaseRegistry.d.ts.map +1 -0
- package/dist/registry/BaseRegistry.js +165 -0
- package/dist/registry/BaseRegistry.js.map +1 -0
- package/dist/registry/SkillRegistry.d.ts +69 -0
- package/dist/registry/SkillRegistry.d.ts.map +1 -0
- package/dist/registry/SkillRegistry.js +88 -0
- package/dist/registry/SkillRegistry.js.map +1 -0
- package/dist/registry/ToolRegistry.d.ts +69 -0
- package/dist/registry/ToolRegistry.d.ts.map +1 -0
- package/dist/registry/ToolRegistry.js +93 -0
- package/dist/registry/ToolRegistry.js.map +1 -0
- package/dist/sanitize.d.ts +63 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +14 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/schema.d.ts +531 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +204 -0
- package/dist/schema.js.map +1 -0
- package/dist/telemetry/Telemetry.d.ts +36 -0
- package/dist/telemetry/Telemetry.d.ts.map +1 -0
- package/dist/telemetry/Telemetry.js +68 -0
- package/dist/telemetry/Telemetry.js.map +1 -0
- package/dist/telemetry/__tests__/Telemetry.test.d.ts +2 -0
- package/dist/telemetry/__tests__/Telemetry.test.d.ts.map +1 -0
- package/dist/transport/BaseTransport.d.ts +184 -0
- package/dist/transport/BaseTransport.d.ts.map +1 -0
- package/dist/transport/BaseTransport.js +200 -0
- package/dist/transport/BaseTransport.js.map +1 -0
- package/dist/transport/HttpHelpers.d.ts +60 -0
- package/dist/transport/HttpHelpers.d.ts.map +1 -0
- package/dist/transport/HttpHelpers.js +50 -0
- package/dist/transport/HttpHelpers.js.map +1 -0
- package/dist/transport/HttpTransport.d.ts +134 -0
- package/dist/transport/HttpTransport.d.ts.map +1 -0
- package/dist/transport/HttpTransport.js +175 -0
- package/dist/transport/HttpTransport.js.map +1 -0
- package/dist/transport/SseTransport.d.ts +133 -0
- package/dist/transport/SseTransport.d.ts.map +1 -0
- package/dist/transport/SseTransport.js +318 -0
- package/dist/transport/SseTransport.js.map +1 -0
- package/dist/transport/StreamableHttpTransport.d.ts +224 -0
- package/dist/transport/StreamableHttpTransport.d.ts.map +1 -0
- package/dist/transport/StreamableHttpTransport.js +407 -0
- package/dist/transport/StreamableHttpTransport.js.map +1 -0
- package/dist/types/disposable.d.ts +22 -0
- package/dist/types/disposable.d.ts.map +1 -0
- package/dist/types/disposable.js +1 -0
- package/dist/types/server-config.d.ts +32 -0
- package/dist/types/server-config.d.ts.map +1 -0
- package/dist/types/server-config.js +1 -0
- package/dist/types/skill.d.ts +69 -0
- package/dist/types/skill.d.ts.map +1 -0
- package/dist/types/skill.js +1 -0
- package/dist/types/tool.d.ts +68 -0
- package/dist/types/tool.d.ts.map +1 -0
- package/dist/types/tool.js +1 -0
- package/dist/watchers/SkillWatcher.d.ts +132 -0
- package/dist/watchers/SkillWatcher.d.ts.map +1 -0
- package/dist/watchers/SkillWatcher.js +73 -0
- package/dist/watchers/SkillWatcher.js.map +1 -0
- package/dist/watchers/ToolWatcher.d.ts +109 -0
- package/dist/watchers/ToolWatcher.d.ts.map +1 -0
- package/dist/watchers/ToolWatcher.js +71 -0
- package/dist/watchers/ToolWatcher.js.map +1 -0
- package/package.json +95 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
class NoopLogger {
|
|
2
|
+
_level = 'info';
|
|
3
|
+
info(_message, _meta) {}
|
|
4
|
+
warn(_message, _meta) {}
|
|
5
|
+
error(_message, _meta) {}
|
|
6
|
+
debug(_message, _meta) {}
|
|
7
|
+
setLevel(level) {
|
|
8
|
+
this._level = level;
|
|
9
|
+
}
|
|
10
|
+
getLevel() {
|
|
11
|
+
return this._level;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const ALLOWED_QUERY_PARAMS = new Set([
|
|
15
|
+
'session',
|
|
16
|
+
'sessionId',
|
|
17
|
+
'client',
|
|
18
|
+
'clientId'
|
|
19
|
+
]);
|
|
20
|
+
const MAX_SESSION_ID_LENGTH = 64;
|
|
21
|
+
const SESSION_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
22
|
+
const RATE_LIMIT_REQUESTS = 100;
|
|
23
|
+
const RATE_LIMIT_WINDOW_MS = 60000;
|
|
24
|
+
class BaseTransport {
|
|
25
|
+
_port;
|
|
26
|
+
_host;
|
|
27
|
+
_corsOrigin;
|
|
28
|
+
_enableCors;
|
|
29
|
+
_rateLimitEnabled;
|
|
30
|
+
_maxRequestsPerMinute;
|
|
31
|
+
_allowedHosts;
|
|
32
|
+
_rateLimitMap = new Map();
|
|
33
|
+
_rateLimitCleanupIntervalId = null;
|
|
34
|
+
_wasHostExplicitlySet;
|
|
35
|
+
_isShuttingDown = false;
|
|
36
|
+
_logger;
|
|
37
|
+
_healthChecker;
|
|
38
|
+
constructor(options = {}){
|
|
39
|
+
this._port = options.port ?? 9108;
|
|
40
|
+
this._host = options.host ?? '127.0.0.1';
|
|
41
|
+
this._wasHostExplicitlySet = void 0 !== options.host;
|
|
42
|
+
this._corsOrigin = options.corsOrigin ?? '*';
|
|
43
|
+
this._enableCors = options.enableCors ?? true;
|
|
44
|
+
this._rateLimitEnabled = options.enableRateLimit ?? true;
|
|
45
|
+
this._maxRequestsPerMinute = options.maxRequestsPerMinute ?? RATE_LIMIT_REQUESTS;
|
|
46
|
+
this._allowedHosts = this._buildAllowedHosts(options.allowedHosts);
|
|
47
|
+
this._isShuttingDown = false;
|
|
48
|
+
this._logger = options.logger ?? new NoopLogger();
|
|
49
|
+
this._healthChecker = options.healthChecker ?? null;
|
|
50
|
+
if (this._rateLimitEnabled) this._startRateLimitCleanup();
|
|
51
|
+
}
|
|
52
|
+
get serverUrl() {
|
|
53
|
+
const host = this._wasHostExplicitlySet || '127.0.0.1' !== this._host ? this._host : 'localhost';
|
|
54
|
+
return `http://${host}:${this._port}`;
|
|
55
|
+
}
|
|
56
|
+
validateSessionId(sessionId) {
|
|
57
|
+
if (sessionId.length > MAX_SESSION_ID_LENGTH) return false;
|
|
58
|
+
return SESSION_ID_PATTERN.test(sessionId);
|
|
59
|
+
}
|
|
60
|
+
sanitizeQueryParams(url) {
|
|
61
|
+
const sanitized = {};
|
|
62
|
+
for (const [key, value] of url.searchParams.entries())if (ALLOWED_QUERY_PARAMS.has(key)) sanitized[key] = value;
|
|
63
|
+
return sanitized;
|
|
64
|
+
}
|
|
65
|
+
checkRateLimit(ip) {
|
|
66
|
+
if (!this._rateLimitEnabled) return false;
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
this._cleanupExpiredRateLimitEntries(now);
|
|
69
|
+
const record = this._rateLimitMap.get(ip);
|
|
70
|
+
if (!record || now > record.resetTime) {
|
|
71
|
+
this._rateLimitMap.set(ip, {
|
|
72
|
+
count: 1,
|
|
73
|
+
resetTime: now + RATE_LIMIT_WINDOW_MS
|
|
74
|
+
});
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
if (record.count >= this._maxRequestsPerMinute) return true;
|
|
78
|
+
record.count++;
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
_cleanupExpiredRateLimitEntries(now = Date.now()) {
|
|
82
|
+
for (const [ip, record] of this._rateLimitMap.entries())if (record.resetTime <= now) this._rateLimitMap.delete(ip);
|
|
83
|
+
}
|
|
84
|
+
_startRateLimitCleanup() {
|
|
85
|
+
if (null !== this._rateLimitCleanupIntervalId) clearInterval(this._rateLimitCleanupIntervalId);
|
|
86
|
+
this._rateLimitCleanupIntervalId = setInterval(()=>{
|
|
87
|
+
this._cleanupExpiredRateLimitEntries();
|
|
88
|
+
}, RATE_LIMIT_WINDOW_MS);
|
|
89
|
+
}
|
|
90
|
+
_stopRateLimitCleanup() {
|
|
91
|
+
if (null !== this._rateLimitCleanupIntervalId) {
|
|
92
|
+
clearInterval(this._rateLimitCleanupIntervalId);
|
|
93
|
+
this._rateLimitCleanupIntervalId = null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
getClientIp(req) {
|
|
97
|
+
const forwardedFor = req.headers['x-forwarded-for'];
|
|
98
|
+
if (forwardedFor && 'string' == typeof forwardedFor) return forwardedFor.split(',')[0].trim();
|
|
99
|
+
const remoteAddress = req.socket.remoteAddress;
|
|
100
|
+
return remoteAddress || 'unknown';
|
|
101
|
+
}
|
|
102
|
+
validateCorsOrigin(req) {
|
|
103
|
+
if ('*' === this._corsOrigin) return true;
|
|
104
|
+
const origin = req.headers.origin;
|
|
105
|
+
if (!origin) return true;
|
|
106
|
+
if (this._corsOrigin === origin) return true;
|
|
107
|
+
if (this._corsOrigin.includes('*')) {
|
|
108
|
+
const escaped = this._corsOrigin.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[a-zA-Z0-9.-]*');
|
|
109
|
+
const regex = new RegExp(`^${escaped}$`);
|
|
110
|
+
return regex.test(origin);
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
setCorsHeaders(res) {
|
|
115
|
+
if (this._enableCors) {
|
|
116
|
+
res.setHeader('Access-Control-Allow-Origin', this._corsOrigin);
|
|
117
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
118
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
validateHostHeader(req) {
|
|
122
|
+
const rawHost = req.headers.host;
|
|
123
|
+
if (!rawHost) return true;
|
|
124
|
+
const hostWithoutPort = rawHost.split(':')[0].trim().toLowerCase();
|
|
125
|
+
if (!hostWithoutPort) return false;
|
|
126
|
+
if (0 === this._allowedHosts.size) return true;
|
|
127
|
+
return this._allowedHosts.has(hostWithoutPort);
|
|
128
|
+
}
|
|
129
|
+
_buildAllowedHosts(configuredHosts) {
|
|
130
|
+
if (configuredHosts && configuredHosts.length > 0) return new Set(configuredHosts.map((host)=>host.toLowerCase().trim()).filter(Boolean));
|
|
131
|
+
const boundHost = this._host.toLowerCase();
|
|
132
|
+
const localHosts = [
|
|
133
|
+
'localhost',
|
|
134
|
+
'127.0.0.1',
|
|
135
|
+
'::1'
|
|
136
|
+
];
|
|
137
|
+
if (localHosts.includes(boundHost)) return new Set(localHosts);
|
|
138
|
+
if ('0.0.0.0' === boundHost || '::' === boundHost) return new Set(localHosts);
|
|
139
|
+
return new Set([
|
|
140
|
+
boundHost
|
|
141
|
+
]);
|
|
142
|
+
}
|
|
143
|
+
log(level, message, meta) {
|
|
144
|
+
if ('info' === level) this._logger.info(message, meta);
|
|
145
|
+
else if ('warn' === level) this._logger.warn(message, meta);
|
|
146
|
+
else this._logger.error(message, meta);
|
|
147
|
+
}
|
|
148
|
+
isShuttingDown() {
|
|
149
|
+
return this._isShuttingDown;
|
|
150
|
+
}
|
|
151
|
+
handleHealthEndpoint(res, extraData) {
|
|
152
|
+
const healthData = {
|
|
153
|
+
status: 'healthy',
|
|
154
|
+
...extraData
|
|
155
|
+
};
|
|
156
|
+
if (this._healthChecker) {
|
|
157
|
+
const liveness = this._healthChecker.checkLiveness();
|
|
158
|
+
healthData.liveness = liveness;
|
|
159
|
+
}
|
|
160
|
+
res.writeHead(200, {
|
|
161
|
+
'Content-Type': 'application/json'
|
|
162
|
+
});
|
|
163
|
+
res.end(JSON.stringify(healthData));
|
|
164
|
+
}
|
|
165
|
+
async handleReadinessEndpoint(res) {
|
|
166
|
+
if (this._healthChecker) {
|
|
167
|
+
const readiness = await this._healthChecker.checkReadiness();
|
|
168
|
+
const statusCode = 'ok' === readiness.status ? 200 : 503;
|
|
169
|
+
res.writeHead(statusCode, {
|
|
170
|
+
'Content-Type': 'application/json'
|
|
171
|
+
});
|
|
172
|
+
res.end(JSON.stringify(readiness));
|
|
173
|
+
} else {
|
|
174
|
+
res.writeHead(200, {
|
|
175
|
+
'Content-Type': 'application/json'
|
|
176
|
+
});
|
|
177
|
+
res.end(JSON.stringify({
|
|
178
|
+
status: 'ok',
|
|
179
|
+
timestamp: new Date().toISOString(),
|
|
180
|
+
components: {}
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
handleMetricsEndpoint(res, metricsProvider) {
|
|
185
|
+
if (!metricsProvider) {
|
|
186
|
+
res.writeHead(404, {
|
|
187
|
+
'Content-Type': 'text/plain'
|
|
188
|
+
});
|
|
189
|
+
res.end('Not Found');
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
res.writeHead(200, {
|
|
193
|
+
'Content-Type': 'text/plain; version=0.0.4; charset=utf-8'
|
|
194
|
+
});
|
|
195
|
+
res.end(metricsProvider());
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
export { BaseTransport };
|
|
199
|
+
|
|
200
|
+
//# sourceMappingURL=BaseTransport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport/BaseTransport.js","sources":["../../src/transport/BaseTransport.ts"],"sourcesContent":["/**\n * Base transport implementation.\n *\n * This class provides shared functionality for all transport implementations,\n * including session validation, rate limiting, CORS handling, and IP extraction.\n *\n * @remarks\n * **Security Features:**\n * - Session ID validation (alphanumeric, max 64 chars)\n * - Query parameter sanitization (whitelist allowed keys)\n * - Rate limiting per IP (configurable, default 100 req/min)\n * - CORS origin validation\n *\n * **Rate Limiting:**\n * - Tracks requests per IP address within a time window\n * - Returns 429 Too Many Requests when limit exceeded\n * - Can be disabled via `enableRateLimit: false`\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { URL } from 'node:url';\nimport type { HealthChecker } from '../health/HealthChecker.js';\nimport type { Logger, LogLevel } from '../logger/StructuredLogger.js';\n\n/**\n * No-op logger that does nothing. Used when no logger is provided.\n */\nclass NoopLogger implements Logger {\n\tprivate _level: LogLevel = 'info';\n\n\tinfo(_message: string, _meta?: Record<string, unknown>): void {}\n\twarn(_message: string, _meta?: Record<string, unknown>): void {}\n\terror(_message: string, _meta?: Record<string, unknown>): void {}\n\tdebug(_message: string, _meta?: Record<string, unknown>): void {}\n\tsetLevel(level: LogLevel): void {\n\t\tthis._level = level;\n\t}\n\tgetLevel(): LogLevel {\n\t\treturn this._level;\n\t}\n}\n\n/**\n * Allowed query parameter names (whitelist for security).\n */\nconst ALLOWED_QUERY_PARAMS = new Set(['session', 'sessionId', 'client', 'clientId']);\n\n/**\n * Maximum session ID length.\n */\nconst MAX_SESSION_ID_LENGTH = 64;\n\n/**\n * Session ID validation pattern (alphanumeric, hyphens, underscores).\n */\nconst SESSION_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;\n\n/**\n * Rate limit settings (requests per minute per IP).\n */\nconst RATE_LIMIT_REQUESTS = 100;\nconst RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minute\n\n/**\n * Transport interface contract for MCP server communication.\n * All transport implementations must implement this interface.\n */\nexport interface ITransport {\n\t/**\n\t * Connect the transport to an MCP server.\n\t */\n\tconnect(mcpServer: unknown): Promise<void>;\n\n\t/**\n\t * Stop the transport with graceful shutdown.\n\t * @param timeout - Maximum time to wait for in-flight requests (default: 30s)\n\t */\n\tstop(timeout?: number): Promise<void>;\n\n\t/**\n\t * Number of currently connected clients.\n\t */\n\treadonly clientCount: number;\n}\n\nexport interface TransportOptions {\n\tport?: number;\n\thost?: string;\n\tallowedHosts?: string[];\n\tcorsOrigin?: string;\n\tenableCors?: boolean;\n\tenableRateLimit?: boolean;\n\tmaxRequestsPerMinute?: number;\n\tlogger?: Logger;\n\thealthChecker?: HealthChecker;\n}\n\nexport abstract class BaseTransport implements ITransport {\n\tprotected _port: number;\n\tprotected _host: string;\n\tprotected _corsOrigin: string;\n\tprotected _enableCors: boolean;\n\tprotected _rateLimitEnabled: boolean;\n\tprotected _maxRequestsPerMinute: number;\n\tprotected _allowedHosts: Set<string>;\n\tprotected _rateLimitMap: Map<string, { count: number; resetTime: number }> = new Map();\n\tprotected _rateLimitCleanupIntervalId: NodeJS.Timeout | null = null;\n\tprotected _wasHostExplicitlySet: boolean;\n\t/** Shutdown state for graceful shutdown. */\n\tprotected _isShuttingDown: boolean = false;\n\tprivate _logger: Logger | NoopLogger;\n\tprotected _healthChecker: HealthChecker | null;\n\n\tconstructor(options: TransportOptions = {}) {\n\t\tthis._port = options.port ?? 9108;\n\t\tthis._host = options.host ?? '127.0.0.1';\n\t\tthis._wasHostExplicitlySet = options.host !== undefined;\n\t\tthis._corsOrigin = options.corsOrigin ?? '*';\n\t\tthis._enableCors = options.enableCors ?? true;\n\t\tthis._rateLimitEnabled = options.enableRateLimit ?? true;\n\t\tthis._maxRequestsPerMinute = options.maxRequestsPerMinute ?? RATE_LIMIT_REQUESTS;\n\t\tthis._allowedHosts = this._buildAllowedHosts(options.allowedHosts);\n\t\tthis._isShuttingDown = false;\n\t\tthis._logger = options.logger ?? new NoopLogger();\n\t\tthis._healthChecker = options.healthChecker ?? null;\n\n\t\tif (this._rateLimitEnabled) {\n\t\t\tthis._startRateLimitCleanup();\n\t\t}\n\t}\n\n\t/**\n\t * Get the server URL with localhost substitution for default host.\n\t */\n\tget serverUrl(): string {\n\t\tconst host =\n\t\t\t!this._wasHostExplicitlySet && this._host === '127.0.0.1' ? 'localhost' : this._host;\n\t\treturn `http://${host}:${this._port}`;\n\t}\n\n\t/**\n\t * Validate session ID format.\n\t *\n\t * @param sessionId - The session ID to validate\n\t * @returns true if valid, false otherwise\n\t */\n\tprotected validateSessionId(sessionId: string): boolean {\n\t\tif (sessionId.length > MAX_SESSION_ID_LENGTH) {\n\t\t\treturn false;\n\t\t}\n\t\treturn SESSION_ID_PATTERN.test(sessionId);\n\t}\n\n\t/**\n\t * Sanitize query parameters by removing any not in whitelist.\n\t *\n\t * @param url - The URL object containing query parameters\n\t * @returns A sanitized record of allowed query parameters\n\t */\n\tprotected sanitizeQueryParams(url: URL): Record<string, string> {\n\t\tconst sanitized: Record<string, string> = {};\n\n\t\tfor (const [key, value] of url.searchParams.entries()) {\n\t\t\tif (ALLOWED_QUERY_PARAMS.has(key)) {\n\t\t\t\tsanitized[key] = value;\n\t\t\t}\n\t\t}\n\n\t\treturn sanitized;\n\t}\n\n\t/**\n\t * Check rate limit for a given IP address.\n\t *\n\t * @param ip - The IP address to check\n\t * @returns true if rate limit exceeded, false otherwise\n\t */\n\tprotected checkRateLimit(ip: string): boolean {\n\t\tif (!this._rateLimitEnabled) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst now = Date.now();\n\t\tthis._cleanupExpiredRateLimitEntries(now);\n\t\tconst record = this._rateLimitMap.get(ip);\n\n\t\tif (!record || now > record.resetTime) {\n\t\t\tthis._rateLimitMap.set(ip, {\n\t\t\t\tcount: 1,\n\t\t\t\tresetTime: now + RATE_LIMIT_WINDOW_MS,\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\n\t\tif (record.count >= this._maxRequestsPerMinute) {\n\t\t\treturn true; // Rate limit exceeded\n\t\t}\n\n\t\trecord.count++;\n\t\treturn false;\n\t}\n\n\tprotected _cleanupExpiredRateLimitEntries(now = Date.now()): void {\n\t\tfor (const [ip, record] of this._rateLimitMap.entries()) {\n\t\t\tif (record.resetTime <= now) {\n\t\t\t\tthis._rateLimitMap.delete(ip);\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected _startRateLimitCleanup(): void {\n\t\tif (this._rateLimitCleanupIntervalId !== null) {\n\t\t\tclearInterval(this._rateLimitCleanupIntervalId);\n\t\t}\n\n\t\tthis._rateLimitCleanupIntervalId = setInterval(() => {\n\t\t\tthis._cleanupExpiredRateLimitEntries();\n\t\t}, RATE_LIMIT_WINDOW_MS);\n\t}\n\n\tprotected _stopRateLimitCleanup(): void {\n\t\tif (this._rateLimitCleanupIntervalId !== null) {\n\t\t\tclearInterval(this._rateLimitCleanupIntervalId);\n\t\t\tthis._rateLimitCleanupIntervalId = null;\n\t\t}\n\t}\n\n\t/**\n\t * Get client IP address from request.\n\t *\n\t * @param req - The incoming request\n\t * @returns The client IP address\n\t */\n\tprotected getClientIp(req: IncomingMessage): string {\n\t\tconst forwardedFor = req.headers['x-forwarded-for'];\n\t\tif (forwardedFor && typeof forwardedFor === 'string') {\n\t\t\treturn forwardedFor.split(',')[0]!.trim();\n\t\t}\n\t\tconst remoteAddress = req.socket.remoteAddress;\n\t\treturn remoteAddress || 'unknown';\n\t}\n\n\t/**\n\t * Validate CORS origin from request headers.\n\t *\n\t * @param req - The incoming request\n\t * @returns true if origin is valid, false otherwise\n\t */\n\tprotected validateCorsOrigin(req: IncomingMessage): boolean {\n\t\tif (this._corsOrigin === '*') {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst origin = req.headers.origin;\n\t\tif (!origin) {\n\t\t\treturn true; // No origin header is acceptable\n\t\t}\n\n\t\t// Exact match\n\t\tif (this._corsOrigin === origin) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// Check if configured origin is a wildcard pattern\n\t\tif (this._corsOrigin.includes('*')) {\n\t\t\t// Escape all regex metacharacters EXCEPT *,\n\t\t\t// then replace * with a hostname-safe pattern (alphanumeric, hyphens, dots)\n\t\t\tconst escaped = this._corsOrigin\n\t\t\t\t.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&') // escape metacharacters (not *)\n\t\t\t\t.replace(/\\*/g, '[a-zA-Z0-9.-]*'); // * matches valid hostname chars only\n\t\t\tconst regex = new RegExp(`^${escaped}$`);\n\t\t\treturn regex.test(origin);\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Set CORS headers on response.\n\t *\n\t * @param res - The server response\n\t */\n\tprotected setCorsHeaders(res: ServerResponse): void {\n\t\tif (this._enableCors) {\n\t\t\tres.setHeader('Access-Control-Allow-Origin', this._corsOrigin);\n\t\t\tres.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n\t\t\tres.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n\t\t}\n\t}\n\n\tprotected validateHostHeader(req: IncomingMessage): boolean {\n\t\tconst rawHost = req.headers.host;\n\t\tif (!rawHost) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst hostWithoutPort = rawHost.split(':')[0]!.trim().toLowerCase();\n\t\tif (!hostWithoutPort) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (this._allowedHosts.size === 0) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn this._allowedHosts.has(hostWithoutPort);\n\t}\n\n\tprivate _buildAllowedHosts(configuredHosts?: string[]): Set<string> {\n\t\tif (configuredHosts && configuredHosts.length > 0) {\n\t\t\treturn new Set(configuredHosts.map((host) => host.toLowerCase().trim()).filter(Boolean));\n\t\t}\n\n\t\tconst boundHost = this._host.toLowerCase();\n\t\tconst localHosts = ['localhost', '127.0.0.1', '::1'];\n\n\t\tif (localHosts.includes(boundHost)) {\n\t\t\treturn new Set(localHosts);\n\t\t}\n\n\t\tif (boundHost === '0.0.0.0' || boundHost === '::') {\n\t\t\treturn new Set(localHosts);\n\t\t}\n\n\t\treturn new Set([boundHost]);\n\t}\n\n\t/**\n\t * Log a message using the configured logger.\n\t *\n\t * @param level - Log level\n\t * @param message - Message to log\n\t * @param meta - Optional metadata\n\t */\n\tprotected log(\n\t\tlevel: 'info' | 'warn' | 'error',\n\t\tmessage: string,\n\t\tmeta?: Record<string, unknown>\n\t): void {\n\t\tif (level === 'info') {\n\t\t\tthis._logger.info(message, meta);\n\t\t} else if (level === 'warn') {\n\t\t\tthis._logger.warn(message, meta);\n\t\t} else {\n\t\t\tthis._logger.error(message, meta);\n\t\t}\n\t}\n\n\t/**\n\t * Check if transport is shutting down.\n\t * @returns true if in shutdown phase\n\t */\n\tprotected isShuttingDown(): boolean {\n\t\treturn this._isShuttingDown;\n\t}\n\n\t/**\n\t * Handle GET /health endpoint — liveness check.\n\t *\n\t * Builds a standard health response with optional liveness data from the health checker.\n\t * Transports can pass extra data (e.g. client counts, session info).\n\t *\n\t * @param res - The server response\n\t * @param extraData - Optional additional health metadata\n\t */\n\tprotected handleHealthEndpoint(res: ServerResponse, extraData?: Record<string, unknown>): void {\n\t\tconst healthData: Record<string, unknown> = { status: 'healthy', ...extraData };\n\t\tif (this._healthChecker) {\n\t\t\tconst liveness = this._healthChecker.checkLiveness();\n\t\t\thealthData.liveness = liveness;\n\t\t}\n\t\tres.writeHead(200, { 'Content-Type': 'application/json' });\n\t\tres.end(JSON.stringify(healthData));\n\t}\n\n\t/**\n\t * Handle GET /ready endpoint — readiness check.\n\t *\n\t * Delegates to the health checker if available, otherwise returns a default OK response.\n\t *\n\t * @param res - The server response\n\t */\n\tprotected async handleReadinessEndpoint(res: ServerResponse): Promise<void> {\n\t\tif (this._healthChecker) {\n\t\t\tconst readiness = await this._healthChecker.checkReadiness();\n\t\t\tconst statusCode = readiness.status === 'ok' ? 200 : 503;\n\t\t\tres.writeHead(statusCode, { 'Content-Type': 'application/json' });\n\t\t\tres.end(JSON.stringify(readiness));\n\t\t} else {\n\t\t\tres.writeHead(200, { 'Content-Type': 'application/json' });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({ status: 'ok', timestamp: new Date().toISOString(), components: {} })\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Handle GET /metrics endpoint — Prometheus metrics.\n\t *\n\t * Returns 404 if no metrics provider is configured.\n\t *\n\t * @param res - The server response\n\t * @param metricsProvider - Function that returns Prometheus-format metrics text\n\t */\n\tprotected handleMetricsEndpoint(res: ServerResponse, metricsProvider: (() => string) | null): void {\n\t\tif (!metricsProvider) {\n\t\t\tres.writeHead(404, { 'Content-Type': 'text/plain' });\n\t\t\tres.end('Not Found');\n\t\t\treturn;\n\t\t}\n\t\tres.writeHead(200, { 'Content-Type': 'text/plain; version=0.0.4; charset=utf-8' });\n\t\tres.end(metricsProvider());\n\t}\n\n\t/**\n\t * Connect to MCP server.\n\t */\n\tabstract connect(mcpServer: unknown): Promise<void>;\n\n\t/**\n\t * Stop transport server with graceful shutdown.\n\t *\n\t * This method should:\n\t * 1. Set shutdown flag to prevent new connections\n\t * 2. Wait for in-flight requests to complete (configurable timeout)\n\t * 3. Close server connections\n\t * 4. Release resources\n\t *\n\t * @param timeout - Maximum time to wait for requests to drain (default: 30 seconds)\n\t * @returns Promise that resolves when shutdown is complete\n\t */\n\tabstract stop(timeout?: number): Promise<void>;\n\n\t/**\n\t * Get number of clients connected.\n\t */\n\tabstract get clientCount(): number;\n\n}\n"],"names":["NoopLogger","_message","_meta","level","ALLOWED_QUERY_PARAMS","Set","MAX_SESSION_ID_LENGTH","SESSION_ID_PATTERN","RATE_LIMIT_REQUESTS","RATE_LIMIT_WINDOW_MS","BaseTransport","Map","options","undefined","host","sessionId","url","sanitized","key","value","ip","now","Date","record","clearInterval","setInterval","req","forwardedFor","remoteAddress","origin","escaped","regex","RegExp","res","rawHost","hostWithoutPort","configuredHosts","Boolean","boundHost","localHosts","message","meta","extraData","healthData","liveness","JSON","readiness","statusCode","metricsProvider"],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP helper utilities for MCP transport implementations.
|
|
3
|
+
*
|
|
4
|
+
* Centralizes JSON-RPC response formatting, request body reading,
|
|
5
|
+
* and common HTTP response patterns to eliminate duplication across
|
|
6
|
+
* HttpTransport, StreamableHttpTransport, and SseTransport.
|
|
7
|
+
*
|
|
8
|
+
* @module transport/HttpHelpers
|
|
9
|
+
*/
|
|
10
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
11
|
+
/**
|
|
12
|
+
* Send a JSON-RPC 2.0 error response.
|
|
13
|
+
*
|
|
14
|
+
* Standardizes error response formatting across all transport implementations.
|
|
15
|
+
* All JSON-RPC errors use the standard `{ jsonrpc, id, error }` shape.
|
|
16
|
+
*
|
|
17
|
+
* @param res - The server response to write to
|
|
18
|
+
* @param statusCode - HTTP status code (e.g. 400, 403, 429, 500)
|
|
19
|
+
* @param code - JSON-RPC error code (e.g. -32700, -32600, -32603)
|
|
20
|
+
* @param message - Human-readable error message
|
|
21
|
+
* @param id - Optional JSON-RPC request ID (defaults to null)
|
|
22
|
+
* @param data - Optional additional error data
|
|
23
|
+
*/
|
|
24
|
+
export declare function sendJsonRpcError(res: ServerResponse, statusCode: number, code: number, message: string, id?: string | number | null, data?: unknown): void;
|
|
25
|
+
/**
|
|
26
|
+
* Send a JSON-RPC 2.0 success response.
|
|
27
|
+
*
|
|
28
|
+
* @param res - The server response to write to
|
|
29
|
+
* @param response - The JSON-RPC response object to send
|
|
30
|
+
* @param statusCode - HTTP status code (default: 200)
|
|
31
|
+
* @param headers - Optional additional response headers
|
|
32
|
+
*/
|
|
33
|
+
export declare function sendJsonRpcResponse(res: ServerResponse, response: unknown, statusCode?: number, headers?: Record<string, string>): void;
|
|
34
|
+
/**
|
|
35
|
+
* Send a CORS preflight (OPTIONS) response.
|
|
36
|
+
*
|
|
37
|
+
* @param res - The server response to write to
|
|
38
|
+
* @param extraAllowHeaders - Optional extra Access-Control-Allow-Headers values
|
|
39
|
+
*/
|
|
40
|
+
export declare function sendCorsPreflight(res: ServerResponse, extraAllowHeaders?: string[]): void;
|
|
41
|
+
/**
|
|
42
|
+
* Read the full request body with optional size limit enforcement.
|
|
43
|
+
*
|
|
44
|
+
* Streams the request body chunks, tracking total size.
|
|
45
|
+
* If the body exceeds `maxBodySize`, reading stops and `null` is returned
|
|
46
|
+
* to indicate the payload is too large.
|
|
47
|
+
*
|
|
48
|
+
* @param req - The incoming HTTP request
|
|
49
|
+
* @param maxBodySize - Maximum allowed body size in bytes (0 = unlimited)
|
|
50
|
+
* @returns The body string, or `null` if the body exceeded the size limit
|
|
51
|
+
*/
|
|
52
|
+
export declare function readRequestBody(req: IncomingMessage, maxBodySize: number): Promise<string | null>;
|
|
53
|
+
/**
|
|
54
|
+
* Send a plain text 404 Not Found response.
|
|
55
|
+
*
|
|
56
|
+
* @param res - The server response to write to
|
|
57
|
+
* @param message - Optional custom message (default: 'Not Found')
|
|
58
|
+
*/
|
|
59
|
+
export declare function sendNotFound(res: ServerResponse, message?: string): void;
|
|
60
|
+
//# sourceMappingURL=HttpHelpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HttpHelpers.d.ts","sourceRoot":"","sources":["../../src/transport/HttpHelpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC/B,GAAG,EAAE,cAAc,EACnB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,EAAE,GAAE,MAAM,GAAG,MAAM,GAAG,IAAW,EACjC,IAAI,CAAC,EAAE,OAAO,GACZ,IAAI,CAWN;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAClC,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,OAAO,EACjB,UAAU,GAAE,MAAY,EACxB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,IAAI,CAIN;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,EAAE,iBAAiB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAMzF;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACpC,GAAG,EAAE,eAAe,EACpB,WAAW,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgBxB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,GAAE,MAAoB,GAAG,IAAI,CAGrF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
function sendJsonRpcError(res, statusCode, code, message, id = null, data) {
|
|
2
|
+
const body = {
|
|
3
|
+
jsonrpc: '2.0',
|
|
4
|
+
id,
|
|
5
|
+
error: {
|
|
6
|
+
code,
|
|
7
|
+
message
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
if (void 0 !== data) body.error.data = data;
|
|
11
|
+
res.writeHead(statusCode, {
|
|
12
|
+
'Content-Type': 'application/json'
|
|
13
|
+
});
|
|
14
|
+
res.end(JSON.stringify(body));
|
|
15
|
+
}
|
|
16
|
+
function sendJsonRpcResponse(res, response, statusCode = 200, headers = {}) {
|
|
17
|
+
const defaultHeaders = {
|
|
18
|
+
'Content-Type': 'application/json'
|
|
19
|
+
};
|
|
20
|
+
res.writeHead(statusCode, {
|
|
21
|
+
...defaultHeaders,
|
|
22
|
+
...headers
|
|
23
|
+
});
|
|
24
|
+
res.end(JSON.stringify(response));
|
|
25
|
+
}
|
|
26
|
+
function sendCorsPreflight(res, extraAllowHeaders) {
|
|
27
|
+
if (extraAllowHeaders && extraAllowHeaders.length > 0) res.setHeader('Access-Control-Allow-Headers', `Content-Type, ${extraAllowHeaders.join(', ')}`);
|
|
28
|
+
res.writeHead(204);
|
|
29
|
+
res.end();
|
|
30
|
+
}
|
|
31
|
+
async function readRequestBody(req, maxBodySize) {
|
|
32
|
+
let body = '';
|
|
33
|
+
let bodySize = 0;
|
|
34
|
+
for await (const chunk of req){
|
|
35
|
+
const chunkStr = 'string' == typeof chunk ? chunk : chunk.toString();
|
|
36
|
+
bodySize += chunkStr.length;
|
|
37
|
+
if (maxBodySize > 0 && bodySize > maxBodySize) return null;
|
|
38
|
+
body += chunkStr;
|
|
39
|
+
}
|
|
40
|
+
return body;
|
|
41
|
+
}
|
|
42
|
+
function sendNotFound(res, message = 'Not Found') {
|
|
43
|
+
res.writeHead(404, {
|
|
44
|
+
'Content-Type': 'text/plain'
|
|
45
|
+
});
|
|
46
|
+
res.end(message);
|
|
47
|
+
}
|
|
48
|
+
export { readRequestBody, sendCorsPreflight, sendJsonRpcError, sendJsonRpcResponse, sendNotFound };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=HttpHelpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport/HttpHelpers.js","sources":["../../src/transport/HttpHelpers.ts"],"sourcesContent":["/**\n * Shared HTTP helper utilities for MCP transport implementations.\n *\n * Centralizes JSON-RPC response formatting, request body reading,\n * and common HTTP response patterns to eliminate duplication across\n * HttpTransport, StreamableHttpTransport, and SseTransport.\n *\n * @module transport/HttpHelpers\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\n/**\n * Send a JSON-RPC 2.0 error response.\n *\n * Standardizes error response formatting across all transport implementations.\n * All JSON-RPC errors use the standard `{ jsonrpc, id, error }` shape.\n *\n * @param res - The server response to write to\n * @param statusCode - HTTP status code (e.g. 400, 403, 429, 500)\n * @param code - JSON-RPC error code (e.g. -32700, -32600, -32603)\n * @param message - Human-readable error message\n * @param id - Optional JSON-RPC request ID (defaults to null)\n * @param data - Optional additional error data\n */\nexport function sendJsonRpcError(\n\tres: ServerResponse,\n\tstatusCode: number,\n\tcode: number,\n\tmessage: string,\n\tid: string | number | null = null,\n\tdata?: unknown\n): void {\n\tconst body: Record<string, unknown> = {\n\t\tjsonrpc: '2.0',\n\t\tid,\n\t\terror: { code, message },\n\t};\n\tif (data !== undefined) {\n\t\t(body.error as Record<string, unknown>).data = data;\n\t}\n\tres.writeHead(statusCode, { 'Content-Type': 'application/json' });\n\tres.end(JSON.stringify(body));\n}\n\n/**\n * Send a JSON-RPC 2.0 success response.\n *\n * @param res - The server response to write to\n * @param response - The JSON-RPC response object to send\n * @param statusCode - HTTP status code (default: 200)\n * @param headers - Optional additional response headers\n */\nexport function sendJsonRpcResponse(\n\tres: ServerResponse,\n\tresponse: unknown,\n\tstatusCode: number = 200,\n\theaders: Record<string, string> = {}\n): void {\n\tconst defaultHeaders: Record<string, string> = { 'Content-Type': 'application/json' };\n\tres.writeHead(statusCode, { ...defaultHeaders, ...headers });\n\tres.end(JSON.stringify(response));\n}\n\n/**\n * Send a CORS preflight (OPTIONS) response.\n *\n * @param res - The server response to write to\n * @param extraAllowHeaders - Optional extra Access-Control-Allow-Headers values\n */\nexport function sendCorsPreflight(res: ServerResponse, extraAllowHeaders?: string[]): void {\n\tif (extraAllowHeaders && extraAllowHeaders.length > 0) {\n\t\tres.setHeader('Access-Control-Allow-Headers', `Content-Type, ${extraAllowHeaders.join(', ')}`);\n\t}\n\tres.writeHead(204);\n\tres.end();\n}\n\n/**\n * Read the full request body with optional size limit enforcement.\n *\n * Streams the request body chunks, tracking total size.\n * If the body exceeds `maxBodySize`, reading stops and `null` is returned\n * to indicate the payload is too large.\n *\n * @param req - The incoming HTTP request\n * @param maxBodySize - Maximum allowed body size in bytes (0 = unlimited)\n * @returns The body string, or `null` if the body exceeded the size limit\n */\nexport async function readRequestBody(\n\treq: IncomingMessage,\n\tmaxBodySize: number\n): Promise<string | null> {\n\tlet body = '';\n\tlet bodySize = 0;\n\n\tfor await (const chunk of req) {\n\t\tconst chunkStr = typeof chunk === 'string' ? chunk : (chunk as Buffer).toString();\n\t\tbodySize += chunkStr.length;\n\n\t\tif (maxBodySize > 0 && bodySize > maxBodySize) {\n\t\t\treturn null;\n\t\t}\n\n\t\tbody += chunkStr;\n\t}\n\n\treturn body;\n}\n\n/**\n * Send a plain text 404 Not Found response.\n *\n * @param res - The server response to write to\n * @param message - Optional custom message (default: 'Not Found')\n */\nexport function sendNotFound(res: ServerResponse, message: string = 'Not Found'): void {\n\tres.writeHead(404, { 'Content-Type': 'text/plain' });\n\tres.end(message);\n}\n"],"names":["sendJsonRpcError","res","statusCode","code","message","id","data","body","undefined","JSON","sendJsonRpcResponse","response","headers","defaultHeaders","sendCorsPreflight","extraAllowHeaders","readRequestBody","req","maxBodySize","bodySize","chunk","chunkStr","sendNotFound"],"mappings":"AAyBO,SAASA,iBACfC,GAAmB,EACnBC,UAAkB,EAClBC,IAAY,EACZC,OAAe,EACfC,KAA6B,IAAI,EACjCC,IAAc;IAEd,MAAMC,OAAgC;QACrC,SAAS;QACTF;QACA,OAAO;YAAEF;YAAMC;QAAQ;IACxB;IACA,IAAIE,AAASE,WAATF,MACFC,KAAK,KAAK,CAA6B,IAAI,GAAGD;IAEhDL,IAAI,SAAS,CAACC,YAAY;QAAE,gBAAgB;IAAmB;IAC/DD,IAAI,GAAG,CAACQ,KAAK,SAAS,CAACF;AACxB;AAUO,SAASG,oBACfT,GAAmB,EACnBU,QAAiB,EACjBT,aAAqB,GAAG,EACxBU,UAAkC,CAAC,CAAC;IAEpC,MAAMC,iBAAyC;QAAE,gBAAgB;IAAmB;IACpFZ,IAAI,SAAS,CAACC,YAAY;QAAE,GAAGW,cAAc;QAAE,GAAGD,OAAO;IAAC;IAC1DX,IAAI,GAAG,CAACQ,KAAK,SAAS,CAACE;AACxB;AAQO,SAASG,kBAAkBb,GAAmB,EAAEc,iBAA4B;IAClF,IAAIA,qBAAqBA,kBAAkB,MAAM,GAAG,GACnDd,IAAI,SAAS,CAAC,gCAAgC,CAAC,cAAc,EAAEc,kBAAkB,IAAI,CAAC,OAAO;IAE9Fd,IAAI,SAAS,CAAC;IACdA,IAAI,GAAG;AACR;AAaO,eAAee,gBACrBC,GAAoB,EACpBC,WAAmB;IAEnB,IAAIX,OAAO;IACX,IAAIY,WAAW;IAEf,WAAW,MAAMC,SAASH,IAAK;QAC9B,MAAMI,WAAW,AAAiB,YAAjB,OAAOD,QAAqBA,QAASA,MAAiB,QAAQ;QAC/ED,YAAYE,SAAS,MAAM;QAE3B,IAAIH,cAAc,KAAKC,WAAWD,aACjC,OAAO;QAGRX,QAAQc;IACT;IAEA,OAAOd;AACR;AAQO,SAASe,aAAarB,GAAmB,EAAEG,UAAkB,WAAW;IAC9EH,IAAI,SAAS,CAAC,KAAK;QAAE,gBAAgB;IAAa;IAClDA,IAAI,GAAG,CAACG;AACT"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Transport implementation.
|
|
3
|
+
*
|
|
4
|
+
* This transport provides a stateless, REST-like API interface for MCP tool invocations
|
|
5
|
+
* using standard HTTP request-response patterns.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const transport = new HttpTransport({
|
|
10
|
+
* port: 3000,
|
|
11
|
+
* host: 'localhost'
|
|
12
|
+
* });
|
|
13
|
+
* await transport.connect(server);
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import type { McpServer } from 'tmcp';
|
|
17
|
+
import type { IMetrics } from '../contracts/index.js';
|
|
18
|
+
import { BaseTransport, type TransportOptions } from './BaseTransport.js';
|
|
19
|
+
export interface HttpTransportOptions extends TransportOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Path for messages endpoint
|
|
22
|
+
* @default '/messages'
|
|
23
|
+
*/
|
|
24
|
+
path?: string;
|
|
25
|
+
metrics?: IMetrics;
|
|
26
|
+
metricsProvider?: () => string;
|
|
27
|
+
/**
|
|
28
|
+
* Enable request body size limit
|
|
29
|
+
* @default true
|
|
30
|
+
*/
|
|
31
|
+
enableBodySizeLimit?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Maximum request body size in bytes
|
|
34
|
+
* @default 10485760 (10MB)
|
|
35
|
+
*/
|
|
36
|
+
maxBodySize?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Request timeout in milliseconds
|
|
39
|
+
* @default 30000 (30 seconds)
|
|
40
|
+
*/
|
|
41
|
+
requestTimeout?: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* HTTP Transport for MCP server.
|
|
45
|
+
*
|
|
46
|
+
* This transport provides a stateless, REST-like API interface for MCP tool invocations
|
|
47
|
+
* using standard HTTP request-response patterns.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* **Security Features:**
|
|
51
|
+
* - Session ID validation (alphanumeric, max 64 chars)
|
|
52
|
+
* - Query parameter sanitization (whitelist allowed keys)
|
|
53
|
+
* - Rate limiting per IP (configurable, default 100 req/min)
|
|
54
|
+
* - CORS origin validation
|
|
55
|
+
* - Request body size limits (configurable, default 10MB)
|
|
56
|
+
* - Request timeout (configurable, default 30s)
|
|
57
|
+
*
|
|
58
|
+
* **Rate Limiting:**
|
|
59
|
+
* - Tracks requests per IP address within a time window
|
|
60
|
+
* - Returns 429 Too Many Requests when limit exceeded
|
|
61
|
+
* - Can be disabled via `enableRateLimit: false`
|
|
62
|
+
*
|
|
63
|
+
* **HTTP Status Code Mapping:**
|
|
64
|
+
* - 200: Success (JSON-RPC response)
|
|
65
|
+
* - 204: CORS Preflight (empty body)
|
|
66
|
+
* - 400: Bad Request
|
|
67
|
+
* - 403: Forbidden (invalid CORS)
|
|
68
|
+
* - 404: Not Found
|
|
69
|
+
* - 413: Payload Too Large
|
|
70
|
+
* - 429: Too Many Requests
|
|
71
|
+
* - 500: Internal Server Error
|
|
72
|
+
* - 503: Server Not Ready
|
|
73
|
+
*/
|
|
74
|
+
export declare class HttpTransport extends BaseTransport {
|
|
75
|
+
private _server;
|
|
76
|
+
private _mcpServer;
|
|
77
|
+
private _requestTimeout;
|
|
78
|
+
private _bodySizeLimitEnabled;
|
|
79
|
+
private _maxBodySize;
|
|
80
|
+
private _requestCount;
|
|
81
|
+
private _activeRequests;
|
|
82
|
+
private _path;
|
|
83
|
+
private _metrics?;
|
|
84
|
+
private _metricsProvider;
|
|
85
|
+
constructor(options?: HttpTransportOptions);
|
|
86
|
+
/**
|
|
87
|
+
* Get number of active HTTP connections.
|
|
88
|
+
*/
|
|
89
|
+
get clientCount(): number;
|
|
90
|
+
/**
|
|
91
|
+
* Connects MCP server to this transport.
|
|
92
|
+
*/
|
|
93
|
+
connect(mcpServer: McpServer): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Track an error in metrics.
|
|
96
|
+
*/
|
|
97
|
+
private _trackError;
|
|
98
|
+
/**
|
|
99
|
+
* Route and handle incoming HTTP requests.
|
|
100
|
+
*
|
|
101
|
+
* Performs security checks (host, shutdown, rate limit, CORS) then
|
|
102
|
+
* dispatches to the appropriate endpoint handler.
|
|
103
|
+
*/
|
|
104
|
+
private _handleRequest;
|
|
105
|
+
/**
|
|
106
|
+
* Handle POST to the MCP messages endpoint.
|
|
107
|
+
*
|
|
108
|
+
* Reads the request body, validates JSON-RPC format, and delegates
|
|
109
|
+
* processing to the MCP server.
|
|
110
|
+
*/
|
|
111
|
+
private _handlePostRequest;
|
|
112
|
+
/**
|
|
113
|
+
* Returns number of requests handled.
|
|
114
|
+
*/
|
|
115
|
+
get requestCount(): number;
|
|
116
|
+
/**
|
|
117
|
+
* Stops transport server.
|
|
118
|
+
*/
|
|
119
|
+
stop(): Promise<void>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Create an HTTP transport with given options.
|
|
123
|
+
*
|
|
124
|
+
* @param options - Transport configuration
|
|
125
|
+
* @returns A configured HTTP transport
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* const transport = new HttpTransport({ port: 3000 });
|
|
130
|
+
* await transport.connect(server);
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
export declare function createHttpTransport(options?: HttpTransportOptions): HttpTransport;
|
|
134
|
+
//# sourceMappingURL=HttpTransport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HttpTransport.d.ts","sourceRoot":"","sources":["../../src/transport/HttpTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAQ1E,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC7D;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;IAE/B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,aAAc,SAAQ,aAAa;IAC/C,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,qBAAqB,CAAU;IACvC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAC,CAAW;IAC5B,OAAO,CAAC,gBAAgB,CAAwB;gBAEpC,OAAO,GAAE,oBAAyB;IAY9C;;OAEG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;OAEG;IACG,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlD;;OAEG;IACH,OAAO,CAAC,WAAW;IASnB;;;;;OAKG;YACW,cAAc;IAiE5B;;;;;OAKG;YACW,kBAAkB;IAqFhC;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAW3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,oBAAyB,GAAG,aAAa,CAErF"}
|