@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.
@@ -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
+ }
@@ -4,3 +4,4 @@ export * from "./payments.js";
4
4
  export * from "./ows-adapter.js";
5
5
  export * from "./formatters.js";
6
6
  export * from "./types.js";
7
+ export * from "./file-upload.js";
@@ -4,3 +4,4 @@ export * from "./payments.js";
4
4
  export * from "./ows-adapter.js";
5
5
  export * from "./formatters.js";
6
6
  export * from "./types.js";
7
+ export * from "./file-upload.js";
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;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentwonderland/mcp",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "MCP server for the Agent Wonderland AI agent marketplace",
6
6
  "bin": {
@@ -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
@@ -4,3 +4,4 @@ export * from "./payments.js";
4
4
  export * from "./ows-adapter.js";
5
5
  export * from "./formatters.js";
6
6
  export * from "./types.js";
7
+ export * from "./file-upload.js";
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) {
@@ -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) {