@j0hanz/fetch-url-mcp 1.3.1 → 1.5.0
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 +24 -21
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +15 -8
- package/dist/http/auth.d.ts +6 -6
- package/dist/http/auth.js +78 -23
- package/dist/http/health.d.ts +1 -2
- package/dist/http/health.js +7 -18
- package/dist/http/helpers.d.ts +3 -11
- package/dist/http/helpers.js +28 -26
- package/dist/http/native.d.ts +0 -1
- package/dist/http/native.js +63 -41
- package/dist/http/rate-limit.d.ts +2 -2
- package/dist/http/rate-limit.js +11 -16
- package/dist/index.d.ts +0 -1
- package/dist/index.js +17 -20
- package/dist/{markdown-cleanup.d.ts → lib/content.d.ts} +4 -2
- package/dist/lib/content.js +1356 -0
- package/dist/lib/core.d.ts +253 -0
- package/dist/lib/core.js +1228 -0
- package/dist/{tool-pipeline.d.ts → lib/fetch-pipeline.d.ts} +1 -3
- package/dist/{tool-pipeline.js → lib/fetch-pipeline.js} +18 -44
- package/dist/{fetch.d.ts → lib/http.d.ts} +7 -9
- package/dist/{fetch.js → lib/http.js} +721 -1004
- package/dist/lib/mcp-tools.d.ts +28 -0
- package/dist/lib/mcp-tools.js +107 -0
- package/dist/{tool-progress.d.ts → lib/progress.d.ts} +0 -2
- package/dist/{tool-progress.js → lib/progress.js} +9 -14
- package/dist/lib/task-handlers.d.ts +5 -0
- package/dist/{mcp.js → lib/task-handlers.js} +95 -31
- package/dist/lib/url.d.ts +70 -0
- package/dist/lib/url.js +686 -0
- package/dist/lib/utils.d.ts +58 -0
- package/dist/lib/utils.js +304 -0
- package/dist/{prompts.d.ts → prompts/index.d.ts} +0 -1
- package/dist/{prompts.js → prompts/index.js} +1 -2
- package/dist/{resources.d.ts → resources/index.d.ts} +0 -1
- package/dist/{resources.js → resources/index.js} +87 -64
- package/dist/{instructions.d.ts → resources/instructions.d.ts} +0 -1
- package/dist/{instructions.js → resources/instructions.js} +5 -3
- package/dist/schemas/inputs.d.ts +7 -0
- package/dist/schemas/inputs.js +24 -0
- package/dist/schemas/outputs.d.ts +23 -0
- package/dist/schemas/outputs.js +77 -0
- package/dist/server.d.ts +0 -1
- package/dist/server.js +26 -25
- package/dist/tasks/execution.d.ts +0 -1
- package/dist/tasks/execution.js +106 -70
- package/dist/tasks/manager.d.ts +11 -3
- package/dist/tasks/manager.js +97 -73
- package/dist/tasks/owner.d.ts +3 -3
- package/dist/tasks/owner.js +2 -2
- package/dist/tasks/tool-registry.d.ts +11 -0
- package/dist/tasks/tool-registry.js +13 -0
- package/dist/tools/fetch-url.d.ts +28 -0
- package/dist/{tools.js → tools/fetch-url.js} +95 -147
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +4 -0
- package/dist/transform/html-translators.d.ts +1 -0
- package/dist/transform/html-translators.js +454 -0
- package/dist/transform/metadata.d.ts +4 -0
- package/dist/transform/metadata.js +183 -0
- package/dist/transform/transform.d.ts +0 -1
- package/dist/transform/transform.js +44 -679
- package/dist/transform/types.d.ts +9 -12
- package/dist/transform/types.js +0 -1
- package/dist/transform/worker-pool.d.ts +0 -1
- package/dist/transform/worker-pool.js +7 -16
- package/dist/transform/workers/shared.d.ts +7 -0
- package/dist/transform/workers/shared.js +130 -0
- package/dist/transform/workers/transform-child.d.ts +0 -1
- package/dist/transform/workers/transform-child.js +5 -135
- package/dist/transform/workers/transform-worker.d.ts +0 -1
- package/dist/transform/workers/transform-worker.js +7 -128
- package/package.json +11 -7
- package/dist/cache.d.ts +0 -54
- package/dist/cache.d.ts.map +0 -1
- package/dist/cache.js +0 -261
- package/dist/cache.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/config.d.ts +0 -141
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -473
- package/dist/config.js.map +0 -1
- package/dist/crypto.d.ts +0 -4
- package/dist/crypto.d.ts.map +0 -1
- package/dist/crypto.js +0 -56
- package/dist/crypto.js.map +0 -1
- package/dist/dom-noise-removal.d.ts +0 -2
- package/dist/dom-noise-removal.d.ts.map +0 -1
- package/dist/dom-noise-removal.js +0 -494
- package/dist/dom-noise-removal.js.map +0 -1
- package/dist/download.d.ts +0 -4
- package/dist/download.d.ts.map +0 -1
- package/dist/download.js +0 -106
- package/dist/download.js.map +0 -1
- package/dist/errors.d.ts +0 -11
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -65
- package/dist/errors.js.map +0 -1
- package/dist/examples/mcp-fetch-url-client.js +0 -329
- package/dist/examples/mcp-fetch-url-client.js.map +0 -1
- package/dist/fetch-content.d.ts +0 -5
- package/dist/fetch-content.d.ts.map +0 -1
- package/dist/fetch-content.js +0 -164
- package/dist/fetch-content.js.map +0 -1
- package/dist/fetch-stream.d.ts +0 -5
- package/dist/fetch-stream.d.ts.map +0 -1
- package/dist/fetch-stream.js +0 -29
- package/dist/fetch-stream.js.map +0 -1
- package/dist/fetch.d.ts.map +0 -1
- package/dist/fetch.js.map +0 -1
- package/dist/host-normalization.d.ts +0 -2
- package/dist/host-normalization.d.ts.map +0 -1
- package/dist/host-normalization.js +0 -91
- package/dist/host-normalization.js.map +0 -1
- package/dist/http/auth.d.ts.map +0 -1
- package/dist/http/auth.js.map +0 -1
- package/dist/http/health.d.ts.map +0 -1
- package/dist/http/health.js.map +0 -1
- package/dist/http/helpers.d.ts.map +0 -1
- package/dist/http/helpers.js.map +0 -1
- package/dist/http/native.d.ts.map +0 -1
- package/dist/http/native.js.map +0 -1
- package/dist/http/rate-limit.d.ts.map +0 -1
- package/dist/http/rate-limit.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/instructions.d.ts.map +0 -1
- package/dist/instructions.js.map +0 -1
- package/dist/ip-blocklist.d.ts +0 -9
- package/dist/ip-blocklist.d.ts.map +0 -1
- package/dist/ip-blocklist.js +0 -79
- package/dist/ip-blocklist.js.map +0 -1
- package/dist/json.d.ts +0 -2
- package/dist/json.d.ts.map +0 -1
- package/dist/json.js +0 -45
- package/dist/json.js.map +0 -1
- package/dist/language-detection.d.ts +0 -3
- package/dist/language-detection.d.ts.map +0 -1
- package/dist/language-detection.js +0 -355
- package/dist/language-detection.js.map +0 -1
- package/dist/markdown-cleanup.d.ts.map +0 -1
- package/dist/markdown-cleanup.js +0 -534
- package/dist/markdown-cleanup.js.map +0 -1
- package/dist/mcp-validator.d.ts +0 -17
- package/dist/mcp-validator.d.ts.map +0 -1
- package/dist/mcp-validator.js +0 -45
- package/dist/mcp-validator.js.map +0 -1
- package/dist/mcp.d.ts +0 -4
- package/dist/mcp.d.ts.map +0 -1
- package/dist/mcp.js.map +0 -1
- package/dist/observability.d.ts +0 -23
- package/dist/observability.d.ts.map +0 -1
- package/dist/observability.js +0 -238
- package/dist/observability.js.map +0 -1
- package/dist/prompts.d.ts.map +0 -1
- package/dist/prompts.js.map +0 -1
- package/dist/resources.d.ts.map +0 -1
- package/dist/resources.js.map +0 -1
- package/dist/server-tuning.d.ts +0 -15
- package/dist/server-tuning.d.ts.map +0 -1
- package/dist/server-tuning.js +0 -49
- package/dist/server-tuning.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/session.d.ts +0 -42
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js +0 -255
- package/dist/session.js.map +0 -1
- package/dist/tasks/execution.d.ts.map +0 -1
- package/dist/tasks/execution.js.map +0 -1
- package/dist/tasks/manager.d.ts.map +0 -1
- package/dist/tasks/manager.js.map +0 -1
- package/dist/tasks/owner.d.ts.map +0 -1
- package/dist/tasks/owner.js.map +0 -1
- package/dist/timer-utils.d.ts +0 -6
- package/dist/timer-utils.d.ts.map +0 -1
- package/dist/timer-utils.js +0 -27
- package/dist/timer-utils.js.map +0 -1
- package/dist/tool-errors.d.ts +0 -12
- package/dist/tool-errors.d.ts.map +0 -1
- package/dist/tool-errors.js +0 -55
- package/dist/tool-errors.js.map +0 -1
- package/dist/tool-pipeline.d.ts.map +0 -1
- package/dist/tool-pipeline.js.map +0 -1
- package/dist/tool-progress.d.ts.map +0 -1
- package/dist/tool-progress.js.map +0 -1
- package/dist/tools.d.ts +0 -54
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js.map +0 -1
- package/dist/transform/transform.d.ts.map +0 -1
- package/dist/transform/transform.js.map +0 -1
- package/dist/transform/types.d.ts.map +0 -1
- package/dist/transform/types.js.map +0 -1
- package/dist/transform/worker-pool.d.ts.map +0 -1
- package/dist/transform/worker-pool.js.map +0 -1
- package/dist/transform/workers/transform-child.d.ts.map +0 -1
- package/dist/transform/workers/transform-child.js.map +0 -1
- package/dist/transform/workers/transform-worker.d.ts.map +0 -1
- package/dist/transform/workers/transform-worker.js.map +0 -1
- package/dist/type-guards.d.ts +0 -16
- package/dist/type-guards.d.ts.map +0 -1
- package/dist/type-guards.js +0 -13
- package/dist/type-guards.js.map +0 -1
package/dist/tasks/manager.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
2
1
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
2
|
import { Buffer } from 'node:buffer';
|
|
4
3
|
import { randomUUID } from 'node:crypto';
|
|
5
4
|
import { setInterval } from 'node:timers';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
import { config } from '../lib/core.js';
|
|
7
|
+
import { RESOURCE_NOT_FOUND_ERROR_CODE, toError } from '../lib/utils.js';
|
|
8
|
+
import { createUnrefTimeout } from '../lib/utils.js';
|
|
8
9
|
const DEFAULT_TTL_MS = 60_000;
|
|
9
10
|
const MIN_TTL_MS = 1_000;
|
|
10
11
|
const MAX_TTL_MS = 86_400_000;
|
|
@@ -31,35 +32,61 @@ class TaskManager {
|
|
|
31
32
|
tasks = new Map();
|
|
32
33
|
ownerCounts = new Map();
|
|
33
34
|
waiters = new Map();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
cleanupInterval = null;
|
|
36
|
+
ensureCleanupLoop() {
|
|
37
|
+
if (this.cleanupInterval)
|
|
38
|
+
return;
|
|
39
|
+
this.cleanupInterval = setInterval(() => {
|
|
39
40
|
this.removeExpiredTasks();
|
|
41
|
+
if (this.tasks.size === 0) {
|
|
42
|
+
this.stopCleanupLoop();
|
|
43
|
+
}
|
|
40
44
|
}, CLEANUP_INTERVAL_MS);
|
|
41
|
-
|
|
45
|
+
this.cleanupInterval.unref();
|
|
46
|
+
}
|
|
47
|
+
stopCleanupLoop() {
|
|
48
|
+
if (!this.cleanupInterval)
|
|
49
|
+
return;
|
|
50
|
+
clearInterval(this.cleanupInterval);
|
|
51
|
+
this.cleanupInterval = null;
|
|
42
52
|
}
|
|
43
53
|
removeExpiredTasks() {
|
|
44
54
|
const now = Date.now();
|
|
45
|
-
const expired = [];
|
|
46
55
|
for (const [id, task] of this.tasks) {
|
|
47
56
|
if (this.isExpired(task, now)) {
|
|
48
|
-
|
|
57
|
+
this.removeTask(id);
|
|
49
58
|
}
|
|
50
59
|
}
|
|
51
|
-
for (const id of expired) {
|
|
52
|
-
this.removeTask(id);
|
|
53
|
-
}
|
|
54
60
|
}
|
|
55
61
|
removeTask(taskId) {
|
|
56
62
|
const task = this.tasks.get(taskId);
|
|
57
63
|
if (!task)
|
|
58
64
|
return false;
|
|
59
65
|
this.tasks.delete(taskId);
|
|
60
|
-
this.
|
|
66
|
+
this.releaseOwnerCount(task);
|
|
61
67
|
return true;
|
|
62
68
|
}
|
|
69
|
+
applyTaskUpdate(task, updates) {
|
|
70
|
+
Object.assign(task, updates);
|
|
71
|
+
task.lastUpdatedAt = new Date().toISOString();
|
|
72
|
+
}
|
|
73
|
+
cancelActiveTask(task, statusMessage) {
|
|
74
|
+
this.applyTaskUpdate(task, {
|
|
75
|
+
status: 'cancelled',
|
|
76
|
+
statusMessage,
|
|
77
|
+
});
|
|
78
|
+
this.notifyWaiters(task);
|
|
79
|
+
this.releaseOwnerCount(task);
|
|
80
|
+
}
|
|
81
|
+
releaseOwnerCount(task) {
|
|
82
|
+
const internal = task;
|
|
83
|
+
if (internal._ownerCounted === false)
|
|
84
|
+
return;
|
|
85
|
+
if ('_ownerCounted' in internal) {
|
|
86
|
+
internal._ownerCounted = false;
|
|
87
|
+
}
|
|
88
|
+
this.decrementOwnerCount(task.ownerKey);
|
|
89
|
+
}
|
|
63
90
|
countTasksForOwner(ownerKey) {
|
|
64
91
|
return this.ownerCounts.get(ownerKey) ?? 0;
|
|
65
92
|
}
|
|
@@ -99,12 +126,14 @@ class TaskManager {
|
|
|
99
126
|
ttl: normalizeTaskTtl(options?.ttl),
|
|
100
127
|
pollInterval: DEFAULT_POLL_INTERVAL_MS,
|
|
101
128
|
_createdAtMs: now.getTime(),
|
|
129
|
+
_ownerCounted: true,
|
|
102
130
|
};
|
|
103
131
|
this.tasks.set(task.taskId, task);
|
|
104
132
|
this.incrementOwnerCount(ownerKey);
|
|
133
|
+
this.ensureCleanupLoop();
|
|
105
134
|
return task;
|
|
106
135
|
}
|
|
107
|
-
|
|
136
|
+
lookupActiveTask(taskId, ownerKey) {
|
|
108
137
|
const task = this.tasks.get(taskId);
|
|
109
138
|
if (!task)
|
|
110
139
|
return undefined;
|
|
@@ -116,30 +145,26 @@ class TaskManager {
|
|
|
116
145
|
}
|
|
117
146
|
return task;
|
|
118
147
|
}
|
|
148
|
+
getTask(taskId, ownerKey) {
|
|
149
|
+
return this.lookupActiveTask(taskId, ownerKey);
|
|
150
|
+
}
|
|
119
151
|
updateTask(taskId, updates) {
|
|
120
152
|
const task = this.tasks.get(taskId);
|
|
121
153
|
if (!task)
|
|
122
154
|
return;
|
|
123
155
|
if (isTerminalStatus(task.status))
|
|
124
156
|
return;
|
|
125
|
-
|
|
126
|
-
...updates,
|
|
127
|
-
lastUpdatedAt: new Date().toISOString(),
|
|
128
|
-
});
|
|
157
|
+
this.applyTaskUpdate(task, updates);
|
|
129
158
|
this.notifyWaiters(task);
|
|
130
159
|
}
|
|
131
160
|
cancelTask(taskId, ownerKey) {
|
|
132
|
-
const task = this.
|
|
161
|
+
const task = this.lookupActiveTask(taskId, ownerKey);
|
|
133
162
|
if (!task)
|
|
134
163
|
return undefined;
|
|
135
164
|
if (isTerminalStatus(task.status)) {
|
|
136
165
|
throw new McpError(ErrorCode.InvalidParams, `Cannot cancel task: already in terminal status '${task.status}'`);
|
|
137
166
|
}
|
|
138
|
-
this.
|
|
139
|
-
status: 'cancelled',
|
|
140
|
-
statusMessage: 'The task was cancelled by request.',
|
|
141
|
-
});
|
|
142
|
-
this.decrementOwnerCount(task.ownerKey);
|
|
167
|
+
this.cancelActiveTask(task, 'The task was cancelled by request.');
|
|
143
168
|
return this.tasks.get(taskId);
|
|
144
169
|
}
|
|
145
170
|
cancelTasksByOwner(ownerKey, statusMessage = 'The task was cancelled because its owner is no longer active.') {
|
|
@@ -151,11 +176,7 @@ class TaskManager {
|
|
|
151
176
|
continue;
|
|
152
177
|
if (isTerminalStatus(task.status))
|
|
153
178
|
continue;
|
|
154
|
-
this.
|
|
155
|
-
status: 'cancelled',
|
|
156
|
-
statusMessage,
|
|
157
|
-
});
|
|
158
|
-
this.decrementOwnerCount(task.ownerKey);
|
|
179
|
+
this.cancelActiveTask(task, statusMessage);
|
|
159
180
|
cancelled.push(task);
|
|
160
181
|
}
|
|
161
182
|
return cancelled;
|
|
@@ -165,12 +186,11 @@ class TaskManager {
|
|
|
165
186
|
let collecting = anchorTaskId === null;
|
|
166
187
|
let anchorFound = anchorTaskId === null;
|
|
167
188
|
const now = Date.now();
|
|
168
|
-
const expired = [];
|
|
169
189
|
for (const task of this.tasks.values()) {
|
|
170
190
|
if (task.ownerKey !== ownerKey)
|
|
171
191
|
continue;
|
|
172
192
|
if (this.isExpired(task, now)) {
|
|
173
|
-
|
|
193
|
+
this.removeTask(task.taskId);
|
|
174
194
|
continue;
|
|
175
195
|
}
|
|
176
196
|
if (!collecting) {
|
|
@@ -184,9 +204,6 @@ class TaskManager {
|
|
|
184
204
|
if (page.length > pageSize)
|
|
185
205
|
break;
|
|
186
206
|
}
|
|
187
|
-
for (const id of expired) {
|
|
188
|
-
this.removeTask(id);
|
|
189
|
-
}
|
|
190
207
|
// Anchor task expired between pages; return empty list so callers stop
|
|
191
208
|
// pagination cleanly. Silently falling back to page 0 risks infinite loops
|
|
192
209
|
// for automated clients that always follow nextCursor.
|
|
@@ -198,14 +215,7 @@ class TaskManager {
|
|
|
198
215
|
listTasks(options) {
|
|
199
216
|
const { ownerKey, cursor, limit } = options;
|
|
200
217
|
const pageSize = limit && limit > 0 ? limit : DEFAULT_PAGE_SIZE;
|
|
201
|
-
|
|
202
|
-
if (cursor !== undefined) {
|
|
203
|
-
const decoded = this.decodeCursor(cursor);
|
|
204
|
-
if (decoded === null) {
|
|
205
|
-
throw new McpError(ErrorCode.InvalidParams, 'Invalid cursor');
|
|
206
|
-
}
|
|
207
|
-
({ anchorTaskId } = decoded);
|
|
208
|
-
}
|
|
218
|
+
const anchorTaskId = this.resolveAnchorTaskId(cursor);
|
|
209
219
|
const page = this.collectPage(ownerKey, anchorTaskId, pageSize);
|
|
210
220
|
const hasMore = page.length > pageSize;
|
|
211
221
|
if (hasMore) {
|
|
@@ -214,27 +224,50 @@ class TaskManager {
|
|
|
214
224
|
const nextCursor = this.resolveNextCursor(page, hasMore);
|
|
215
225
|
return nextCursor ? { tasks: page, nextCursor } : { tasks: page };
|
|
216
226
|
}
|
|
227
|
+
resolveAnchorTaskId(cursor) {
|
|
228
|
+
if (cursor === undefined)
|
|
229
|
+
return null;
|
|
230
|
+
const decoded = this.decodeCursor(cursor);
|
|
231
|
+
if (decoded === null) {
|
|
232
|
+
throw new McpError(ErrorCode.InvalidParams, 'Invalid cursor');
|
|
233
|
+
}
|
|
234
|
+
return decoded.anchorTaskId;
|
|
235
|
+
}
|
|
236
|
+
addWaiter(taskId, waiter) {
|
|
237
|
+
let set = this.waiters.get(taskId);
|
|
238
|
+
if (!set) {
|
|
239
|
+
set = new Set();
|
|
240
|
+
this.waiters.set(taskId, set);
|
|
241
|
+
}
|
|
242
|
+
set.add(waiter);
|
|
243
|
+
}
|
|
244
|
+
removeWaiter(taskId, waiter) {
|
|
245
|
+
if (!waiter)
|
|
246
|
+
return;
|
|
247
|
+
const set = this.waiters.get(taskId);
|
|
248
|
+
if (!set)
|
|
249
|
+
return;
|
|
250
|
+
set.delete(waiter);
|
|
251
|
+
if (set.size === 0) {
|
|
252
|
+
this.waiters.delete(taskId);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
217
255
|
async waitForTerminalTask(taskId, ownerKey, signal) {
|
|
218
|
-
const task = this.
|
|
256
|
+
const task = this.lookupActiveTask(taskId, ownerKey);
|
|
219
257
|
if (!task)
|
|
220
258
|
return undefined;
|
|
221
|
-
if (ownerKey && task.ownerKey !== ownerKey)
|
|
222
|
-
return undefined;
|
|
223
|
-
if (this.isExpired(task)) {
|
|
224
|
-
this.removeTask(taskId);
|
|
225
|
-
return undefined;
|
|
226
|
-
}
|
|
227
259
|
if (isTerminalStatus(task.status))
|
|
228
260
|
return task;
|
|
229
261
|
// isExpired() above guarantees task.ttl has not elapsed; compute deadlineMs
|
|
230
262
|
// for the promise-based timeout below.
|
|
231
263
|
const deadlineMs = task._createdAtMs + task.ttl;
|
|
232
264
|
return new Promise((resolve, reject) => {
|
|
265
|
+
// Bind resolve/reject to the AsyncLocalStorage context of the caller, so that any context values (e.g. requestId) are preserved when we later call them from a different tick.
|
|
233
266
|
const resolveInContext = AsyncLocalStorage.bind((value) => {
|
|
234
267
|
resolve(value);
|
|
235
268
|
});
|
|
236
269
|
const rejectInContext = AsyncLocalStorage.bind((error) => {
|
|
237
|
-
reject(
|
|
270
|
+
reject(toError(error));
|
|
238
271
|
});
|
|
239
272
|
let settled = false;
|
|
240
273
|
let waiter = null;
|
|
@@ -248,16 +281,6 @@ class TaskManager {
|
|
|
248
281
|
signal.removeEventListener('abort', onAbort);
|
|
249
282
|
}
|
|
250
283
|
};
|
|
251
|
-
const removeWaiter = () => {
|
|
252
|
-
if (waiter) {
|
|
253
|
-
const set = this.waiters.get(taskId);
|
|
254
|
-
if (set) {
|
|
255
|
-
set.delete(waiter);
|
|
256
|
-
if (set.size === 0)
|
|
257
|
-
this.waiters.delete(taskId);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
284
|
const settleOnce = (fn) => {
|
|
262
285
|
if (settled)
|
|
263
286
|
return;
|
|
@@ -267,7 +290,7 @@ class TaskManager {
|
|
|
267
290
|
const onAbort = () => {
|
|
268
291
|
settleOnce(() => {
|
|
269
292
|
cleanup();
|
|
270
|
-
removeWaiter();
|
|
293
|
+
this.removeWaiter(taskId, waiter);
|
|
271
294
|
rejectInContext(new McpError(ErrorCode.ConnectionClosed, 'Request was cancelled'));
|
|
272
295
|
});
|
|
273
296
|
};
|
|
@@ -285,12 +308,7 @@ class TaskManager {
|
|
|
285
308
|
onAbort();
|
|
286
309
|
return;
|
|
287
310
|
}
|
|
288
|
-
|
|
289
|
-
if (!set) {
|
|
290
|
-
set = new Set();
|
|
291
|
-
this.waiters.set(taskId, set);
|
|
292
|
-
}
|
|
293
|
-
set.add(waiter);
|
|
311
|
+
this.addWaiter(taskId, waiter);
|
|
294
312
|
if (signal) {
|
|
295
313
|
signal.addEventListener('abort', onAbort, { once: true });
|
|
296
314
|
}
|
|
@@ -300,9 +318,11 @@ class TaskManager {
|
|
|
300
318
|
.then(() => {
|
|
301
319
|
settleOnce(() => {
|
|
302
320
|
cleanup();
|
|
303
|
-
removeWaiter();
|
|
321
|
+
this.removeWaiter(taskId, waiter);
|
|
304
322
|
this.removeTask(taskId);
|
|
305
|
-
|
|
323
|
+
rejectInContext(new McpError(RESOURCE_NOT_FOUND_ERROR_CODE, 'Task expired', {
|
|
324
|
+
taskId,
|
|
325
|
+
}));
|
|
306
326
|
});
|
|
307
327
|
})
|
|
308
328
|
.catch(rejectInContext);
|
|
@@ -321,6 +341,9 @@ class TaskManager {
|
|
|
321
341
|
isExpired(task, now = Date.now()) {
|
|
322
342
|
return now - task._createdAtMs > task.ttl;
|
|
323
343
|
}
|
|
344
|
+
maybeUpdateLastUpdatedAt(task) {
|
|
345
|
+
task.lastUpdatedAt = new Date().toISOString();
|
|
346
|
+
}
|
|
324
347
|
shrinkTtlAfterDelivery(taskId) {
|
|
325
348
|
const task = this.tasks.get(taskId);
|
|
326
349
|
if (!task)
|
|
@@ -331,10 +354,12 @@ class TaskManager {
|
|
|
331
354
|
const newTtl = elapsed + RESULT_DELIVERY_GRACE_MS;
|
|
332
355
|
if (newTtl < task.ttl) {
|
|
333
356
|
task.ttl = newTtl;
|
|
334
|
-
|
|
357
|
+
this.maybeUpdateLastUpdatedAt(task);
|
|
335
358
|
}
|
|
336
359
|
}
|
|
337
360
|
encodeCursor(taskId) {
|
|
361
|
+
// Base64url-encode the taskId to produce a compact opaque cursor string.
|
|
362
|
+
// The taskId is a UUID, which is 36 ASCII chars, so the resulting cursor will be 48 chars (36 * 4/3) plus padding if any.
|
|
338
363
|
return Buffer.from(taskId, 'utf8').toString('base64url');
|
|
339
364
|
}
|
|
340
365
|
decodeCursor(cursor) {
|
|
@@ -376,4 +401,3 @@ function isValidBase64UrlCursor(cursor) {
|
|
|
376
401
|
return cursor.length % 4 !== 1;
|
|
377
402
|
}
|
|
378
403
|
export const taskManager = new TaskManager();
|
|
379
|
-
//# sourceMappingURL=manager.js.map
|
package/dist/tasks/owner.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ServerResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
-
import type { ProgressNotification } from '../tools.js';
|
|
3
|
-
|
|
2
|
+
import type { ProgressNotification } from '../lib/mcp-tools.js';
|
|
3
|
+
interface HandlerExtra {
|
|
4
4
|
sessionId?: string;
|
|
5
5
|
authInfo?: {
|
|
6
6
|
clientId?: string;
|
|
@@ -12,6 +12,7 @@ export interface HandlerExtra {
|
|
|
12
12
|
}
|
|
13
13
|
export interface ToolCallContext {
|
|
14
14
|
ownerKey: string;
|
|
15
|
+
sessionId?: string;
|
|
15
16
|
signal?: AbortSignal;
|
|
16
17
|
requestId?: string | number;
|
|
17
18
|
sendNotification?: (notification: ProgressNotification) => Promise<void>;
|
|
@@ -29,4 +30,3 @@ export declare function resolveToolCallContext(extra?: HandlerExtra): ToolCallCo
|
|
|
29
30
|
export declare function isServerResult(value: unknown): value is ServerResult;
|
|
30
31
|
export declare function tryReadToolStructuredError(value: unknown): string | undefined;
|
|
31
32
|
export {};
|
|
32
|
-
//# sourceMappingURL=owner.d.ts.map
|
package/dist/tasks/owner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { isObject } from '../
|
|
2
|
+
import { isObject } from '../lib/utils.js';
|
|
3
3
|
export function compact(obj) {
|
|
4
4
|
const result = {};
|
|
5
5
|
for (const key of Object.keys(obj)) {
|
|
@@ -62,6 +62,7 @@ export function resolveTaskOwnerKey(extra) {
|
|
|
62
62
|
export function resolveToolCallContext(extra) {
|
|
63
63
|
return compact({
|
|
64
64
|
ownerKey: resolveTaskOwnerKey(extra),
|
|
65
|
+
sessionId: extra?.sessionId,
|
|
65
66
|
signal: extra?.signal,
|
|
66
67
|
requestId: extra?.requestId,
|
|
67
68
|
sendNotification: extra?.sendNotification,
|
|
@@ -89,4 +90,3 @@ export function tryReadToolStructuredError(value) {
|
|
|
89
90
|
return undefined;
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
|
-
//# sourceMappingURL=owner.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ServerResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import type { ToolHandlerExtra } from '../lib/mcp-tools.js';
|
|
3
|
+
export interface TaskCapableToolDescriptor<TArgs = unknown> {
|
|
4
|
+
name: string;
|
|
5
|
+
parseArguments: (args: unknown) => TArgs;
|
|
6
|
+
execute: (args: TArgs, extra?: ToolHandlerExtra) => Promise<ServerResult>;
|
|
7
|
+
}
|
|
8
|
+
export declare function registerTaskCapableTool<TArgs>(descriptor: TaskCapableToolDescriptor<TArgs>): void;
|
|
9
|
+
export declare function unregisterTaskCapableTool(name: string): void;
|
|
10
|
+
export declare function getTaskCapableTool(name: string): TaskCapableToolDescriptor | undefined;
|
|
11
|
+
export declare function hasTaskCapableTool(name: string): boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const taskCapableTools = new Map();
|
|
2
|
+
export function registerTaskCapableTool(descriptor) {
|
|
3
|
+
taskCapableTools.set(descriptor.name, descriptor);
|
|
4
|
+
}
|
|
5
|
+
export function unregisterTaskCapableTool(name) {
|
|
6
|
+
taskCapableTools.delete(name);
|
|
7
|
+
}
|
|
8
|
+
export function getTaskCapableTool(name) {
|
|
9
|
+
return taskCapableTools.get(name);
|
|
10
|
+
}
|
|
11
|
+
export function hasTaskCapableTool(name) {
|
|
12
|
+
return taskCapableTools.has(name);
|
|
13
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { ContentBlock } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { type ToolHandlerExtra } from '../lib/mcp-tools.js';
|
|
4
|
+
interface FetchUrlInput {
|
|
5
|
+
url: string;
|
|
6
|
+
skipNoiseRemoval?: boolean | undefined;
|
|
7
|
+
forceRefresh?: boolean | undefined;
|
|
8
|
+
maxInlineChars?: number | undefined;
|
|
9
|
+
}
|
|
10
|
+
type ToolContentBlockUnion = ContentBlock;
|
|
11
|
+
interface ToolResponseBase {
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
content: ToolContentBlockUnion[];
|
|
14
|
+
structuredContent?: Record<string, unknown> | undefined;
|
|
15
|
+
isError?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare const FETCH_URL_TOOL_NAME = "fetch-url";
|
|
18
|
+
export declare function fetchUrlToolHandler(input: FetchUrlInput, extra?: ToolHandlerExtra): Promise<ToolResponseBase>;
|
|
19
|
+
/**
|
|
20
|
+
* Stdio-path guard: ensures a request context (requestId, sessionId) is set
|
|
21
|
+
* in AsyncLocalStorage before invoking the handler. On the HTTP path the SDK
|
|
22
|
+
* populates `extra.requestId`/`extra.requestInfo`, so this is a no-op there.
|
|
23
|
+
* On the stdio path there is no SDK-provided context, so we derive one from
|
|
24
|
+
* the extra fields or generate a fresh UUID.
|
|
25
|
+
*/
|
|
26
|
+
export declare function withRequestContextIfMissing<TParams, TResult, TExtra = unknown>(handler: (params: TParams, extra?: TExtra) => Promise<TResult>): (params: TParams, extra?: TExtra) => Promise<TResult>;
|
|
27
|
+
export declare function registerTools(server: McpServer): void;
|
|
28
|
+
export {};
|