@bedolla/enriweb 0.1.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 (35) hide show
  1. package/LICENSE +619 -0
  2. package/README.md +182 -0
  3. package/dist/client/EnriProxyClient.d.ts +271 -0
  4. package/dist/client/EnriProxyClient.d.ts.map +1 -0
  5. package/dist/client/EnriProxyClient.js +221 -0
  6. package/dist/client/EnriProxyClient.js.map +1 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +148 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/package-info.d.ts +8 -0
  12. package/dist/package-info.d.ts.map +1 -0
  13. package/dist/package-info.js +28 -0
  14. package/dist/package-info.js.map +1 -0
  15. package/dist/server/EnriWebServer.d.ts +70 -0
  16. package/dist/server/EnriWebServer.d.ts.map +1 -0
  17. package/dist/server/EnriWebServer.js +218 -0
  18. package/dist/server/EnriWebServer.js.map +1 -0
  19. package/dist/shared/validation.d.ts +54 -0
  20. package/dist/shared/validation.d.ts.map +1 -0
  21. package/dist/shared/validation.js +112 -0
  22. package/dist/shared/validation.js.map +1 -0
  23. package/dist/tools/WebFetchTool.d.ts +232 -0
  24. package/dist/tools/WebFetchTool.d.ts.map +1 -0
  25. package/dist/tools/WebFetchTool.js +429 -0
  26. package/dist/tools/WebFetchTool.js.map +1 -0
  27. package/dist/tools/WebSearchRegistryVerifier.d.ts +323 -0
  28. package/dist/tools/WebSearchRegistryVerifier.d.ts.map +1 -0
  29. package/dist/tools/WebSearchRegistryVerifier.js +890 -0
  30. package/dist/tools/WebSearchRegistryVerifier.js.map +1 -0
  31. package/dist/tools/WebSearchTool.d.ts +132 -0
  32. package/dist/tools/WebSearchTool.d.ts.map +1 -0
  33. package/dist/tools/WebSearchTool.js +101 -0
  34. package/dist/tools/WebSearchTool.js.map +1 -0
  35. package/package.json +54 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/shared/validation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,232 @@
1
+ /**
2
+ * WEB FETCH TOOL
3
+ *
4
+ * Implements the `web_fetch` MCP tool by delegating to EnriProxy.
5
+ *
6
+ * @module tools/WebFetchTool
7
+ */
8
+ import type { EnriProxyClient } from "../client/EnriProxyClient.js";
9
+ /**
10
+ * Tool parameters for `web_fetch`.
11
+ */
12
+ export interface WebFetchToolParams {
13
+ /**
14
+ * URL to fetch.
15
+ */
16
+ readonly url?: string;
17
+ /**
18
+ * Cursor identifier returned by a previous call.
19
+ */
20
+ readonly cursor?: string;
21
+ /**
22
+ * Optional prompt for extraction.
23
+ */
24
+ readonly prompt?: string;
25
+ /**
26
+ * Maximum content length in characters.
27
+ */
28
+ readonly maxChars?: number;
29
+ /**
30
+ * Offset in characters for cursor pagination.
31
+ */
32
+ readonly offsetChars?: number;
33
+ /**
34
+ * Limit in characters for cursor pagination.
35
+ */
36
+ readonly limitChars?: number;
37
+ }
38
+ /**
39
+ * Tool result for `web_fetch`.
40
+ */
41
+ export interface WebFetchToolResult extends Record<string, unknown> {
42
+ /**
43
+ * Fetched content.
44
+ */
45
+ readonly content: string;
46
+ /**
47
+ * HTTP status code.
48
+ */
49
+ readonly status: number;
50
+ /**
51
+ * Content type of the response.
52
+ */
53
+ readonly content_type: string;
54
+ /**
55
+ * Whether content was truncated.
56
+ */
57
+ readonly truncated: boolean;
58
+ /**
59
+ * URL that was fetched.
60
+ */
61
+ readonly url: string;
62
+ /**
63
+ * Cursor identifier for pagination (when available).
64
+ */
65
+ readonly cursor?: string;
66
+ /**
67
+ * Offset in characters (cursor reads).
68
+ */
69
+ readonly offset_chars?: number;
70
+ /**
71
+ * Limit in characters (cursor reads).
72
+ */
73
+ readonly limit_chars?: number;
74
+ /**
75
+ * Total captured characters (cursor reads).
76
+ */
77
+ readonly total_chars?: number;
78
+ /**
79
+ * Whether more content exists beyond this slice.
80
+ */
81
+ readonly has_more?: boolean;
82
+ /**
83
+ * Whether content was reduced into an excerpt pack.
84
+ */
85
+ readonly reduced?: boolean;
86
+ /**
87
+ * Whether the upstream fetch was truncated.
88
+ */
89
+ readonly fetched_truncated?: boolean;
90
+ }
91
+ /**
92
+ * Dependencies for {@link WebFetchTool}.
93
+ */
94
+ export interface WebFetchToolDeps {
95
+ /**
96
+ * Creates an EnriProxy client with a base URL, API key, and timeout.
97
+ *
98
+ * @param serverUrl - EnriProxy URL
99
+ * @param apiKey - EnriProxy API key
100
+ * @param timeoutMs - Timeout in ms
101
+ * @returns Client instance
102
+ */
103
+ readonly createClient: (serverUrl: string, apiKey: string, timeoutMs: number) => EnriProxyClient;
104
+ /**
105
+ * Default EnriProxy server URL.
106
+ */
107
+ readonly defaultServerUrl: string;
108
+ /**
109
+ * Default EnriProxy API key.
110
+ */
111
+ readonly defaultApiKey: string;
112
+ /**
113
+ * Default timeout in milliseconds.
114
+ */
115
+ readonly defaultTimeoutMs: number;
116
+ /**
117
+ * Default maximum content length in characters returned by the tool when
118
+ * `max_chars` is not provided.
119
+ */
120
+ readonly defaultMaxChars: number;
121
+ }
122
+ /**
123
+ * MCP tool that fetches URL content via EnriProxy.
124
+ */
125
+ export declare class WebFetchTool {
126
+ /**
127
+ * Readme file candidates commonly used in GitHub repositories.
128
+ */
129
+ private static readonly README_FILENAMES;
130
+ /**
131
+ * Default branches to try when resolving GitHub raw README URLs.
132
+ */
133
+ private static readonly README_BRANCHES;
134
+ /**
135
+ * Tool dependencies.
136
+ */
137
+ private readonly deps;
138
+ /**
139
+ * Creates a new {@link WebFetchTool}.
140
+ *
141
+ * @param deps - Tool dependencies
142
+ */
143
+ constructor(deps: WebFetchToolDeps);
144
+ /**
145
+ * Gets the configured default max chars for web fetch results.
146
+ *
147
+ * @returns Default max chars
148
+ */
149
+ getDefaultMaxChars(): number;
150
+ /**
151
+ * Validates raw MCP tool arguments.
152
+ *
153
+ * @param raw - Raw tool arguments
154
+ * @returns Validated parameters
155
+ */
156
+ parseParams(raw: unknown): WebFetchToolParams;
157
+ /**
158
+ * Executes the web fetch tool.
159
+ *
160
+ * @param params - Validated parameters
161
+ * @returns Tool result
162
+ */
163
+ execute(params: WebFetchToolParams): Promise<WebFetchToolResult>;
164
+ /**
165
+ * Attempts to provide a higher-quality fetch for npm package pages.
166
+ *
167
+ * @param params - Tool parameters
168
+ * @param client - EnriProxy client
169
+ * @param maxChars - Maximum content length to return
170
+ * @returns Tool result if the URL is an npm package page, otherwise null
171
+ */
172
+ private tryExecuteNpmPackageFetch;
173
+ /**
174
+ * Attempts to parse an npm package name from an npmjs.com package page URL.
175
+ *
176
+ * @param url - Parsed URL
177
+ * @returns npm package name (e.g. "chalk" or "@scope/name") or null
178
+ */
179
+ private tryParseNpmPackageName;
180
+ /**
181
+ * Tries to parse a JSON object from a string.
182
+ *
183
+ * @param input - JSON string
184
+ * @returns Parsed object or null
185
+ */
186
+ private tryParseJsonObject;
187
+ /**
188
+ * Extracts a string from an unknown value if possible.
189
+ *
190
+ * @param value - Unknown input
191
+ * @returns Trimmed string or null
192
+ */
193
+ private tryGetString;
194
+ /**
195
+ * Extracts a repository URL from npm metadata.
196
+ *
197
+ * @param repository - Repository field value
198
+ * @returns Normalized URL string or null
199
+ */
200
+ private tryGetRepositoryUrl;
201
+ /**
202
+ * Normalizes common git repository URL schemes into an https URL.
203
+ *
204
+ * @param rawUrl - Raw repository URL from metadata
205
+ * @returns Normalized URL string or null
206
+ */
207
+ private normalizeRepositoryUrl;
208
+ /**
209
+ * Normalizes a GitHub repository URL to the canonical https form.
210
+ *
211
+ * @param repositoryUrl - Repository URL
212
+ * @returns Canonical GitHub repo URL (https://github.com/{owner}/{repo}) or null
213
+ */
214
+ private tryNormalizeGitHubRepoUrl;
215
+ /**
216
+ * Attempts to fetch a GitHub repository README via raw.githubusercontent.com.
217
+ *
218
+ * @param client - EnriProxy client
219
+ * @param githubRepoUrl - Canonical GitHub repo URL
220
+ * @param maxChars - Maximum content length
221
+ * @returns README content if found, otherwise null
222
+ */
223
+ private tryFetchGitHubReadme;
224
+ /**
225
+ * Formats results for MCP text output.
226
+ *
227
+ * @param result - Tool result
228
+ * @returns Formatted text
229
+ */
230
+ formatOutput(result: WebFetchToolResult): string;
231
+ }
232
+ //# sourceMappingURL=WebFetchTool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebFetchTool.d.ts","sourceRoot":"","sources":["../../src/tools/WebFetchTool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,429 @@
1
+ import { assertHttpUrl, assertNonEmptyString, assertObject, optionalInt, optionalString } from "../shared/validation.js";
2
+ /**
3
+ * Default number of characters to include in the human-readable MCP output.
4
+ *
5
+ * @remarks
6
+ * The full fetched payload is still available in `structuredContent.content`,
7
+ * but MCP clients may enforce tool-result token limits. Keeping the human
8
+ * output short avoids duplication and reduces the chance of overflows.
9
+ */
10
+ const DEFAULT_TEXT_PREVIEW_CHARS = 2000;
11
+ /**
12
+ * MCP tool that fetches URL content via EnriProxy.
13
+ */
14
+ export class WebFetchTool {
15
+ /**
16
+ * Readme file candidates commonly used in GitHub repositories.
17
+ */
18
+ static README_FILENAMES = [
19
+ "README.md",
20
+ "readme.md"
21
+ ];
22
+ /**
23
+ * Default branches to try when resolving GitHub raw README URLs.
24
+ */
25
+ static README_BRANCHES = ["main", "master"];
26
+ /**
27
+ * Tool dependencies.
28
+ */
29
+ deps;
30
+ /**
31
+ * Creates a new {@link WebFetchTool}.
32
+ *
33
+ * @param deps - Tool dependencies
34
+ */
35
+ constructor(deps) {
36
+ this.deps = deps;
37
+ }
38
+ /**
39
+ * Gets the configured default max chars for web fetch results.
40
+ *
41
+ * @returns Default max chars
42
+ */
43
+ getDefaultMaxChars() {
44
+ return this.deps.defaultMaxChars;
45
+ }
46
+ /**
47
+ * Validates raw MCP tool arguments.
48
+ *
49
+ * @param raw - Raw tool arguments
50
+ * @returns Validated parameters
51
+ */
52
+ parseParams(raw) {
53
+ const obj = assertObject(raw, "arguments");
54
+ const cursorRaw = optionalString(obj["cursor"]);
55
+ const cursor = cursorRaw?.trim() ? cursorRaw.trim() : undefined;
56
+ const urlRaw = optionalString(obj["url"]);
57
+ const url = urlRaw?.trim() ? assertHttpUrl(urlRaw.trim(), "url") : undefined;
58
+ if (!cursor && !url) {
59
+ throw new Error("web_fetch requires either 'url' or 'cursor'.");
60
+ }
61
+ const prompt = optionalString(obj["prompt"]);
62
+ const maxChars = optionalInt(obj["max_chars"]);
63
+ const offsetCharsRaw = optionalInt(obj["offset_chars"]) ?? optionalInt(obj["offset"]);
64
+ const limitCharsRaw = optionalInt(obj["limit_chars"]) ?? optionalInt(obj["limit"]);
65
+ if (maxChars !== undefined && maxChars < 1) {
66
+ throw new Error("max_chars must be positive.");
67
+ }
68
+ const offsetChars = cursor ? offsetCharsRaw : undefined;
69
+ let limitChars = cursor ? limitCharsRaw : undefined;
70
+ if (offsetChars !== undefined && offsetChars < 0) {
71
+ throw new Error("offset must be non-negative.");
72
+ }
73
+ if (limitChars !== undefined) {
74
+ if (limitChars < 0) {
75
+ throw new Error("limit must be positive.");
76
+ }
77
+ if (limitChars === 0) {
78
+ limitChars = undefined;
79
+ }
80
+ }
81
+ return {
82
+ url,
83
+ cursor,
84
+ prompt,
85
+ maxChars,
86
+ offsetChars,
87
+ limitChars
88
+ };
89
+ }
90
+ /**
91
+ * Executes the web fetch tool.
92
+ *
93
+ * @param params - Validated parameters
94
+ * @returns Tool result
95
+ */
96
+ async execute(params) {
97
+ const serverUrl = assertHttpUrl(this.deps.defaultServerUrl, "ENRIPROXY_URL");
98
+ const apiKey = assertNonEmptyString(this.deps.defaultApiKey, "ENRIPROXY_API_KEY");
99
+ const client = this.deps.createClient(serverUrl, apiKey, this.deps.defaultTimeoutMs);
100
+ const effectiveMaxChars = typeof params.maxChars === "number" ? params.maxChars : this.deps.defaultMaxChars;
101
+ if (typeof params.cursor === "string" && params.cursor.trim()) {
102
+ const response = await client.webFetch({
103
+ cursor: params.cursor.trim(),
104
+ offsetChars: params.offsetChars,
105
+ limitChars: params.limitChars,
106
+ maxChars: effectiveMaxChars
107
+ });
108
+ const resolvedUrl = response.url ?? params.url ?? "(cursor)";
109
+ return {
110
+ content: response.content,
111
+ status: response.status,
112
+ content_type: response.content_type,
113
+ truncated: response.truncated,
114
+ url: resolvedUrl,
115
+ cursor: response.cursor,
116
+ offset_chars: response.offset_chars,
117
+ limit_chars: response.limit_chars,
118
+ total_chars: response.total_chars,
119
+ has_more: response.has_more,
120
+ reduced: response.reduced,
121
+ fetched_truncated: response.fetched_truncated
122
+ };
123
+ }
124
+ if (!params.url) {
125
+ throw new Error("web_fetch requires a URL when cursor is not provided.");
126
+ }
127
+ const url = params.url;
128
+ const urlParams = {
129
+ ...params,
130
+ url
131
+ };
132
+ const npmResult = await this.tryExecuteNpmPackageFetch(urlParams, client, effectiveMaxChars);
133
+ if (npmResult) {
134
+ return npmResult;
135
+ }
136
+ const response = await client.webFetch({
137
+ url,
138
+ prompt: params.prompt,
139
+ maxChars: effectiveMaxChars
140
+ });
141
+ return {
142
+ content: response.content,
143
+ status: response.status,
144
+ content_type: response.content_type,
145
+ truncated: response.truncated,
146
+ url: response.url ?? url,
147
+ cursor: response.cursor,
148
+ total_chars: response.total_chars,
149
+ has_more: response.has_more,
150
+ reduced: response.reduced,
151
+ fetched_truncated: response.fetched_truncated
152
+ };
153
+ }
154
+ /**
155
+ * Attempts to provide a higher-quality fetch for npm package pages.
156
+ *
157
+ * @param params - Tool parameters
158
+ * @param client - EnriProxy client
159
+ * @param maxChars - Maximum content length to return
160
+ * @returns Tool result if the URL is an npm package page, otherwise null
161
+ */
162
+ async tryExecuteNpmPackageFetch(params, client, maxChars) {
163
+ const requestedUrl = new URL(params.url);
164
+ const packageName = this.tryParseNpmPackageName(requestedUrl);
165
+ if (!packageName) {
166
+ return null;
167
+ }
168
+ const metadataUrl = `https://registry.npmjs.org/${packageName}/latest`;
169
+ const metadataResponse = await client.webFetch({
170
+ url: metadataUrl,
171
+ maxChars: Math.min(maxChars, 20000)
172
+ });
173
+ if (metadataResponse.status < 200 || metadataResponse.status >= 300) {
174
+ return null;
175
+ }
176
+ const metadata = this.tryParseJsonObject(metadataResponse.content);
177
+ if (!metadata) {
178
+ return null;
179
+ }
180
+ const name = this.tryGetString(metadata["name"]) ?? packageName;
181
+ const version = this.tryGetString(metadata["version"]);
182
+ const description = this.tryGetString(metadata["description"]);
183
+ const license = this.tryGetString(metadata["license"]);
184
+ const repositoryUrl = this.tryGetRepositoryUrl(metadata["repository"]);
185
+ const homepageUrl = this.tryGetString(metadata["homepage"]);
186
+ let gitHubRepoUrl = null;
187
+ if (repositoryUrl) {
188
+ gitHubRepoUrl = this.tryNormalizeGitHubRepoUrl(repositoryUrl);
189
+ }
190
+ let readmeText = null;
191
+ let readmeTruncated = false;
192
+ if (gitHubRepoUrl) {
193
+ const readmeResult = await this.tryFetchGitHubReadme(client, gitHubRepoUrl, maxChars);
194
+ if (readmeResult) {
195
+ readmeText = readmeResult.content;
196
+ readmeTruncated = readmeResult.truncated;
197
+ }
198
+ }
199
+ const lines = [];
200
+ lines.push(`# ${name}`);
201
+ lines.push("");
202
+ lines.push(`Requested URL: ${params.url}`);
203
+ lines.push("");
204
+ if (description) {
205
+ lines.push(`Description: ${description}`);
206
+ }
207
+ if (version) {
208
+ lines.push(`Latest version: ${version}`);
209
+ }
210
+ if (license) {
211
+ lines.push(`License: ${license}`);
212
+ }
213
+ if (homepageUrl) {
214
+ lines.push(`Homepage: ${homepageUrl}`);
215
+ }
216
+ if (gitHubRepoUrl) {
217
+ lines.push(`Repository: ${gitHubRepoUrl}`);
218
+ }
219
+ else if (repositoryUrl) {
220
+ lines.push(`Repository: ${repositoryUrl}`);
221
+ }
222
+ if (readmeText) {
223
+ lines.push("");
224
+ lines.push("## README");
225
+ lines.push("");
226
+ lines.push(readmeText);
227
+ }
228
+ const combined = lines.join("\n").trim() + "\n";
229
+ const shouldTrim = combined.length > maxChars;
230
+ const content = shouldTrim ? combined.slice(0, maxChars) : combined;
231
+ return {
232
+ content,
233
+ status: 200,
234
+ content_type: "text/markdown",
235
+ truncated: shouldTrim || readmeTruncated || metadataResponse.truncated,
236
+ url: params.url
237
+ };
238
+ }
239
+ /**
240
+ * Attempts to parse an npm package name from an npmjs.com package page URL.
241
+ *
242
+ * @param url - Parsed URL
243
+ * @returns npm package name (e.g. "chalk" or "@scope/name") or null
244
+ */
245
+ tryParseNpmPackageName(url) {
246
+ const hostname = url.hostname.toLowerCase();
247
+ if (hostname !== "www.npmjs.com" && hostname !== "npmjs.com") {
248
+ return null;
249
+ }
250
+ const segments = url.pathname.split("/").filter(Boolean);
251
+ if (segments.length < 2) {
252
+ return null;
253
+ }
254
+ if (segments[0] !== "package") {
255
+ return null;
256
+ }
257
+ const first = segments[1];
258
+ if (!first) {
259
+ return null;
260
+ }
261
+ if (first.startsWith("@")) {
262
+ const second = segments[2];
263
+ if (!second) {
264
+ return null;
265
+ }
266
+ return `${first}/${second}`;
267
+ }
268
+ return first;
269
+ }
270
+ /**
271
+ * Tries to parse a JSON object from a string.
272
+ *
273
+ * @param input - JSON string
274
+ * @returns Parsed object or null
275
+ */
276
+ tryParseJsonObject(input) {
277
+ try {
278
+ const parsed = JSON.parse(input);
279
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
280
+ return null;
281
+ }
282
+ return parsed;
283
+ }
284
+ catch {
285
+ return null;
286
+ }
287
+ }
288
+ /**
289
+ * Extracts a string from an unknown value if possible.
290
+ *
291
+ * @param value - Unknown input
292
+ * @returns Trimmed string or null
293
+ */
294
+ tryGetString(value) {
295
+ if (typeof value !== "string") {
296
+ return null;
297
+ }
298
+ const trimmed = value.trim();
299
+ return trimmed.length > 0 ? trimmed : null;
300
+ }
301
+ /**
302
+ * Extracts a repository URL from npm metadata.
303
+ *
304
+ * @param repository - Repository field value
305
+ * @returns Normalized URL string or null
306
+ */
307
+ tryGetRepositoryUrl(repository) {
308
+ if (typeof repository === "string") {
309
+ return this.normalizeRepositoryUrl(repository);
310
+ }
311
+ if (typeof repository === "object" && repository !== null && !Array.isArray(repository)) {
312
+ const record = repository;
313
+ const rawUrl = this.tryGetString(record["url"]);
314
+ if (!rawUrl) {
315
+ return null;
316
+ }
317
+ return this.normalizeRepositoryUrl(rawUrl);
318
+ }
319
+ return null;
320
+ }
321
+ /**
322
+ * Normalizes common git repository URL schemes into an https URL.
323
+ *
324
+ * @param rawUrl - Raw repository URL from metadata
325
+ * @returns Normalized URL string or null
326
+ */
327
+ normalizeRepositoryUrl(rawUrl) {
328
+ let urlText = rawUrl.trim();
329
+ if (urlText.startsWith("git+")) {
330
+ urlText = urlText.slice("git+".length);
331
+ }
332
+ if (urlText.startsWith("git://")) {
333
+ urlText = `https://${urlText.slice("git://".length)}`;
334
+ }
335
+ if (urlText.endsWith(".git")) {
336
+ urlText = urlText.slice(0, -".git".length);
337
+ }
338
+ try {
339
+ const parsed = new URL(urlText);
340
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
341
+ return null;
342
+ }
343
+ return parsed.toString();
344
+ }
345
+ catch {
346
+ return null;
347
+ }
348
+ }
349
+ /**
350
+ * Normalizes a GitHub repository URL to the canonical https form.
351
+ *
352
+ * @param repositoryUrl - Repository URL
353
+ * @returns Canonical GitHub repo URL (https://github.com/{owner}/{repo}) or null
354
+ */
355
+ tryNormalizeGitHubRepoUrl(repositoryUrl) {
356
+ try {
357
+ const parsed = new URL(repositoryUrl);
358
+ if (parsed.hostname.toLowerCase() !== "github.com") {
359
+ return null;
360
+ }
361
+ const segments = parsed.pathname.split("/").filter(Boolean);
362
+ if (segments.length < 2) {
363
+ return null;
364
+ }
365
+ const owner = segments[0];
366
+ const repo = segments[1];
367
+ if (!owner || !repo) {
368
+ return null;
369
+ }
370
+ return `https://github.com/${owner}/${repo}`;
371
+ }
372
+ catch {
373
+ return null;
374
+ }
375
+ }
376
+ /**
377
+ * Attempts to fetch a GitHub repository README via raw.githubusercontent.com.
378
+ *
379
+ * @param client - EnriProxy client
380
+ * @param githubRepoUrl - Canonical GitHub repo URL
381
+ * @param maxChars - Maximum content length
382
+ * @returns README content if found, otherwise null
383
+ */
384
+ async tryFetchGitHubReadme(client, githubRepoUrl, maxChars) {
385
+ const parsed = new URL(githubRepoUrl);
386
+ const segments = parsed.pathname.split("/").filter(Boolean);
387
+ if (segments.length < 2) {
388
+ return null;
389
+ }
390
+ const owner = segments[0];
391
+ const repo = segments[1];
392
+ if (!owner || !repo) {
393
+ return null;
394
+ }
395
+ for (const branch of WebFetchTool.README_BRANCHES) {
396
+ for (const filename of WebFetchTool.README_FILENAMES) {
397
+ const url = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${filename}`;
398
+ const response = await client.webFetch({
399
+ url,
400
+ maxChars
401
+ });
402
+ if (response.status >= 200 && response.status < 300 && response.content.trim().length > 0) {
403
+ return {
404
+ content: response.content,
405
+ truncated: response.truncated
406
+ };
407
+ }
408
+ }
409
+ }
410
+ return null;
411
+ }
412
+ /**
413
+ * Formats results for MCP text output.
414
+ *
415
+ * @param result - Tool result
416
+ * @returns Formatted text
417
+ */
418
+ formatOutput(result) {
419
+ const truncatedNote = result.truncated ? " [TRUNCATED]" : "";
420
+ const previewChars = Math.min(DEFAULT_TEXT_PREVIEW_CHARS, result.content.length);
421
+ const preview = result.content.slice(0, previewChars);
422
+ const header = `Fetched ${result.url} (${result.content_type}, ${result.content.length} chars)${truncatedNote}.`;
423
+ const previewNote = previewChars < result.content.length
424
+ ? `\n\nPreview (first ${previewChars} chars):\n\n`
425
+ : "\n\nContent:\n\n";
426
+ return header + previewNote + preview;
427
+ }
428
+ }
429
+ //# sourceMappingURL=WebFetchTool.js.map