@j0hanz/fetch-url-mcp 1.13.0 → 2.0.1
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 +21 -21
- package/README.md +815 -807
- package/dist/assets/logo.svg +53 -53
- package/dist/http/auth.d.ts +7 -3
- package/dist/http/auth.d.ts.map +1 -1
- package/dist/http/auth.js +65 -37
- package/dist/http/helpers.d.ts +21 -0
- package/dist/http/helpers.d.ts.map +1 -0
- package/dist/http/helpers.js +31 -0
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/index.js +1 -0
- package/dist/http/native.d.ts +6 -24
- package/dist/http/native.d.ts.map +1 -1
- package/dist/http/native.js +141 -86
- package/dist/http/rate-limit.d.ts +1 -1
- package/dist/http/rate-limit.d.ts.map +1 -1
- package/dist/http/rate-limit.js +3 -9
- package/dist/index.js +0 -0
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +1 -0
- package/dist/lib/core.d.ts +4 -3
- package/dist/lib/core.d.ts.map +1 -1
- package/dist/lib/core.js +4 -1
- package/dist/lib/error/classify.d.ts.map +1 -1
- package/dist/lib/error/classify.js +27 -6
- package/dist/lib/error/payload.d.ts +2 -2
- package/dist/lib/error/payload.d.ts.map +1 -1
- package/dist/lib/error/payload.js +2 -2
- package/dist/lib/mcp-interop.d.ts +9 -108
- package/dist/lib/mcp-interop.d.ts.map +1 -1
- package/dist/lib/mcp-interop.js +39 -240
- package/dist/lib/net/http.d.ts.map +1 -1
- package/dist/lib/net/http.js +4 -17
- package/dist/resources/index.d.ts +1 -1
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +67 -60
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +39 -34
- package/dist/tasks/adapter.d.ts +15 -0
- package/dist/tasks/adapter.d.ts.map +1 -0
- package/dist/tasks/adapter.js +138 -0
- package/dist/tasks/manager.d.ts +14 -82
- package/dist/tasks/manager.d.ts.map +1 -1
- package/dist/tasks/manager.js +48 -607
- package/dist/tasks/store.d.ts +33 -19
- package/dist/tasks/store.d.ts.map +1 -1
- package/dist/tasks/store.js +143 -80
- package/dist/tools/index.d.ts +7 -13
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +153 -58
- package/dist/transform/index.js +1 -1
- package/package.json +110 -108
package/dist/resources/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { completable } from '@modelcontextprotocol/
|
|
1
|
+
import { completable, } from '@modelcontextprotocol/server';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { config } from '../lib/config.js';
|
|
4
4
|
import { buildOptionalIcons } from '../lib/utils.js';
|
|
@@ -46,63 +46,70 @@ export function filterInstructions(instructions, topic) {
|
|
|
46
46
|
}
|
|
47
47
|
export function buildServerInstructions() {
|
|
48
48
|
const maxHtmlSizeMb = config.constants.maxHtmlBytes / 1024 / 1024;
|
|
49
|
-
return `# Fetch public webpages and return clean, readable Markdown
|
|
50
|
-
|
|
51
|
-
# Capabilities
|
|
52
|
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
Add \`_meta: {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
\`
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
|
103
|
-
|
|
|
104
|
-
|
|
|
105
|
-
|
|
|
49
|
+
return `# Fetch public webpages and return clean, readable Markdown
|
|
50
|
+
|
|
51
|
+
# Capabilities
|
|
52
|
+
|
|
53
|
+
- Tool: \`${FETCH_URL_TOOL_NAME}\` — fetch a URL, return Markdown with metadata.
|
|
54
|
+
- Resource: \`internal://instructions\` — this document.
|
|
55
|
+
- Prompt: \`get-help\` — returns these instructions. Accepts optional \`topic\` filter (${HELP_TOPICS.join(' | ')}).
|
|
56
|
+
|
|
57
|
+
# Workflows
|
|
58
|
+
|
|
59
|
+
## Standard
|
|
60
|
+
|
|
61
|
+
Call \`${FETCH_URL_TOOL_NAME}\` with \`{ url }\` → read \`markdown\` from result. Check \`truncated: true\` for incomplete content.
|
|
62
|
+
|
|
63
|
+
## Progress
|
|
64
|
+
|
|
65
|
+
Add \`_meta: { progressToken: "<token>" }\` to \`tools/call\` → receive \`notifications/progress\`.
|
|
66
|
+
|
|
67
|
+
## Async (task mode)
|
|
68
|
+
|
|
69
|
+
Add \`task: { ttl?: <ms> }\` to \`tools/call\`.
|
|
70
|
+
|
|
71
|
+
Lifecycle: \`working\` → \`completed\` | \`failed\` | \`cancelled\`.
|
|
72
|
+
|
|
73
|
+
Endpoints:
|
|
74
|
+
|
|
75
|
+
- \`tasks/get\` — poll for task summaries with \`status\`, \`statusMessage\`, \`createdAt\`, \`lastUpdatedAt\`, \`ttl\`, and \`pollInterval\`.
|
|
76
|
+
- \`tasks/result\` — wait for terminal status, then retrieve the stored result or terminal error payload.
|
|
77
|
+
- \`tasks/list\` — list tasks visible to the current caller.
|
|
78
|
+
- \`tasks/cancel\` — cancel an active task.
|
|
79
|
+
- \`tasks/delete\` — remove a terminal task.
|
|
80
|
+
|
|
81
|
+
Task-linked responses and notifications include
|
|
82
|
+
\`_meta["io.modelcontextprotocol/related-task"] = { taskId }\`.
|
|
83
|
+
|
|
84
|
+
Notifications (opt-in via \`TASKS_STATUS_NOTIFICATIONS=true\`):
|
|
85
|
+
|
|
86
|
+
- \`notifications/tasks/status\` — emitted on each status transition with related-task metadata.
|
|
87
|
+
|
|
88
|
+
HTTP mode: tasks are bound to the authenticated caller and resumable across sessions for the same authenticated subject.
|
|
89
|
+
|
|
90
|
+
# Constraints
|
|
91
|
+
|
|
92
|
+
- Blocked: localhost, private IPs, link-local, cloud metadata endpoints, \`.local\`/\`.internal\` domains.
|
|
93
|
+
- Max HTML: ${maxHtmlSizeMb}MB. Max redirects: ${config.fetcher.maxRedirects}.
|
|
94
|
+
- No JS rendering — client-rendered pages may return incomplete content.
|
|
95
|
+
- Binary content: not supported.
|
|
96
|
+
- Batch JSON-RPC (\`[{...}]\`): rejected with HTTP 400.
|
|
97
|
+
- \`internal://\` URIs are session-scoped.
|
|
98
|
+
- Tasks API is experimental — endpoints may change.
|
|
99
|
+
|
|
100
|
+
# Errors
|
|
101
|
+
|
|
102
|
+
| Code | Cause | Action |
|
|
103
|
+
| --------------------- | ----------------------- | ----------------------------- |
|
|
104
|
+
| VALIDATION_ERROR | Invalid or blocked URL | Do not retry |
|
|
105
|
+
| FETCH_ERROR | Network failure | Retry once with backoff |
|
|
106
|
+
| UPSTREAM_HTTP_ERROR | Upstream HTTP error | Retry only for 5xx |
|
|
107
|
+
| UPSTREAM_RATE_LIMITED | 429 from upstream | Back off, then retry |
|
|
108
|
+
| UPSTREAM_TIMEOUT | Upstream timed out | Retry with backoff |
|
|
109
|
+
| UPSTREAM_ABORTED | Request cancelled | Retry if needed |
|
|
110
|
+
| MCP_ERROR | Internal protocol error | Do not retry |
|
|
111
|
+
| queue_full | Worker pool saturated | Wait, retry, or use task mode |
|
|
112
|
+
`;
|
|
106
113
|
}
|
|
107
114
|
function buildTopicSchema() {
|
|
108
115
|
return completable(z
|
|
@@ -118,9 +125,9 @@ export function registerGetHelpPrompt(server, instructions, iconInfo) {
|
|
|
118
125
|
server.registerPrompt('get-help', {
|
|
119
126
|
title: 'Get Help',
|
|
120
127
|
description,
|
|
121
|
-
argsSchema: {
|
|
128
|
+
argsSchema: z.object({
|
|
122
129
|
topic: buildTopicSchema(),
|
|
123
|
-
},
|
|
130
|
+
}),
|
|
124
131
|
...buildOptionalIcons(iconInfo),
|
|
125
132
|
}, (args) => ({
|
|
126
133
|
description,
|
package/dist/server.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
export declare function createMcpServer(): Promise<McpServer>;
|
|
3
3
|
export declare function createMcpServerForHttpSession(): Promise<McpServer>;
|
|
4
4
|
export declare function startStdioServer(): Promise<{
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAwB,MAAM,8BAA8B,CAAC;AAkH/E,wBAAsB,eAAe,IAAI,OAAO,CAAC,SAAS,CAAC,CAE1D;AAkED,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,SAAS,CAAC,CAExE;AAwGD,wBAAsB,gBAAgB,IAAI,OAAO,CAAC;IAChD,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C,CAAC,CASD"}
|
package/dist/server.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/
|
|
2
|
-
import {
|
|
3
|
-
import { SetLevelRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
-
import * as fs from 'node:fs/promises';
|
|
1
|
+
import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
5
3
|
import process from 'node:process';
|
|
6
4
|
import { config } from './lib/config.js';
|
|
7
|
-
import {
|
|
5
|
+
import { logError, Loggers, logInfo, resolveMcpSessionIdByServer, resolveMcpSessionOwnerKey, setMcpServer, } from './lib/core.js';
|
|
8
6
|
import { toError } from './lib/error/index.js';
|
|
9
|
-
import { setTaskToolCallCapability } from './lib/mcp-interop.js';
|
|
10
7
|
import { buildServerInstructions, registerGetHelpPrompt, registerInstructionResource, } from './resources/index.js';
|
|
11
|
-
import {
|
|
8
|
+
import { TaskStoreAdapter } from './tasks/adapter.js';
|
|
9
|
+
import { abortAllTaskExecutions, emitTaskStatusNotification, registerTaskHandlers, taskManager, } from './tasks/index.js';
|
|
12
10
|
import { registerTools as registerFetchUrlTool } from './tools/index.js';
|
|
13
11
|
import { shutdownTransformWorkerPool } from './transform/index.js';
|
|
14
12
|
/* -------------------------------------------------------------------------------------------------
|
|
@@ -21,7 +19,7 @@ async function getLocalIconInfo() {
|
|
|
21
19
|
const mime = 'image/svg+xml';
|
|
22
20
|
try {
|
|
23
21
|
const iconPath = new URL(`../assets/${name}`, import.meta.url);
|
|
24
|
-
const buffer = await
|
|
22
|
+
const buffer = await readFile(iconPath);
|
|
25
23
|
return {
|
|
26
24
|
src: `data:${mime};base64,${buffer.toString('base64')}`,
|
|
27
25
|
mimeType: mime,
|
|
@@ -35,28 +33,30 @@ async function getLocalIconInfo() {
|
|
|
35
33
|
}
|
|
36
34
|
const serverInstructions = buildServerInstructions();
|
|
37
35
|
function createServerCapabilities() {
|
|
36
|
+
const taskCapableToolCalls = config.tools.enabled.includes('fetch-url');
|
|
38
37
|
return {
|
|
39
38
|
completions: {},
|
|
40
39
|
logging: {},
|
|
41
|
-
resources: {
|
|
42
|
-
// SDK auto-adds listChanged to tools capability at runtime.
|
|
40
|
+
resources: {},
|
|
43
41
|
tools: {},
|
|
44
42
|
prompts: {},
|
|
45
43
|
tasks: {
|
|
46
44
|
list: {},
|
|
47
45
|
cancel: {},
|
|
48
46
|
delete: {},
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
...(taskCapableToolCalls
|
|
48
|
+
? {
|
|
49
|
+
requests: {
|
|
50
|
+
tools: {
|
|
51
|
+
call: {},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
: {}),
|
|
56
|
+
taskStore: new TaskStoreAdapter(),
|
|
54
57
|
},
|
|
55
58
|
};
|
|
56
59
|
}
|
|
57
|
-
function syncTaskCapabilityAdvertisement(server, taskToolCallEnabled) {
|
|
58
|
-
setTaskToolCallCapability(server, taskToolCallEnabled);
|
|
59
|
-
}
|
|
60
60
|
function createServerInfo(icons) {
|
|
61
61
|
return {
|
|
62
62
|
name: config.server.name,
|
|
@@ -90,13 +90,29 @@ async function createMcpServerWithOptions(options) {
|
|
|
90
90
|
const toolControls = registerFetchUrlTool(server);
|
|
91
91
|
registerGetHelpPrompt(server, serverInstructions, localIcon);
|
|
92
92
|
registerInstructionResource(server, serverInstructions, localIcon);
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
server.server.registerCapabilities({
|
|
94
|
+
resources: { listChanged: false },
|
|
95
|
+
prompts: { listChanged: false },
|
|
96
|
+
tools: { listChanged: false },
|
|
95
97
|
});
|
|
96
|
-
const
|
|
98
|
+
const taskRegistration = registerTaskHandlers(server);
|
|
99
|
+
const taskToolCallEnabled = taskRegistration.taskCapableToolsRegistered;
|
|
97
100
|
toolControls.setTaskSupport(taskToolCallEnabled ? 'optional' : 'forbidden');
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
const disposeTaskStatusListener = taskManager.onStatusChange((task) => {
|
|
102
|
+
const sessionId = resolveMcpSessionIdByServer(server);
|
|
103
|
+
if (sessionId) {
|
|
104
|
+
const ownerKey = resolveMcpSessionOwnerKey(sessionId);
|
|
105
|
+
if (ownerKey && task.ownerKey !== ownerKey) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
emitTaskStatusNotification(server, task);
|
|
110
|
+
});
|
|
111
|
+
const close = server.close.bind(server);
|
|
112
|
+
server.close = async () => {
|
|
113
|
+
disposeTaskStatusListener();
|
|
114
|
+
await close();
|
|
115
|
+
};
|
|
100
116
|
attachServerErrorHandler(server);
|
|
101
117
|
// Set global ref only after all registrations succeed so callers
|
|
102
118
|
// never observe a half-initialised server instance.
|
|
@@ -108,17 +124,6 @@ async function createMcpServerWithOptions(options) {
|
|
|
108
124
|
export async function createMcpServerForHttpSession() {
|
|
109
125
|
return createMcpServerWithOptions({ registerObservabilityServer: false });
|
|
110
126
|
}
|
|
111
|
-
function registerLoggingSetLevelHandler(server) {
|
|
112
|
-
server.server.setRequestHandler(SetLevelRequestSchema, (request) => {
|
|
113
|
-
const sessionId = getSessionId();
|
|
114
|
-
setLogLevel(request.params.level, sessionId);
|
|
115
|
-
logNotice('Logging level updated', {
|
|
116
|
-
level: request.params.level,
|
|
117
|
-
scope: sessionId ? 'session' : 'stdio',
|
|
118
|
-
}, 'logging');
|
|
119
|
-
return {};
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
127
|
function attachServerErrorHandler(server) {
|
|
123
128
|
server.server.onerror = (error) => {
|
|
124
129
|
logError('MCP server error', toError(error), Loggers.LOG_SERVER);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type RequestId, type Result, type CreateTaskOptions as SdkCreateTaskOptions, type Task, type TaskStore } from '@modelcontextprotocol/server';
|
|
2
|
+
type SdkRequest = Parameters<TaskStore['createTask']>[2];
|
|
3
|
+
export declare class TaskStoreAdapter implements TaskStore {
|
|
4
|
+
createTask(taskParams: SdkCreateTaskOptions, requestId: RequestId, request: SdkRequest, sessionId?: string): Promise<Task>;
|
|
5
|
+
getTask(taskId: string, sessionId?: string): Promise<Task | null>;
|
|
6
|
+
storeTaskResult(taskId: string, status: 'completed' | 'failed', result: Result, sessionId?: string): Promise<void>;
|
|
7
|
+
getTaskResult(taskId: string, sessionId?: string): Promise<Result>;
|
|
8
|
+
updateTaskStatus(taskId: string, status: Task['status'], statusMessage?: string, sessionId?: string): Promise<void>;
|
|
9
|
+
listTasks(cursor?: string, sessionId?: string): Promise<{
|
|
10
|
+
tasks: Task[];
|
|
11
|
+
nextCursor?: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/tasks/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,iBAAiB,IAAI,oBAAoB,EAC9C,KAAK,IAAI,EACT,KAAK,SAAS,EACf,MAAM,8BAA8B,CAAC;AAiBtC,KAAK,UAAU,GAAG,UAAU,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAqEzD,qBAAa,gBAAiB,YAAW,SAAS;IAChD,UAAU,CACR,UAAU,EAAE,oBAAoB,EAChC,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,UAAU,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAqBhB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAMjE,eAAe,CACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,WAAW,GAAG,QAAQ,EAC9B,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAOhB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAkBlE,gBAAgB,CACd,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,EACtB,aAAa,CAAC,EAAE,MAAM,EACtB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAiChB,SAAS,CACP,MAAM,CAAC,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC;QAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAWnD"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { ProtocolErrorCode, } from '@modelcontextprotocol/server';
|
|
2
|
+
import { resolveMcpSessionOwnerKey } from '../lib/core.js';
|
|
3
|
+
import { createProtocolError } from '../lib/mcp-interop.js';
|
|
4
|
+
import { abortTaskExecution, isServerResult, withRelatedTaskMeta, } from './manager.js';
|
|
5
|
+
import { createTerminalTaskErrorResult, taskManager, } from './store.js';
|
|
6
|
+
function resolveOwnerKey(sessionId, context) {
|
|
7
|
+
const ownerKey = context?.['ownerKey'];
|
|
8
|
+
if (typeof ownerKey === 'string' && ownerKey.length > 0)
|
|
9
|
+
return ownerKey;
|
|
10
|
+
if (!sessionId)
|
|
11
|
+
return 'default';
|
|
12
|
+
return resolveMcpSessionOwnerKey(sessionId) ?? `session:${sessionId}`;
|
|
13
|
+
}
|
|
14
|
+
function toSdkTask(state) {
|
|
15
|
+
return {
|
|
16
|
+
taskId: state.taskId,
|
|
17
|
+
status: state.status,
|
|
18
|
+
ttl: state.ttl,
|
|
19
|
+
createdAt: state.createdAt,
|
|
20
|
+
lastUpdatedAt: state.lastUpdatedAt,
|
|
21
|
+
pollInterval: state.pollInterval,
|
|
22
|
+
...(state.statusMessage ? { statusMessage: state.statusMessage } : {}),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function readRequestMeta(request) {
|
|
26
|
+
const meta = request.params?._meta;
|
|
27
|
+
return meta && typeof meta === 'object' && !Array.isArray(meta)
|
|
28
|
+
? meta
|
|
29
|
+
: undefined;
|
|
30
|
+
}
|
|
31
|
+
function readStoredTaskResult(state, taskId) {
|
|
32
|
+
if (!state) {
|
|
33
|
+
throw createProtocolError(ProtocolErrorCode.ResourceNotFound, `Task not found: ${taskId}`);
|
|
34
|
+
}
|
|
35
|
+
if (state.result === undefined || state.result === null) {
|
|
36
|
+
const terminalResult = createTerminalTaskErrorResult(state);
|
|
37
|
+
if (terminalResult) {
|
|
38
|
+
const result = {
|
|
39
|
+
...terminalResult,
|
|
40
|
+
_meta: {
|
|
41
|
+
'io.modelcontextprotocol/related-task': {
|
|
42
|
+
taskId,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
throw createProtocolError(ProtocolErrorCode.InvalidParams, `Task ${taskId} has no result stored`);
|
|
49
|
+
}
|
|
50
|
+
if (isServerResult(state.result)) {
|
|
51
|
+
return withRelatedTaskMeta(state.result, taskId);
|
|
52
|
+
}
|
|
53
|
+
return state.result;
|
|
54
|
+
}
|
|
55
|
+
export class TaskStoreAdapter {
|
|
56
|
+
createTask(taskParams, requestId, request, sessionId) {
|
|
57
|
+
const ownerKey = resolveOwnerKey(sessionId, taskParams.context);
|
|
58
|
+
const requestMeta = readRequestMeta(request);
|
|
59
|
+
const createTaskOptions = {
|
|
60
|
+
requestId: String(requestId),
|
|
61
|
+
requestMethod: request.method,
|
|
62
|
+
...(taskParams.ttl !== undefined ? { ttl: taskParams.ttl } : {}),
|
|
63
|
+
...(taskParams.pollInterval !== undefined
|
|
64
|
+
? { pollInterval: taskParams.pollInterval }
|
|
65
|
+
: {}),
|
|
66
|
+
...(taskParams.context ? { context: taskParams.context } : {}),
|
|
67
|
+
...(requestMeta ? { requestMeta } : {}),
|
|
68
|
+
};
|
|
69
|
+
const state = taskManager.createTask(createTaskOptions, 'Task submitted', ownerKey);
|
|
70
|
+
return Promise.resolve(toSdkTask(state));
|
|
71
|
+
}
|
|
72
|
+
getTask(taskId, sessionId) {
|
|
73
|
+
const ownerKey = resolveOwnerKey(sessionId);
|
|
74
|
+
const state = taskManager.getTask(taskId, ownerKey);
|
|
75
|
+
return Promise.resolve(state ? toSdkTask(state) : null);
|
|
76
|
+
}
|
|
77
|
+
storeTaskResult(taskId, status, result, sessionId) {
|
|
78
|
+
const ownerKey = resolveOwnerKey(sessionId);
|
|
79
|
+
if (!taskManager.getTask(taskId, ownerKey))
|
|
80
|
+
return Promise.resolve();
|
|
81
|
+
taskManager.updateTask(taskId, { status, result }, true);
|
|
82
|
+
return Promise.resolve();
|
|
83
|
+
}
|
|
84
|
+
getTaskResult(taskId, sessionId) {
|
|
85
|
+
const ownerKey = resolveOwnerKey(sessionId);
|
|
86
|
+
const state = taskManager.getTask(taskId, ownerKey);
|
|
87
|
+
if (state?.result !== undefined && state.result !== null) {
|
|
88
|
+
const result = readStoredTaskResult(state, taskId);
|
|
89
|
+
taskManager.shrinkTtlAfterDelivery(taskId);
|
|
90
|
+
return Promise.resolve(result);
|
|
91
|
+
}
|
|
92
|
+
return taskManager
|
|
93
|
+
.waitForTerminalTask(taskId, ownerKey)
|
|
94
|
+
.then((terminalState) => {
|
|
95
|
+
const result = readStoredTaskResult(terminalState, taskId);
|
|
96
|
+
taskManager.shrinkTtlAfterDelivery(taskId);
|
|
97
|
+
return result;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
updateTaskStatus(taskId, status, statusMessage, sessionId) {
|
|
101
|
+
const ownerKey = resolveOwnerKey(sessionId);
|
|
102
|
+
if (status === 'cancelled') {
|
|
103
|
+
if (!taskManager.cancelTask(taskId, ownerKey, true))
|
|
104
|
+
return Promise.resolve();
|
|
105
|
+
abortTaskExecution(taskId);
|
|
106
|
+
return Promise.resolve();
|
|
107
|
+
}
|
|
108
|
+
const task = taskManager.getTask(taskId, ownerKey);
|
|
109
|
+
if (!task)
|
|
110
|
+
return Promise.resolve();
|
|
111
|
+
taskManager.updateTask(taskId, {
|
|
112
|
+
status,
|
|
113
|
+
...(statusMessage ? { statusMessage } : {}),
|
|
114
|
+
...(status === 'failed'
|
|
115
|
+
? {
|
|
116
|
+
result: createTerminalTaskErrorResult({
|
|
117
|
+
taskId,
|
|
118
|
+
status: 'failed',
|
|
119
|
+
...(statusMessage ? { statusMessage } : {}),
|
|
120
|
+
...(task.error ? { error: task.error } : {}),
|
|
121
|
+
}),
|
|
122
|
+
}
|
|
123
|
+
: {}),
|
|
124
|
+
}, true);
|
|
125
|
+
return Promise.resolve();
|
|
126
|
+
}
|
|
127
|
+
listTasks(cursor, sessionId) {
|
|
128
|
+
const ownerKey = resolveOwnerKey(sessionId);
|
|
129
|
+
const result = taskManager.listTasks({
|
|
130
|
+
ownerKey,
|
|
131
|
+
...(cursor ? { cursor } : {}),
|
|
132
|
+
});
|
|
133
|
+
return Promise.resolve({
|
|
134
|
+
tasks: result.tasks.map(toSdkTask),
|
|
135
|
+
...(result.nextCursor ? { nextCursor: result.nextCursor } : {}),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
package/dist/tasks/manager.d.ts
CHANGED
|
@@ -1,105 +1,37 @@
|
|
|
1
|
-
import { type McpServer } from '@modelcontextprotocol/
|
|
2
|
-
import { type ServerResult } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import { type ProgressNotification, type ToolHandlerExtra } from '../lib/mcp-interop.js';
|
|
1
|
+
import { type McpServer, RELATED_TASK_META_KEY, type ServerContext, type ServerResult } from '@modelcontextprotocol/server';
|
|
5
2
|
import { type CreateTaskResult, decodeTaskCursor, encodeTaskCursor, taskManager, type TaskState, type TaskStatus, TaskWaiterRegistry, waitForTerminalTask } from './store.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
'modelcontextprotocol.io/task': z.ZodOptional<z.ZodObject<{
|
|
14
|
-
taskId: z.ZodString;
|
|
15
|
-
keepAlive: z.ZodOptional<z.ZodNumber>;
|
|
16
|
-
}, z.core.$strict>>;
|
|
17
|
-
"modelcontextprotocol.io/related-task": z.ZodOptional<z.ZodObject<{
|
|
18
|
-
taskId: z.ZodString;
|
|
19
|
-
}, z.core.$strict>>;
|
|
20
|
-
}, z.core.$loose>>;
|
|
21
|
-
task: z.ZodOptional<z.ZodObject<{
|
|
22
|
-
ttl: z.ZodOptional<z.ZodNumber>;
|
|
23
|
-
}, z.core.$strip>>;
|
|
24
|
-
}, z.core.$strict>;
|
|
25
|
-
}, z.core.$loose>;
|
|
26
|
-
export type ExtendedCallToolRequest = z.infer<typeof extendedCallToolRequestSchema>;
|
|
27
|
-
export type ToolCallRequestMeta = ExtendedCallToolRequest['params']['_meta'];
|
|
3
|
+
interface RelatedTaskMeta {
|
|
4
|
+
taskId: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ToolCallRequestMeta extends Record<string, unknown> {
|
|
7
|
+
progressToken?: string | number;
|
|
8
|
+
[RELATED_TASK_META_KEY]?: RelatedTaskMeta;
|
|
9
|
+
}
|
|
28
10
|
export { decodeTaskCursor, encodeTaskCursor, taskManager, TaskWaiterRegistry, waitForTerminalTask, };
|
|
29
11
|
export type { CreateTaskResult, TaskState, TaskStatus };
|
|
30
|
-
export declare function parseExtendedCallToolRequest(request: unknown): ExtendedCallToolRequest;
|
|
31
12
|
export declare function sanitizeToolCallMeta(meta?: ToolCallRequestMeta): ToolCallRequestMeta | undefined;
|
|
32
13
|
export declare function buildRelatedTaskMeta(taskId: string, meta?: ToolCallRequestMeta): Record<string, unknown>;
|
|
33
14
|
export declare function withRelatedTaskMeta(result: ServerResult, taskId: string): ServerResult;
|
|
15
|
+
export declare function detachAbortController(taskId: string): AbortController | undefined;
|
|
16
|
+
export declare function attachAbortController(taskId: string): AbortController;
|
|
34
17
|
export declare function abortTaskExecution(taskId: string): void;
|
|
35
18
|
export declare function cancelTasksForOwner(ownerKey: string, statusMessage?: string): number;
|
|
36
19
|
export declare function abortAllTaskExecutions(): void;
|
|
37
20
|
type TaskSummary = CreateTaskResult['task'];
|
|
38
|
-
type TaskLifecycleProjection = Pick<TaskState, 'taskId' | 'status' | 'statusMessage' | '
|
|
21
|
+
type TaskLifecycleProjection = Pick<TaskState, 'taskId' | 'status' | 'statusMessage' | 'createdAt' | 'lastUpdatedAt' | 'ttl' | 'pollInterval'>;
|
|
39
22
|
export declare function toTaskSummary(task: TaskLifecycleProjection): TaskSummary;
|
|
40
23
|
export declare function emitTaskStatusNotification(server: McpServer, task: TaskState): void;
|
|
41
24
|
export declare function throwTaskNotFound(): never;
|
|
42
|
-
export declare function handleToolCallRequest(server: McpServer, request: ExtendedCallToolRequest, context: ToolCallContext): Promise<ServerResult>;
|
|
43
|
-
interface TaskHandlerRegistrationOptions {
|
|
44
|
-
requireInterception?: boolean;
|
|
45
|
-
}
|
|
46
25
|
interface TaskHandlerRegistrationResult {
|
|
47
|
-
interceptedToolsCall: boolean;
|
|
48
26
|
taskCapableToolsRegistered: boolean;
|
|
49
27
|
}
|
|
50
|
-
export declare function registerTaskHandlers(server: McpServer
|
|
51
|
-
interface HandlerExtra {
|
|
52
|
-
sessionId?: string;
|
|
53
|
-
authInfo?: {
|
|
54
|
-
clientId?: string;
|
|
55
|
-
token?: string;
|
|
56
|
-
};
|
|
57
|
-
signal?: AbortSignal;
|
|
58
|
-
requestId?: string | number;
|
|
59
|
-
sendNotification?: (notification: ProgressNotification) => Promise<void>;
|
|
60
|
-
requestInfo?: unknown;
|
|
61
|
-
}
|
|
62
|
-
export interface ToolExecutionContext {
|
|
63
|
-
ownerKey: string;
|
|
64
|
-
sessionId?: string;
|
|
65
|
-
signal?: AbortSignal;
|
|
66
|
-
requestId?: string | number;
|
|
67
|
-
sendNotification?: (notification: ProgressNotification) => Promise<void>;
|
|
68
|
-
requestMeta?: ToolCallRequestMeta;
|
|
69
|
-
}
|
|
70
|
-
export type ToolCallContext = ToolExecutionContext;
|
|
28
|
+
export declare function registerTaskHandlers(server: McpServer): TaskHandlerRegistrationResult;
|
|
71
29
|
interface AuthIdentity {
|
|
72
30
|
clientId?: string;
|
|
73
31
|
token?: string;
|
|
32
|
+
extra?: Record<string, unknown>;
|
|
74
33
|
}
|
|
75
|
-
/** Strip keys whose value is `undefined`, returning an object with only the
|
|
76
|
-
* present keys. Return type correctly omits the `undefined` union so the result
|
|
77
|
-
* is compatible with `exactOptionalPropertyTypes`. */
|
|
78
|
-
type Compacted<T extends object> = {
|
|
79
|
-
[K in keyof T as Exclude<T[K], undefined> extends never ? never : K]?: Exclude<T[K], undefined>;
|
|
80
|
-
};
|
|
81
|
-
export declare function compact<T extends object>(obj: T): Compacted<T>;
|
|
82
|
-
export declare function parseHandlerExtra(extra: unknown): HandlerExtra | undefined;
|
|
83
34
|
export declare function buildAuthenticatedOwnerKey(authInfo?: AuthIdentity): string | undefined;
|
|
84
|
-
export declare function
|
|
85
|
-
export declare function resolveToolCallContext(extra?: HandlerExtra, requestMeta?: ToolCallRequestMeta): ToolCallContext;
|
|
86
|
-
export declare function buildToolHandlerExtra(context: ToolExecutionContext, requestMeta?: ToolCallRequestMeta): ToolHandlerExtra;
|
|
87
|
-
export declare function withRequestContextIfMissing<TParams, TResult, TExtra = unknown>(handler: (params: TParams, extra?: TExtra) => Promise<TResult>): (params: TParams, extra?: TExtra) => Promise<TResult>;
|
|
35
|
+
export declare function resolveOwnerKeyFromContext(ctx: ServerContext): string;
|
|
88
36
|
export declare function isServerResult(value: unknown): value is ServerResult;
|
|
89
|
-
export type TaskCapableToolSupport = 'required' | 'optional' | 'forbidden';
|
|
90
|
-
export interface TaskCapableToolDescriptor<TArgs = unknown> {
|
|
91
|
-
name: string;
|
|
92
|
-
parseArguments: (args: unknown) => TArgs;
|
|
93
|
-
execute: (args: TArgs, extra?: ToolHandlerExtra) => Promise<ServerResult>;
|
|
94
|
-
getCompletionStatusMessage?: (result: ServerResult) => string | undefined;
|
|
95
|
-
taskSupport?: TaskCapableToolSupport;
|
|
96
|
-
immediateResponse?: string;
|
|
97
|
-
}
|
|
98
|
-
export declare function registerTaskCapableTool<TArgs>(server: McpServer, descriptor: TaskCapableToolDescriptor<TArgs>): void;
|
|
99
|
-
export declare function unregisterTaskCapableTool(server: McpServer, name: string): void;
|
|
100
|
-
export declare function getTaskCapableTool(server: McpServer, name: string): TaskCapableToolDescriptor | undefined;
|
|
101
|
-
export declare function getTaskCapableToolSupport(server: McpServer, name: string): TaskCapableToolSupport | undefined;
|
|
102
|
-
export declare function hasTaskCapableTool(server: McpServer, name: string): boolean;
|
|
103
|
-
export declare function hasRegisteredTaskCapableTools(server: McpServer): boolean;
|
|
104
|
-
export declare function setTaskCapableToolSupport(server: McpServer, name: string, support: TaskCapableToolSupport): void;
|
|
105
37
|
//# sourceMappingURL=manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/tasks/manager.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/tasks/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EAEd,qBAAqB,EACrB,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,8BAA8B,CAAC;AAkBtC,OAAO,EACL,KAAK,gBAAgB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,KAAK,SAAS,EACd,KAAK,UAAU,EACf,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAWpB,UAAU,eAAe;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAoB,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAClE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,CAAC,qBAAqB,CAAC,CAAC,EAAE,eAAe,CAAC;CAC3C;AAED,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,kBAAkB,EAClB,mBAAmB,GACpB,CAAC;AACF,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;AAExD,wBAAgB,oBAAoB,CAClC,IAAI,CAAC,EAAE,mBAAmB,GACzB,mBAAmB,GAAG,SAAS,CAOjC;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,mBAAmB,GACzB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzB;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,MAAM,GACb,YAAY,CAQd;AAuBD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,GACb,eAAe,GAAG,SAAS,CAM7B;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAiBrE;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,aAAa,SAA4D,GACxE,MAAM,CAOR;AAED,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAMD,KAAK,WAAW,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAC5C,KAAK,uBAAuB,GAAG,IAAI,CACjC,SAAS,EACP,QAAQ,GACR,QAAQ,GACR,eAAe,GACf,WAAW,GACX,eAAe,GACf,KAAK,GACL,cAAc,CACjB,CAAC;AAEF,wBAAgB,aAAa,CAAC,IAAI,EAAE,uBAAuB,GAAG,WAAW,CAUxE;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,SAAS,GACd,IAAI,CAmBN;AAED,wBAAgB,iBAAiB,IAAI,KAAK,CAKzC;AAgBD,UAAU,6BAA6B;IACrC,0BAA0B,EAAE,OAAO,CAAC;CACrC;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,GAChB,6BAA6B,CAyB/B;AAMD,UAAU,YAAY;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAgBD,wBAAgB,0BAA0B,CACxC,QAAQ,CAAC,EAAE,YAAY,GACtB,MAAM,GAAG,SAAS,CAiBpB;AAED,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAkBrE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAEpE"}
|