@agentwonderland/mcp 0.1.33 → 0.1.35
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.js +8 -2
- package/dist/index.js +2 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +1 -0
- package/dist/tools/run.js +1 -1
- package/dist/tools/solve.js +1 -1
- package/dist/tools/upload.d.ts +2 -0
- package/dist/tools/upload.js +46 -0
- package/package.json +1 -1
- package/src/core/file-upload.ts +10 -1
- package/src/index.ts +2 -0
- package/src/tools/index.ts +1 -0
- package/src/tools/run.ts +1 -1
- package/src/tools/solve.ts +1 -1
- package/src/tools/upload.ts +65 -0
- package/test-x402-flow.mjs +0 -58
package/dist/core/file-upload.js
CHANGED
|
@@ -90,8 +90,14 @@ export async function uploadLocalFiles(input) {
|
|
|
90
90
|
if (!isLocalFilePath(value))
|
|
91
91
|
continue;
|
|
92
92
|
const resolved = resolvePath(value);
|
|
93
|
-
if (!existsSync(resolved))
|
|
94
|
-
|
|
93
|
+
if (!existsSync(resolved)) {
|
|
94
|
+
throw new Error(`File not found at "${value}" (for input field "${key}"). ` +
|
|
95
|
+
`This MCP server reads files from its own filesystem — if your client ` +
|
|
96
|
+
`sandboxes attachments (e.g. a web session with a /mnt/... path), the ` +
|
|
97
|
+
`bytes aren't reachable here. Fix: call upload_file({ filename: "${basename(resolved)}" }) ` +
|
|
98
|
+
`to get a presigned upload URL, PUT the bytes to it (e.g. curl -T), then pass the ` +
|
|
99
|
+
`returned GET URL as "${key}" instead of the path.`);
|
|
100
|
+
}
|
|
95
101
|
try {
|
|
96
102
|
const url = await uploadFile(value);
|
|
97
103
|
result[key] = url;
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { registerWalletTools } from "./tools/wallet.js";
|
|
|
12
12
|
import { registerFavoriteTools } from "./tools/favorites.js";
|
|
13
13
|
import { registerTipTools } from "./tools/tip.js";
|
|
14
14
|
import { registerPassTools } from "./tools/passes.js";
|
|
15
|
+
import { registerUploadTools } from "./tools/upload.js";
|
|
15
16
|
// ── Resources ────────────────────────────────────────────────────
|
|
16
17
|
import { registerAgentResources } from "./resources/agents.js";
|
|
17
18
|
import { registerWalletResources } from "./resources/wallet.js";
|
|
@@ -73,6 +74,7 @@ export async function startMcpServer() {
|
|
|
73
74
|
registerFavoriteTools(server);
|
|
74
75
|
registerTipTools(server);
|
|
75
76
|
registerPassTools(server);
|
|
77
|
+
registerUploadTools(server);
|
|
76
78
|
// Register resources
|
|
77
79
|
registerAgentResources(server);
|
|
78
80
|
registerWalletResources(server);
|
package/dist/tools/index.d.ts
CHANGED
package/dist/tools/index.js
CHANGED
package/dist/tools/run.js
CHANGED
|
@@ -85,7 +85,7 @@ function buildCreditPackOfferLines(agent) {
|
|
|
85
85
|
];
|
|
86
86
|
}
|
|
87
87
|
export function registerRunTools(server) {
|
|
88
|
-
server.tool("run_agent", "Run an AI agent from the marketplace. Pays automatically via configured wallet. Returns the agent's output, cost, and job ID for tracking. If spending confirmation is enabled, first call returns a price quote — call again with confirmed: true to execute. Local file paths in the input (e.g. /Users/.../photo.jpg) are automatically uploaded to temporary storage and replaced with download URLs before execution.", {
|
|
88
|
+
server.tool("run_agent", "Run an AI agent from the marketplace. Pays automatically via configured wallet. Returns the agent's output, cost, and job ID for tracking. If spending confirmation is enabled, first call returns a price quote — call again with confirmed: true to execute. Local file paths in the input (e.g. /Users/.../photo.jpg) are automatically uploaded to temporary storage and replaced with download URLs before execution. If a file you need isn't on this MCP server's filesystem (e.g. a sandboxed /mnt/... attachment), call upload_file first to get a presigned upload URL, PUT the bytes to it, then pass the returned GET URL as input instead — that keeps the bytes out of the conversation context.", {
|
|
89
89
|
agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
|
|
90
90
|
input: z.record(z.unknown()).describe("Input payload for the agent"),
|
|
91
91
|
pay_with: z.string().trim().min(1).optional().describe("Payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Auto-detected if omitted."),
|
package/dist/tools/solve.js
CHANGED
|
@@ -105,7 +105,7 @@ function buildCreditPackSummary(agent) {
|
|
|
105
105
|
];
|
|
106
106
|
}
|
|
107
107
|
export function registerSolveTools(server) {
|
|
108
|
-
server.tool("solve", "Solve a task by finding the best agent, paying, and executing. The primary way to use the marketplace. If spending confirmation is enabled, returns a price quote first — call again with confirmed: true to execute. Local file paths in the input are auto-uploaded before execution.", {
|
|
108
|
+
server.tool("solve", "Solve a task by finding the best agent, paying, and executing. The primary way to use the marketplace. If spending confirmation is enabled, returns a price quote first — call again with confirmed: true to execute. Local file paths in the input are auto-uploaded before execution. If a file you need isn't on this MCP server's filesystem (e.g. a sandboxed /mnt/... attachment), call upload_file first to get a presigned upload URL, PUT the bytes to it, then pass the returned GET URL as input instead.", {
|
|
109
109
|
intent: z
|
|
110
110
|
.string()
|
|
111
111
|
.describe("What you want to accomplish (natural language)"),
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiPost } from "../core/api-client.js";
|
|
3
|
+
function text(t) {
|
|
4
|
+
return { content: [{ type: "text", text: t }] };
|
|
5
|
+
}
|
|
6
|
+
export function registerUploadTools(server) {
|
|
7
|
+
server.tool("upload_file", [
|
|
8
|
+
"Get a pair of presigned R2 URLs to upload a file without passing bytes through the conversation.",
|
|
9
|
+
"Use this when the file lives in a sandbox the MCP server can't read (e.g. Claude web /mnt/user-data/..., any hosted-agent environment).",
|
|
10
|
+
"Flow:",
|
|
11
|
+
" 1. Call this tool with the filename and content_type.",
|
|
12
|
+
" 2. PUT the file bytes to `upload_url` over HTTP (e.g. via bash curl). The bytes never touch the LLM context.",
|
|
13
|
+
" 3. Pass `get_url` as the input value when running an agent that needs a URL.",
|
|
14
|
+
"On Claude Desktop with real filesystem paths (/Users/... or ~/...), just pass the path to run_agent — it auto-uploads. Only reach for upload_file when auto-upload isn't possible.",
|
|
15
|
+
].join("\n"), {
|
|
16
|
+
filename: z
|
|
17
|
+
.string()
|
|
18
|
+
.min(1)
|
|
19
|
+
.max(255)
|
|
20
|
+
.describe("Base filename, e.g. 'photo.jpg'. Path separators are stripped."),
|
|
21
|
+
content_type: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("MIME type (e.g. 'image/jpeg'). Inferred from filename if omitted."),
|
|
25
|
+
}, async ({ filename, content_type }) => {
|
|
26
|
+
const result = await apiPost("/uploads/presign-put", {
|
|
27
|
+
filename,
|
|
28
|
+
content_type,
|
|
29
|
+
});
|
|
30
|
+
const lines = [
|
|
31
|
+
`Upload slot ready for ${filename} (${result.content_type}).`,
|
|
32
|
+
"",
|
|
33
|
+
`Upload URL (PUT, expires in ${Math.round(result.upload_expires_in / 60)}m):`,
|
|
34
|
+
result.upload_url,
|
|
35
|
+
"",
|
|
36
|
+
`Public URL to pass to run_agent (GET, expires in ${Math.round(result.get_expires_in / 3600)}h):`,
|
|
37
|
+
result.get_url,
|
|
38
|
+
"",
|
|
39
|
+
"Upload the file with:",
|
|
40
|
+
` curl -X PUT -H "Content-Type: ${result.content_type}" --data-binary @<path-to-file> "${result.upload_url}"`,
|
|
41
|
+
"",
|
|
42
|
+
"Then pass the GET URL above as the input value (e.g. { image: \"<get_url>\" }).",
|
|
43
|
+
];
|
|
44
|
+
return text(lines.join("\n"));
|
|
45
|
+
});
|
|
46
|
+
}
|
package/package.json
CHANGED
package/src/core/file-upload.ts
CHANGED
|
@@ -112,7 +112,16 @@ export async function uploadLocalFiles(
|
|
|
112
112
|
if (!isLocalFilePath(value)) continue;
|
|
113
113
|
|
|
114
114
|
const resolved = resolvePath(value);
|
|
115
|
-
if (!existsSync(resolved))
|
|
115
|
+
if (!existsSync(resolved)) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`File not found at "${value}" (for input field "${key}"). ` +
|
|
118
|
+
`This MCP server reads files from its own filesystem — if your client ` +
|
|
119
|
+
`sandboxes attachments (e.g. a web session with a /mnt/... path), the ` +
|
|
120
|
+
`bytes aren't reachable here. Fix: call upload_file({ filename: "${basename(resolved)}" }) ` +
|
|
121
|
+
`to get a presigned upload URL, PUT the bytes to it (e.g. curl -T), then pass the ` +
|
|
122
|
+
`returned GET URL as "${key}" instead of the path.`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
116
125
|
|
|
117
126
|
try {
|
|
118
127
|
const url = await uploadFile(value);
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { registerWalletTools } from "./tools/wallet.js";
|
|
|
14
14
|
import { registerFavoriteTools } from "./tools/favorites.js";
|
|
15
15
|
import { registerTipTools } from "./tools/tip.js";
|
|
16
16
|
import { registerPassTools } from "./tools/passes.js";
|
|
17
|
+
import { registerUploadTools } from "./tools/upload.js";
|
|
17
18
|
|
|
18
19
|
// ── Resources ────────────────────────────────────────────────────
|
|
19
20
|
import { registerAgentResources } from "./resources/agents.js";
|
|
@@ -82,6 +83,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
82
83
|
registerFavoriteTools(server);
|
|
83
84
|
registerTipTools(server);
|
|
84
85
|
registerPassTools(server);
|
|
86
|
+
registerUploadTools(server);
|
|
85
87
|
|
|
86
88
|
// Register resources
|
|
87
89
|
registerAgentResources(server);
|
package/src/tools/index.ts
CHANGED
package/src/tools/run.ts
CHANGED
|
@@ -123,7 +123,7 @@ function buildCreditPackOfferLines(agent: AgentRecord): string[] {
|
|
|
123
123
|
export function registerRunTools(server: McpServer): void {
|
|
124
124
|
server.tool(
|
|
125
125
|
"run_agent",
|
|
126
|
-
"Run an AI agent from the marketplace. Pays automatically via configured wallet. Returns the agent's output, cost, and job ID for tracking. If spending confirmation is enabled, first call returns a price quote — call again with confirmed: true to execute. Local file paths in the input (e.g. /Users/.../photo.jpg) are automatically uploaded to temporary storage and replaced with download URLs before execution.",
|
|
126
|
+
"Run an AI agent from the marketplace. Pays automatically via configured wallet. Returns the agent's output, cost, and job ID for tracking. If spending confirmation is enabled, first call returns a price quote — call again with confirmed: true to execute. Local file paths in the input (e.g. /Users/.../photo.jpg) are automatically uploaded to temporary storage and replaced with download URLs before execution. If a file you need isn't on this MCP server's filesystem (e.g. a sandboxed /mnt/... attachment), call upload_file first to get a presigned upload URL, PUT the bytes to it, then pass the returned GET URL as input instead — that keeps the bytes out of the conversation context.",
|
|
127
127
|
{
|
|
128
128
|
agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
|
|
129
129
|
input: z.record(z.unknown()).describe("Input payload for the agent"),
|
package/src/tools/solve.ts
CHANGED
|
@@ -137,7 +137,7 @@ function buildCreditPackSummary(agent: AgentRecord): string[] {
|
|
|
137
137
|
export function registerSolveTools(server: McpServer): void {
|
|
138
138
|
server.tool(
|
|
139
139
|
"solve",
|
|
140
|
-
"Solve a task by finding the best agent, paying, and executing. The primary way to use the marketplace. If spending confirmation is enabled, returns a price quote first — call again with confirmed: true to execute. Local file paths in the input are auto-uploaded before execution.",
|
|
140
|
+
"Solve a task by finding the best agent, paying, and executing. The primary way to use the marketplace. If spending confirmation is enabled, returns a price quote first — call again with confirmed: true to execute. Local file paths in the input are auto-uploaded before execution. If a file you need isn't on this MCP server's filesystem (e.g. a sandboxed /mnt/... attachment), call upload_file first to get a presigned upload URL, PUT the bytes to it, then pass the returned GET URL as input instead.",
|
|
141
141
|
{
|
|
142
142
|
intent: z
|
|
143
143
|
.string()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { apiPost } from "../core/api-client.js";
|
|
4
|
+
|
|
5
|
+
function text(t: string) {
|
|
6
|
+
return { content: [{ type: "text" as const, text: t }] };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface PresignResponse {
|
|
10
|
+
upload_url: string;
|
|
11
|
+
get_url: string;
|
|
12
|
+
key: string;
|
|
13
|
+
content_type: string;
|
|
14
|
+
upload_expires_in: number;
|
|
15
|
+
get_expires_in: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function registerUploadTools(server: McpServer) {
|
|
19
|
+
server.tool(
|
|
20
|
+
"upload_file",
|
|
21
|
+
[
|
|
22
|
+
"Get a pair of presigned R2 URLs to upload a file without passing bytes through the conversation.",
|
|
23
|
+
"Use this when the file lives in a sandbox the MCP server can't read (e.g. Claude web /mnt/user-data/..., any hosted-agent environment).",
|
|
24
|
+
"Flow:",
|
|
25
|
+
" 1. Call this tool with the filename and content_type.",
|
|
26
|
+
" 2. PUT the file bytes to `upload_url` over HTTP (e.g. via bash curl). The bytes never touch the LLM context.",
|
|
27
|
+
" 3. Pass `get_url` as the input value when running an agent that needs a URL.",
|
|
28
|
+
"On Claude Desktop with real filesystem paths (/Users/... or ~/...), just pass the path to run_agent — it auto-uploads. Only reach for upload_file when auto-upload isn't possible.",
|
|
29
|
+
].join("\n"),
|
|
30
|
+
{
|
|
31
|
+
filename: z
|
|
32
|
+
.string()
|
|
33
|
+
.min(1)
|
|
34
|
+
.max(255)
|
|
35
|
+
.describe("Base filename, e.g. 'photo.jpg'. Path separators are stripped."),
|
|
36
|
+
content_type: z
|
|
37
|
+
.string()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("MIME type (e.g. 'image/jpeg'). Inferred from filename if omitted."),
|
|
40
|
+
},
|
|
41
|
+
async ({ filename, content_type }) => {
|
|
42
|
+
const result = await apiPost<PresignResponse>("/uploads/presign-put", {
|
|
43
|
+
filename,
|
|
44
|
+
content_type,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const lines = [
|
|
48
|
+
`Upload slot ready for ${filename} (${result.content_type}).`,
|
|
49
|
+
"",
|
|
50
|
+
`Upload URL (PUT, expires in ${Math.round(result.upload_expires_in / 60)}m):`,
|
|
51
|
+
result.upload_url,
|
|
52
|
+
"",
|
|
53
|
+
`Public URL to pass to run_agent (GET, expires in ${Math.round(result.get_expires_in / 3600)}h):`,
|
|
54
|
+
result.get_url,
|
|
55
|
+
"",
|
|
56
|
+
"Upload the file with:",
|
|
57
|
+
` curl -X PUT -H "Content-Type: ${result.content_type}" --data-binary @<path-to-file> "${result.upload_url}"`,
|
|
58
|
+
"",
|
|
59
|
+
"Then pass the GET URL above as the input value (e.g. { image: \"<get_url>\" }).",
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
return text(lines.join("\n"));
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
}
|
package/test-x402-flow.mjs
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// End-to-end x402 client test against prod Agent Wonderland on Base mainnet.
|
|
2
|
-
//
|
|
3
|
-
// 1. POST /x402/agents/:id/run (no PAYMENT header) → 402 with Stripe-issued payTo
|
|
4
|
-
// 2. Sign the payment authorization on Base mainnet with user's OWS-managed wallet
|
|
5
|
-
// 3. Retry with PAYMENT header → agent runs, USDC transferred to Stripe
|
|
6
|
-
//
|
|
7
|
-
// Then we inspect the resulting payment_attempt + transfer rows to confirm the
|
|
8
|
-
// Connect transfer is funded via source_transaction (same path MPP uses).
|
|
9
|
-
|
|
10
|
-
import { owsAccountFromWalletId } from "./src/core/ows-adapter.ts";
|
|
11
|
-
import { x402Client } from "@x402/core/client";
|
|
12
|
-
import { registerExactEvmScheme } from "@x402/evm/exact/client";
|
|
13
|
-
import { encodePaymentSignatureHeader } from "@x402/core/http";
|
|
14
|
-
|
|
15
|
-
const API = "https://api.agentwonderland.com";
|
|
16
|
-
const AGENT_SLUG = "diagram-renderer-qyf0";
|
|
17
|
-
const INPUT = { code: "graph TD; A-->B;", format: "png" };
|
|
18
|
-
const OWS_WALLET_ID = "9558b02c-93bf-480f-8708-5fe135bf53d9"; // from ~/.agentwonderland/config.json
|
|
19
|
-
|
|
20
|
-
// 1. Get the account
|
|
21
|
-
const account = await owsAccountFromWalletId(OWS_WALLET_ID);
|
|
22
|
-
console.log(`signer: ${account.address}`);
|
|
23
|
-
|
|
24
|
-
// 2. Fetch the 402 challenge
|
|
25
|
-
const first = await fetch(`${API}/x402/agents/${AGENT_SLUG}/run`, {
|
|
26
|
-
method: "POST",
|
|
27
|
-
headers: { "Content-Type": "application/json" },
|
|
28
|
-
body: JSON.stringify({ input: INPUT }),
|
|
29
|
-
});
|
|
30
|
-
console.log(`first response: ${first.status}`);
|
|
31
|
-
const paymentRequired = await first.json();
|
|
32
|
-
console.log("accepts[0].payTo:", paymentRequired.accepts?.[0]?.payTo);
|
|
33
|
-
console.log("payment_attempt_id:", paymentRequired.payment_attempt_id);
|
|
34
|
-
|
|
35
|
-
// 3. Build + sign the payment
|
|
36
|
-
const client = new x402Client();
|
|
37
|
-
registerExactEvmScheme(client, {
|
|
38
|
-
signer: account,
|
|
39
|
-
networks: ["eip155:8453"],
|
|
40
|
-
});
|
|
41
|
-
const paymentPayload = await client.createPaymentPayload(paymentRequired);
|
|
42
|
-
console.log("FULL payment payload:", JSON.stringify(paymentPayload, null, 2));
|
|
43
|
-
|
|
44
|
-
const paymentHeader = encodePaymentSignatureHeader(paymentPayload);
|
|
45
|
-
console.log("PAYMENT header length:", paymentHeader.length);
|
|
46
|
-
|
|
47
|
-
// 4. Retry with PAYMENT header
|
|
48
|
-
const second = await fetch(`${API}/x402/agents/${AGENT_SLUG}/run`, {
|
|
49
|
-
method: "POST",
|
|
50
|
-
headers: {
|
|
51
|
-
"Content-Type": "application/json",
|
|
52
|
-
PAYMENT: paymentHeader,
|
|
53
|
-
},
|
|
54
|
-
body: JSON.stringify({ input: INPUT }),
|
|
55
|
-
});
|
|
56
|
-
console.log(`second response: ${second.status}`);
|
|
57
|
-
const body = await second.text();
|
|
58
|
-
console.log("body preview:", body.slice(0, 400));
|