@j0hanz/superfetch 1.1.1 → 1.1.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 +57 -32
- package/dist/config/formatting.d.ts +9 -0
- package/dist/config/formatting.d.ts.map +1 -0
- package/dist/config/formatting.js +11 -0
- package/dist/config/formatting.js.map +1 -0
- package/dist/config/index.d.ts +16 -2
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +43 -14
- package/dist/config/index.js.map +1 -1
- package/dist/config/types/content.d.ts +107 -0
- package/dist/config/types/content.d.ts.map +1 -0
- package/dist/config/types/content.js +2 -0
- package/dist/config/types/content.js.map +1 -0
- package/dist/config/types/runtime.d.ts +78 -0
- package/dist/config/types/runtime.d.ts.map +1 -0
- package/dist/config/types/runtime.js +2 -0
- package/dist/config/types/runtime.js.map +1 -0
- package/dist/config/types/tools.d.ts +99 -0
- package/dist/config/types/tools.d.ts.map +1 -0
- package/dist/config/types/tools.js +2 -0
- package/dist/config/types/tools.js.map +1 -0
- package/dist/config/types.d.ts +3 -296
- package/dist/config/types.d.ts.map +1 -1
- package/dist/http/auth.d.ts +3 -0
- package/dist/http/auth.d.ts.map +1 -0
- package/dist/http/auth.js +34 -0
- package/dist/http/auth.js.map +1 -0
- package/dist/http/cors.d.ts +8 -0
- package/dist/http/cors.d.ts.map +1 -0
- package/dist/http/cors.js +47 -0
- package/dist/http/cors.js.map +1 -0
- package/dist/http/mcp-routes.d.ts +5 -0
- package/dist/http/mcp-routes.d.ts.map +1 -0
- package/dist/http/mcp-routes.js +110 -0
- package/dist/http/mcp-routes.js.map +1 -0
- package/dist/http/mcp-session.d.ts +12 -0
- package/dist/http/mcp-session.d.ts.map +1 -0
- package/dist/http/mcp-session.js +209 -0
- package/dist/http/mcp-session.js.map +1 -0
- package/dist/http/mcp-validation.d.ts +3 -0
- package/dist/http/mcp-validation.d.ts.map +1 -0
- package/dist/http/mcp-validation.js +34 -0
- package/dist/http/mcp-validation.js.map +1 -0
- package/dist/http/rate-limit.d.ts +13 -0
- package/dist/http/rate-limit.d.ts.map +1 -0
- package/dist/http/rate-limit.js +91 -0
- package/dist/http/rate-limit.js.map +1 -0
- package/dist/http/server.d.ts +4 -0
- package/dist/http/server.d.ts.map +1 -0
- package/dist/http/server.js +183 -0
- package/dist/http/server.js.map +1 -0
- package/dist/http/sessions.d.ts +15 -0
- package/dist/http/sessions.d.ts.map +1 -0
- package/dist/http/sessions.js +64 -0
- package/dist/http/sessions.js.map +1 -0
- package/dist/index.js +26 -223
- package/dist/index.js.map +1 -1
- package/dist/middleware/error-handler.d.ts +2 -2
- package/dist/middleware/error-handler.d.ts.map +1 -1
- package/dist/middleware/error-handler.js +46 -15
- package/dist/middleware/error-handler.js.map +1 -1
- package/dist/resources/cached-content.d.ts.map +1 -1
- package/dist/resources/cached-content.js +104 -44
- package/dist/resources/cached-content.js.map +1 -1
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +77 -69
- package/dist/resources/index.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +9 -3
- package/dist/server.js.map +1 -1
- package/dist/services/cache.d.ts +13 -1
- package/dist/services/cache.d.ts.map +1 -1
- package/dist/services/cache.js +90 -13
- package/dist/services/cache.js.map +1 -1
- package/dist/services/context.d.ts +9 -0
- package/dist/services/context.d.ts.map +1 -0
- package/dist/services/context.js +9 -0
- package/dist/services/context.js.map +1 -0
- package/dist/services/extractor.d.ts.map +1 -1
- package/dist/services/extractor.js +122 -87
- package/dist/services/extractor.js.map +1 -1
- package/dist/services/fetcher/agents.d.ts +4 -0
- package/dist/services/fetcher/agents.d.ts.map +1 -0
- package/dist/services/fetcher/agents.js +111 -0
- package/dist/services/fetcher/agents.js.map +1 -0
- package/dist/services/fetcher/errors.d.ts +5 -0
- package/dist/services/fetcher/errors.d.ts.map +1 -0
- package/dist/services/fetcher/errors.js +71 -0
- package/dist/services/fetcher/errors.js.map +1 -0
- package/dist/services/fetcher/headers.d.ts +2 -0
- package/dist/services/fetcher/headers.d.ts.map +1 -0
- package/dist/services/fetcher/headers.js +28 -0
- package/dist/services/fetcher/headers.js.map +1 -0
- package/dist/services/fetcher/interceptors.d.ts +10 -0
- package/dist/services/fetcher/interceptors.d.ts.map +1 -0
- package/dist/services/fetcher/interceptors.js +82 -0
- package/dist/services/fetcher/interceptors.js.map +1 -0
- package/dist/services/fetcher/redirects.d.ts +6 -0
- package/dist/services/fetcher/redirects.d.ts.map +1 -0
- package/dist/services/fetcher/redirects.js +67 -0
- package/dist/services/fetcher/redirects.js.map +1 -0
- package/dist/services/fetcher/response.d.ts +5 -0
- package/dist/services/fetcher/response.d.ts.map +1 -0
- package/dist/services/fetcher/response.js +39 -0
- package/dist/services/fetcher/response.js.map +1 -0
- package/dist/services/fetcher/retry-policy.d.ts +28 -0
- package/dist/services/fetcher/retry-policy.d.ts.map +1 -0
- package/dist/services/fetcher/retry-policy.js +138 -0
- package/dist/services/fetcher/retry-policy.js.map +1 -0
- package/dist/services/fetcher.d.ts +2 -1
- package/dist/services/fetcher.d.ts.map +1 -1
- package/dist/services/fetcher.js +61 -254
- package/dist/services/fetcher.js.map +1 -1
- package/dist/services/logger.d.ts.map +1 -1
- package/dist/services/logger.js +14 -5
- package/dist/services/logger.js.map +1 -1
- package/dist/services/parser.d.ts +1 -0
- package/dist/services/parser.d.ts.map +1 -1
- package/dist/services/parser.js +55 -35
- package/dist/services/parser.js.map +1 -1
- package/dist/tools/handlers/fetch-links/link-extractor.d.ts +4 -0
- package/dist/tools/handlers/fetch-links/link-extractor.d.ts.map +1 -0
- package/dist/tools/handlers/fetch-links/link-extractor.js +163 -0
- package/dist/tools/handlers/fetch-links/link-extractor.js.map +1 -0
- package/dist/tools/handlers/fetch-links.tool.d.ts.map +1 -1
- package/dist/tools/handlers/fetch-links.tool.js +78 -116
- package/dist/tools/handlers/fetch-links.tool.js.map +1 -1
- package/dist/tools/handlers/fetch-markdown.tool.d.ts +3 -13
- package/dist/tools/handlers/fetch-markdown.tool.d.ts.map +1 -1
- package/dist/tools/handlers/fetch-markdown.tool.js +74 -83
- package/dist/tools/handlers/fetch-markdown.tool.js.map +1 -1
- package/dist/tools/handlers/fetch-single.shared.d.ts +26 -0
- package/dist/tools/handlers/fetch-single.shared.d.ts.map +1 -0
- package/dist/tools/handlers/fetch-single.shared.js +49 -0
- package/dist/tools/handlers/fetch-single.shared.js.map +1 -0
- package/dist/tools/handlers/fetch-url.tool.d.ts.map +1 -1
- package/dist/tools/handlers/fetch-url.tool.js +82 -54
- package/dist/tools/handlers/fetch-url.tool.js.map +1 -1
- package/dist/tools/handlers/fetch-urls/processor.d.ts +13 -0
- package/dist/tools/handlers/fetch-urls/processor.d.ts.map +1 -0
- package/dist/tools/handlers/fetch-urls/processor.js +153 -0
- package/dist/tools/handlers/fetch-urls/processor.js.map +1 -0
- package/dist/tools/handlers/fetch-urls/response.d.ts +3 -0
- package/dist/tools/handlers/fetch-urls/response.d.ts.map +1 -0
- package/dist/tools/handlers/fetch-urls/response.js +58 -0
- package/dist/tools/handlers/fetch-urls/response.js.map +1 -0
- package/dist/tools/handlers/fetch-urls/validation.d.ts +6 -0
- package/dist/tools/handlers/fetch-urls/validation.d.ts.map +1 -0
- package/dist/tools/handlers/fetch-urls/validation.js +18 -0
- package/dist/tools/handlers/fetch-urls/validation.js.map +1 -0
- package/dist/tools/handlers/fetch-urls.tool.d.ts.map +1 -1
- package/dist/tools/handlers/fetch-urls.tool.js +104 -197
- package/dist/tools/handlers/fetch-urls.tool.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +36 -237
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/schemas.d.ts +357 -0
- package/dist/tools/schemas.d.ts.map +1 -0
- package/dist/tools/schemas.js +272 -0
- package/dist/tools/schemas.js.map +1 -0
- package/dist/tools/utils/cache-vary.d.ts +3 -0
- package/dist/tools/utils/cache-vary.d.ts.map +1 -0
- package/dist/tools/utils/cache-vary.js +44 -0
- package/dist/tools/utils/cache-vary.js.map +1 -0
- package/dist/tools/utils/common.d.ts +2 -2
- package/dist/tools/utils/common.d.ts.map +1 -1
- package/dist/tools/utils/common.js +5 -1
- package/dist/tools/utils/common.js.map +1 -1
- package/dist/tools/utils/content-transform.d.ts +16 -0
- package/dist/tools/utils/content-transform.d.ts.map +1 -0
- package/dist/tools/utils/content-transform.js +49 -0
- package/dist/tools/utils/content-transform.js.map +1 -0
- package/dist/tools/utils/fetch-pipeline.d.ts.map +1 -1
- package/dist/tools/utils/fetch-pipeline.js +32 -18
- package/dist/tools/utils/fetch-pipeline.js.map +1 -1
- package/dist/tools/utils/inline-content.d.ts +11 -0
- package/dist/tools/utils/inline-content.d.ts.map +1 -0
- package/dist/tools/utils/inline-content.js +39 -0
- package/dist/tools/utils/inline-content.js.map +1 -0
- package/dist/tools/utils/markdown-toc.d.ts +3 -0
- package/dist/tools/utils/markdown-toc.d.ts.map +1 -0
- package/dist/tools/utils/markdown-toc.js +35 -0
- package/dist/tools/utils/markdown-toc.js.map +1 -0
- package/dist/tools/utils/tool-response.d.ts +9 -0
- package/dist/tools/utils/tool-response.d.ts.map +1 -0
- package/dist/tools/utils/tool-response.js +19 -0
- package/dist/tools/utils/tool-response.js.map +1 -0
- package/dist/transformers/jsonl.transformer.d.ts.map +1 -1
- package/dist/transformers/jsonl.transformer.js +51 -28
- package/dist/transformers/jsonl.transformer.js.map +1 -1
- package/dist/transformers/markdown.transformer.d.ts.map +1 -1
- package/dist/transformers/markdown.transformer.js +82 -111
- package/dist/transformers/markdown.transformer.js.map +1 -1
- package/dist/utils/header-normalizer.d.ts +5 -0
- package/dist/utils/header-normalizer.d.ts.map +1 -0
- package/dist/utils/header-normalizer.js +25 -0
- package/dist/utils/header-normalizer.js.map +1 -0
- package/dist/utils/tool-error-handler.d.ts +1 -0
- package/dist/utils/tool-error-handler.d.ts.map +1 -1
- package/dist/utils/tool-error-handler.js +29 -1
- package/dist/utils/tool-error-handler.js.map +1 -1
- package/dist/utils/url-validator.d.ts +0 -3
- package/dist/utils/url-validator.d.ts.map +1 -1
- package/dist/utils/url-validator.js +98 -18
- package/dist/utils/url-validator.js.map +1 -1
- package/package.json +11 -6
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export function getSessionId(req) {
|
|
2
|
+
const header = req.headers['mcp-session-id'];
|
|
3
|
+
return Array.isArray(header) ? header[0] : header;
|
|
4
|
+
}
|
|
5
|
+
export function createSessionStore(sessionTtlMs) {
|
|
6
|
+
const sessions = new Map();
|
|
7
|
+
return {
|
|
8
|
+
get: (sessionId) => sessions.get(sessionId),
|
|
9
|
+
touch: (sessionId) => {
|
|
10
|
+
touchSession(sessions, sessionId);
|
|
11
|
+
},
|
|
12
|
+
set: (sessionId, entry) => {
|
|
13
|
+
sessions.set(sessionId, entry);
|
|
14
|
+
},
|
|
15
|
+
remove: (sessionId) => removeSession(sessions, sessionId),
|
|
16
|
+
size: () => sessions.size,
|
|
17
|
+
clear: () => clearSessions(sessions),
|
|
18
|
+
evictExpired: () => evictExpiredSessions(sessions, sessionTtlMs),
|
|
19
|
+
evictOldest: () => evictOldestSession(sessions),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function touchSession(sessions, sessionId) {
|
|
23
|
+
const session = sessions.get(sessionId);
|
|
24
|
+
if (session) {
|
|
25
|
+
session.lastSeen = Date.now();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function removeSession(sessions, sessionId) {
|
|
29
|
+
const session = sessions.get(sessionId);
|
|
30
|
+
sessions.delete(sessionId);
|
|
31
|
+
return session;
|
|
32
|
+
}
|
|
33
|
+
function clearSessions(sessions) {
|
|
34
|
+
const entries = Array.from(sessions.values());
|
|
35
|
+
sessions.clear();
|
|
36
|
+
return entries;
|
|
37
|
+
}
|
|
38
|
+
function evictExpiredSessions(sessions, sessionTtlMs) {
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
const evicted = [];
|
|
41
|
+
for (const [id, session] of sessions.entries()) {
|
|
42
|
+
if (now - session.lastSeen > sessionTtlMs) {
|
|
43
|
+
sessions.delete(id);
|
|
44
|
+
evicted.push(session);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return evicted;
|
|
48
|
+
}
|
|
49
|
+
function evictOldestSession(sessions) {
|
|
50
|
+
let oldestId;
|
|
51
|
+
let oldestSeen = Number.POSITIVE_INFINITY;
|
|
52
|
+
for (const [id, session] of sessions.entries()) {
|
|
53
|
+
if (session.lastSeen < oldestSeen) {
|
|
54
|
+
oldestSeen = session.lastSeen;
|
|
55
|
+
oldestId = id;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!oldestId)
|
|
59
|
+
return undefined;
|
|
60
|
+
const session = sessions.get(oldestId);
|
|
61
|
+
sessions.delete(oldestId);
|
|
62
|
+
return session;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/http/sessions.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,242 +1,45 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { logError } from './services/logger.js';
|
|
4
|
+
import { startHttpServer } from './http/server.js';
|
|
5
|
+
import { startStdioServer } from './server.js';
|
|
6
|
+
const { values } = parseArgs({
|
|
7
|
+
options: {
|
|
8
|
+
stdio: { type: 'boolean', default: false },
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
const isStdioMode = values.stdio;
|
|
10
12
|
let isShuttingDown = false;
|
|
11
13
|
const shutdownHandlerRef = {};
|
|
14
|
+
function shouldAttemptShutdown() {
|
|
15
|
+
return !isShuttingDown && !isStdioMode && Boolean(shutdownHandlerRef.current);
|
|
16
|
+
}
|
|
17
|
+
function attemptShutdown(signal) {
|
|
18
|
+
if (!shutdownHandlerRef.current)
|
|
19
|
+
return;
|
|
20
|
+
isShuttingDown = true;
|
|
21
|
+
process.stderr.write('Attempting graceful shutdown...\n');
|
|
22
|
+
void shutdownHandlerRef.current(signal);
|
|
23
|
+
}
|
|
12
24
|
process.on('uncaughtException', (error) => {
|
|
13
25
|
logError('Uncaught exception', error);
|
|
14
26
|
process.stderr.write(`Uncaught exception: ${error.message}\n`);
|
|
15
|
-
if (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
process.stderr.write('Attempting graceful shutdown...\n');
|
|
19
|
-
void shutdownHandlerRef.current('UNCAUGHT_EXCEPTION');
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
process.exit(1);
|
|
27
|
+
if (shouldAttemptShutdown()) {
|
|
28
|
+
attemptShutdown('UNCAUGHT_EXCEPTION');
|
|
29
|
+
return;
|
|
23
30
|
}
|
|
31
|
+
process.exit(1);
|
|
24
32
|
});
|
|
25
33
|
process.on('unhandledRejection', (reason) => {
|
|
26
34
|
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
27
35
|
logError('Unhandled rejection', error);
|
|
28
36
|
process.stderr.write(`Unhandled rejection: ${error.message}\n`);
|
|
29
37
|
});
|
|
30
|
-
const isStdioMode = process.argv.includes('--stdio');
|
|
31
|
-
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS
|
|
32
|
-
? process.env.ALLOWED_ORIGINS.split(',').map((o) => o.trim())
|
|
33
|
-
: [];
|
|
34
|
-
const ALLOW_ALL_ORIGINS = process.env.CORS_ALLOW_ALL === 'true';
|
|
35
|
-
function getSessionId(req) {
|
|
36
|
-
const header = req.headers['mcp-session-id'];
|
|
37
|
-
return Array.isArray(header) ? header[0] : header;
|
|
38
|
-
}
|
|
39
|
-
function isMcpRequestBody(body) {
|
|
40
|
-
if (!body || typeof body !== 'object')
|
|
41
|
-
return false;
|
|
42
|
-
const obj = body;
|
|
43
|
-
return ((obj.method === undefined || typeof obj.method === 'string') &&
|
|
44
|
-
(obj.id === undefined ||
|
|
45
|
-
typeof obj.id === 'string' ||
|
|
46
|
-
typeof obj.id === 'number') &&
|
|
47
|
-
(obj.jsonrpc === undefined || obj.jsonrpc === '2.0') &&
|
|
48
|
-
(obj.params === undefined || typeof obj.params === 'object'));
|
|
49
|
-
}
|
|
50
|
-
const asyncHandler = (fn) => {
|
|
51
|
-
return (req, res, next) => {
|
|
52
|
-
Promise.resolve(fn(req, res, next)).catch(next);
|
|
53
|
-
};
|
|
54
|
-
};
|
|
55
38
|
if (isStdioMode) {
|
|
56
|
-
const { startStdioServer } = await import('./server.js');
|
|
57
39
|
await startStdioServer();
|
|
58
40
|
}
|
|
59
41
|
else {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
app.use((err, _req, res, next) => {
|
|
63
|
-
if (err instanceof SyntaxError && 'body' in err) {
|
|
64
|
-
res.status(400).json({
|
|
65
|
-
jsonrpc: '2.0',
|
|
66
|
-
error: {
|
|
67
|
-
code: -32700,
|
|
68
|
-
message: 'Parse error: Invalid JSON',
|
|
69
|
-
},
|
|
70
|
-
id: null,
|
|
71
|
-
});
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
next(err);
|
|
75
|
-
});
|
|
76
|
-
app.use((req, res, next) => {
|
|
77
|
-
const { origin } = req.headers;
|
|
78
|
-
if (origin) {
|
|
79
|
-
try {
|
|
80
|
-
new URL(origin);
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
next();
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (!origin ||
|
|
88
|
-
ALLOW_ALL_ORIGINS ||
|
|
89
|
-
(ALLOWED_ORIGINS.length > 0 && ALLOWED_ORIGINS.includes(origin))) {
|
|
90
|
-
res.header('Access-Control-Allow-Origin', origin ?? '*');
|
|
91
|
-
res.header('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
92
|
-
res.header('Access-Control-Allow-Headers', 'Content-Type, mcp-session-id');
|
|
93
|
-
res.header('Access-Control-Max-Age', '86400');
|
|
94
|
-
}
|
|
95
|
-
else if (ALLOWED_ORIGINS.length > 0) {
|
|
96
|
-
// Origin not in allowlist
|
|
97
|
-
next();
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
if (req.method === 'OPTIONS') {
|
|
101
|
-
res.sendStatus(200);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
next();
|
|
105
|
-
});
|
|
106
|
-
app.get('/health', (_req, res) => {
|
|
107
|
-
res.json({
|
|
108
|
-
status: 'healthy',
|
|
109
|
-
name: config.server.name,
|
|
110
|
-
version: config.server.version,
|
|
111
|
-
uptime: process.uptime(),
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
// Simple session storage - Map suffices for debugging HTTP mode
|
|
115
|
-
const sessions = new Map();
|
|
116
|
-
app.post('/mcp', asyncHandler(async (req, res) => {
|
|
117
|
-
const sessionId = getSessionId(req);
|
|
118
|
-
let transport;
|
|
119
|
-
// Validate request body structure
|
|
120
|
-
if (!isMcpRequestBody(req.body)) {
|
|
121
|
-
res.status(400).json({
|
|
122
|
-
jsonrpc: '2.0',
|
|
123
|
-
error: {
|
|
124
|
-
code: -32600,
|
|
125
|
-
message: 'Invalid Request: Malformed request body',
|
|
126
|
-
},
|
|
127
|
-
id: null,
|
|
128
|
-
});
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
// Body is validated above as McpRequestBody via type guard
|
|
132
|
-
logInfo('[MCP POST]', {
|
|
133
|
-
method: req.body.method,
|
|
134
|
-
id: req.body.id,
|
|
135
|
-
sessionId: sessionId ?? 'none',
|
|
136
|
-
isInitialize: isInitializeRequest(req.body),
|
|
137
|
-
sessionCount: sessions.size,
|
|
138
|
-
});
|
|
139
|
-
const existingSession = sessionId ? sessions.get(sessionId) : undefined;
|
|
140
|
-
if (existingSession && sessionId) {
|
|
141
|
-
({ transport } = existingSession);
|
|
142
|
-
}
|
|
143
|
-
else if (!sessionId && isInitializeRequest(req.body)) {
|
|
144
|
-
transport = new StreamableHTTPServerTransport({
|
|
145
|
-
sessionIdGenerator: () => crypto.randomUUID(),
|
|
146
|
-
onsessioninitialized: (id) => {
|
|
147
|
-
sessions.set(id, { transport, createdAt: Date.now() });
|
|
148
|
-
logInfo('Session initialized', { sessionId: id });
|
|
149
|
-
},
|
|
150
|
-
onsessionclosed: (id) => {
|
|
151
|
-
sessions.delete(id);
|
|
152
|
-
logInfo('Session closed', { sessionId: id });
|
|
153
|
-
},
|
|
154
|
-
});
|
|
155
|
-
transport.onclose = () => {
|
|
156
|
-
if (transport.sessionId) {
|
|
157
|
-
sessions.delete(transport.sessionId);
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
const mcpServer = createMcpServer();
|
|
161
|
-
await mcpServer.connect(transport);
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
res.status(400).json({
|
|
165
|
-
jsonrpc: '2.0',
|
|
166
|
-
error: {
|
|
167
|
-
code: -32000,
|
|
168
|
-
message: 'Bad Request: Missing session ID or not an initialize request',
|
|
169
|
-
},
|
|
170
|
-
id: null,
|
|
171
|
-
});
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
await transport.handleRequest(req, res, req.body);
|
|
175
|
-
}));
|
|
176
|
-
app.get('/mcp', asyncHandler(async (req, res) => {
|
|
177
|
-
const sessionId = getSessionId(req);
|
|
178
|
-
if (!sessionId) {
|
|
179
|
-
res.status(400).json({ error: 'Missing mcp-session-id header' });
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
const session = sessionId ? sessions.get(sessionId) : undefined;
|
|
183
|
-
if (!session) {
|
|
184
|
-
res.status(404).json({ error: 'Session not found' });
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
await session.transport.handleRequest(req, res);
|
|
188
|
-
}));
|
|
189
|
-
app.delete('/mcp', asyncHandler(async (req, res) => {
|
|
190
|
-
const sessionId = getSessionId(req);
|
|
191
|
-
const session = sessionId ? sessions.get(sessionId) : undefined;
|
|
192
|
-
if (session) {
|
|
193
|
-
await session.transport.handleRequest(req, res);
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
res.status(204).end();
|
|
197
|
-
}
|
|
198
|
-
}));
|
|
199
|
-
app.use(errorHandler);
|
|
200
|
-
const server = app
|
|
201
|
-
.listen(config.server.port, config.server.host, () => {
|
|
202
|
-
logInfo(`superFetch MCP server started`, {
|
|
203
|
-
host: config.server.host,
|
|
204
|
-
port: config.server.port,
|
|
205
|
-
});
|
|
206
|
-
process.stdout.write(`✓ superFetch MCP server running at http://${config.server.host}:${config.server.port}\n`);
|
|
207
|
-
process.stdout.write(` Health check: http://${config.server.host}:${config.server.port}/health\n`);
|
|
208
|
-
process.stdout.write(` MCP endpoint: http://${config.server.host}:${config.server.port}/mcp\n`);
|
|
209
|
-
process.stdout.write(`\nRun with --stdio flag for direct stdio integration\n`);
|
|
210
|
-
})
|
|
211
|
-
.on('error', (err) => {
|
|
212
|
-
logError('Failed to start server', err);
|
|
213
|
-
process.exit(1);
|
|
214
|
-
});
|
|
215
|
-
const shutdownFn = async (signal) => {
|
|
216
|
-
if (isShuttingDown)
|
|
217
|
-
return;
|
|
218
|
-
isShuttingDown = true;
|
|
219
|
-
process.stdout.write(`\n${signal} received, shutting down gracefully...\n`);
|
|
220
|
-
// Close all sessions
|
|
221
|
-
const closePromises = Array.from(sessions.values()).map((session) => session.transport.close());
|
|
222
|
-
await Promise.allSettled(closePromises);
|
|
223
|
-
sessions.clear();
|
|
224
|
-
destroyAgents();
|
|
225
|
-
server.close(() => {
|
|
226
|
-
logInfo('HTTP server closed');
|
|
227
|
-
process.exit(0);
|
|
228
|
-
});
|
|
229
|
-
setTimeout(() => {
|
|
230
|
-
logError('Forced shutdown after timeout');
|
|
231
|
-
process.exit(1);
|
|
232
|
-
}, 10000).unref();
|
|
233
|
-
};
|
|
234
|
-
shutdownHandlerRef.current = shutdownFn;
|
|
235
|
-
process.on('SIGINT', () => {
|
|
236
|
-
void shutdownFn('SIGINT');
|
|
237
|
-
});
|
|
238
|
-
process.on('SIGTERM', () => {
|
|
239
|
-
void shutdownFn('SIGTERM');
|
|
240
|
-
});
|
|
42
|
+
const { shutdown } = await startHttpServer();
|
|
43
|
+
shutdownHandlerRef.current = shutdown;
|
|
241
44
|
}
|
|
242
45
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE;QACP,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;KAC3C;CACF,CAAC,CAAC;AACH,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC;AACjC,IAAI,cAAc,GAAG,KAAK,CAAC;AAE3B,MAAM,kBAAkB,GAAoD,EAAE,CAAC;AAE/E,SAAS,qBAAqB;IAC5B,OAAO,CAAC,cAAc,IAAI,CAAC,WAAW,IAAI,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAChF,CAAC;AAED,SAAS,eAAe,CAAC,MAAc;IACrC,IAAI,CAAC,kBAAkB,CAAC,OAAO;QAAE,OAAO;IACxC,cAAc,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;IAC1D,KAAK,kBAAkB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AAC1C,CAAC;AAED,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,KAAK,EAAE,EAAE;IACxC,QAAQ,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;IACtC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,uBAAuB,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;IAE/D,IAAI,qBAAqB,EAAE,EAAE,CAAC;QAC5B,eAAe,CAAC,oBAAoB,CAAC,CAAC;QACtC,OAAO;IACT,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAM,EAAE,EAAE;IAC1C,MAAM,KAAK,GAAG,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3E,QAAQ,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;IACvC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC;AAEH,IAAI,WAAW,EAAE,CAAC;IAChB,MAAM,gBAAgB,EAAE,CAAC;AAC3B,CAAC;KAAM,CAAC;IACN,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,eAAe,EAAE,CAAC;IAC7C,kBAAkB,CAAC,OAAO,GAAG,QAAQ,CAAC;AACxC,CAAC"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { Request, Response } from 'express';
|
|
2
|
-
export declare function errorHandler(err: Error, req: Request, res: Response): void;
|
|
1
|
+
import type { NextFunction, Request, Response } from 'express';
|
|
2
|
+
export declare function errorHandler(err: Error, req: Request, res: Response, _next: NextFunction): void;
|
|
3
3
|
//# sourceMappingURL=error-handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAgE/D,wBAAgB,YAAY,CAC1B,GAAG,EAAE,KAAK,EACV,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,YAAY,GAClB,IAAI,CAWN"}
|
|
@@ -1,27 +1,58 @@
|
|
|
1
1
|
import { FetchError } from '../errors/app-error.js';
|
|
2
2
|
import { logError } from '../services/logger.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
function getStatusCode(err) {
|
|
4
|
+
return err instanceof FetchError ? err.statusCode : 500;
|
|
5
|
+
}
|
|
6
|
+
function getErrorCode(err) {
|
|
7
|
+
return err instanceof FetchError ? err.code : 'INTERNAL_ERROR';
|
|
8
|
+
}
|
|
9
|
+
function getErrorMessage(err) {
|
|
10
|
+
return err instanceof FetchError ? err.message : 'Internal Server Error';
|
|
11
|
+
}
|
|
12
|
+
function getErrorDetails(err) {
|
|
13
|
+
if (err instanceof FetchError && Object.keys(err.details).length > 0) {
|
|
14
|
+
return err.details;
|
|
12
15
|
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
function setRetryAfterHeader(res, err) {
|
|
19
|
+
const retryAfter = resolveRetryAfter(err);
|
|
20
|
+
if (!retryAfter)
|
|
21
|
+
return;
|
|
22
|
+
res.set('Retry-After', retryAfter);
|
|
23
|
+
}
|
|
24
|
+
function buildErrorResponse(err) {
|
|
25
|
+
const details = getErrorDetails(err);
|
|
13
26
|
const response = {
|
|
14
27
|
error: {
|
|
15
|
-
message,
|
|
16
|
-
code,
|
|
17
|
-
statusCode,
|
|
18
|
-
...(
|
|
19
|
-
Object.keys(err.details).length > 0 && { details: err.details }),
|
|
28
|
+
message: getErrorMessage(err),
|
|
29
|
+
code: getErrorCode(err),
|
|
30
|
+
statusCode: getStatusCode(err),
|
|
31
|
+
...(details && { details }),
|
|
20
32
|
},
|
|
21
33
|
};
|
|
22
34
|
if (process.env.NODE_ENV === 'development') {
|
|
23
35
|
response.error.stack = err.stack;
|
|
24
36
|
}
|
|
25
|
-
|
|
37
|
+
return response;
|
|
38
|
+
}
|
|
39
|
+
function resolveRetryAfter(err) {
|
|
40
|
+
if (!(err instanceof FetchError))
|
|
41
|
+
return null;
|
|
42
|
+
if (err.statusCode !== 429)
|
|
43
|
+
return null;
|
|
44
|
+
const { retryAfter } = err.details;
|
|
45
|
+
if (!isRetryAfterValue(retryAfter))
|
|
46
|
+
return null;
|
|
47
|
+
return String(retryAfter);
|
|
48
|
+
}
|
|
49
|
+
function isRetryAfterValue(value) {
|
|
50
|
+
return typeof value === 'number' || typeof value === 'string';
|
|
51
|
+
}
|
|
52
|
+
export function errorHandler(err, req, res, _next) {
|
|
53
|
+
const statusCode = getStatusCode(err);
|
|
54
|
+
logError(`HTTP ${statusCode}: ${err.message} - ${req.method} ${req.path}`, err);
|
|
55
|
+
setRetryAfterHeader(res, err);
|
|
56
|
+
res.status(statusCode).json(buildErrorResponse(err));
|
|
26
57
|
}
|
|
27
58
|
//# sourceMappingURL=error-handler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,
|
|
1
|
+
{"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cached-content.d.ts","sourceRoot":"","sources":["../../src/resources/cached-content.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"cached-content.d.ts","sourceRoot":"","sources":["../../src/resources/cached-content.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAgEzE,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAIrE"}
|
|
@@ -1,62 +1,122 @@
|
|
|
1
1
|
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import * as cache from '../services/cache.js';
|
|
3
|
+
import { logWarn } from '../services/logger.js';
|
|
4
|
+
function buildResourceEntry(namespace, urlHash) {
|
|
5
|
+
return {
|
|
6
|
+
name: `${namespace}:${urlHash}`,
|
|
7
|
+
uri: `superfetch://cache/${namespace}/${urlHash}`,
|
|
8
|
+
description: `Cached content entry for ${namespace}`,
|
|
9
|
+
mimeType: 'application/json',
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function listCachedResources() {
|
|
13
|
+
const resources = cache
|
|
14
|
+
.keys()
|
|
15
|
+
.map((key) => {
|
|
16
|
+
const parts = cache.parseCacheKey(key);
|
|
17
|
+
return parts ? buildResourceEntry(parts.namespace, parts.urlHash) : null;
|
|
18
|
+
})
|
|
19
|
+
.filter((entry) => entry !== null);
|
|
20
|
+
return { resources };
|
|
21
|
+
}
|
|
22
|
+
function buildCacheListPayload() {
|
|
23
|
+
const cacheKeys = cache.keys();
|
|
24
|
+
return {
|
|
25
|
+
totalEntries: cacheKeys.length,
|
|
26
|
+
entries: cacheKeys.map((key) => {
|
|
27
|
+
const parts = cache.parseCacheKey(key);
|
|
28
|
+
const namespace = parts?.namespace ?? 'unknown';
|
|
29
|
+
const urlHash = parts?.urlHash ?? 'unknown';
|
|
30
|
+
return {
|
|
31
|
+
namespace,
|
|
32
|
+
urlHash,
|
|
33
|
+
resourceUri: `superfetch://cache/${namespace}/${urlHash}`,
|
|
34
|
+
};
|
|
35
|
+
}),
|
|
36
|
+
timestamp: new Date().toISOString(),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function notifyResourceUpdate(server, uri) {
|
|
40
|
+
if (!server.isConnected())
|
|
41
|
+
return;
|
|
42
|
+
void server.server.sendResourceUpdated({ uri }).catch((error) => {
|
|
43
|
+
logWarn('Failed to send resource update notification', {
|
|
44
|
+
uri,
|
|
45
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
3
49
|
export function registerCachedContentResource(server) {
|
|
50
|
+
registerCacheContentResource(server);
|
|
51
|
+
registerCacheListResource(server);
|
|
52
|
+
registerCacheUpdateSubscription(server);
|
|
53
|
+
}
|
|
54
|
+
function resolveCacheParams(params) {
|
|
55
|
+
const namespace = params.namespace;
|
|
56
|
+
const urlHash = params.urlHash;
|
|
57
|
+
if (!namespace || !urlHash) {
|
|
58
|
+
throw new Error('Both namespace and urlHash parameters are required');
|
|
59
|
+
}
|
|
60
|
+
return { namespace, urlHash };
|
|
61
|
+
}
|
|
62
|
+
function buildCachedContentResponse(uri, cacheKey) {
|
|
63
|
+
const cached = cache.get(cacheKey);
|
|
64
|
+
if (!cached) {
|
|
65
|
+
throw new Error(`Content not found in cache for key: ${cacheKey}. Use superfetch://stats to see available cache entries.`);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
contents: [
|
|
69
|
+
{
|
|
70
|
+
uri: uri.href,
|
|
71
|
+
mimeType: 'application/json',
|
|
72
|
+
text: cached.content,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function registerCacheContentResource(server) {
|
|
4
78
|
server.registerResource('cached-content', new ResourceTemplate('superfetch://cache/{namespace}/{urlHash}', {
|
|
5
|
-
list:
|
|
79
|
+
list: listCachedResources,
|
|
6
80
|
}), {
|
|
7
81
|
title: 'Cached Content',
|
|
8
82
|
description: 'Access previously fetched web content from cache. Namespace: url, links, markdown. UrlHash: SHA-256 hash of the URL.',
|
|
9
83
|
mimeType: 'application/json',
|
|
10
84
|
}, (uri, params) => {
|
|
11
|
-
const namespace = params
|
|
12
|
-
const urlHash = params.urlHash;
|
|
13
|
-
if (!namespace || !urlHash) {
|
|
14
|
-
throw new Error('Both namespace and urlHash parameters are required');
|
|
15
|
-
}
|
|
85
|
+
const { namespace, urlHash } = resolveCacheParams(params);
|
|
16
86
|
const cacheKey = `${namespace}:${urlHash}`;
|
|
17
|
-
|
|
18
|
-
if (!cached) {
|
|
19
|
-
throw new Error(`Content not found in cache for key: ${cacheKey}. Use superfetch://stats to see available cache entries.`);
|
|
20
|
-
}
|
|
21
|
-
return {
|
|
22
|
-
contents: [
|
|
23
|
-
{
|
|
24
|
-
uri: uri.href,
|
|
25
|
-
mimeType: 'application/json',
|
|
26
|
-
text: cached.content,
|
|
27
|
-
},
|
|
28
|
-
],
|
|
29
|
-
};
|
|
87
|
+
return buildCachedContentResponse(uri, cacheKey);
|
|
30
88
|
});
|
|
89
|
+
}
|
|
90
|
+
function registerCacheListResource(server) {
|
|
31
91
|
server.registerResource('cached-urls', 'superfetch://cache/list', {
|
|
32
92
|
title: 'Cached URLs List',
|
|
33
93
|
description: 'List all URLs currently in cache with their namespaces',
|
|
34
94
|
mimeType: 'application/json',
|
|
35
|
-
}, (uri) => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
mimeType: 'application/json',
|
|
56
|
-
text: JSON.stringify(cacheList, null, 2),
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
};
|
|
95
|
+
}, (uri) => ({
|
|
96
|
+
contents: [
|
|
97
|
+
{
|
|
98
|
+
uri: uri.href,
|
|
99
|
+
mimeType: 'application/json',
|
|
100
|
+
text: JSON.stringify(buildCacheListPayload(), null, 2),
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
function registerCacheUpdateSubscription(server) {
|
|
106
|
+
const unsubscribe = cache.onCacheUpdate(({ cacheKey }) => {
|
|
107
|
+
const resourceUri = cache.toResourceUri(cacheKey);
|
|
108
|
+
if (!resourceUri)
|
|
109
|
+
return;
|
|
110
|
+
notifyResourceUpdate(server, resourceUri);
|
|
111
|
+
notifyResourceUpdate(server, 'superfetch://cache/list');
|
|
112
|
+
if (server.isConnected()) {
|
|
113
|
+
server.sendResourceListChanged();
|
|
114
|
+
}
|
|
60
115
|
});
|
|
116
|
+
const previousOnClose = server.server.onclose;
|
|
117
|
+
server.server.onclose = () => {
|
|
118
|
+
previousOnClose?.();
|
|
119
|
+
unsubscribe();
|
|
120
|
+
};
|
|
61
121
|
}
|
|
62
122
|
//# sourceMappingURL=cached-content.js.map
|