@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.
- package/README.md +352 -325
- package/dist/api/zhipu.d.ts +32 -0
- package/dist/api/zhipu.js +68 -0
- package/dist/error-handler.d.ts +3 -9
- package/dist/error-handler.js +23 -35
- package/dist/index.d.ts +1 -9
- package/dist/index.js +123 -86
- package/dist/resources.js +43 -16
- package/dist/search.js +25 -30
- package/dist/tools/image-generate.d.ts +5 -0
- package/dist/tools/image-generate.js +29 -0
- package/dist/tools/image-ocr.d.ts +5 -0
- package/dist/tools/image-ocr.js +102 -0
- package/dist/tools/image-understand.d.ts +5 -0
- package/dist/tools/image-understand.js +54 -0
- package/dist/types.d.ts +22 -7
- package/dist/types.js +112 -5
- package/dist/url-reader.d.ts +2 -1
- package/dist/url-reader.js +21 -27
- package/dist/utils/file-helper.d.ts +19 -0
- package/dist/utils/file-helper.js +77 -0
- package/package.json +4 -3
package/dist/url-reader.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
116
|
+
new URL(url);
|
|
118
117
|
}
|
|
119
|
-
catch
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
147
|
-
url
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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",
|