@agentwonderland/mcp 0.1.3 → 0.1.4
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/dist/core/file-upload.d.ts +9 -0
- package/dist/core/file-upload.js +101 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/tools/run.js +3 -1
- package/dist/tools/solve.js +5 -2
- package/package.json +1 -1
- package/src/core/file-upload.ts +114 -0
- package/src/core/index.ts +1 -0
- package/src/tools/run.ts +3 -1
- package/src/tools/solve.ts +5 -2
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a string looks like a local file path (not a URL).
|
|
3
|
+
*/
|
|
4
|
+
export declare function isLocalFilePath(value: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Scan input object for local file paths, upload them to temporary storage,
|
|
7
|
+
* and replace with download URLs. Non-file values are left unchanged.
|
|
8
|
+
*/
|
|
9
|
+
export declare function uploadLocalFiles(input: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { basename, resolve } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { getApiUrl } from "./config.js";
|
|
5
|
+
const EXT_TO_MIME = {
|
|
6
|
+
pdf: "application/pdf",
|
|
7
|
+
png: "image/png",
|
|
8
|
+
jpg: "image/jpeg",
|
|
9
|
+
jpeg: "image/jpeg",
|
|
10
|
+
gif: "image/gif",
|
|
11
|
+
webp: "image/webp",
|
|
12
|
+
svg: "image/svg+xml",
|
|
13
|
+
mp3: "audio/mpeg",
|
|
14
|
+
mp4: "video/mp4",
|
|
15
|
+
wav: "audio/wav",
|
|
16
|
+
csv: "text/csv",
|
|
17
|
+
json: "application/json",
|
|
18
|
+
txt: "text/plain",
|
|
19
|
+
html: "text/html",
|
|
20
|
+
xml: "application/xml",
|
|
21
|
+
zip: "application/zip",
|
|
22
|
+
doc: "application/msword",
|
|
23
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
24
|
+
xls: "application/vnd.ms-excel",
|
|
25
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
26
|
+
};
|
|
27
|
+
const MAX_UPLOAD_SIZE = 50 * 1024 * 1024; // 50MB
|
|
28
|
+
/**
|
|
29
|
+
* Check if a string looks like a local file path (not a URL).
|
|
30
|
+
*/
|
|
31
|
+
export function isLocalFilePath(value) {
|
|
32
|
+
if (value.startsWith("http://") || value.startsWith("https://") || value.startsWith("data:")) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (value.includes("\n"))
|
|
36
|
+
return false;
|
|
37
|
+
// Unix absolute path, home dir path, or Windows drive path
|
|
38
|
+
return value.startsWith("/") || value.startsWith("~/") || /^[A-Za-z]:\\/.test(value);
|
|
39
|
+
}
|
|
40
|
+
function resolvePath(filePath) {
|
|
41
|
+
if (filePath.startsWith("~/")) {
|
|
42
|
+
return resolve(homedir(), filePath.slice(2));
|
|
43
|
+
}
|
|
44
|
+
return resolve(filePath);
|
|
45
|
+
}
|
|
46
|
+
function getMimeType(filename) {
|
|
47
|
+
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
48
|
+
return EXT_TO_MIME[ext] ?? "application/octet-stream";
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Upload a local file to the gateway's temporary storage.
|
|
52
|
+
* Returns the presigned download URL.
|
|
53
|
+
*/
|
|
54
|
+
async function uploadFile(filePath) {
|
|
55
|
+
const resolved = resolvePath(filePath);
|
|
56
|
+
const buffer = readFileSync(resolved);
|
|
57
|
+
if (buffer.length > MAX_UPLOAD_SIZE) {
|
|
58
|
+
throw new Error(`File ${basename(resolved)} exceeds 50MB upload limit (${(buffer.length / (1024 * 1024)).toFixed(1)}MB)`);
|
|
59
|
+
}
|
|
60
|
+
const filename = basename(resolved);
|
|
61
|
+
const contentType = getMimeType(filename);
|
|
62
|
+
const apiUrl = getApiUrl();
|
|
63
|
+
const res = await fetch(`${apiUrl}/uploads`, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: { "Content-Type": "application/json" },
|
|
66
|
+
body: JSON.stringify({
|
|
67
|
+
file: buffer.toString("base64"),
|
|
68
|
+
filename,
|
|
69
|
+
content_type: contentType,
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
const body = await res.json().catch(() => ({}));
|
|
74
|
+
throw new Error(`Upload failed: ${body.error ?? res.statusText}`);
|
|
75
|
+
}
|
|
76
|
+
const result = await res.json();
|
|
77
|
+
return result.url;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Scan input object for local file paths, upload them to temporary storage,
|
|
81
|
+
* and replace with download URLs. Non-file values are left unchanged.
|
|
82
|
+
*/
|
|
83
|
+
export async function uploadLocalFiles(input) {
|
|
84
|
+
const result = { ...input };
|
|
85
|
+
for (const [key, value] of Object.entries(result)) {
|
|
86
|
+
if (typeof value !== "string")
|
|
87
|
+
continue;
|
|
88
|
+
if (!isLocalFilePath(value))
|
|
89
|
+
continue;
|
|
90
|
+
const resolved = resolvePath(value);
|
|
91
|
+
if (!existsSync(resolved))
|
|
92
|
+
continue;
|
|
93
|
+
try {
|
|
94
|
+
result[key] = await uploadFile(value);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Leave original value if upload fails
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
package/dist/core/index.d.ts
CHANGED
package/dist/core/index.js
CHANGED
package/dist/tools/run.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { apiGet, apiPostWithPayment } from "../core/api-client.js";
|
|
3
|
+
import { uploadLocalFiles } from "../core/file-upload.js";
|
|
3
4
|
import { getConfiguredMethods, hasWalletConfigured } from "../core/payments.js";
|
|
4
5
|
import { formatRunResult } from "../core/formatters.js";
|
|
5
6
|
import { storeFeedbackToken } from "./_token-cache.js";
|
|
@@ -30,9 +31,10 @@ export function registerRunTools(server) {
|
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
const method = pay_with;
|
|
34
|
+
const processedInput = await uploadLocalFiles(input);
|
|
33
35
|
let result;
|
|
34
36
|
try {
|
|
35
|
-
result = await apiPostWithPayment(`/agents/${resolvedId}/run`, { input }, method);
|
|
37
|
+
result = await apiPostWithPayment(`/agents/${resolvedId}/run`, { input: processedInput }, method);
|
|
36
38
|
}
|
|
37
39
|
catch (err) {
|
|
38
40
|
const apiErr = err;
|
package/dist/tools/solve.js
CHANGED
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { apiGet, apiPost, apiPostWithPayment } from "../core/api-client.js";
|
|
3
3
|
import { hasWalletConfigured, getConfiguredMethods, getAcceptedPaymentMethods, } from "../core/payments.js";
|
|
4
4
|
import { agentList, formatRunResult } from "../core/formatters.js";
|
|
5
|
+
import { uploadLocalFiles } from "../core/file-upload.js";
|
|
5
6
|
import { storeFeedbackToken } from "./_token-cache.js";
|
|
6
7
|
function text(t) {
|
|
7
8
|
return { content: [{ type: "text", text: t }] };
|
|
@@ -46,9 +47,10 @@ export function registerSolveTools(server) {
|
|
|
46
47
|
const method = pay_with ?? getConfiguredMethods()[0];
|
|
47
48
|
// Path 1: If authenticated, use the platform /solve route
|
|
48
49
|
try {
|
|
50
|
+
const processedInput = await uploadLocalFiles(input);
|
|
49
51
|
const result = await apiPost("/solve", {
|
|
50
52
|
intent,
|
|
51
|
-
input,
|
|
53
|
+
input: processedInput,
|
|
52
54
|
budget,
|
|
53
55
|
});
|
|
54
56
|
const jobId = result.job_id ?? "";
|
|
@@ -91,8 +93,9 @@ export function registerSolveTools(server) {
|
|
|
91
93
|
? selectedPrice
|
|
92
94
|
: (inputTokens / 1000) * selectedPrice;
|
|
93
95
|
let result;
|
|
96
|
+
const processedInput2 = await uploadLocalFiles(input);
|
|
94
97
|
try {
|
|
95
|
-
result = await apiPostWithPayment(`/agents/${selected.id}/run`, { input }, method);
|
|
98
|
+
result = await apiPostWithPayment(`/agents/${selected.id}/run`, { input: processedInput2 }, method);
|
|
96
99
|
}
|
|
97
100
|
catch (err) {
|
|
98
101
|
const apiErr = err;
|
package/package.json
CHANGED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { basename, resolve } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { getApiUrl } from "./config.js";
|
|
5
|
+
|
|
6
|
+
const EXT_TO_MIME: Record<string, string> = {
|
|
7
|
+
pdf: "application/pdf",
|
|
8
|
+
png: "image/png",
|
|
9
|
+
jpg: "image/jpeg",
|
|
10
|
+
jpeg: "image/jpeg",
|
|
11
|
+
gif: "image/gif",
|
|
12
|
+
webp: "image/webp",
|
|
13
|
+
svg: "image/svg+xml",
|
|
14
|
+
mp3: "audio/mpeg",
|
|
15
|
+
mp4: "video/mp4",
|
|
16
|
+
wav: "audio/wav",
|
|
17
|
+
csv: "text/csv",
|
|
18
|
+
json: "application/json",
|
|
19
|
+
txt: "text/plain",
|
|
20
|
+
html: "text/html",
|
|
21
|
+
xml: "application/xml",
|
|
22
|
+
zip: "application/zip",
|
|
23
|
+
doc: "application/msword",
|
|
24
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
25
|
+
xls: "application/vnd.ms-excel",
|
|
26
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const MAX_UPLOAD_SIZE = 50 * 1024 * 1024; // 50MB
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if a string looks like a local file path (not a URL).
|
|
33
|
+
*/
|
|
34
|
+
export function isLocalFilePath(value: string): boolean {
|
|
35
|
+
if (value.startsWith("http://") || value.startsWith("https://") || value.startsWith("data:")) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (value.includes("\n")) return false;
|
|
39
|
+
// Unix absolute path, home dir path, or Windows drive path
|
|
40
|
+
return value.startsWith("/") || value.startsWith("~/") || /^[A-Za-z]:\\/.test(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function resolvePath(filePath: string): string {
|
|
44
|
+
if (filePath.startsWith("~/")) {
|
|
45
|
+
return resolve(homedir(), filePath.slice(2));
|
|
46
|
+
}
|
|
47
|
+
return resolve(filePath);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getMimeType(filename: string): string {
|
|
51
|
+
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
52
|
+
return EXT_TO_MIME[ext] ?? "application/octet-stream";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Upload a local file to the gateway's temporary storage.
|
|
57
|
+
* Returns the presigned download URL.
|
|
58
|
+
*/
|
|
59
|
+
async function uploadFile(filePath: string): Promise<string> {
|
|
60
|
+
const resolved = resolvePath(filePath);
|
|
61
|
+
const buffer = readFileSync(resolved);
|
|
62
|
+
|
|
63
|
+
if (buffer.length > MAX_UPLOAD_SIZE) {
|
|
64
|
+
throw new Error(`File ${basename(resolved)} exceeds 50MB upload limit (${(buffer.length / (1024 * 1024)).toFixed(1)}MB)`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const filename = basename(resolved);
|
|
68
|
+
const contentType = getMimeType(filename);
|
|
69
|
+
const apiUrl = getApiUrl();
|
|
70
|
+
|
|
71
|
+
const res = await fetch(`${apiUrl}/uploads`, {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: { "Content-Type": "application/json" },
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
file: buffer.toString("base64"),
|
|
76
|
+
filename,
|
|
77
|
+
content_type: contentType,
|
|
78
|
+
}),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
const body = await res.json().catch(() => ({})) as Record<string, unknown>;
|
|
83
|
+
throw new Error(`Upload failed: ${body.error ?? res.statusText}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const result = await res.json() as { url: string };
|
|
87
|
+
return result.url;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Scan input object for local file paths, upload them to temporary storage,
|
|
92
|
+
* and replace with download URLs. Non-file values are left unchanged.
|
|
93
|
+
*/
|
|
94
|
+
export async function uploadLocalFiles(
|
|
95
|
+
input: Record<string, unknown>,
|
|
96
|
+
): Promise<Record<string, unknown>> {
|
|
97
|
+
const result = { ...input };
|
|
98
|
+
|
|
99
|
+
for (const [key, value] of Object.entries(result)) {
|
|
100
|
+
if (typeof value !== "string") continue;
|
|
101
|
+
if (!isLocalFilePath(value)) continue;
|
|
102
|
+
|
|
103
|
+
const resolved = resolvePath(value);
|
|
104
|
+
if (!existsSync(resolved)) continue;
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
result[key] = await uploadFile(value);
|
|
108
|
+
} catch {
|
|
109
|
+
// Leave original value if upload fails
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return result;
|
|
114
|
+
}
|
package/src/core/index.ts
CHANGED
package/src/tools/run.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { apiGet, apiPostWithPayment } from "../core/api-client.js";
|
|
4
|
+
import { uploadLocalFiles } from "../core/file-upload.js";
|
|
4
5
|
import { getConfiguredMethods, hasWalletConfigured } from "../core/payments.js";
|
|
5
6
|
import { formatRunResult } from "../core/formatters.js";
|
|
6
7
|
import { storeFeedbackToken } from "./_token-cache.js";
|
|
@@ -40,11 +41,12 @@ export function registerRunTools(server: McpServer): void {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
const method = pay_with;
|
|
44
|
+
const processedInput = await uploadLocalFiles(input);
|
|
43
45
|
let result: Record<string, unknown>;
|
|
44
46
|
try {
|
|
45
47
|
result = await apiPostWithPayment<Record<string, unknown>>(
|
|
46
48
|
`/agents/${resolvedId}/run`,
|
|
47
|
-
{ input },
|
|
49
|
+
{ input: processedInput },
|
|
48
50
|
method,
|
|
49
51
|
);
|
|
50
52
|
} catch (err: unknown) {
|
package/src/tools/solve.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getAcceptedPaymentMethods,
|
|
8
8
|
} from "../core/payments.js";
|
|
9
9
|
import { agentList, formatRunResult } from "../core/formatters.js";
|
|
10
|
+
import { uploadLocalFiles } from "../core/file-upload.js";
|
|
10
11
|
import type { AgentRecord } from "../core/types.js";
|
|
11
12
|
import { storeFeedbackToken } from "./_token-cache.js";
|
|
12
13
|
|
|
@@ -65,9 +66,10 @@ export function registerSolveTools(server: McpServer): void {
|
|
|
65
66
|
|
|
66
67
|
// Path 1: If authenticated, use the platform /solve route
|
|
67
68
|
try {
|
|
69
|
+
const processedInput = await uploadLocalFiles(input);
|
|
68
70
|
const result = await apiPost<Record<string, unknown>>("/solve", {
|
|
69
71
|
intent,
|
|
70
|
-
input,
|
|
72
|
+
input: processedInput,
|
|
71
73
|
budget,
|
|
72
74
|
});
|
|
73
75
|
const jobId = (result as Record<string, unknown>).job_id as string ?? "";
|
|
@@ -119,10 +121,11 @@ export function registerSolveTools(server: McpServer): void {
|
|
|
119
121
|
: (inputTokens / 1000) * selectedPrice;
|
|
120
122
|
|
|
121
123
|
let result: Record<string, unknown>;
|
|
124
|
+
const processedInput2 = await uploadLocalFiles(input);
|
|
122
125
|
try {
|
|
123
126
|
result = await apiPostWithPayment<Record<string, unknown>>(
|
|
124
127
|
`/agents/${selected.id}/run`,
|
|
125
|
-
{ input },
|
|
128
|
+
{ input: processedInput2 },
|
|
126
129
|
method,
|
|
127
130
|
);
|
|
128
131
|
} catch (err: unknown) {
|