@j0hanz/superfetch 2.1.3 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache.js +36 -6
- package/dist/http.d.ts +1 -0
- package/dist/http.js +33 -5
- package/package.json +4 -4
package/dist/cache.js
CHANGED
|
@@ -410,6 +410,30 @@ function appendServerOnClose(server, handler) {
|
|
|
410
410
|
handler();
|
|
411
411
|
};
|
|
412
412
|
}
|
|
413
|
+
function attachInitializedGate(server) {
|
|
414
|
+
let initialized = false;
|
|
415
|
+
const previousInitialized = server.server.oninitialized;
|
|
416
|
+
server.server.oninitialized = () => {
|
|
417
|
+
initialized = true;
|
|
418
|
+
previousInitialized?.();
|
|
419
|
+
};
|
|
420
|
+
return () => initialized;
|
|
421
|
+
}
|
|
422
|
+
function getClientResourceCapabilities(server) {
|
|
423
|
+
const caps = server.server.getClientCapabilities();
|
|
424
|
+
if (!caps || !isRecord(caps)) {
|
|
425
|
+
return { listChanged: true, subscribe: true };
|
|
426
|
+
}
|
|
427
|
+
const { resources } = caps;
|
|
428
|
+
if (!isRecord(resources)) {
|
|
429
|
+
return { listChanged: true, subscribe: true };
|
|
430
|
+
}
|
|
431
|
+
const { listChanged, subscribe } = resources;
|
|
432
|
+
return {
|
|
433
|
+
listChanged: listChanged === true,
|
|
434
|
+
subscribe: subscribe === true,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
413
437
|
function registerResourceSubscriptionHandlers(server) {
|
|
414
438
|
const subscriptions = new Set();
|
|
415
439
|
server.server.setRequestHandler(SubscribeRequestSchema, (request) => {
|
|
@@ -438,9 +462,10 @@ function notifyResourceUpdate(server, uri, subscriptions) {
|
|
|
438
462
|
});
|
|
439
463
|
}
|
|
440
464
|
export function registerCachedContentResource(server) {
|
|
465
|
+
const isInitialized = attachInitializedGate(server);
|
|
441
466
|
const subscriptions = registerResourceSubscriptionHandlers(server);
|
|
442
467
|
registerCacheContentResource(server);
|
|
443
|
-
registerCacheUpdateSubscription(server, subscriptions);
|
|
468
|
+
registerCacheUpdateSubscription(server, subscriptions, isInitialized);
|
|
444
469
|
}
|
|
445
470
|
function buildCachedContentResponse(uri, cacheKey) {
|
|
446
471
|
const cached = requireCacheEntry(cacheKey);
|
|
@@ -459,13 +484,18 @@ function registerCacheContentResource(server) {
|
|
|
459
484
|
return buildCachedContentResponse(uri, cacheKey);
|
|
460
485
|
});
|
|
461
486
|
}
|
|
462
|
-
function registerCacheUpdateSubscription(server, subscriptions) {
|
|
487
|
+
function registerCacheUpdateSubscription(server, subscriptions, isInitialized) {
|
|
463
488
|
const unsubscribe = onCacheUpdate(({ cacheKey }) => {
|
|
464
|
-
|
|
465
|
-
if (!resourceUri)
|
|
489
|
+
if (!server.isConnected() || !isInitialized())
|
|
466
490
|
return;
|
|
467
|
-
|
|
468
|
-
if (
|
|
491
|
+
const { listChanged, subscribe } = getClientResourceCapabilities(server);
|
|
492
|
+
if (subscribe) {
|
|
493
|
+
const resourceUri = toResourceUri(cacheKey);
|
|
494
|
+
if (resourceUri) {
|
|
495
|
+
notifyResourceUpdate(server, resourceUri, subscriptions);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (listChanged) {
|
|
469
499
|
server.sendResourceListChanged();
|
|
470
500
|
}
|
|
471
501
|
});
|
package/dist/http.d.ts
CHANGED
package/dist/http.js
CHANGED
|
@@ -988,6 +988,14 @@ function respondServerBusy(res, requestId) {
|
|
|
988
988
|
function respondBadRequest(res, id) {
|
|
989
989
|
sendJsonRpcErrorOrNoContent(res, -32000, 'Bad Request: Missing session ID or not an initialize request', 400, id);
|
|
990
990
|
}
|
|
991
|
+
function respondSessionNotInitialized(res, requestId) {
|
|
992
|
+
sendJsonRpcErrorOrNoContent(res, -32000, 'Bad Request: Session not initialized', 400, requestId);
|
|
993
|
+
}
|
|
994
|
+
function isAllowedBeforeInitialized(method) {
|
|
995
|
+
return (method === 'initialize' ||
|
|
996
|
+
method === 'notifications/initialized' ||
|
|
997
|
+
method === 'ping');
|
|
998
|
+
}
|
|
991
999
|
function createTimeoutController() {
|
|
992
1000
|
let initTimeout = null;
|
|
993
1001
|
return {
|
|
@@ -1116,6 +1124,7 @@ async function connectTransportOrThrow({ transport, clearInitTimeout, releaseSlo
|
|
|
1116
1124
|
logError('Failed to initialize MCP session', error instanceof Error ? error : undefined);
|
|
1117
1125
|
throw error;
|
|
1118
1126
|
}
|
|
1127
|
+
return mcpServer;
|
|
1119
1128
|
}
|
|
1120
1129
|
function evictExpiredSessionsWithClose(store) {
|
|
1121
1130
|
const evicted = store.evictExpired();
|
|
@@ -1156,9 +1165,14 @@ function reserveSessionIfPossible({ options, res, requestId, }) {
|
|
|
1156
1165
|
}
|
|
1157
1166
|
return true;
|
|
1158
1167
|
}
|
|
1159
|
-
function resolveExistingSessionTransport(store, sessionId, res, requestId) {
|
|
1168
|
+
function resolveExistingSessionTransport(store, sessionId, res, requestId, method) {
|
|
1160
1169
|
const existingSession = store.get(sessionId);
|
|
1161
1170
|
if (existingSession) {
|
|
1171
|
+
if (!existingSession.protocolInitialized &&
|
|
1172
|
+
!isAllowedBeforeInitialized(method)) {
|
|
1173
|
+
respondSessionNotInitialized(res, requestId);
|
|
1174
|
+
return null;
|
|
1175
|
+
}
|
|
1162
1176
|
store.touch(sessionId);
|
|
1163
1177
|
return existingSession.transport;
|
|
1164
1178
|
}
|
|
@@ -1172,7 +1186,17 @@ function createSessionContext() {
|
|
|
1172
1186
|
const transport = createSessionTransport({ tracker, timeoutController });
|
|
1173
1187
|
return { tracker, timeoutController, transport };
|
|
1174
1188
|
}
|
|
1175
|
-
function
|
|
1189
|
+
function attachSessionInitializedHandler(server, store, sessionId) {
|
|
1190
|
+
const previousInitialized = server.server.oninitialized;
|
|
1191
|
+
server.server.oninitialized = () => {
|
|
1192
|
+
const entry = store.get(sessionId);
|
|
1193
|
+
if (entry) {
|
|
1194
|
+
entry.protocolInitialized = true;
|
|
1195
|
+
}
|
|
1196
|
+
previousInitialized?.();
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
function finalizeSessionIfValid({ store, transport, mcpServer, tracker, clearInitTimeout, res, requestId, }) {
|
|
1176
1200
|
const { sessionId } = transport;
|
|
1177
1201
|
if (typeof sessionId !== 'string') {
|
|
1178
1202
|
clearInitTimeout();
|
|
@@ -1184,12 +1208,13 @@ function finalizeSessionIfValid({ store, transport, tracker, clearInitTimeout, r
|
|
|
1184
1208
|
store,
|
|
1185
1209
|
transport,
|
|
1186
1210
|
sessionId,
|
|
1211
|
+
mcpServer,
|
|
1187
1212
|
tracker,
|
|
1188
1213
|
clearInitTimeout,
|
|
1189
1214
|
});
|
|
1190
1215
|
return true;
|
|
1191
1216
|
}
|
|
1192
|
-
function finalizeSession({ store, transport, sessionId, tracker, clearInitTimeout, }) {
|
|
1217
|
+
function finalizeSession({ store, transport, sessionId, mcpServer, tracker, clearInitTimeout, }) {
|
|
1193
1218
|
clearInitTimeout();
|
|
1194
1219
|
tracker.markInitialized();
|
|
1195
1220
|
tracker.releaseSlot();
|
|
@@ -1198,7 +1223,9 @@ function finalizeSession({ store, transport, sessionId, tracker, clearInitTimeou
|
|
|
1198
1223
|
transport,
|
|
1199
1224
|
createdAt: now,
|
|
1200
1225
|
lastSeen: now,
|
|
1226
|
+
protocolInitialized: false,
|
|
1201
1227
|
});
|
|
1228
|
+
attachSessionInitializedHandler(mcpServer, store, sessionId);
|
|
1202
1229
|
const previousOnClose = transport.onclose;
|
|
1203
1230
|
transport.onclose = composeCloseHandlers(previousOnClose, () => {
|
|
1204
1231
|
store.remove(sessionId);
|
|
@@ -1215,7 +1242,7 @@ async function createAndConnectTransport({ options, res, requestId, }) {
|
|
|
1215
1242
|
if (!reserveSessionIfPossible(reserveArgs))
|
|
1216
1243
|
return null;
|
|
1217
1244
|
const { tracker, timeoutController, transport } = createSessionContext();
|
|
1218
|
-
await connectTransportOrThrow({
|
|
1245
|
+
const mcpServer = await connectTransportOrThrow({
|
|
1219
1246
|
transport,
|
|
1220
1247
|
clearInitTimeout: timeoutController.clear,
|
|
1221
1248
|
releaseSlot: tracker.releaseSlot,
|
|
@@ -1223,6 +1250,7 @@ async function createAndConnectTransport({ options, res, requestId, }) {
|
|
|
1223
1250
|
if (!finalizeSessionIfValid({
|
|
1224
1251
|
store: options.sessionStore,
|
|
1225
1252
|
transport,
|
|
1253
|
+
mcpServer,
|
|
1226
1254
|
tracker,
|
|
1227
1255
|
clearInitTimeout: timeoutController.clear,
|
|
1228
1256
|
res,
|
|
@@ -1235,7 +1263,7 @@ async function createAndConnectTransport({ options, res, requestId, }) {
|
|
|
1235
1263
|
export async function resolveTransportForPost({ res, body, sessionId, options, }) {
|
|
1236
1264
|
const requestId = body.id ?? null;
|
|
1237
1265
|
if (sessionId) {
|
|
1238
|
-
return resolveExistingSessionTransport(options.sessionStore, sessionId, res, requestId);
|
|
1266
|
+
return resolveExistingSessionTransport(options.sessionStore, sessionId, res, requestId, body.method);
|
|
1239
1267
|
}
|
|
1240
1268
|
if (!isInitializeRequest(body)) {
|
|
1241
1269
|
respondBadRequest(res, requestId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@j0hanz/superfetch",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.4",
|
|
4
4
|
"mcpName": "io.github.j0hanz/superfetch",
|
|
5
5
|
"description": "Intelligent web content fetcher MCP server that converts HTML to clean, AI-readable Markdown",
|
|
6
6
|
"type": "module",
|
|
@@ -64,15 +64,15 @@
|
|
|
64
64
|
"@eslint/js": "^9.39.2",
|
|
65
65
|
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
|
66
66
|
"@types/express": "^5.0.6",
|
|
67
|
-
"@types/node": "^22.19.
|
|
67
|
+
"@types/node": "^22.19.6",
|
|
68
68
|
"eslint": "^9.23.2",
|
|
69
69
|
"eslint-config-prettier": "^10.1.8",
|
|
70
70
|
"eslint-plugin-de-morgan": "^2.0.0",
|
|
71
71
|
"eslint-plugin-depend": "^1.4.0",
|
|
72
72
|
"eslint-plugin-sonarjs": "^3.0.5",
|
|
73
73
|
"eslint-plugin-unused-imports": "^4.3.0",
|
|
74
|
-
"knip": "^5.
|
|
75
|
-
"prettier": "^3.
|
|
74
|
+
"knip": "^5.81.0",
|
|
75
|
+
"prettier": "^3.8.0",
|
|
76
76
|
"tsx": "^4.21.0",
|
|
77
77
|
"typescript": "^5.9.3",
|
|
78
78
|
"typescript-eslint": "^8.53.0"
|