@amplify-studio/open-mcp 0.9.0 → 0.10.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.
@@ -1,7 +1,6 @@
1
- import { createProxyAgent } from "./proxy.js";
2
1
  import { logMessage } from "./logging.js";
3
2
  import { urlCache } from "./cache.js";
4
- import { createURLFormatError, createNetworkError, createServerError, createContentError, createTimeoutError, createEmptyContentWarning, createUnexpectedError } from "./error-handler.js";
3
+ import { createURLFormatError, createConfigurationError, createNetworkError, createServerError, createContentError, createTimeoutError, createEmptyContentWarning, createUnexpectedError, GATEWAY_URL_REQUIRED_MESSAGE } from "./error-handler.js";
5
4
  function applyCharacterPagination(content, startChar = 0, maxLength) {
6
5
  if (startChar >= content.length) {
7
6
  return "";
@@ -99,7 +98,8 @@ function applyPaginationOptions(markdownContent, options) {
99
98
  }
100
99
  return result;
101
100
  }
102
- export async function fetchAndConvertToMarkdown(server, url, timeoutMs = 10000, paginationOptions = {}) {
101
+ export async function fetchAndConvertToMarkdown(server, url, timeoutMs = 30000, // Increased default from 10s to 30s
102
+ paginationOptions = {}) {
103
103
  const startTime = Date.now();
104
104
  logMessage(server, "info", `Fetching URL: ${url}`);
105
105
  // Check cache first
@@ -112,44 +112,40 @@ export async function fetchAndConvertToMarkdown(server, url, timeoutMs = 10000,
112
112
  return result;
113
113
  }
114
114
  // Validate URL format
115
- let parsedUrl;
116
115
  try {
117
- parsedUrl = new URL(url);
116
+ new URL(url);
118
117
  }
119
- catch (error) {
118
+ catch {
120
119
  logMessage(server, "error", `Invalid URL format: ${url}`);
121
120
  throw createURLFormatError(url);
122
121
  }
123
122
  // Build gateway API URL
124
- const gatewayUrl = process.env.GATEWAY_URL || "http://115.190.91.253:80";
123
+ const gatewayUrl = process.env.GATEWAY_URL;
124
+ if (!gatewayUrl) {
125
+ throw createConfigurationError(GATEWAY_URL_REQUIRED_MESSAGE);
126
+ }
125
127
  const gatewayApiUrl = `${gatewayUrl}/api/read/${encodeURIComponent(url)}`;
126
128
  // Create an AbortController instance
127
129
  const controller = new AbortController();
128
130
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
129
131
  try {
130
- // Prepare request options with proxy support
132
+ // Prepare request options
131
133
  const requestOptions = {
132
134
  signal: controller.signal,
133
135
  };
134
- // Add proxy dispatcher if proxy is configured
135
- // Node.js fetch uses 'dispatcher' option for proxy, not 'agent'
136
- const proxyAgent = createProxyAgent(gatewayApiUrl);
137
- if (proxyAgent) {
138
- requestOptions.dispatcher = proxyAgent;
139
- }
140
136
  let response;
141
137
  try {
142
- // Fetch the URL via gateway API with the abort signal
138
+ logMessage(server, "info", `Fetching from Gateway: ${gatewayApiUrl}`);
143
139
  response = await fetch(gatewayApiUrl, requestOptions);
140
+ logMessage(server, "info", `Gateway response status: ${response.status}`);
144
141
  }
145
142
  catch (error) {
146
- const context = {
147
- url,
148
- gatewayUrl,
149
- proxyAgent: !!proxyAgent,
150
- timeout: timeoutMs
151
- };
152
- throw createNetworkError(error, context);
143
+ if (error.name === 'AbortError') {
144
+ logMessage(server, "error", `Gateway request timeout: ${url} (${timeoutMs}ms)`);
145
+ throw createTimeoutError(timeoutMs, url);
146
+ }
147
+ logMessage(server, "error", `Gateway network error: ${error.message}`, { url, gatewayUrl });
148
+ throw createNetworkError(error, { url, gatewayUrl, timeout: timeoutMs });
153
149
  }
154
150
  if (!response.ok) {
155
151
  let responseBody;
@@ -159,8 +155,7 @@ export async function fetchAndConvertToMarkdown(server, url, timeoutMs = 10000,
159
155
  catch {
160
156
  responseBody = '[Could not read response body]';
161
157
  }
162
- const context = { url, gatewayUrl };
163
- throw createServerError(response.status, response.statusText, responseBody, context);
158
+ throw createServerError(response.status, response.statusText, responseBody, { url, gatewayUrl });
164
159
  }
165
160
  // Retrieve content from gateway API (JSON response)
166
161
  let markdownContent;
@@ -180,7 +175,7 @@ export async function fetchAndConvertToMarkdown(server, url, timeoutMs = 10000,
180
175
  }
181
176
  if (!markdownContent || markdownContent.trim().length === 0) {
182
177
  logMessage(server, "warning", `Empty content from gateway: ${url}`);
183
- return createEmptyContentWarning(url, 0, "");
178
+ return createEmptyContentWarning(url);
184
179
  }
185
180
  // Cache the markdown content from gateway
186
181
  urlCache.set(url, "", markdownContent);
@@ -209,8 +204,7 @@ export async function fetchAndConvertToMarkdown(server, url, timeoutMs = 10000,
209
204
  }
210
205
  // Catch any unexpected errors
211
206
  logMessage(server, "error", `Unexpected error fetching URL: ${url}`, error);
212
- const context = { url };
213
- throw createUnexpectedError(error, context);
207
+ throw createUnexpectedError(error, { url });
214
208
  }
215
209
  finally {
216
210
  // Clean up the timeout to prevent memory leaks
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Detect content type based on file extension
3
+ */
4
+ export declare function detectFileType(filePath: string): 'image' | 'video' | 'file' | null;
5
+ /**
6
+ * Normalize user input - detect if it's a local path, URL, or base64
7
+ */
8
+ export declare function normalizeInput(input: string): {
9
+ type: 'local' | 'url' | 'base64';
10
+ value: string;
11
+ };
12
+ /**
13
+ * Read local file and convert to base64
14
+ */
15
+ export declare function readAsBase64(filePath: string): Promise<string>;
16
+ /**
17
+ * Get MIME type based on file extension
18
+ */
19
+ export declare function getMimeType(filePath: string): string;
@@ -0,0 +1,77 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ // MIME type mapping for common extensions
4
+ const MIME_TYPES = {
5
+ // Images
6
+ '.png': 'image/png',
7
+ '.jpg': 'image/jpeg',
8
+ '.jpeg': 'image/jpeg',
9
+ '.gif': 'image/gif',
10
+ '.webp': 'image/webp',
11
+ '.bmp': 'image/bmp',
12
+ // Videos
13
+ '.mp4': 'video/mp4',
14
+ '.mov': 'video/quicktime',
15
+ '.avi': 'video/x-msvideo',
16
+ '.mkv': 'video/x-matroska',
17
+ '.webm': 'video/webm',
18
+ // Documents
19
+ '.pdf': 'application/pdf',
20
+ '.txt': 'text/plain',
21
+ '.doc': 'application/msword',
22
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
23
+ '.xls': 'application/vnd.ms-excel',
24
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
25
+ };
26
+ // Content type mappings by extension
27
+ const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp']);
28
+ const VIDEO_EXTENSIONS = new Set(['.mp4', '.mov', '.avi', '.mkv', '.webm']);
29
+ const FILE_EXTENSIONS = new Set(['.pdf', '.txt', '.doc', '.docx', '.xls', '.xlsx']);
30
+ /**
31
+ * Detect content type based on file extension
32
+ */
33
+ export function detectFileType(filePath) {
34
+ const ext = path.extname(filePath).toLowerCase();
35
+ if (IMAGE_EXTENSIONS.has(ext))
36
+ return 'image';
37
+ if (VIDEO_EXTENSIONS.has(ext))
38
+ return 'video';
39
+ if (FILE_EXTENSIONS.has(ext))
40
+ return 'file';
41
+ return null;
42
+ }
43
+ /**
44
+ * Normalize user input - detect if it's a local path, URL, or base64
45
+ */
46
+ export function normalizeInput(input) {
47
+ if (input.startsWith('data:')) {
48
+ return { type: 'base64', value: input };
49
+ }
50
+ if (input.startsWith('http://') || input.startsWith('https://')) {
51
+ return { type: 'url', value: input };
52
+ }
53
+ if (/^[A-Za-z0-9+/=]{20,}$/.test(input) && !input.includes(' ')) {
54
+ return { type: 'base64', value: input };
55
+ }
56
+ return { type: 'local', value: input };
57
+ }
58
+ /**
59
+ * Read local file and convert to base64
60
+ */
61
+ export async function readAsBase64(filePath) {
62
+ try {
63
+ const buffer = await fs.readFile(filePath);
64
+ return buffer.toString('base64');
65
+ }
66
+ catch (error) {
67
+ const message = error instanceof Error ? error.message : String(error);
68
+ throw new Error(`Failed to read file: ${filePath}. Error: ${message}`);
69
+ }
70
+ }
71
+ /**
72
+ * Get MIME type based on file extension
73
+ */
74
+ export function getMimeType(filePath) {
75
+ const ext = path.extname(filePath).toLowerCase();
76
+ return MIME_TYPES[ext] || 'application/octet-stream';
77
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amplify-studio/open-mcp",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Open MCP server for web search and URL reading via Gateway API",
5
5
  "license": "MIT",
6
6
  "author": "Amplify Studio (https://github.com/amplify-studio)",
@@ -39,7 +39,7 @@
39
39
  "test": "SEARXNG_URL=https://test-searx.example.com tsx __tests__/run-all.ts",
40
40
  "test:coverage": "SEARXNG_URL=https://test-searx.example.com c8 --reporter=text tsx __tests__/run-all.ts",
41
41
  "bootstrap": "npm install && npm run build",
42
- "inspector": "DANGEROUSLY_OMIT_AUTH=true npx @modelcontextprotocol/inspector node dist/index.js",
42
+ "inspector": "DANGEROUSLY_OMIT_AUTH=true GATEWAY_URL=http://localhost:80 npx @modelcontextprotocol/inspector node dist/index.js",
43
43
  "postversion": "TAG=$(node scripts/update-version.js | tail -1) && git add src/index.ts && git commit --amend --no-edit && git tag -f $TAG"
44
44
  },
45
45
  "dependencies": {
@@ -47,15 +47,16 @@
47
47
  "@types/cors": "^2.8.19",
48
48
  "@types/express": "^5.0.3",
49
49
  "cors": "^2.8.5",
50
+ "dotenv": "^17.2.3",
50
51
  "express": "^5.1.0",
51
52
  "node-html-markdown": "^1.3.0",
52
53
  "undici": "^6.20.1"
53
54
  },
54
55
  "devDependencies": {
55
- "mcp-evals": "^1.0.18",
56
56
  "@types/node": "^22.17.2",
57
57
  "@types/supertest": "^6.0.3",
58
58
  "c8": "^10.1.3",
59
+ "mcp-evals": "^1.0.18",
59
60
  "shx": "^0.4.0",
60
61
  "supertest": "^7.1.4",
61
62
  "tsx": "^4.20.5",