crawlforge-mcp-server 5.10.0 → 6.0.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/package.json +7 -5
- package/server.js +201 -249
- package/src/core/ActionExecutor.js +1 -1
- package/src/core/ChangeTracker.js +1 -1
- package/src/core/ElicitationHelper.js +83 -34
- package/src/core/SamplingClient.js +8 -2
- package/src/core/analysis/ContentAnalyzer.js +1 -1
- package/src/core/processing/BrowserProcessor.js +1 -1
- package/src/core/processing/ContentProcessor.js +1 -1
- package/src/core/processing/PDFProcessor.js +2 -2
- package/src/server/registerTool.js +1 -1
- package/src/server/specHygiene.js +17 -22
- package/src/server/transports/stdio.js +2 -3
- package/src/server/transports/streamableHttp.js +128 -66
- package/src/tools/crawl/crawlDeep.js +4 -4
- package/src/tools/extract/analyzeContent.js +1 -1
- package/src/tools/extract/extractContent.js +1 -1
- package/src/tools/extract/processDocument.js +1 -1
- package/src/tools/extract/summarizeContent.js +1 -1
- package/src/tools/llmstxt/generateLLMsTxt.js +2 -2
- package/src/tools/research/deepResearch.js +1 -1
- package/src/tools/tracking/trackChanges/schema.js +4 -4
- package/src/utils/HumanBehaviorSimulator.js +7 -7
- package/src/server/taskSupport.js +0 -233
- package/src/server/transports/http.js +0 -22
|
@@ -37,7 +37,7 @@ const ChangeTrackingSchema = z.object({
|
|
|
37
37
|
moderate: z.number().min(0).max(1).default(0.3),
|
|
38
38
|
major: z.number().min(0).max(1).default(0.7)
|
|
39
39
|
}).optional()
|
|
40
|
-
}).optional().
|
|
40
|
+
}).optional().prefault({})
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
const ChangeComparisonSchema = z.object({
|
|
@@ -5,8 +5,58 @@
|
|
|
5
5
|
* expensive or ambiguous operations. Falls back gracefully when the
|
|
6
6
|
* MCP client does not support elicitation.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* The request goes out as a 2025-era server→client `elicitation/create`,
|
|
9
|
+
* awaited inline in the middle of a tool's work. Two things the SDK does NOT
|
|
10
|
+
* do for us, and which `supported` therefore decides before we send:
|
|
11
|
+
*
|
|
12
|
+
* - `Server.elicitInput()` refuses a client that declared a bare
|
|
13
|
+
* `elicitation: {}` (it demands `elicitation.form`), even though the SDK's
|
|
14
|
+
* own capability rule counts a bare declaration as form-capable — that is
|
|
15
|
+
* the pre-mode 2025 meaning, and it is what the SDK's legacy shim and its
|
|
16
|
+
* 2026 seam both apply. We send through `Server.request()`, which applies
|
|
17
|
+
* that same lenient rule, so those clients get their prompt.
|
|
18
|
+
*
|
|
19
|
+
* - Both `elicitInput()` and `request()` throw on a 2026-07-28-era
|
|
20
|
+
* connection: that revision has no server→client request channel at all.
|
|
21
|
+
* There is no inline substitute — the replacement is an `input_required`
|
|
22
|
+
* result RETURNED by a tools/call handler, which this helper cannot do
|
|
23
|
+
* from the middle of a tool's execution. So `supported` reports false and
|
|
24
|
+
* the operation proceeds unasked, exactly as it does for a client with no
|
|
25
|
+
* elicitation capability. A confirmation prompt is a nicety; failing the
|
|
26
|
+
* call is not an acceptable substitute. See docs/mcp-spec-adoption.md.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* First revision of the modern protocol era. Revisions are ISO dates, so
|
|
31
|
+
* lexicographic comparison orders them chronologically (the SDK's own rule).
|
|
9
32
|
*/
|
|
33
|
+
const FIRST_MODERN_PROTOCOL_VERSION = '2026-07-28';
|
|
34
|
+
|
|
35
|
+
/** The one-boolean schema a confirmation asks with. */
|
|
36
|
+
const CONFIRM_SCHEMA = {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
confirmed: {
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
title: 'Proceed?',
|
|
42
|
+
description: 'Confirm to proceed with the operation',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
required: ['confirmed'],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Whether the client's declared capabilities cover FORM elicitation, by the
|
|
50
|
+
* SDK's own rule: `elicitation.form` counts, and so does a bare `elicitation`
|
|
51
|
+
* declaration naming neither mode (the pre-mode 2025 meaning). A client that
|
|
52
|
+
* declared only `elicitation.url` has not declared form support.
|
|
53
|
+
*/
|
|
54
|
+
function formElicitationDeclared(caps) {
|
|
55
|
+
const elicitation = caps?.elicitation;
|
|
56
|
+
if (!elicitation) return false;
|
|
57
|
+
if (elicitation.form !== undefined) return true;
|
|
58
|
+
return elicitation.url === undefined;
|
|
59
|
+
}
|
|
10
60
|
|
|
11
61
|
export class ElicitationHelper {
|
|
12
62
|
/**
|
|
@@ -20,22 +70,35 @@ export class ElicitationHelper {
|
|
|
20
70
|
}
|
|
21
71
|
|
|
22
72
|
/**
|
|
23
|
-
* Whether
|
|
73
|
+
* Whether an inline elicitation round trip will actually reach the user.
|
|
24
74
|
* @returns {boolean}
|
|
25
75
|
*/
|
|
26
76
|
get supported() {
|
|
27
77
|
const server = this._mcpServer?.server;
|
|
28
|
-
|
|
29
|
-
// usable when the connected CLIENT advertised the `elicitation` capability.
|
|
30
|
-
if (typeof server?.elicitInput !== 'function') return false;
|
|
78
|
+
if (typeof server?.request !== 'function') return false;
|
|
31
79
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
80
|
+
// No server→client request channel exists on the 2026-07-28 era.
|
|
81
|
+
const negotiated = server.getNegotiatedProtocolVersion?.();
|
|
82
|
+
if (typeof negotiated === 'string' && negotiated >= FIRST_MODERN_PROTOCOL_VERSION) return false;
|
|
83
|
+
return formElicitationDeclared(server.getClientCapabilities?.());
|
|
34
84
|
} catch {
|
|
35
85
|
return false;
|
|
36
86
|
}
|
|
37
87
|
}
|
|
38
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Send one form-mode `elicitation/create` and return the ElicitResult.
|
|
91
|
+
* This is byte-for-byte the message `Server.elicitInput()` sends; it just
|
|
92
|
+
* does not impose that method's stricter `elicitation.form` gate.
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
async _elicit(message, requestedSchema) {
|
|
96
|
+
return this._mcpServer.server.request({
|
|
97
|
+
method: 'elicitation/create',
|
|
98
|
+
params: { message, requestedSchema, mode: 'form' },
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
39
102
|
/**
|
|
40
103
|
* Ask for user confirmation before proceeding with an expensive operation.
|
|
41
104
|
* Returns true if confirmed (or if elicitation is unsupported — fail-open
|
|
@@ -57,20 +120,7 @@ export class ElicitationHelper {
|
|
|
57
120
|
.join('\n');
|
|
58
121
|
const fullMessage = detailLines ? `${message}\n\n${detailLines}` : message;
|
|
59
122
|
|
|
60
|
-
const result = await this.
|
|
61
|
-
message: fullMessage,
|
|
62
|
-
requestedSchema: {
|
|
63
|
-
type: 'object',
|
|
64
|
-
properties: {
|
|
65
|
-
confirmed: {
|
|
66
|
-
type: 'boolean',
|
|
67
|
-
title: 'Proceed?',
|
|
68
|
-
description: 'Confirm to proceed with the operation',
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
required: ['confirmed'],
|
|
72
|
-
},
|
|
73
|
-
});
|
|
123
|
+
const result = await this._elicit(fullMessage, CONFIRM_SCHEMA);
|
|
74
124
|
|
|
75
125
|
// Only an explicit accept + confirmed=true proceeds; decline/cancel = stop.
|
|
76
126
|
return result?.action === 'accept' && result?.content?.confirmed === true;
|
|
@@ -97,23 +147,22 @@ export class ElicitationHelper {
|
|
|
97
147
|
}
|
|
98
148
|
|
|
99
149
|
try {
|
|
100
|
-
const result = await this.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
description: fieldDescription,
|
|
109
|
-
...(defaultValue ? { default: defaultValue } : {}),
|
|
110
|
-
},
|
|
150
|
+
const result = await this._elicit(message, {
|
|
151
|
+
type: 'object',
|
|
152
|
+
properties: {
|
|
153
|
+
[fieldName]: {
|
|
154
|
+
type: 'string',
|
|
155
|
+
title: fieldName,
|
|
156
|
+
description: fieldDescription,
|
|
157
|
+
...(defaultValue ? { default: defaultValue } : {}),
|
|
111
158
|
},
|
|
112
|
-
required: [fieldName],
|
|
113
159
|
},
|
|
160
|
+
required: [fieldName],
|
|
114
161
|
});
|
|
115
162
|
|
|
116
|
-
|
|
163
|
+
// The answer is client-supplied and no longer schema-checked by the SDK
|
|
164
|
+
// on this path, so hold it to the type we asked for.
|
|
165
|
+
if (result?.action === 'accept' && typeof result?.content?.[fieldName] === 'string') {
|
|
117
166
|
return result.content[fieldName];
|
|
118
167
|
}
|
|
119
168
|
return defaultValue || null;
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* Fallback chain (applied in resolveCompletion):
|
|
8
8
|
* 1. Ollama (local, no API key needed)
|
|
9
9
|
* 2. Server-side API key (OPENAI_API_KEY / ANTHROPIC_API_KEY)
|
|
10
|
-
* 3. MCP sampling request to client
|
|
10
|
+
* 3. MCP sampling request to client — DEPRECATED in MCP revision 2026-07-28
|
|
11
|
+
* (SEP-2577); removal on or after 2027-07-28. Emits a one-line stderr
|
|
12
|
+
* deprecation notice when it serves a completion.
|
|
11
13
|
* 4. Error
|
|
12
14
|
*/
|
|
13
15
|
|
|
@@ -157,7 +159,11 @@ export class SamplingClient {
|
|
|
157
159
|
includeContext: 'none',
|
|
158
160
|
});
|
|
159
161
|
const text = samplingResult?.content?.text || '';
|
|
160
|
-
if (text)
|
|
162
|
+
if (text) {
|
|
163
|
+
// stderr, never stdout — stdout is the JSON-RPC stream on stdio.
|
|
164
|
+
console.error('[deprecation] MCP sampling served this completion. Sampling was deprecated in MCP revision 2026-07-28 (SEP-2577); CrawlForge removes this fallback on or after 2027-07-28. Run Ollama or set OPENAI_API_KEY / ANTHROPIC_API_KEY.');
|
|
165
|
+
return { text, provider: 'sampling' };
|
|
166
|
+
}
|
|
161
167
|
} catch (_samplingErr) {
|
|
162
168
|
// Sampling not supported or failed
|
|
163
169
|
}
|
|
@@ -28,7 +28,7 @@ const ContentAnalyzerSchema = z.object({
|
|
|
28
28
|
maxKeywords: z.number().min(1).max(50).default(15),
|
|
29
29
|
includeReadabilityMetrics: z.boolean().default(true),
|
|
30
30
|
includeSentiment: z.boolean().default(true)
|
|
31
|
-
}).optional().
|
|
31
|
+
}).optional().prefault({})
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
const AnalysisResult = z.object({
|
|
@@ -74,7 +74,7 @@ const BrowserProcessorSchema = z.object({
|
|
|
74
74
|
enableTimezoneSpoof: z.boolean().default(true),
|
|
75
75
|
enableGeoLocationSpoof: z.boolean().default(true)
|
|
76
76
|
}).optional()
|
|
77
|
-
}).optional().
|
|
77
|
+
}).optional().prefault({})
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
const BrowserResult = z.object({
|
|
@@ -20,7 +20,7 @@ const ContentProcessorSchema = z.object({
|
|
|
20
20
|
removeBoilerplate: z.boolean().default(true),
|
|
21
21
|
preserveImageInfo: z.boolean().default(true),
|
|
22
22
|
extractMetadata: z.boolean().default(true)
|
|
23
|
-
}).optional().
|
|
23
|
+
}).optional().prefault({})
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
const ReadabilityResult = z.object({
|
|
@@ -31,8 +31,8 @@ const PDFProcessorSchema = z.object({
|
|
|
31
31
|
parseOptions: z.object({
|
|
32
32
|
normalizeWhitespace: z.boolean().default(true),
|
|
33
33
|
disableCombineTextItems: z.boolean().default(false)
|
|
34
|
-
}).optional().
|
|
35
|
-
}).optional().
|
|
34
|
+
}).optional().prefault({})
|
|
35
|
+
}).optional().prefault({})
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
const PDFResult = z.object({
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* validates `structuredContent` against the schema; legacy clients keep
|
|
11
11
|
* reading the JSON-stringified `content` for backward compatibility.
|
|
12
12
|
*
|
|
13
|
-
* @param {import('@modelcontextprotocol/
|
|
13
|
+
* @param {import('@modelcontextprotocol/server').McpServer} server
|
|
14
14
|
* @param {Function} withAuth — from makeWithAuth() in src/server/withAuth.js
|
|
15
15
|
* @param {Object} descriptor
|
|
16
16
|
* @param {string} descriptor.name — tool name (MCP identifier)
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* specHygiene — Phase 6 protocol-hygiene wrapper.
|
|
3
3
|
*
|
|
4
|
-
* Applies
|
|
4
|
+
* Applies four MCP wire-level upgrades to an already-registered McpServer
|
|
5
5
|
* without changing how tools/prompts are declared anywhere else:
|
|
6
6
|
*
|
|
7
7
|
* 1. JSON Schema 2020-12 dialect stamping on every tool's inputSchema /
|
|
8
|
-
* outputSchema
|
|
9
|
-
*
|
|
8
|
+
* outputSchema. Under the v2 SDK + zod 4 the conversion already emits
|
|
9
|
+
* 2020-12 with `$defs`, so the `definitions` -> `$defs` rewrite below is
|
|
10
|
+
* now defensive rather than load-bearing; it is kept because the stamping
|
|
11
|
+
* and ordering passes still walk the same tree (Phase 4.1 kept behaviour
|
|
12
|
+
* identical — removing the rewrite is a separate, verifiable change).
|
|
10
13
|
* 2. Deterministic (alphabetical) tool ordering in tools/list, so clients
|
|
11
14
|
* that prompt-cache tools/list get stable hits across restarts.
|
|
12
15
|
* 3. SEP-973 icons metadata on every tool/prompt lacking one.
|
|
13
16
|
* 4. SEP-2549-style cache hints on tools/call results for a fixed
|
|
14
17
|
* allowlist of read-only, idempotent tools (see note below).
|
|
15
18
|
*
|
|
16
|
-
* Wiring: SDK
|
|
19
|
+
* Wiring: the SDK has no plugin hook for this, so applySpecHygiene() must be
|
|
17
20
|
* called once, after all server.registerTool()/registerPrompt() calls and
|
|
18
21
|
* before transport.connect(). It reaches into `server.server` (the
|
|
19
22
|
* underlying Protocol), captures the ListTools/CallTool/ListPrompts handlers
|
|
@@ -22,26 +25,18 @@
|
|
|
22
25
|
* handler for the same method (confirmed from the SDK source: only
|
|
23
26
|
* `assertCanSetRequestHandler`, called by McpServer's own registration path,
|
|
24
27
|
* throws on a pre-existing handler; `setRequestHandler` itself does not).
|
|
28
|
+
* In v2 `setRequestHandler` takes a method string rather than a Zod schema.
|
|
25
29
|
*
|
|
26
|
-
* SEP-2549 note: as specified (2026-07-28
|
|
27
|
-
* ("public"|"private") are defined on
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* namespaced `_meta` key following the SDK's own
|
|
32
|
-
* `io.modelcontextprotocol/<name>` convention (see RELATED_TASK_META_KEY in
|
|
33
|
-
* @modelcontextprotocol/sdk/types.js):
|
|
30
|
+
* SEP-2549 note: as specified (2026-07-28), `ttlMs` / `cacheScope`
|
|
31
|
+
* ("public"|"private") are defined on server/discover results — there is no
|
|
32
|
+
* CacheableResult for tools/call. Since this deliverable asks for tools/call
|
|
33
|
+
* cache hints, the same two field names are carried into a namespaced `_meta`
|
|
34
|
+
* key following the SDK's own `io.modelcontextprotocol/<name>` convention:
|
|
34
35
|
* result._meta["io.modelcontextprotocol/cacheable"] = { ttlMs, cacheScope }
|
|
35
36
|
* This is a documented adaptation, not a key confirmed by the spec text for
|
|
36
37
|
* tools/call — revisit if/when a SEP defines call-result caching explicitly.
|
|
37
38
|
*/
|
|
38
39
|
|
|
39
|
-
import {
|
|
40
|
-
ListToolsRequestSchema,
|
|
41
|
-
CallToolRequestSchema,
|
|
42
|
-
ListPromptsRequestSchema
|
|
43
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
44
|
-
|
|
45
40
|
const APPLIED = Symbol('crawlforge.specHygiene.applied');
|
|
46
41
|
|
|
47
42
|
const JSON_SCHEMA_2020_12 = 'https://json-schema.org/draft/2020-12/schema';
|
|
@@ -158,7 +153,7 @@ function wrapToolsCall(innerHandler, cacheableTools) {
|
|
|
158
153
|
* tools/prompts are registered and before transport.connect(). Idempotent:
|
|
159
154
|
* a second call on the same server is a no-op.
|
|
160
155
|
*
|
|
161
|
-
* @param {import('@modelcontextprotocol/
|
|
156
|
+
* @param {import('@modelcontextprotocol/server').McpServer} server
|
|
162
157
|
* @param {object} [overrides]
|
|
163
158
|
* @param {{src:string,mimeType?:string,sizes?:string[]}} [overrides.icon] — default icon injected into tools/prompts lacking one
|
|
164
159
|
* @param {Record<string,{ttlMs:number,cacheScope:'public'|'private'}>} [overrides.cacheableTools] — tool-name -> cache hint map for tools/call
|
|
@@ -177,16 +172,16 @@ export function applySpecHygiene(server, overrides = {}) {
|
|
|
177
172
|
|
|
178
173
|
const innerToolsList = protocol._requestHandlers.get('tools/list');
|
|
179
174
|
if (innerToolsList) {
|
|
180
|
-
protocol.setRequestHandler(
|
|
175
|
+
protocol.setRequestHandler('tools/list', wrapToolsList(innerToolsList, icon));
|
|
181
176
|
}
|
|
182
177
|
|
|
183
178
|
const innerToolsCall = protocol._requestHandlers.get('tools/call');
|
|
184
179
|
if (innerToolsCall) {
|
|
185
|
-
protocol.setRequestHandler(
|
|
180
|
+
protocol.setRequestHandler('tools/call', wrapToolsCall(innerToolsCall, cacheableTools));
|
|
186
181
|
}
|
|
187
182
|
|
|
188
183
|
const innerPromptsList = protocol._requestHandlers.get('prompts/list');
|
|
189
184
|
if (innerPromptsList) {
|
|
190
|
-
protocol.setRequestHandler(
|
|
185
|
+
protocol.setRequestHandler('prompts/list', wrapPromptsList(innerPromptsList, icon));
|
|
191
186
|
}
|
|
192
187
|
}
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
* stdio transport setup — extracted from server.js runServer().
|
|
3
3
|
* Used when server is launched without the --http flag.
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* Connect the MCP server to stdio transport and log startup message.
|
|
10
|
-
* @param {import('@modelcontextprotocol/
|
|
9
|
+
* @param {import('@modelcontextprotocol/server').McpServer} server
|
|
11
10
|
*/
|
|
12
11
|
export async function connectStdio(server) {
|
|
13
12
|
const transport = new StdioServerTransport();
|