@j0hanz/fetch-url-mcp 1.12.13 → 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 -809
- 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/tools/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ProtocolErrorCode, } from '@modelcontextprotocol/server';
|
|
2
2
|
import { config } from '../lib/config.js';
|
|
3
|
-
import { Loggers, logInfo } from '../lib/core.js';
|
|
4
|
-
import { classifyAndLogToolError, isAbortError } from '../lib/error/index.js';
|
|
5
|
-
import {
|
|
3
|
+
import { logError, Loggers, logInfo } from '../lib/core.js';
|
|
4
|
+
import { classifyAndLogToolError, FetchError, getErrorMessage, handleToolError, isAbortError, } from '../lib/error/index.js';
|
|
5
|
+
import { createProgressReporter, createProtocolError, validateOrThrow, } from '../lib/mcp-interop.js';
|
|
6
6
|
import { finalizeInlineMarkdown, markdownTransform, performSharedFetch, withSignal, } from '../lib/net/index.js';
|
|
7
7
|
import { composeAbortSignal, isObject } from '../lib/utils.js';
|
|
8
8
|
import { fetchUrlInputSchema, fetchUrlOutputSchema, normalizeExtractedMetadata, normalizePageTitle, } from '../schemas.js';
|
|
9
|
-
import {
|
|
9
|
+
import { attachAbortController, detachAbortController, withRelatedTaskMeta, } from '../tasks/index.js';
|
|
10
10
|
export const FETCH_URL_TOOL_NAME = 'fetch-url';
|
|
11
|
-
const FETCH_URL_TOOL_DESCRIPTION = `
|
|
12
|
-
Fetch public webpages and convert HTML to clean Markdown.
|
|
11
|
+
const FETCH_URL_TOOL_DESCRIPTION = `
|
|
12
|
+
Fetch public webpages and convert HTML to clean Markdown.
|
|
13
13
|
`.trim();
|
|
14
14
|
const TOOL_ICON = {
|
|
15
15
|
src: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMjEgMTV2NGEyIDIgMCAwIDEtMiAySDVhMiAyIDAgMCAxLTItMnYtNCIvPjxwb2x5bGluZSBwb2ludHM9IjcgMTAgMTIgMTUgMTcgMTAiLz48bGluZSB4MT0iMTIiIHkxPSIxNSIgeDI9IjEyIiB5Mj0iMyIvPjwvc3ZnPg==',
|
|
@@ -63,7 +63,7 @@ function buildStructuredContent(pipeline, inlineResult, inputUrl) {
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
function validateStructuredContent(structuredContent) {
|
|
66
|
-
validateOrThrow(fetchUrlOutputSchema, structuredContent,
|
|
66
|
+
validateOrThrow(fetchUrlOutputSchema, structuredContent, ProtocolErrorCode.InternalError, 'Output validation failed', Loggers.LOG_FETCH_URL);
|
|
67
67
|
}
|
|
68
68
|
export function buildFetchUrlContentBlocks(structuredContent) {
|
|
69
69
|
const markdown = typeof structuredContent['markdown'] === 'string'
|
|
@@ -92,14 +92,16 @@ const Step = {
|
|
|
92
92
|
DONE: 7,
|
|
93
93
|
};
|
|
94
94
|
function formatContentSize(contentSize) {
|
|
95
|
-
if (contentSize <
|
|
96
|
-
return `${contentSize}
|
|
97
|
-
if (contentSize <
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
if (contentSize < 1024)
|
|
96
|
+
return `${contentSize} B`;
|
|
97
|
+
if (contentSize < 1_048_576) {
|
|
98
|
+
const kb = contentSize / 1024;
|
|
99
|
+
return kb < 10 ? `${kb.toFixed(1)} KB` : `${kb.toFixed(0)} KB`;
|
|
100
|
+
}
|
|
101
|
+
return `${(contentSize / 1_048_576).toFixed(1)} MB`;
|
|
100
102
|
}
|
|
101
103
|
function buildFetchSuccessSummary(contentSize) {
|
|
102
|
-
return `
|
|
104
|
+
return `Fetch completed \u2022 ${formatContentSize(contentSize)}`;
|
|
103
105
|
}
|
|
104
106
|
export function getFetchCompletionStatusMessage(result) {
|
|
105
107
|
if (!isObject(result))
|
|
@@ -115,25 +117,59 @@ export function getFetchCompletionStatusMessage(result) {
|
|
|
115
117
|
export class FetchUrlProgressPlan {
|
|
116
118
|
reporter;
|
|
117
119
|
context;
|
|
120
|
+
onStatusMessage;
|
|
118
121
|
total = Step.DONE;
|
|
119
|
-
constructor(reporter, context) {
|
|
122
|
+
constructor(reporter, context, onStatusMessage) {
|
|
120
123
|
this.reporter = reporter;
|
|
121
124
|
this.context = context;
|
|
125
|
+
this.onStatusMessage = onStatusMessage;
|
|
122
126
|
}
|
|
123
127
|
reportStart() {
|
|
124
|
-
|
|
128
|
+
const message = 'Preparing';
|
|
129
|
+
this.onStatusMessage?.(message);
|
|
130
|
+
this.reporter.report({
|
|
131
|
+
progress: Step.START,
|
|
132
|
+
message,
|
|
133
|
+
total: this.total,
|
|
134
|
+
});
|
|
125
135
|
}
|
|
126
136
|
reportStage(stage) {
|
|
127
137
|
const mapped = this.mapStage(stage);
|
|
128
138
|
if (!mapped)
|
|
129
139
|
return;
|
|
130
|
-
this.
|
|
140
|
+
this.onStatusMessage?.(mapped.message);
|
|
141
|
+
this.reporter.report({
|
|
142
|
+
progress: mapped.step,
|
|
143
|
+
message: mapped.message,
|
|
144
|
+
total: this.total,
|
|
145
|
+
});
|
|
131
146
|
}
|
|
132
147
|
reportSuccess(contentSize) {
|
|
133
|
-
|
|
148
|
+
const message = buildFetchSuccessSummary(contentSize);
|
|
149
|
+
this.onStatusMessage?.(message);
|
|
150
|
+
this.reporter.report({
|
|
151
|
+
progress: Step.DONE,
|
|
152
|
+
message,
|
|
153
|
+
total: this.total,
|
|
154
|
+
});
|
|
134
155
|
}
|
|
135
|
-
reportFailure(cancelled) {
|
|
136
|
-
|
|
156
|
+
reportFailure(cancelled, error) {
|
|
157
|
+
let message;
|
|
158
|
+
if (cancelled) {
|
|
159
|
+
message = 'Cancelled';
|
|
160
|
+
}
|
|
161
|
+
else if (error instanceof FetchError && error.statusCode > 0) {
|
|
162
|
+
message = `Fetch failed \u2022 ${String(error.statusCode)}`;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
message = 'Fetch failed';
|
|
166
|
+
}
|
|
167
|
+
this.onStatusMessage?.(message);
|
|
168
|
+
this.reporter.report({
|
|
169
|
+
progress: Step.DONE,
|
|
170
|
+
message,
|
|
171
|
+
total: this.total,
|
|
172
|
+
});
|
|
137
173
|
}
|
|
138
174
|
mapStage(stage) {
|
|
139
175
|
switch (stage) {
|
|
@@ -150,20 +186,23 @@ export class FetchUrlProgressPlan {
|
|
|
150
186
|
case 'response_ready':
|
|
151
187
|
return {
|
|
152
188
|
step: Step.RESPONSE,
|
|
153
|
-
message: '
|
|
189
|
+
message: 'Processing response',
|
|
154
190
|
};
|
|
155
191
|
case 'transform_start':
|
|
156
192
|
return {
|
|
157
193
|
step: Step.TRANSFORM,
|
|
158
|
-
message: '
|
|
194
|
+
message: 'Converting to Markdown',
|
|
159
195
|
};
|
|
160
196
|
case 'prepare_output':
|
|
161
197
|
return {
|
|
162
198
|
step: Step.PREPARE,
|
|
163
|
-
message: '
|
|
199
|
+
message: 'Finalizing',
|
|
164
200
|
};
|
|
165
201
|
case 'finalize_output':
|
|
166
|
-
return
|
|
202
|
+
return {
|
|
203
|
+
step: Step.DONE,
|
|
204
|
+
message: 'Fetch completed',
|
|
205
|
+
};
|
|
167
206
|
}
|
|
168
207
|
}
|
|
169
208
|
}
|
|
@@ -174,7 +213,7 @@ function buildToolAbortSignal(extraSignal) {
|
|
|
174
213
|
const timeout = config.tools.timeoutMs > 0 ? config.tools.timeoutMs : HARD_TOOL_TIMEOUT_MS;
|
|
175
214
|
const signal = composeAbortSignal(extraSignal, timeout);
|
|
176
215
|
if (!signal) {
|
|
177
|
-
throw
|
|
216
|
+
throw createProtocolError(ProtocolErrorCode.InternalError, 'Failed to create timeout signal');
|
|
178
217
|
}
|
|
179
218
|
return signal;
|
|
180
219
|
}
|
|
@@ -190,21 +229,35 @@ function buildFetchOptions(url, signal, progressPlan) {
|
|
|
190
229
|
},
|
|
191
230
|
};
|
|
192
231
|
}
|
|
193
|
-
async function
|
|
232
|
+
async function writeRequestScopedLog(ctx, level, message, data) {
|
|
233
|
+
if (!ctx)
|
|
234
|
+
return;
|
|
235
|
+
try {
|
|
236
|
+
await ctx.mcpReq.log(level, { message, ...data }, Loggers.LOG_FETCH_URL);
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
// Request-scoped logging is best-effort; internal logging remains authoritative.
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async function executeFetch(input, ctx, executionSignal, onStatusMessage) {
|
|
194
243
|
const { url } = input;
|
|
195
|
-
const
|
|
244
|
+
const mcpReq = ctx?.mcpReq;
|
|
245
|
+
const signal = buildToolAbortSignal(executionSignal ?? mcpReq?.signal);
|
|
196
246
|
const startedAt = performance.now();
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
const
|
|
247
|
+
const meta = mcpReq?._meta;
|
|
248
|
+
const progressToken = meta?.progressToken;
|
|
249
|
+
const taskId = ctx?.task?.id;
|
|
250
|
+
const progressPlan = new FetchUrlProgressPlan(createProgressReporter(ctx), formatUrlForDisplay(url), onStatusMessage);
|
|
200
251
|
try {
|
|
201
252
|
logInfo('fetch-url started', {
|
|
202
253
|
inputUrl: url,
|
|
203
|
-
hasProgressToken:
|
|
204
|
-
...(
|
|
205
|
-
? { taskId: relatedTask['taskId'] }
|
|
206
|
-
: {}),
|
|
254
|
+
hasProgressToken: progressToken !== undefined,
|
|
255
|
+
...(taskId ? { taskId } : {}),
|
|
207
256
|
}, Loggers.LOG_FETCH_URL);
|
|
257
|
+
await writeRequestScopedLog(ctx, 'info', 'fetch-url started', {
|
|
258
|
+
inputUrl: url,
|
|
259
|
+
...(taskId ? { taskId } : {}),
|
|
260
|
+
});
|
|
208
261
|
progressPlan.reportStart();
|
|
209
262
|
const { pipeline, inlineResult } = await performSharedFetch(buildFetchOptions(url, signal, progressPlan));
|
|
210
263
|
const truncated = inlineResult.truncated ?? pipeline.data.truncated;
|
|
@@ -216,18 +269,30 @@ async function executeFetch(input, extra) {
|
|
|
216
269
|
durationMs: Math.round(performance.now() - startedAt),
|
|
217
270
|
...(truncated ? { truncated: true } : {}),
|
|
218
271
|
}, Loggers.LOG_FETCH_URL);
|
|
272
|
+
await writeRequestScopedLog(ctx, 'info', 'fetch-url completed', {
|
|
273
|
+
inputUrl: url,
|
|
274
|
+
resolvedUrl: pipeline.url,
|
|
275
|
+
contentSize: inlineResult.contentSize,
|
|
276
|
+
...(taskId ? { taskId } : {}),
|
|
277
|
+
...(truncated ? { truncated: true } : {}),
|
|
278
|
+
});
|
|
219
279
|
const response = buildResponse(pipeline, inlineResult, url);
|
|
220
280
|
progressPlan.reportSuccess(inlineResult.contentSize);
|
|
221
281
|
return response;
|
|
222
282
|
}
|
|
223
283
|
catch (error) {
|
|
224
|
-
progressPlan.reportFailure(isAbortError(error));
|
|
284
|
+
progressPlan.reportFailure(isAbortError(error), error);
|
|
285
|
+
await writeRequestScopedLog(ctx, isAbortError(error) ? 'warning' : 'error', 'fetch-url failed', {
|
|
286
|
+
inputUrl: url,
|
|
287
|
+
error: getErrorMessage(error),
|
|
288
|
+
...(taskId ? { taskId } : {}),
|
|
289
|
+
});
|
|
225
290
|
throw error;
|
|
226
291
|
}
|
|
227
292
|
}
|
|
228
|
-
export async function fetchUrlToolHandler(input,
|
|
293
|
+
export async function fetchUrlToolHandler(input, ctx) {
|
|
229
294
|
const startedAt = performance.now();
|
|
230
|
-
return executeFetch(input,
|
|
295
|
+
return executeFetch(input, ctx).catch((error) => {
|
|
231
296
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
232
297
|
return classifyAndLogToolError(error, { url: input.url, durationMs }, Loggers.LOG_FETCH_URL, 'fetch-url', 'Failed to fetch URL');
|
|
233
298
|
});
|
|
@@ -249,42 +314,72 @@ const TOOL_DEFINITION = {
|
|
|
249
314
|
openWorldHint: true,
|
|
250
315
|
},
|
|
251
316
|
};
|
|
252
|
-
function createTaskCapableDescriptor() {
|
|
253
|
-
return {
|
|
254
|
-
name: TOOL_DEFINITION.name,
|
|
255
|
-
parseArguments: (args) => {
|
|
256
|
-
return validateOrThrow(TOOL_DEFINITION.inputSchema, args, ErrorCode.InvalidParams, 'Invalid parameters for fetch-url', Loggers.LOG_FETCH_URL);
|
|
257
|
-
},
|
|
258
|
-
execute: TOOL_DEFINITION.handler,
|
|
259
|
-
getCompletionStatusMessage: getFetchCompletionStatusMessage,
|
|
260
|
-
taskSupport: 'optional',
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
317
|
export function registerTools(server) {
|
|
264
318
|
if (!config.tools.enabled.includes(FETCH_URL_TOOL_NAME)) {
|
|
265
|
-
unregisterTaskCapableTool(server, FETCH_URL_TOOL_NAME);
|
|
266
319
|
return {
|
|
267
320
|
setTaskSupport: () => { },
|
|
268
321
|
};
|
|
269
322
|
}
|
|
270
|
-
const
|
|
271
|
-
registerTaskCapableTool(server, descriptor);
|
|
272
|
-
const registeredTool = server.registerTool(TOOL_DEFINITION.name, {
|
|
323
|
+
const registeredTool = server.experimental.tasks.registerToolTask(TOOL_DEFINITION.name, {
|
|
273
324
|
title: TOOL_DEFINITION.title,
|
|
274
325
|
description: TOOL_DEFINITION.description,
|
|
275
326
|
inputSchema: TOOL_DEFINITION.inputSchema,
|
|
276
327
|
outputSchema: TOOL_DEFINITION.outputSchema,
|
|
277
328
|
annotations: TOOL_DEFINITION.annotations,
|
|
278
329
|
execution: { taskSupport: 'optional' },
|
|
279
|
-
icons: [TOOL_ICON],
|
|
280
|
-
},
|
|
281
|
-
|
|
282
|
-
|
|
330
|
+
_meta: { icons: [TOOL_ICON] },
|
|
331
|
+
}, {
|
|
332
|
+
createTask: async (args, ctx) => {
|
|
333
|
+
const task = await ctx.task.store.createTask(ctx.task.requestedTtl !== undefined
|
|
334
|
+
? { ttl: ctx.task.requestedTtl }
|
|
335
|
+
: {});
|
|
336
|
+
const taskSignal = attachAbortController(task.taskId).signal;
|
|
337
|
+
const updateStatus = (message) => {
|
|
338
|
+
void ctx.task.store.updateTaskStatus(task.taskId, 'working', message);
|
|
339
|
+
};
|
|
340
|
+
// Spin off background execution
|
|
341
|
+
executeFetch(args, ctx, taskSignal, updateStatus)
|
|
342
|
+
.then(async (result) => {
|
|
343
|
+
try {
|
|
344
|
+
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
|
345
|
+
}
|
|
346
|
+
catch (storeError) {
|
|
347
|
+
logError('Failed to store completed task result', {
|
|
348
|
+
taskId: task.taskId,
|
|
349
|
+
error: getErrorMessage(storeError),
|
|
350
|
+
}, Loggers.LOG_TASKS);
|
|
351
|
+
await ctx.task.store.updateTaskStatus(task.taskId, 'failed', 'Failed to store result');
|
|
352
|
+
}
|
|
353
|
+
})
|
|
354
|
+
.catch(async (error) => {
|
|
355
|
+
logError('Background execution crashed', { taskId: task.taskId, error: getErrorMessage(error) }, Loggers.LOG_TASKS);
|
|
356
|
+
const errorResult = handleToolError(error, args.url, 'Background execution failed');
|
|
357
|
+
try {
|
|
358
|
+
await ctx.task.store.storeTaskResult(task.taskId, 'failed', errorResult);
|
|
359
|
+
}
|
|
360
|
+
catch (storeError) {
|
|
361
|
+
logError('Failed to store task error result', {
|
|
362
|
+
taskId: task.taskId,
|
|
363
|
+
error: getErrorMessage(storeError),
|
|
364
|
+
}, Loggers.LOG_TASKS);
|
|
365
|
+
await ctx.task.store.updateTaskStatus(task.taskId, 'failed', getErrorMessage(error));
|
|
366
|
+
}
|
|
367
|
+
})
|
|
368
|
+
.finally(() => {
|
|
369
|
+
detachAbortController(task.taskId);
|
|
370
|
+
});
|
|
371
|
+
return { task };
|
|
372
|
+
},
|
|
373
|
+
getTask: async (_args, ctx) => {
|
|
374
|
+
return ctx.task.store.getTask(ctx.task.id);
|
|
375
|
+
},
|
|
376
|
+
getTaskResult: async (_args, ctx) => {
|
|
377
|
+
const result = (await ctx.task.store.getTaskResult(ctx.task.id));
|
|
378
|
+
return withRelatedTaskMeta(result, ctx.task.id);
|
|
379
|
+
},
|
|
283
380
|
});
|
|
284
381
|
const updateTaskSupport = (support) => {
|
|
285
|
-
setTaskCapableToolSupport(server, FETCH_URL_TOOL_NAME, support);
|
|
286
382
|
registeredTool.execution = { taskSupport: support };
|
|
287
383
|
};
|
|
288
|
-
updateTaskSupport('optional');
|
|
289
384
|
return { setTaskSupport: updateTaskSupport };
|
|
290
385
|
}
|
package/dist/transform/index.js
CHANGED
|
@@ -540,7 +540,7 @@ export function buildMetadataFooter(metadata, fallbackUrl) {
|
|
|
540
540
|
if (parts.length > 0)
|
|
541
541
|
lines.push(` ${parts.join(' | ')}`);
|
|
542
542
|
if (metadata.description)
|
|
543
|
-
lines.push(`
|
|
543
|
+
lines.push(` ${metadata.description}`);
|
|
544
544
|
return lines.join('\n');
|
|
545
545
|
}
|
|
546
546
|
// eslint-disable-next-line sonarjs/slow-regex -- bounded title separator, no user input
|
package/package.json
CHANGED
|
@@ -1,108 +1,110 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@j0hanz/fetch-url-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"mcpName": "io.github.j0hanz/fetch-url-mcp",
|
|
5
|
-
"description": "An MCP server that fetches web pages and converts them to clean, readable Markdown.",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"main": "./dist/index.js",
|
|
8
|
-
"types": "./dist/index.d.ts",
|
|
9
|
-
"bin": {
|
|
10
|
-
"fetch-url-mcp": "dist/index.js"
|
|
11
|
-
},
|
|
12
|
-
"exports": {
|
|
13
|
-
".": {
|
|
14
|
-
"types": "./dist/index.d.ts",
|
|
15
|
-
"default": "./dist/index.js"
|
|
16
|
-
},
|
|
17
|
-
"./package.json": "./package.json"
|
|
18
|
-
},
|
|
19
|
-
"files": [
|
|
20
|
-
"dist",
|
|
21
|
-
"README.md"
|
|
22
|
-
],
|
|
23
|
-
"repository": {
|
|
24
|
-
"type": "git",
|
|
25
|
-
"url": "https://github.com/j0hanz/fetch-url-mcp.git"
|
|
26
|
-
},
|
|
27
|
-
"homepage": "https://github.com/j0hanz/fetch-url-mcp#readme",
|
|
28
|
-
"bugs": {
|
|
29
|
-
"url": "https://github.com/j0hanz/fetch-url-mcp/issues"
|
|
30
|
-
},
|
|
31
|
-
"author": "j0hanz",
|
|
32
|
-
"license": "MIT",
|
|
33
|
-
"keywords": [
|
|
34
|
-
"mcp",
|
|
35
|
-
"mcp-server",
|
|
36
|
-
"model-context-protocol",
|
|
37
|
-
"web-fetching",
|
|
38
|
-
"content-extraction",
|
|
39
|
-
"readability",
|
|
40
|
-
"markdown",
|
|
41
|
-
"html-to-markdown",
|
|
42
|
-
"web-scraper",
|
|
43
|
-
"llm-context",
|
|
44
|
-
"ai-tools",
|
|
45
|
-
"fetch-url-mcp",
|
|
46
|
-
"typescript",
|
|
47
|
-
"nodejs",
|
|
48
|
-
"stdio",
|
|
49
|
-
"streamable-http",
|
|
50
|
-
"structured-output",
|
|
51
|
-
"metadata-extraction",
|
|
52
|
-
"url-rewriting",
|
|
53
|
-
"worker-threads",
|
|
54
|
-
"content-cleanup"
|
|
55
|
-
],
|
|
56
|
-
"scripts": {
|
|
57
|
-
"clean": "node scripts/tasks.mjs clean",
|
|
58
|
-
"build": "node scripts/tasks.mjs build",
|
|
59
|
-
"copy:assets": "node scripts/tasks.mjs copy:assets",
|
|
60
|
-
"prepare": "npm run build",
|
|
61
|
-
"dev": "tsc --watch --preserveWatchOutput",
|
|
62
|
-
"dev:run": "node --env-file=.env --watch dist/index.js",
|
|
63
|
-
"start": "node dist/index.js",
|
|
64
|
-
"format": "prettier --write .",
|
|
65
|
-
"type-check": "node scripts/tasks.mjs type-check",
|
|
66
|
-
"type-check:src": "node node_modules/typescript/bin/tsc -p tsconfig.json --noEmit",
|
|
67
|
-
"type-check:tests": "node node_modules/typescript/bin/tsc -p tsconfig.test.json --noEmit",
|
|
68
|
-
"type-check:diagnostics": "tsc --noEmit --extendedDiagnostics",
|
|
69
|
-
"type-check:trace": "node -e \"require('fs').rmSync('.ts-trace',{recursive:true,force:true})\" && tsc --noEmit --generateTrace .ts-trace",
|
|
70
|
-
"lint": "eslint .",
|
|
71
|
-
"lint:tests": "eslint __tests__",
|
|
72
|
-
"lint:fix": "eslint . --fix",
|
|
73
|
-
"test": "node scripts/tasks.mjs test",
|
|
74
|
-
"test:coverage": "node scripts/tasks.mjs test --coverage",
|
|
75
|
-
"knip": "knip",
|
|
76
|
-
"knip:fix": "knip --fix",
|
|
77
|
-
"inspector": "npm run build && npx -y @modelcontextprotocol/inspector node dist/index.js",
|
|
78
|
-
"prepublishOnly": "npm run lint && npm run type-check && npm run build"
|
|
79
|
-
},
|
|
80
|
-
"dependencies": {
|
|
81
|
-
"@
|
|
82
|
-
"@
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"@
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"eslint
|
|
95
|
-
"eslint-
|
|
96
|
-
"eslint-plugin-
|
|
97
|
-
"eslint-plugin-
|
|
98
|
-
"eslint-plugin-
|
|
99
|
-
"
|
|
100
|
-
"
|
|
101
|
-
"
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@j0hanz/fetch-url-mcp",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"mcpName": "io.github.j0hanz/fetch-url-mcp",
|
|
5
|
+
"description": "An MCP server that fetches web pages and converts them to clean, readable Markdown.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"fetch-url-mcp": "dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/j0hanz/fetch-url-mcp.git"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/j0hanz/fetch-url-mcp#readme",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/j0hanz/fetch-url-mcp/issues"
|
|
30
|
+
},
|
|
31
|
+
"author": "j0hanz",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"keywords": [
|
|
34
|
+
"mcp",
|
|
35
|
+
"mcp-server",
|
|
36
|
+
"model-context-protocol",
|
|
37
|
+
"web-fetching",
|
|
38
|
+
"content-extraction",
|
|
39
|
+
"readability",
|
|
40
|
+
"markdown",
|
|
41
|
+
"html-to-markdown",
|
|
42
|
+
"web-scraper",
|
|
43
|
+
"llm-context",
|
|
44
|
+
"ai-tools",
|
|
45
|
+
"fetch-url-mcp",
|
|
46
|
+
"typescript",
|
|
47
|
+
"nodejs",
|
|
48
|
+
"stdio",
|
|
49
|
+
"streamable-http",
|
|
50
|
+
"structured-output",
|
|
51
|
+
"metadata-extraction",
|
|
52
|
+
"url-rewriting",
|
|
53
|
+
"worker-threads",
|
|
54
|
+
"content-cleanup"
|
|
55
|
+
],
|
|
56
|
+
"scripts": {
|
|
57
|
+
"clean": "node scripts/tasks.mjs clean",
|
|
58
|
+
"build": "node scripts/tasks.mjs build",
|
|
59
|
+
"copy:assets": "node scripts/tasks.mjs copy:assets",
|
|
60
|
+
"prepare": "npm run build",
|
|
61
|
+
"dev": "tsc --watch --preserveWatchOutput",
|
|
62
|
+
"dev:run": "node --env-file=.env --watch dist/index.js",
|
|
63
|
+
"start": "node dist/index.js",
|
|
64
|
+
"format": "prettier --write .",
|
|
65
|
+
"type-check": "node scripts/tasks.mjs type-check",
|
|
66
|
+
"type-check:src": "node node_modules/typescript/bin/tsc -p tsconfig.json --noEmit",
|
|
67
|
+
"type-check:tests": "node node_modules/typescript/bin/tsc -p tsconfig.test.json --noEmit",
|
|
68
|
+
"type-check:diagnostics": "tsc --noEmit --extendedDiagnostics",
|
|
69
|
+
"type-check:trace": "node -e \"require('fs').rmSync('.ts-trace',{recursive:true,force:true})\" && tsc --noEmit --generateTrace .ts-trace",
|
|
70
|
+
"lint": "eslint .",
|
|
71
|
+
"lint:tests": "eslint __tests__",
|
|
72
|
+
"lint:fix": "eslint . --fix",
|
|
73
|
+
"test": "node scripts/tasks.mjs test",
|
|
74
|
+
"test:coverage": "node scripts/tasks.mjs test --coverage",
|
|
75
|
+
"knip": "knip",
|
|
76
|
+
"knip:fix": "knip --fix",
|
|
77
|
+
"inspector": "npm run build && npx -y @modelcontextprotocol/inspector node dist/index.js",
|
|
78
|
+
"prepublishOnly": "npm run lint && npm run type-check && npm run build"
|
|
79
|
+
},
|
|
80
|
+
"dependencies": {
|
|
81
|
+
"@cfworker/json-schema": "^4.1.1",
|
|
82
|
+
"@modelcontextprotocol/node": "^2.0.0-alpha.2",
|
|
83
|
+
"@modelcontextprotocol/server": "^2.0.0-alpha.2",
|
|
84
|
+
"@mozilla/readability": "^0.6.0",
|
|
85
|
+
"linkedom": "^0.18.12",
|
|
86
|
+
"node-html-markdown": "^2.0.0",
|
|
87
|
+
"undici": "^8.0.2",
|
|
88
|
+
"zod": "^4.3.6"
|
|
89
|
+
},
|
|
90
|
+
"devDependencies": {
|
|
91
|
+
"@eslint/js": "^10.0.1",
|
|
92
|
+
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
|
93
|
+
"@types/node": "^24",
|
|
94
|
+
"eslint": "^10.2.0",
|
|
95
|
+
"eslint-config-prettier": "^10.1.8",
|
|
96
|
+
"eslint-plugin-de-morgan": "^2.1.1",
|
|
97
|
+
"eslint-plugin-depend": "^1.5.0",
|
|
98
|
+
"eslint-plugin-sonarjs": "^4.0.2",
|
|
99
|
+
"eslint-plugin-unicorn": "^64.0.0",
|
|
100
|
+
"eslint-plugin-unused-imports": "^4.4.1",
|
|
101
|
+
"knip": "^6.3.1",
|
|
102
|
+
"prettier": "^3.8.2",
|
|
103
|
+
"tsx": "^4.21.0",
|
|
104
|
+
"typescript": "^6.0.2",
|
|
105
|
+
"typescript-eslint": "^8.58.1"
|
|
106
|
+
},
|
|
107
|
+
"engines": {
|
|
108
|
+
"node": ">=24"
|
|
109
|
+
}
|
|
110
|
+
}
|