@j0hanz/fetch-url-mcp 1.11.7 → 1.12.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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +177 -99
  3. package/dist/http/health.d.ts.map +1 -1
  4. package/dist/http/health.js +0 -2
  5. package/dist/http/native.d.ts.map +1 -1
  6. package/dist/http/native.js +0 -25
  7. package/dist/lib/config.d.ts +0 -7
  8. package/dist/lib/config.d.ts.map +1 -1
  9. package/dist/lib/config.js +0 -9
  10. package/dist/lib/core.d.ts +0 -3
  11. package/dist/lib/core.d.ts.map +1 -1
  12. package/dist/lib/core.js +0 -7
  13. package/dist/lib/fetch-pipeline.d.ts +1 -14
  14. package/dist/lib/fetch-pipeline.d.ts.map +1 -1
  15. package/dist/lib/fetch-pipeline.js +4 -147
  16. package/dist/lib/http.d.ts +0 -3
  17. package/dist/lib/http.d.ts.map +1 -1
  18. package/dist/lib/http.js +2 -105
  19. package/dist/lib/mcp-interop.js +2 -2
  20. package/dist/lib/utils.d.ts +0 -2
  21. package/dist/lib/utils.d.ts.map +1 -1
  22. package/dist/lib/utils.js +4 -39
  23. package/dist/resources/index.d.ts +1 -23
  24. package/dist/resources/index.d.ts.map +1 -1
  25. package/dist/resources/index.js +3 -294
  26. package/dist/schemas.d.ts +0 -14
  27. package/dist/schemas.d.ts.map +1 -1
  28. package/dist/schemas.js +1 -77
  29. package/dist/server.d.ts.map +1 -1
  30. package/dist/server.js +1 -2
  31. package/dist/tools/fetch-url.d.ts +0 -2
  32. package/dist/tools/fetch-url.d.ts.map +1 -1
  33. package/dist/tools/fetch-url.js +12 -43
  34. package/dist/transform/shared.js +4 -4
  35. package/dist/transform/transform.d.ts +1 -1
  36. package/dist/transform/transform.d.ts.map +1 -1
  37. package/dist/transform/transform.js +10 -10
  38. package/dist/transform/types.d.ts +2 -2
  39. package/dist/transform/types.d.ts.map +1 -1
  40. package/dist/transform/worker-pool.d.ts +3 -3
  41. package/dist/transform/worker-pool.d.ts.map +1 -1
  42. package/dist/transform/worker-pool.js +2 -2
  43. package/package.json +13 -4
  44. package/dist/lib/cache.d.ts +0 -48
  45. package/dist/lib/cache.d.ts.map +0 -1
  46. package/dist/lib/cache.js +0 -264
@@ -1,271 +1,6 @@
1
- import { ResourceTemplate, } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { ErrorCode, McpError, SubscribeRequestSchema, UnsubscribeRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
3
- import { get as getCacheEntry, getEntryMeta, keys as listCacheKeys, onCacheUpdate, parseCacheKey, toCacheScopeId, } from '../lib/cache.js';
4
- import { config, logWarn, resolveMcpSessionIdByServer } from '../lib/core.js';
5
- import { registerServerLifecycleCleanup } from '../lib/mcp-interop.js';
1
+ import { config } from '../lib/core.js';
6
2
  import { buildOptionalIcons } from '../lib/utils.js';
7
- import { isObject } from '../lib/utils.js';
8
- import { parseCachedPayload, resolveCachedPayloadContent } from '../schemas.js';
9
3
  import { FETCH_URL_TOOL_NAME } from '../tools/fetch-url.js';
10
- const RESOURCE_NOT_FOUND_ERROR_CODE = -32002;
11
- const CACHE_RESOURCE_TEMPLATE_URI = 'internal://cache/{namespace}/{hash}';
12
- const CACHE_RESOURCE_PREFIX = 'internal://cache/';
13
- const CACHE_NAMESPACE_PATTERN = /^[a-z0-9_-]{1,64}$/i;
14
- const CACHE_HASH_PATTERN = /^[a-f0-9.]{8,64}$/i;
15
- const MAX_COMPLETION_VALUES = 100;
16
- function normalizeCompletionPrefix(value) {
17
- return value.trim().toLowerCase();
18
- }
19
- function sortAndLimitValues(values) {
20
- return [...values]
21
- .sort((left, right) => left.localeCompare(right))
22
- .slice(0, MAX_COMPLETION_VALUES);
23
- }
24
- function isValidCacheResourceParts(parts) {
25
- return (CACHE_NAMESPACE_PATTERN.test(parts.namespace) &&
26
- CACHE_HASH_PATTERN.test(parts.hash));
27
- }
28
- function decodeSegment(value) {
29
- try {
30
- return decodeURIComponent(value);
31
- }
32
- catch {
33
- return value;
34
- }
35
- }
36
- function trimToValue(value) {
37
- const trimmed = value.trim();
38
- return trimmed.length > 0 ? trimmed : undefined;
39
- }
40
- function firstVariableValue(value) {
41
- if (typeof value === 'string') {
42
- return trimToValue(value);
43
- }
44
- if (Array.isArray(value)) {
45
- const first = value[0];
46
- if (typeof first !== 'string')
47
- return undefined;
48
- return trimToValue(first);
49
- }
50
- return undefined;
51
- }
52
- function validateCacheResourceParts(namespace, hash) {
53
- const decoded = {
54
- namespace: decodeSegment(namespace),
55
- hash: decodeSegment(hash),
56
- };
57
- return isValidCacheResourceParts(decoded) ? decoded : null;
58
- }
59
- function parseCacheResourceFromVariables(variables) {
60
- const namespace = firstVariableValue(variables['namespace']);
61
- const hash = firstVariableValue(variables['hash']);
62
- if (!namespace || !hash)
63
- return null;
64
- return validateCacheResourceParts(namespace, hash);
65
- }
66
- function parseCacheResourceFromUri(uri) {
67
- if (!uri.href.startsWith(CACHE_RESOURCE_PREFIX))
68
- return null;
69
- const rawPath = uri.pathname.startsWith('/')
70
- ? uri.pathname.slice(1)
71
- : uri.pathname;
72
- const segments = rawPath.split('/');
73
- if (segments.length !== 2)
74
- return null;
75
- const namespace = segments[0];
76
- const hash = segments[1];
77
- if (!namespace || !hash)
78
- return null;
79
- return validateCacheResourceParts(namespace, hash);
80
- }
81
- function toCacheResourceUri(parts) {
82
- const namespace = encodeURIComponent(parts.namespace);
83
- const hash = encodeURIComponent(parts.hash);
84
- return `${CACHE_RESOURCE_PREFIX}${namespace}/${hash}`;
85
- }
86
- export function isCacheEntryVisibleToScope(scopeId, meta) {
87
- return meta.scopeIds.includes(scopeId);
88
- }
89
- function getVisibleCacheEntries(scopeId) {
90
- return listCacheKeys()
91
- .map((key) => parseCacheKey(key))
92
- .filter((parts) => parts !== null)
93
- .map((parts) => ({ namespace: parts.namespace, hash: parts.urlHash }))
94
- .filter((parts) => {
95
- const meta = getEntryMeta(`${parts.namespace}:${parts.hash}`);
96
- return meta ? isCacheEntryVisibleToScope(scopeId, meta) : false;
97
- });
98
- }
99
- function completeCacheNamespaces(value, scopeId) {
100
- const normalized = normalizeCompletionPrefix(value);
101
- const namespaces = [
102
- ...new Set(getVisibleCacheEntries(scopeId).map((entry) => entry.namespace)),
103
- ].filter((ns) => ns.toLowerCase().startsWith(normalized));
104
- return sortAndLimitValues(namespaces);
105
- }
106
- function completeCacheHashes(value, scopeId, context) {
107
- const normalized = normalizeCompletionPrefix(value);
108
- const namespace = context?.arguments?.['namespace']?.trim();
109
- const hashes = getVisibleCacheEntries(scopeId)
110
- .filter((entry) => namespace === undefined || entry.namespace === namespace)
111
- .map((entry) => entry.hash)
112
- .filter((h) => h.toLowerCase().startsWith(normalized));
113
- return sortAndLimitValues(hashes);
114
- }
115
- function getServerCacheScopeId(server) {
116
- return toCacheScopeId(resolveMcpSessionIdByServer(server));
117
- }
118
- export function listCacheResourcesForScope(scopeId) {
119
- const resources = getVisibleCacheEntries(scopeId)
120
- .map((parts) => {
121
- const cacheParts = {
122
- namespace: parts.namespace,
123
- hash: parts.hash,
124
- };
125
- const cacheKey = `${parts.namespace}:${parts.hash}`;
126
- const meta = getEntryMeta(cacheKey);
127
- if (!meta)
128
- return null; // expired between keys() and meta read — skip
129
- return {
130
- uri: toCacheResourceUri(cacheParts),
131
- name: `${parts.namespace}:${parts.hash}`,
132
- title: meta.title ?? 'Cached Markdown',
133
- description: 'Cached markdown output generated by fetch-url',
134
- mimeType: 'text/markdown',
135
- annotations: {
136
- audience: ['assistant'],
137
- priority: 0.6,
138
- ...(meta.fetchedAt ? { lastModified: meta.fetchedAt } : {}),
139
- },
140
- };
141
- })
142
- .filter((r) => r !== null);
143
- return { resources };
144
- }
145
- function normalizeSubscriptionUri(uri) {
146
- const parsedUri = URL.parse(uri);
147
- if (!parsedUri) {
148
- throw new McpError(ErrorCode.InvalidParams, 'Invalid resource URI');
149
- }
150
- const cacheParts = parseCacheResourceFromUri(parsedUri);
151
- if (cacheParts)
152
- return toCacheResourceUri(cacheParts);
153
- return parsedUri.href;
154
- }
155
- const cacheNotificationServers = new WeakSet();
156
- function registerCacheResourceNotifications(server) {
157
- if (cacheNotificationServers.has(server))
158
- return;
159
- cacheNotificationServers.add(server);
160
- const subscribedResourceUris = new Set();
161
- const setSubscription = (uri, subscribed) => {
162
- const normalized = normalizeSubscriptionUri(uri);
163
- if (subscribed) {
164
- subscribedResourceUris.add(normalized);
165
- }
166
- else {
167
- subscribedResourceUris.delete(normalized);
168
- }
169
- };
170
- server.server.setRequestHandler(SubscribeRequestSchema, async (request) => {
171
- setSubscription(request.params.uri, true);
172
- return Promise.resolve({});
173
- });
174
- server.server.setRequestHandler(UnsubscribeRequestSchema, async (request) => {
175
- setSubscription(request.params.uri, false);
176
- return Promise.resolve({});
177
- });
178
- const unsubscribe = onCacheUpdate((event) => {
179
- const scopeId = getServerCacheScopeId(server);
180
- const changedUri = toCacheResourceUri({
181
- namespace: event.namespace,
182
- hash: event.urlHash,
183
- });
184
- const isVisibleToServer = event.scopeIds.includes(scopeId);
185
- if (isVisibleToServer &&
186
- server.isConnected() &&
187
- subscribedResourceUris.has(changedUri)) {
188
- void server.server
189
- .sendResourceUpdated({ uri: changedUri })
190
- .catch((error) => {
191
- logWarn('Failed to send resource updated notification', {
192
- uri: changedUri,
193
- error,
194
- });
195
- });
196
- }
197
- if (!event.listChanged)
198
- return;
199
- if (!server.isConnected() || !isVisibleToServer)
200
- return;
201
- try {
202
- server.sendResourceListChanged();
203
- }
204
- catch (error) {
205
- logWarn('Failed to send resources list changed notification', { error });
206
- }
207
- });
208
- let cleanedUp = false;
209
- const cleanup = () => {
210
- if (cleanedUp)
211
- return;
212
- cleanedUp = true;
213
- unsubscribe();
214
- };
215
- registerServerLifecycleCleanup(server, cleanup);
216
- }
217
- function normalizeTemplateVariables(variables) {
218
- if (!isObject(variables))
219
- return {};
220
- const normalized = {};
221
- for (const [key, value] of Object.entries(variables)) {
222
- if (typeof value === 'string' || value === undefined) {
223
- normalized[key] = value;
224
- continue;
225
- }
226
- if (Array.isArray(value)) {
227
- normalized[key] = value.filter((item) => typeof item === 'string');
228
- }
229
- }
230
- return normalized;
231
- }
232
- function resolveCacheResourceParts(uri, variables) {
233
- const fromVariables = parseCacheResourceFromVariables(variables);
234
- if (fromVariables)
235
- return fromVariables;
236
- const fromUri = parseCacheResourceFromUri(uri);
237
- if (fromUri)
238
- return fromUri;
239
- throw new McpError(ErrorCode.InvalidParams, 'Invalid cache resource URI or template arguments');
240
- }
241
- export function readCacheResourceForScope(uri, variables, scopeId) {
242
- const parts = resolveCacheResourceParts(uri, variables);
243
- const cacheKey = `${parts.namespace}:${parts.hash}`;
244
- const meta = getEntryMeta(cacheKey);
245
- if (!meta || !isCacheEntryVisibleToScope(scopeId, meta)) {
246
- throw new McpError(RESOURCE_NOT_FOUND_ERROR_CODE, 'Resource not found', {
247
- uri: uri.href,
248
- });
249
- }
250
- const entry = getCacheEntry(cacheKey);
251
- if (!entry) {
252
- throw new McpError(RESOURCE_NOT_FOUND_ERROR_CODE, 'Resource not found', {
253
- uri: uri.href,
254
- });
255
- }
256
- const payload = parseCachedPayload(entry.content);
257
- const markdown = payload ? resolveCachedPayloadContent(payload) : null;
258
- const text = markdown ?? entry.content;
259
- return {
260
- contents: [
261
- {
262
- uri: uri.href,
263
- mimeType: 'text/markdown',
264
- text,
265
- },
266
- ],
267
- };
268
- }
269
4
  export function registerInstructionResource(server, instructions, iconInfo) {
270
5
  server.registerResource('fetch-url-mcp-instructions', 'internal://instructions', {
271
6
  title: 'Server Instructions',
@@ -286,48 +21,22 @@ export function registerInstructionResource(server, instructions, iconInfo) {
286
21
  ],
287
22
  }));
288
23
  }
289
- export function registerCacheResourceTemplate(server, iconInfo) {
290
- const template = new ResourceTemplate(CACHE_RESOURCE_TEMPLATE_URI, {
291
- list: () => listCacheResourcesForScope(getServerCacheScopeId(server)),
292
- complete: {
293
- namespace: (value) => completeCacheNamespaces(value, getServerCacheScopeId(server)),
294
- hash: (value, context) => completeCacheHashes(value, getServerCacheScopeId(server), context),
295
- },
296
- });
297
- server.registerResource('fetch-url-mcp-cache-entry', template, {
298
- title: 'Cached Fetch Output',
299
- description: 'Read cached markdown generated by previous fetch-url calls.',
300
- mimeType: 'text/markdown',
301
- annotations: {
302
- audience: ['assistant'],
303
- priority: 0.6,
304
- },
305
- ...buildOptionalIcons(iconInfo),
306
- }, (uri, variables) => readCacheResourceForScope(uri, normalizeTemplateVariables(variables), getServerCacheScopeId(server)));
307
- registerCacheResourceNotifications(server);
308
- }
309
24
  export function buildServerInstructions() {
310
25
  const maxHtmlSizeMb = config.constants.maxHtmlBytes / 1024 / 1024;
311
- const cacheSizeMb = config.cache.maxSizeBytes / 1024 / 1024;
312
- const cacheTtlHours = config.cache.ttl / 3600;
313
26
  return `# Fetch public webpages and return clean, readable Markdown.
314
27
 
315
28
  # Capabilities
316
29
  - Tool: \`${FETCH_URL_TOOL_NAME}\` (fetch URL, return Markdown)
317
30
  - Resource: \`internal://instructions\` (this document)
318
- - Resource template: \`internal://cache/{namespace}/{hash}\` (cached Markdown)
319
31
  - Prompt: \`get-help\` (returns these instructions)
320
- - Completions: resource-template argument completion for cache entries
321
32
 
322
33
  # Workflows
323
34
  1. Standard: Call \`${FETCH_URL_TOOL_NAME}\` → read \`markdown\`. \`truncated: true\` means content was cut at server size limit.
324
- 2. Fresh: \`forceRefresh: true\` bypasses cache (does not fix truncation).
325
- 3. Async: \`task: { ttl: <ms> }\` in \`tools/call\` → poll \`tasks/get\` → \`tasks/result\`.
35
+ 2. Async: \`task: { ttl: <ms> }\` in \`tools/call\` poll \`tasks/get\` → \`tasks/result\`.
326
36
 
327
37
  # Constraints
328
38
  - Blocked URLs: localhost, private IPs (10.x, 172.16-31.x, 192.168.x), metadata (169.254.169.254), .local/.internal.
329
39
  - Max HTML: ${maxHtmlSizeMb}MB. Max redirects: ${config.fetcher.maxRedirects}.
330
- - Cache: ${config.cache.maxKeys} entries, ${cacheSizeMb}MB, ${cacheTtlHours}h TTL. Process-local, ephemeral.
331
40
  - No JS rendering — client-side pages may be incomplete.
332
41
  - Binary: not supported.
333
42
  - Batch JSON-RPC (\`[{...}]\`): rejected with HTTP 400.
@@ -343,7 +52,7 @@ export function buildServerInstructions() {
343
52
  - queue_full: worker pool busy. Wait and retry, or use task mode.`;
344
53
  }
345
54
  export function registerGetHelpPrompt(server, instructions, iconInfo) {
346
- const description = 'Return Fetch URL server instructions: workflows, cache usage, task mode, and error handling.';
55
+ const description = 'Return Fetch URL server instructions: workflows, task mode, and error handling.';
347
56
  server.registerPrompt('get-help', {
348
57
  title: 'Get Help',
349
58
  description,
package/dist/schemas.d.ts CHANGED
@@ -3,16 +3,8 @@ import type { ExtractedMetadata } from './transform/types.js';
3
3
  export declare const extractedMetadataSchema: z.ZodType<ExtractedMetadata>;
4
4
  export declare function normalizeExtractedMetadata(value: unknown): ExtractedMetadata | undefined;
5
5
  export declare function normalizePageTitle(value: unknown): string | undefined;
6
- export interface CachedPayload {
7
- markdown: string;
8
- title?: string | undefined;
9
- metadata?: ExtractedMetadata | undefined;
10
- truncated?: boolean | undefined;
11
- }
12
- export declare const cachedPayloadValueSchema: z.ZodType<CachedPayload>;
13
6
  export declare const fetchUrlInputSchema: z.ZodObject<{
14
7
  url: z.ZodURL;
15
- forceRefresh: z.ZodOptional<z.ZodBoolean>;
16
8
  }, z.core.$strict>;
17
9
  export declare const fetchUrlOutputSchema: z.ZodObject<{
18
10
  url: z.ZodURL;
@@ -22,14 +14,8 @@ export declare const fetchUrlOutputSchema: z.ZodObject<{
22
14
  title: z.ZodOptional<z.ZodString>;
23
15
  metadata: z.ZodOptional<z.ZodType<ExtractedMetadata, unknown, z.core.$ZodTypeInternals<ExtractedMetadata, unknown>>>;
24
16
  markdown: z.ZodOptional<z.ZodString>;
25
- fromCache: z.ZodOptional<z.ZodBoolean>;
26
17
  fetchedAt: z.ZodOptional<z.ZodISODateTime>;
27
18
  contentSize: z.ZodOptional<z.ZodNumber>;
28
19
  truncated: z.ZodOptional<z.ZodBoolean>;
29
20
  }, z.core.$strict>;
30
- export declare function parseCachedPayload(raw: string): CachedPayload | null;
31
- export declare function stringifyCachedPayload(payload: z.input<typeof cachedPayloadValueSchema>): string;
32
- export declare function resolveCachedPayloadContent(payload: {
33
- markdown?: string | null;
34
- }): string | null;
35
21
  //# sourceMappingURL=schemas.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAoD9D,eAAO,MAAM,uBAAuB,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAG9D,CAAC;AAgBJ,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,OAAO,GACb,iBAAiB,GAAG,SAAS,CAQ/B;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAErE;AAUD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,QAAQ,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACzC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACjC;AAED,eAAO,MAAM,wBAAwB,EAAE,CAAC,CAAC,OAAO,CAAC,aAAa,CAM1D,CAAC;AAwCL,eAAO,MAAM,mBAAmB;;;kBAkB/B,CAAC;AAEF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;kBAqC/B,CAAC;AAEH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAkBpE;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,GAChD,MAAM,CAER;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE;IACnD,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,GAAG,MAAM,GAAG,IAAI,CAEhB"}
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAoD9D,eAAO,MAAM,uBAAuB,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAG9D,CAAC;AAgBJ,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,OAAO,GACb,iBAAiB,GAAG,SAAS,CAQ/B;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAErE;AAED,eAAO,MAAM,mBAAmB;;kBAc/B,CAAC;AAEF,eAAO,MAAM,oBAAoB;;;;;;;;;;;kBAoC/B,CAAC"}
package/dist/schemas.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { config, logWarn } from './lib/core.js';
2
+ import { config } from './lib/core.js';
3
3
  const URL_FIELD_MAX_LENGTH = 2048;
4
4
  const METADATA_LIMITS = {
5
5
  title: 512,
@@ -49,61 +49,12 @@ export function normalizeExtractedMetadata(value) {
49
49
  export function normalizePageTitle(value) {
50
50
  return normalizeWithSchema(pageTitleSchema, value);
51
51
  }
52
- function normalizeString(value) {
53
- return typeof value === 'string' ? value : undefined;
54
- }
55
- function normalizeBoolean(value) {
56
- return typeof value === 'boolean' ? value : undefined;
57
- }
58
- export const cachedPayloadValueSchema = z.strictObject({
59
- markdown: z.string(),
60
- title: pageTitleSchema.optional(),
61
- metadata: extractedMetadataSchema.optional(),
62
- truncated: z.boolean().optional(),
63
- });
64
- const cachedPayloadCompatSchema = z.object({
65
- markdown: z.unknown().transform((value) => normalizeString(value)),
66
- title: z
67
- .unknown()
68
- .transform((value) => normalizePageTitle(value))
69
- .optional(),
70
- metadata: z
71
- .unknown()
72
- .transform((value) => normalizeExtractedMetadata(value))
73
- .optional(),
74
- truncated: z
75
- .unknown()
76
- .transform((value) => normalizeBoolean(value))
77
- .optional(),
78
- });
79
- const cachedPayloadSchema = cachedPayloadCompatSchema
80
- .superRefine((value, ctx) => {
81
- if (typeof value.markdown === 'string')
82
- return;
83
- ctx.addIssue({
84
- code: 'custom',
85
- message: 'Missing markdown',
86
- path: ['markdown'],
87
- });
88
- })
89
- .transform((value) => cachedPayloadValueSchema.parse({
90
- markdown: value.markdown,
91
- ...(value.title !== undefined ? { title: value.title } : {}),
92
- ...(value.metadata ? { metadata: value.metadata } : {}),
93
- ...(value.truncated !== undefined
94
- ? { truncated: value.truncated }
95
- : {}),
96
- }));
97
52
  export const fetchUrlInputSchema = z.strictObject({
98
53
  url: z
99
54
  .httpUrl('Expected HTTP or HTTPS URL')
100
55
  .min(1, 'URL required')
101
56
  .max(config.constants.maxUrlLength, `URL exceeds ${config.constants.maxUrlLength} chars`)
102
57
  .describe(`Target URL. Max ${config.constants.maxUrlLength} chars. Example: https://example.com`),
103
- forceRefresh: z
104
- .boolean('Expected boolean')
105
- .optional()
106
- .describe('Bypass cache and fetch fresh content.'),
107
58
  }, 'Invalid input');
108
59
  export const fetchUrlOutputSchema = z.strictObject({
109
60
  url: z.httpUrl().max(config.constants.maxUrlLength).describe('Fetched URL.'),
@@ -131,7 +82,6 @@ export const fetchUrlOutputSchema = z.strictObject({
131
82
  : z.string())
132
83
  .optional()
133
84
  .describe('Extracted Markdown. May be truncated (check truncated field).'),
134
- fromCache: z.boolean().optional().describe('True if served from cache.'),
135
85
  fetchedAt: z.iso.datetime().optional().describe('ISO timestamp of fetch.'),
136
86
  contentSize: z
137
87
  .number()
@@ -142,29 +92,3 @@ export const fetchUrlOutputSchema = z.strictObject({
142
92
  .describe('Markdown fragment size before final inline truncation.'),
143
93
  truncated: z.boolean().optional().describe('True if markdown was truncated.'),
144
94
  });
145
- export function parseCachedPayload(raw) {
146
- try {
147
- const parsed = JSON.parse(raw);
148
- const result = cachedPayloadSchema.safeParse(parsed);
149
- if (!result.success) {
150
- logWarn('Rejected invalid cached payload', {
151
- issues: result.error.issues.map((issue) => ({
152
- path: issue.path,
153
- message: issue.message,
154
- code: issue.code,
155
- })),
156
- });
157
- return null;
158
- }
159
- return result.data;
160
- }
161
- catch {
162
- return null;
163
- }
164
- }
165
- export function stringifyCachedPayload(payload) {
166
- return JSON.stringify(cachedPayloadValueSchema.parse(payload));
167
- }
168
- export function resolveCachedPayloadContent(payload) {
169
- return typeof payload.markdown === 'string' ? payload.markdown : null;
170
- }
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA+GpE,wBAAsB,eAAe,IAAI,OAAO,CAAC,SAAS,CAAC,CAE1D;AAmDD,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,SAAS,CAAC,CAExE;AA4FD,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAMtD"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA8GpE,wBAAsB,eAAe,IAAI,OAAO,CAAC,SAAS,CAAC,CAE1D;AAkDD,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,SAAS,CAAC,CAExE;AA4FD,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAMtD"}
package/dist/server.js CHANGED
@@ -7,7 +7,7 @@ import { config } from './lib/core.js';
7
7
  import { getSessionId, logError, logInfo, setLogLevel, setMcpServer, } from './lib/core.js';
8
8
  import { setTaskToolCallCapability } from './lib/mcp-interop.js';
9
9
  import { toError } from './lib/utils.js';
10
- import { buildServerInstructions, registerCacheResourceTemplate, registerGetHelpPrompt, registerInstructionResource, } from './resources/index.js';
10
+ import { buildServerInstructions, registerGetHelpPrompt, registerInstructionResource, } from './resources/index.js';
11
11
  import { abortAllTaskExecutions, registerTaskHandlers, } from './tasks/handlers.js';
12
12
  import { registerTools as registerFetchUrlTool } from './tools/fetch-url.js';
13
13
  import { shutdownTransformWorkerPool } from './transform/transform.js';
@@ -89,7 +89,6 @@ async function createMcpServerWithOptions(options) {
89
89
  const toolControls = registerFetchUrlTool(server);
90
90
  registerGetHelpPrompt(server, serverInstructions, localIcon);
91
91
  registerInstructionResource(server, serverInstructions, localIcon);
92
- registerCacheResourceTemplate(server, localIcon);
93
92
  const taskRegistration = registerTaskHandlers(server, {
94
93
  requireInterception: config.tasks.requireInterception,
95
94
  });
@@ -18,13 +18,11 @@ export declare function getFetchCompletionStatusMessage(result: ServerResult): s
18
18
  export declare class FetchUrlProgressPlan {
19
19
  private readonly reporter;
20
20
  private readonly context;
21
- private cacheStatus;
22
21
  constructor(reporter: ProgressReporter, context: string);
23
22
  reportStart(): void;
24
23
  reportStage(stage: SharedFetchStage): void;
25
24
  reportSuccess(contentSize: number): void;
26
25
  reportFailure(cancelled: boolean): void;
27
- private updateCacheStatus;
28
26
  private mapStage;
29
27
  }
30
28
  export declare function fetchUrlToolHandler(input: FetchUrlInput, extra?: ToolHandlerExtra): Promise<ToolResponseBase>;
@@ -1 +1 @@
1
- {"version":3,"file":"fetch-url.d.ts","sourceRoot":"","sources":["../../src/tools/fetch-url.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EACV,YAAY,EAEb,MAAM,oCAAoC,CAAC;AAE5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAc7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACtB,MAAM,uBAAuB,CAAC;AAU/B,OAAO,EACL,mBAAmB,EAIpB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAIL,KAAK,sBAAsB,EAE5B,MAAM,sBAAsB,CAAC;AAE9B,KAAK,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEzD,UAAU,gBAAgB;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACxD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAwI/C,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,YAAY,GACnB,MAAM,GAAG,SAAS,CAUpB;AAED,qBAAa,oBAAoB;IAI7B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJ1B,OAAO,CAAC,WAAW,CAA0B;gBAG1B,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,MAAM;IAGlC,WAAW,IAAI,IAAI;IAInB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAO1C,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAIxC,aAAa,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI;IAIvC,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,QAAQ;CAmCjB;AAuED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,aAAa,EACpB,KAAK,CAAC,EAAE,gBAAgB,GACvB,OAAO,CAAC,gBAAgB,CAAC,CAQ3B;AAqBD,MAAM,WAAW,wBAAwB;IACvC,cAAc,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;CAC3D;AAqBD,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,wBAAwB,CAoCzE"}
1
+ {"version":3,"file":"fetch-url.d.ts","sourceRoot":"","sources":["../../src/tools/fetch-url.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EACV,YAAY,EAEb,MAAM,oCAAoC,CAAC;AAE5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAY7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACtB,MAAM,uBAAuB,CAAC;AAU/B,OAAO,EACL,mBAAmB,EAIpB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAIL,KAAK,sBAAsB,EAE5B,MAAM,sBAAsB,CAAC;AAE9B,KAAK,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEzD,UAAU,gBAAgB;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACxD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAoI/C,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,YAAY,GACnB,MAAM,GAAG,SAAS,CAUpB;AAED,qBAAa,oBAAoB;IAE7B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,MAAM;IAGlC,WAAW,IAAI,IAAI;IAInB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAM1C,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAIxC,aAAa,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI;IAIvC,OAAO,CAAC,QAAQ;CAqBjB;AAmED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,aAAa,EACpB,KAAK,CAAC,EAAE,gBAAgB,GACvB,OAAO,CAAC,gBAAgB,CAAC,CAQ3B;AAqBD,MAAM,WAAW,wBAAwB;IACvC,cAAc,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;CAC3D;AAqBD,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,wBAAwB,CAoCzE"}
@@ -1,6 +1,6 @@
1
1
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
2
  import { config, logDebug, logError, logWarn } from '../lib/core.js';
3
- import { finalizeInlineMarkdown, markdownTransform, parseCachedMarkdownResult, performSharedFetch, serializeMarkdownResult, withSignal, } from '../lib/fetch-pipeline.js';
3
+ import { finalizeInlineMarkdown, markdownTransform, performSharedFetch, withSignal, } from '../lib/fetch-pipeline.js';
4
4
  import { createProgressReporter, handleToolError, } from '../lib/mcp-interop.js';
5
5
  import { composeAbortSignal, isAbortError, isObject, parseUrlOrNull, toError, } from '../lib/utils.js';
6
6
  import { formatZodError } from '../lib/zod.js';
@@ -57,7 +57,6 @@ function buildStructuredContent(pipeline, inlineResult, inputUrl) {
57
57
  ...(title ? { title } : {}),
58
58
  ...(metadata ? { metadata } : {}),
59
59
  markdown,
60
- fromCache: pipeline.fromCache,
61
60
  fetchedAt: pipeline.fetchedAt,
62
61
  contentSize: inlineResult.contentSize,
63
62
  ...(truncated ? { truncated: true } : {}),
@@ -88,12 +87,11 @@ function buildResponse(pipeline, inlineResult, inputUrl) {
88
87
  const Step = {
89
88
  START: 1,
90
89
  RESOLVE_URL: 2,
91
- CHECK_CACHE: 3,
92
- CACHE_OR_FETCH: 4,
93
- RESTORE_OR_RESPONSE: 5,
94
- TRANSFORM: 6,
95
- PREPARE: 7,
96
- DONE: 8,
90
+ FETCH: 3,
91
+ RESPONSE: 4,
92
+ TRANSFORM: 5,
93
+ PREPARE: 6,
94
+ DONE: 7,
97
95
  };
98
96
  function formatContentSize(contentSize) {
99
97
  if (contentSize < 1000)
@@ -119,7 +117,6 @@ export function getFetchCompletionStatusMessage(result) {
119
117
  export class FetchUrlProgressPlan {
120
118
  reporter;
121
119
  context;
122
- cacheStatus = 'unknown';
123
120
  constructor(reporter, context) {
124
121
  this.reporter = reporter;
125
122
  this.context = context;
@@ -128,7 +125,6 @@ export class FetchUrlProgressPlan {
128
125
  this.reporter.report(Step.START, 'Preparing request');
129
126
  }
130
127
  reportStage(stage) {
131
- this.updateCacheStatus(stage);
132
128
  const mapped = this.mapStage(stage);
133
129
  if (!mapped)
134
130
  return;
@@ -140,47 +136,23 @@ export class FetchUrlProgressPlan {
140
136
  reportFailure(cancelled) {
141
137
  this.reporter.report(Step.DONE, cancelled ? 'Cancelled' : 'Failed');
142
138
  }
143
- updateCacheStatus(stage) {
144
- if (stage === 'cache_hit' || stage === 'cache_restore') {
145
- this.cacheStatus = 'cache_hit';
146
- }
147
- else if (stage === 'fetch_remote' ||
148
- stage === 'response_ready' ||
149
- stage === 'transform_start') {
150
- this.cacheStatus = 'cache_miss';
151
- }
152
- }
153
139
  mapStage(stage) {
154
140
  switch (stage) {
155
141
  case 'resolve_url':
156
142
  return { step: Step.RESOLVE_URL, message: 'Resolving URL' };
157
- case 'check_cache':
158
- return { step: Step.CHECK_CACHE, message: 'Checking cache' };
159
- case 'cache_hit':
160
- return { step: Step.CACHE_OR_FETCH, message: 'Loaded from cache' };
161
- case 'cache_restore':
162
- return {
163
- step: Step.RESTORE_OR_RESPONSE,
164
- message: 'Restoring cached content',
165
- };
166
143
  case 'fetch_remote':
167
144
  return {
168
- step: Step.CACHE_OR_FETCH,
145
+ step: Step.FETCH,
169
146
  message: `Fetching ${this.context}`,
170
147
  };
171
148
  case 'response_ready':
172
- return { step: Step.RESTORE_OR_RESPONSE, message: 'Received response' };
149
+ return { step: Step.RESPONSE, message: 'Received response' };
173
150
  case 'transform_start':
174
151
  return { step: Step.TRANSFORM, message: 'Parsing HTML -> Markdown' };
175
152
  case 'prepare_output':
176
- return {
177
- step: this.cacheStatus === 'cache_miss' ? Step.PREPARE : Step.TRANSFORM,
178
- message: 'Fetch completed',
179
- };
153
+ return { step: Step.PREPARE, message: 'Fetch completed' };
180
154
  case 'finalize_output':
181
- if (this.cacheStatus === 'cache_miss')
182
- return undefined;
183
- return { step: Step.PREPARE, message: 'Finalizing output' };
155
+ return undefined;
184
156
  }
185
157
  }
186
158
  }
@@ -195,19 +167,16 @@ function buildToolAbortSignal(extraSignal) {
195
167
  }
196
168
  return signal;
197
169
  }
198
- function buildFetchOptions(url, signal, progressPlan, forceRefresh) {
170
+ function buildFetchOptions(url, signal, progressPlan) {
199
171
  return {
200
172
  url,
201
173
  ...withSignal(signal),
202
- ...(forceRefresh ? { forceRefresh: true } : {}),
203
174
  onStage: (stage) => {
204
175
  progressPlan.reportStage(stage);
205
176
  },
206
177
  transform: async ({ buffer, encoding, truncated }, normalizedUrl) => {
207
178
  return markdownTransform({ buffer, encoding, ...(truncated ? { truncated } : {}) }, normalizedUrl, signal);
208
179
  },
209
- serialize: serializeMarkdownResult,
210
- deserialize: parseCachedMarkdownResult,
211
180
  };
212
181
  }
213
182
  async function executeFetch(input, extra) {
@@ -217,7 +186,7 @@ async function executeFetch(input, extra) {
217
186
  logDebug('Fetching URL', { url });
218
187
  try {
219
188
  progressPlan.reportStart();
220
- const { pipeline, inlineResult } = await performSharedFetch(buildFetchOptions(url, signal, progressPlan, input.forceRefresh));
189
+ const { pipeline, inlineResult } = await performSharedFetch(buildFetchOptions(url, signal, progressPlan));
221
190
  progressPlan.reportSuccess(inlineResult.contentSize);
222
191
  return buildResponse(pipeline, inlineResult, url);
223
192
  }