@agentwonderland/mcp 0.1.37 → 0.1.39
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/__tests__/api-client.test.js +4 -0
- package/dist/core/__tests__/mpp-client.test.d.ts +1 -0
- package/dist/core/__tests__/mpp-client.test.js +46 -0
- package/dist/core/__tests__/payments.test.js +5 -14
- package/dist/core/api-client.js +27 -5
- package/dist/core/base-charge.js +1 -1
- package/dist/core/formatters.d.ts +4 -0
- package/dist/core/formatters.js +2 -1
- package/dist/core/mpp-client.d.ts +62 -0
- package/dist/core/mpp-client.js +208 -0
- package/dist/core/payments.js +3 -3
- package/dist/core/solana-charge.d.ts +1 -0
- package/dist/core/solana-charge.js +7 -3
- package/dist/core/tempo-charge.js +1 -1
- package/dist/core/types.d.ts +13 -0
- package/dist/index.js +2 -0
- package/dist/tools/__tests__/wallet.test.js +1 -0
- package/dist/tools/agent-info.js +4 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +1 -0
- package/dist/tools/providers.d.ts +2 -0
- package/dist/tools/providers.js +40 -0
- package/dist/tools/run.js +1 -1
- package/dist/tools/search.js +7 -1
- package/dist/tools/solve.js +1 -1
- package/package.json +3 -4
- package/src/core/__tests__/api-client.test.ts +4 -0
- package/src/core/__tests__/mpp-client.test.ts +53 -0
- package/src/core/__tests__/payments.test.ts +6 -20
- package/src/core/api-client.ts +29 -5
- package/src/core/base-charge.ts +1 -1
- package/src/core/formatters.ts +3 -1
- package/src/core/mpp-client.ts +286 -0
- package/src/core/payments.ts +3 -3
- package/src/core/solana-charge.ts +7 -3
- package/src/core/tempo-charge.ts +1 -1
- package/src/core/types.ts +13 -0
- package/src/index.ts +2 -0
- package/src/tools/__tests__/wallet.test.ts +1 -0
- package/src/tools/agent-info.ts +11 -1
- package/src/tools/index.ts +1 -0
- package/src/tools/providers.ts +64 -0
- package/src/tools/run.ts +1 -1
- package/src/tools/search.ts +5 -1
- package/src/tools/solve.ts +1 -1
|
@@ -45,6 +45,10 @@ describe("api-client headers", () => {
|
|
|
45
45
|
Accept: "application/json",
|
|
46
46
|
"X-AW-Consumer-Principal": "did:pkh:solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:42W2HfLfveSm1T5et9WTLp2CZ2QXdF2EYCUvyJ2gPpxF",
|
|
47
47
|
"X-AW-Rebate-Principal": "did:pkh:eip155:8453:0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
48
|
+
"X-AW-Surface": "mcp",
|
|
49
|
+
"X-AW-MCP-Version": "0.1.37",
|
|
50
|
+
"X-AW-MCP-Tool": "run_agent",
|
|
51
|
+
"X-AW-MCP-Action": "execute",
|
|
48
52
|
}),
|
|
49
53
|
}));
|
|
50
54
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { Credential, Method, Mppx, z } from "../mpp-client.js";
|
|
3
|
+
describe("local MPP client", () => {
|
|
4
|
+
it("handles a 402 challenge and retries with a payment credential", async () => {
|
|
5
|
+
const challengeRequest = Buffer.from(JSON.stringify({
|
|
6
|
+
amount: "1000000",
|
|
7
|
+
currency: "USDC",
|
|
8
|
+
recipient: "0x0000000000000000000000000000000000000001",
|
|
9
|
+
})).toString("base64url");
|
|
10
|
+
const fetchMock = vi
|
|
11
|
+
.fn()
|
|
12
|
+
.mockResolvedValueOnce(new Response("payment required", {
|
|
13
|
+
status: 402,
|
|
14
|
+
headers: {
|
|
15
|
+
"WWW-Authenticate": `Payment id="test-id", realm="api.test", method="base", intent="charge", request="${challengeRequest}"`,
|
|
16
|
+
},
|
|
17
|
+
}))
|
|
18
|
+
.mockResolvedValueOnce(new Response("ok", { status: 200 }));
|
|
19
|
+
const method = Method.toClient(Method.from({
|
|
20
|
+
name: "base",
|
|
21
|
+
intent: "charge",
|
|
22
|
+
schema: {
|
|
23
|
+
credential: { payload: z.object({ type: z.literal("hash"), hash: z.string() }) },
|
|
24
|
+
request: z.object({ amount: z.string() }),
|
|
25
|
+
},
|
|
26
|
+
}), {
|
|
27
|
+
createCredential({ challenge }) {
|
|
28
|
+
return Credential.serialize({
|
|
29
|
+
challenge,
|
|
30
|
+
payload: { type: "hash", hash: "0xabc" },
|
|
31
|
+
source: "did:pkh:eip155:8453:0x0000000000000000000000000000000000000002",
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
const client = Mppx.create({ fetch: fetchMock, methods: [method], polyfill: false });
|
|
36
|
+
const response = await client.fetch("https://api.test/agents/example/run", {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: { "Content-Type": "application/json" },
|
|
39
|
+
body: "{}",
|
|
40
|
+
});
|
|
41
|
+
expect(response.status).toBe(200);
|
|
42
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
43
|
+
const retryInit = fetchMock.mock.calls[1]?.[1];
|
|
44
|
+
expect(new Headers(retryInit?.headers).get("Authorization")).toMatch(/^Payment\s+/);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -22,7 +22,7 @@ vi.mock("../config.js", () => ({
|
|
|
22
22
|
getWallets: () => currentWallets,
|
|
23
23
|
resolveWalletAndChain: () => currentResolvedMethod,
|
|
24
24
|
}));
|
|
25
|
-
vi.mock("
|
|
25
|
+
vi.mock("../mpp-client.js", () => ({
|
|
26
26
|
Mppx: {
|
|
27
27
|
create: (config) => mockMppxCreate(config),
|
|
28
28
|
},
|
|
@@ -53,20 +53,11 @@ describe("payment method initialization", () => {
|
|
|
53
53
|
.mockReturnValueOnce({ fetch: createdFetches[2] })
|
|
54
54
|
.mockReturnValueOnce({ fetch: createdFetches[3] });
|
|
55
55
|
});
|
|
56
|
-
it("
|
|
56
|
+
it("rejects explicit card payment while card payments are launch-gated", async () => {
|
|
57
57
|
const { getPaymentFetch } = await import("../payments.js");
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
paymentMethodId: "pm_two",
|
|
62
|
-
last4: "2222",
|
|
63
|
-
brand: "mastercard",
|
|
64
|
-
};
|
|
65
|
-
const secondFetch = await getPaymentFetch("card");
|
|
66
|
-
expect(firstFetch).not.toBe(secondFetch);
|
|
67
|
-
expect(mockMppxCreate).toHaveBeenCalledTimes(2);
|
|
68
|
-
expect(mockMppxCreate).toHaveBeenNthCalledWith(1, expect.objectContaining({ polyfill: false }));
|
|
69
|
-
expect(mockMppxCreate).toHaveBeenNthCalledWith(2, expect.objectContaining({ polyfill: false }));
|
|
58
|
+
await expect(getPaymentFetch("card")).rejects.toThrow("Card payments are temporarily unavailable");
|
|
59
|
+
expect(mockMppxCreate).not.toHaveBeenCalled();
|
|
60
|
+
expect(mockStripe).not.toHaveBeenCalled();
|
|
70
61
|
});
|
|
71
62
|
it("initializes only the Base method when base is requested", async () => {
|
|
72
63
|
const wallet = {
|
package/dist/core/api-client.js
CHANGED
|
@@ -12,10 +12,32 @@ export class ApiError extends Error {
|
|
|
12
12
|
this.name = "ApiError";
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
const MCP_VERSION = "0.1.37";
|
|
16
|
+
function inferToolHeaders(path, method) {
|
|
17
|
+
if (path === "/solve") {
|
|
18
|
+
return { "X-AW-MCP-Tool": "solve", "X-AW-MCP-Action": method === "POST" ? "execute" : "view" };
|
|
19
|
+
}
|
|
20
|
+
if (/^\/agents\/[^/]+\/run$/.test(path)) {
|
|
21
|
+
return { "X-AW-MCP-Tool": "run_agent", "X-AW-MCP-Action": method === "POST" ? "execute" : "view" };
|
|
22
|
+
}
|
|
23
|
+
if (path.startsWith("/agents?") || path === "/agents" || path === "/agents/search") {
|
|
24
|
+
return { "X-AW-MCP-Tool": "search_agents", "X-AW-MCP-Action": "search" };
|
|
25
|
+
}
|
|
26
|
+
if (/^\/agents\/[^/]+$/.test(path)) {
|
|
27
|
+
return { "X-AW-MCP-Tool": "get_agent", "X-AW-MCP-Action": "view" };
|
|
28
|
+
}
|
|
29
|
+
if (path.startsWith("/jobs")) {
|
|
30
|
+
return { "X-AW-MCP-Tool": "get_job", "X-AW-MCP-Action": "poll" };
|
|
31
|
+
}
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
async function buildHeaders(path, method, options) {
|
|
16
35
|
const headers = {
|
|
17
36
|
"Content-Type": "application/json",
|
|
18
37
|
Accept: "application/json",
|
|
38
|
+
"X-AW-Surface": "mcp",
|
|
39
|
+
"X-AW-MCP-Version": MCP_VERSION,
|
|
40
|
+
...inferToolHeaders(path, method),
|
|
19
41
|
};
|
|
20
42
|
const apiKey = getApiKey();
|
|
21
43
|
if (apiKey) {
|
|
@@ -86,7 +108,7 @@ export async function apiGet(path, options) {
|
|
|
86
108
|
const url = `${getApiUrl()}${path}`;
|
|
87
109
|
const response = await fetch(url, {
|
|
88
110
|
method: "GET",
|
|
89
|
-
headers: await buildHeaders(options),
|
|
111
|
+
headers: await buildHeaders(path, "GET", options),
|
|
90
112
|
});
|
|
91
113
|
const result = await handleResponse(response);
|
|
92
114
|
return attachResponseMetadata(result, response);
|
|
@@ -95,7 +117,7 @@ export async function apiPost(path, body, options) {
|
|
|
95
117
|
const url = `${getApiUrl()}${path}`;
|
|
96
118
|
const response = await fetch(url, {
|
|
97
119
|
method: "POST",
|
|
98
|
-
headers: await buildHeaders(options),
|
|
120
|
+
headers: await buildHeaders(path, "POST", options),
|
|
99
121
|
body: JSON.stringify(body),
|
|
100
122
|
});
|
|
101
123
|
const result = await handleResponse(response);
|
|
@@ -111,7 +133,7 @@ export async function apiPostWithPayment(path, body, payWith, options) {
|
|
|
111
133
|
const paymentFetch = await getPaymentFetch(payWith);
|
|
112
134
|
const response = await paymentFetch(url, {
|
|
113
135
|
method: "POST",
|
|
114
|
-
headers: await buildHeaders({
|
|
136
|
+
headers: await buildHeaders(path, "POST", {
|
|
115
137
|
ensureConsumerPrincipal: true,
|
|
116
138
|
principalMethod: payWith,
|
|
117
139
|
...options,
|
|
@@ -125,7 +147,7 @@ export async function apiPut(path, body, options) {
|
|
|
125
147
|
const url = `${getApiUrl()}${path}`;
|
|
126
148
|
const response = await fetch(url, {
|
|
127
149
|
method: "PUT",
|
|
128
|
-
headers: await buildHeaders(options),
|
|
150
|
+
headers: await buildHeaders(path, "PUT", options),
|
|
129
151
|
body: JSON.stringify(body),
|
|
130
152
|
});
|
|
131
153
|
const result = await handleResponse(response);
|
package/dist/core/base-charge.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Signs and sends a standard ERC-20 transfer on Base chain, then returns
|
|
5
5
|
* the tx hash as a credential. Plugs into mppx's compose/dispatch system.
|
|
6
6
|
*/
|
|
7
|
-
import { Method, Credential, z } from "
|
|
7
|
+
import { Method, Credential, z } from "./mpp-client.js";
|
|
8
8
|
import { toAtomicAmount } from "./amount-utils.js";
|
|
9
9
|
// Base USDC (Circle native)
|
|
10
10
|
const BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
package/dist/core/formatters.js
CHANGED
|
@@ -44,10 +44,11 @@ export function agentLine(agent) {
|
|
|
44
44
|
const rating = agent.avgRating ?? agent.stats?.avgRating ?? null;
|
|
45
45
|
const jobs = agent.stats?.completedJobs ?? agent.totalExecutions ?? 0;
|
|
46
46
|
const price = formatPrice(agent.pricePerRunUsd);
|
|
47
|
+
const provider = agent.provider?.name ? ` by ${agent.provider.name}` : "";
|
|
47
48
|
const reliability = agent.successRate != null && Number(agent.successRate) < 1
|
|
48
49
|
? ` • ${(Number(agent.successRate) * 100).toFixed(0)}% reliable`
|
|
49
50
|
: "";
|
|
50
|
-
return `${name}${slug} ${stars(rating)} ${compactNumber(jobs)} jobs • ${price}${reliability}`;
|
|
51
|
+
return `${name}${slug}${provider} ${stars(rating)} ${compactNumber(jobs)} jobs • ${price}${reliability}`;
|
|
51
52
|
}
|
|
52
53
|
export function formatLastActive(lastActiveAt) {
|
|
53
54
|
if (!lastActiveAt)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
type Challenge = {
|
|
3
|
+
id: string;
|
|
4
|
+
realm: string;
|
|
5
|
+
method: string;
|
|
6
|
+
intent: string;
|
|
7
|
+
request: Record<string, unknown>;
|
|
8
|
+
description?: string;
|
|
9
|
+
digest?: string;
|
|
10
|
+
expires?: string;
|
|
11
|
+
opaque?: Record<string, string>;
|
|
12
|
+
};
|
|
13
|
+
type ClientMethod = {
|
|
14
|
+
name: string;
|
|
15
|
+
intent: string;
|
|
16
|
+
context?: {
|
|
17
|
+
parse(value: unknown): unknown;
|
|
18
|
+
};
|
|
19
|
+
createCredential(args: {
|
|
20
|
+
challenge: Challenge;
|
|
21
|
+
context?: unknown;
|
|
22
|
+
}): Promise<string> | string;
|
|
23
|
+
};
|
|
24
|
+
type MethodDefinition = {
|
|
25
|
+
name: string;
|
|
26
|
+
intent: string;
|
|
27
|
+
schema?: unknown;
|
|
28
|
+
};
|
|
29
|
+
type CreateConfig = {
|
|
30
|
+
methods: ClientMethod[];
|
|
31
|
+
fetch?: typeof fetch;
|
|
32
|
+
polyfill?: boolean;
|
|
33
|
+
};
|
|
34
|
+
type CredentialInput = {
|
|
35
|
+
challenge: Challenge;
|
|
36
|
+
payload: unknown;
|
|
37
|
+
source?: string;
|
|
38
|
+
};
|
|
39
|
+
export { z };
|
|
40
|
+
export declare const Method: {
|
|
41
|
+
from<T extends MethodDefinition>(method: T): T;
|
|
42
|
+
toClient<T extends MethodDefinition>(method: T, options: {
|
|
43
|
+
context?: {
|
|
44
|
+
parse(value: unknown): unknown;
|
|
45
|
+
};
|
|
46
|
+
createCredential(args: {
|
|
47
|
+
challenge: Challenge;
|
|
48
|
+
context?: unknown;
|
|
49
|
+
}): Promise<string> | string;
|
|
50
|
+
}): T & ClientMethod;
|
|
51
|
+
};
|
|
52
|
+
export declare const Credential: {
|
|
53
|
+
serialize(credential: CredentialInput): string;
|
|
54
|
+
};
|
|
55
|
+
export declare const Mppx: {
|
|
56
|
+
create(config: CreateConfig): {
|
|
57
|
+
fetch: typeof fetch;
|
|
58
|
+
methods: ClientMethod[];
|
|
59
|
+
rawFetch: typeof fetch;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
export declare function stripe(_parameters?: unknown): ClientMethod;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
const PAYMENT_FETCH_WRAPPER = Symbol.for("agentwonderland.mpp.fetch.wrapper");
|
|
3
|
+
export { z };
|
|
4
|
+
export const Method = {
|
|
5
|
+
from(method) {
|
|
6
|
+
return method;
|
|
7
|
+
},
|
|
8
|
+
toClient(method, options) {
|
|
9
|
+
return {
|
|
10
|
+
...method,
|
|
11
|
+
context: options.context,
|
|
12
|
+
createCredential: options.createCredential,
|
|
13
|
+
};
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
export const Credential = {
|
|
17
|
+
serialize(credential) {
|
|
18
|
+
const wire = {
|
|
19
|
+
challenge: {
|
|
20
|
+
...credential.challenge,
|
|
21
|
+
request: serializePaymentRequest(credential.challenge.request),
|
|
22
|
+
},
|
|
23
|
+
payload: credential.payload,
|
|
24
|
+
...(credential.source ? { source: credential.source } : {}),
|
|
25
|
+
};
|
|
26
|
+
return `Payment ${base64UrlEncode(JSON.stringify(wire))}`;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
export const Mppx = {
|
|
30
|
+
create(config) {
|
|
31
|
+
const rawFetch = unwrapFetch(config.fetch ?? globalThis.fetch);
|
|
32
|
+
const methods = config.methods.flat();
|
|
33
|
+
const paymentFetch = createPaymentFetch(rawFetch, methods);
|
|
34
|
+
if (config.polyfill) {
|
|
35
|
+
globalThis.fetch = paymentFetch;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
fetch: paymentFetch,
|
|
39
|
+
methods,
|
|
40
|
+
rawFetch,
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
export function stripe(_parameters) {
|
|
45
|
+
throw new Error("Stripe card payments are temporarily unavailable.");
|
|
46
|
+
}
|
|
47
|
+
function createPaymentFetch(baseFetch, methods) {
|
|
48
|
+
const wrapped = (async (input, init) => {
|
|
49
|
+
const response = await baseFetch(input, init);
|
|
50
|
+
if (response.status !== 402) {
|
|
51
|
+
return response;
|
|
52
|
+
}
|
|
53
|
+
const challenges = challengesFromResponse(response);
|
|
54
|
+
const method = methods.find((candidate) => challenges.some((challenge) => challenge.method === candidate.name && challenge.intent === candidate.intent));
|
|
55
|
+
const challenge = method
|
|
56
|
+
? challenges.find((candidate) => candidate.method === method.name && candidate.intent === method.intent)
|
|
57
|
+
: undefined;
|
|
58
|
+
if (!method || !challenge) {
|
|
59
|
+
throw new Error(`No method found for challenges: ${challenges.map((item) => `${item.method}.${item.intent}`).join(", ")}. ` +
|
|
60
|
+
`Available: ${methods.map((item) => `${item.name}.${item.intent}`).join(", ")}`);
|
|
61
|
+
}
|
|
62
|
+
const context = init?.context;
|
|
63
|
+
const parsedContext = method.context && context !== undefined ? method.context.parse(context) : undefined;
|
|
64
|
+
const credential = await method.createCredential(parsedContext !== undefined ? { challenge, context: parsedContext } : { challenge });
|
|
65
|
+
return baseFetch(input, {
|
|
66
|
+
...init,
|
|
67
|
+
headers: withAuthorizationHeader(init?.headers, credential),
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
wrapped[PAYMENT_FETCH_WRAPPER] = baseFetch;
|
|
71
|
+
return wrapped;
|
|
72
|
+
}
|
|
73
|
+
function challengesFromResponse(response) {
|
|
74
|
+
const header = response.headers.get("WWW-Authenticate");
|
|
75
|
+
if (!header) {
|
|
76
|
+
throw new Error("Missing WWW-Authenticate header.");
|
|
77
|
+
}
|
|
78
|
+
const starts = Array.from(header.matchAll(/Payment\s+/gi), (match) => match.index).filter((index) => index !== undefined);
|
|
79
|
+
if (starts.length === 0) {
|
|
80
|
+
throw new Error("No Payment schemes found.");
|
|
81
|
+
}
|
|
82
|
+
return starts.map((start, index) => {
|
|
83
|
+
const end = index + 1 < starts.length ? starts[index + 1] : header.length;
|
|
84
|
+
return deserializeChallenge(header.slice(start, end).replace(/,\s*$/, ""));
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function deserializeChallenge(value) {
|
|
88
|
+
const paramsStart = value.search(/\s/);
|
|
89
|
+
if (!/^Payment\s+/i.test(value) || paramsStart < 0) {
|
|
90
|
+
throw new Error("Missing Payment scheme.");
|
|
91
|
+
}
|
|
92
|
+
const params = parseAuthParams(value.slice(paramsStart + 1));
|
|
93
|
+
if (!params.request) {
|
|
94
|
+
throw new Error("Missing request parameter.");
|
|
95
|
+
}
|
|
96
|
+
if (!params.id || !params.realm || !params.method || !params.intent) {
|
|
97
|
+
throw new Error("Malformed payment challenge.");
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
id: params.id,
|
|
101
|
+
realm: params.realm,
|
|
102
|
+
method: params.method,
|
|
103
|
+
intent: params.intent,
|
|
104
|
+
request: deserializePaymentRequest(params.request),
|
|
105
|
+
...(params.description ? { description: params.description } : {}),
|
|
106
|
+
...(params.digest ? { digest: params.digest } : {}),
|
|
107
|
+
...(params.expires ? { expires: params.expires } : {}),
|
|
108
|
+
...(params.opaque ? { opaque: deserializePaymentRequest(params.opaque) } : {}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function parseAuthParams(input) {
|
|
112
|
+
const result = {};
|
|
113
|
+
let index = 0;
|
|
114
|
+
while (index < input.length) {
|
|
115
|
+
while (index < input.length && /[\s,]/.test(input[index] ?? ""))
|
|
116
|
+
index++;
|
|
117
|
+
if (index >= input.length)
|
|
118
|
+
break;
|
|
119
|
+
const keyStart = index;
|
|
120
|
+
while (index < input.length && /[A-Za-z0-9_-]/.test(input[index] ?? ""))
|
|
121
|
+
index++;
|
|
122
|
+
const key = input.slice(keyStart, index);
|
|
123
|
+
if (!key)
|
|
124
|
+
throw new Error("Malformed auth-param.");
|
|
125
|
+
while (index < input.length && /\s/.test(input[index] ?? ""))
|
|
126
|
+
index++;
|
|
127
|
+
if (input[index] !== "=")
|
|
128
|
+
break;
|
|
129
|
+
index++;
|
|
130
|
+
while (index < input.length && /\s/.test(input[index] ?? ""))
|
|
131
|
+
index++;
|
|
132
|
+
const [value, nextIndex] = readAuthParamValue(input, index);
|
|
133
|
+
result[key] = value;
|
|
134
|
+
index = nextIndex;
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
function readAuthParamValue(input, start) {
|
|
139
|
+
if (input[start] !== "\"") {
|
|
140
|
+
let index = start;
|
|
141
|
+
while (index < input.length && input[index] !== ",")
|
|
142
|
+
index++;
|
|
143
|
+
return [input.slice(start, index).trim(), index];
|
|
144
|
+
}
|
|
145
|
+
let index = start + 1;
|
|
146
|
+
let value = "";
|
|
147
|
+
let escaped = false;
|
|
148
|
+
while (index < input.length) {
|
|
149
|
+
const char = input[index];
|
|
150
|
+
index++;
|
|
151
|
+
if (escaped) {
|
|
152
|
+
value += char;
|
|
153
|
+
escaped = false;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (char === "\\") {
|
|
157
|
+
escaped = true;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (char === "\"") {
|
|
161
|
+
return [value, index];
|
|
162
|
+
}
|
|
163
|
+
value += char;
|
|
164
|
+
}
|
|
165
|
+
throw new Error("Unterminated quoted-string.");
|
|
166
|
+
}
|
|
167
|
+
function withAuthorizationHeader(headers, credential) {
|
|
168
|
+
const next = new Headers(headers);
|
|
169
|
+
next.set("Authorization", credential);
|
|
170
|
+
return next;
|
|
171
|
+
}
|
|
172
|
+
function unwrapFetch(candidate) {
|
|
173
|
+
let current = candidate;
|
|
174
|
+
while (current[PAYMENT_FETCH_WRAPPER]) {
|
|
175
|
+
current = current[PAYMENT_FETCH_WRAPPER];
|
|
176
|
+
}
|
|
177
|
+
return current;
|
|
178
|
+
}
|
|
179
|
+
function deserializePaymentRequest(encoded) {
|
|
180
|
+
return JSON.parse(base64UrlDecode(encoded));
|
|
181
|
+
}
|
|
182
|
+
function serializePaymentRequest(request) {
|
|
183
|
+
return base64UrlEncode(stableStringify(request));
|
|
184
|
+
}
|
|
185
|
+
function stableStringify(value) {
|
|
186
|
+
if (value === null || typeof value !== "object") {
|
|
187
|
+
return JSON.stringify(value);
|
|
188
|
+
}
|
|
189
|
+
if (Array.isArray(value)) {
|
|
190
|
+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
191
|
+
}
|
|
192
|
+
const record = value;
|
|
193
|
+
return `{${Object.keys(record)
|
|
194
|
+
.sort()
|
|
195
|
+
.map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)
|
|
196
|
+
.join(",")}}`;
|
|
197
|
+
}
|
|
198
|
+
function base64UrlEncode(value) {
|
|
199
|
+
return Buffer.from(value, "utf8")
|
|
200
|
+
.toString("base64")
|
|
201
|
+
.replace(/=/g, "")
|
|
202
|
+
.replace(/\+/g, "-")
|
|
203
|
+
.replace(/\//g, "_");
|
|
204
|
+
}
|
|
205
|
+
function base64UrlDecode(value) {
|
|
206
|
+
const padded = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
|
|
207
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
208
|
+
}
|
package/dist/core/payments.js
CHANGED
|
@@ -54,7 +54,7 @@ function clearStaleCardCache(activeKey) {
|
|
|
54
54
|
// ── Per-protocol initializers ───────────────────────────────────
|
|
55
55
|
async function initEvmMppForChain(wallet, chain) {
|
|
56
56
|
try {
|
|
57
|
-
const { Mppx } = await import("
|
|
57
|
+
const { Mppx } = await import("./mpp-client.js");
|
|
58
58
|
let account;
|
|
59
59
|
if (wallet.keyType === "ows" && wallet.owsWalletId) {
|
|
60
60
|
const { owsAccountFromWalletId } = await import("./ows-adapter.js");
|
|
@@ -88,7 +88,7 @@ async function initSolanaMpp(wallet) {
|
|
|
88
88
|
if (wallet.keyType !== "ows" && !wallet.key) {
|
|
89
89
|
return null;
|
|
90
90
|
}
|
|
91
|
-
const { Mppx } = await import("
|
|
91
|
+
const { Mppx } = await import("./mpp-client.js");
|
|
92
92
|
const { solanaChargeClient } = await import("./solana-charge.js");
|
|
93
93
|
const mppx = Mppx.create({
|
|
94
94
|
methods: [solanaChargeClient({ wallet })],
|
|
@@ -105,7 +105,7 @@ async function initCard() {
|
|
|
105
105
|
if (!cardConfig)
|
|
106
106
|
return null;
|
|
107
107
|
try {
|
|
108
|
-
const { Mppx, stripe } = await import("
|
|
108
|
+
const { Mppx, stripe } = await import("./mpp-client.js");
|
|
109
109
|
const apiUrl = getApiUrl();
|
|
110
110
|
const pmId = cardConfig.paymentMethodId ?? undefined;
|
|
111
111
|
const mppx = Mppx.create({
|
|
@@ -2,6 +2,7 @@ import { Connection, PublicKey } from "@solana/web3.js";
|
|
|
2
2
|
import type { WalletEntry } from "./config.js";
|
|
3
3
|
export declare const SOLANA_USDC_MINT: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
4
4
|
export declare const SOLANA_CHAIN_ID: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
5
|
+
export declare const SOLANA_CHAIN_ID_DEVNET: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG";
|
|
5
6
|
interface SolanaChargeClientConfig {
|
|
6
7
|
wallet: WalletEntry;
|
|
7
8
|
rpcUrl?: string;
|
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
* Sends an SPL Token transfer on Solana mainnet and returns the transaction
|
|
5
5
|
* signature as the payment credential.
|
|
6
6
|
*/
|
|
7
|
-
import { Credential, Method, z } from "
|
|
7
|
+
import { Credential, Method, z } from "./mpp-client.js";
|
|
8
8
|
import { Connection, Keypair, PublicKey, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
|
|
9
9
|
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, createAssociatedTokenAccountInstruction, createTransferCheckedInstruction, getAssociatedTokenAddressSync, } from "@solana/spl-token";
|
|
10
10
|
import { toAtomicAmount } from "./amount-utils.js";
|
|
11
11
|
export const SOLANA_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
12
12
|
export const SOLANA_CHAIN_ID = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
13
|
+
export const SOLANA_CHAIN_ID_DEVNET = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG";
|
|
13
14
|
const SOLANA_RPC = "https://api.mainnet-beta.solana.com";
|
|
15
|
+
const SOLANA_RPC_DEVNET = "https://api.devnet.solana.com";
|
|
14
16
|
const solanaChargeMethod = Method.from({
|
|
15
17
|
name: "solana",
|
|
16
18
|
intent: "charge",
|
|
@@ -83,13 +85,15 @@ export function solanaChargeClient(config) {
|
|
|
83
85
|
return Method.toClient(solanaChargeMethod, {
|
|
84
86
|
async createCredential({ challenge }) {
|
|
85
87
|
const { request } = challenge;
|
|
88
|
+
const chainId = request.chainId ?? request.methodDetails?.chainId ?? SOLANA_CHAIN_ID;
|
|
89
|
+
const rpcUrl = config.rpcUrl ?? (chainId === SOLANA_CHAIN_ID_DEVNET ? SOLANA_RPC_DEVNET : SOLANA_RPC);
|
|
86
90
|
const amount = toAtomicAmount(request.amount, request.decimals ?? 6);
|
|
87
91
|
const decimals = request.decimals ?? 6;
|
|
88
92
|
const mint = new PublicKey(request.currency ?? SOLANA_USDC_MINT);
|
|
89
93
|
const recipient = new PublicKey(request.recipient);
|
|
90
94
|
const keypair = await getKeypair(config.wallet);
|
|
91
95
|
const owner = keypair.publicKey;
|
|
92
|
-
const connection = new Connection(
|
|
96
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
93
97
|
const sourceTokenAccount = getAssociatedTokenAddressSync(mint, owner, false, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
94
98
|
const destination = await resolveRecipientTokenAccount(connection, mint, recipient);
|
|
95
99
|
const instructions = [];
|
|
@@ -111,7 +115,7 @@ export function solanaChargeClient(config) {
|
|
|
111
115
|
return Credential.serialize({
|
|
112
116
|
challenge,
|
|
113
117
|
payload: { signature, type: "signature" },
|
|
114
|
-
source: `did:pkh:${
|
|
118
|
+
source: `did:pkh:${chainId}:${owner.toBase58()}`,
|
|
115
119
|
});
|
|
116
120
|
},
|
|
117
121
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Sends a standard ERC-20 transfer on Tempo and returns the tx hash as the
|
|
5
5
|
* payment credential.
|
|
6
6
|
*/
|
|
7
|
-
import { Method, Credential, z } from "
|
|
7
|
+
import { Method, Credential, z } from "./mpp-client.js";
|
|
8
8
|
import { toAtomicAmount } from "./amount-utils.js";
|
|
9
9
|
const TEMPO_USDC = "0x20c000000000000000000000b9537d11c60e8b50";
|
|
10
10
|
const PATH_USD = "0x20c0000000000000000000000000000000000000";
|
package/dist/core/types.d.ts
CHANGED
|
@@ -18,6 +18,19 @@ export interface AgentRecord {
|
|
|
18
18
|
ratingCount?: number;
|
|
19
19
|
[key: string]: unknown;
|
|
20
20
|
};
|
|
21
|
+
provider?: {
|
|
22
|
+
id?: string;
|
|
23
|
+
name?: string;
|
|
24
|
+
slug?: string;
|
|
25
|
+
website_url?: string | null;
|
|
26
|
+
} | null;
|
|
27
|
+
urls?: {
|
|
28
|
+
public_url?: string;
|
|
29
|
+
normalized_x402_url?: string;
|
|
30
|
+
mirrored_x402_url?: string | null;
|
|
31
|
+
agent_card_url?: string;
|
|
32
|
+
provider_url?: string | null;
|
|
33
|
+
};
|
|
21
34
|
payment?: {
|
|
22
35
|
pricing?: Record<string, unknown>;
|
|
23
36
|
accepted_payments?: string[];
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { registerTipTools } from "./tools/tip.js";
|
|
|
14
14
|
import { registerPassTools } from "./tools/passes.js";
|
|
15
15
|
import { registerUploadTools } from "./tools/upload.js";
|
|
16
16
|
import { registerProbeTools } from "./tools/probe.js";
|
|
17
|
+
import { registerProviderTools } from "./tools/providers.js";
|
|
17
18
|
// ── Resources ────────────────────────────────────────────────────
|
|
18
19
|
import { registerAgentResources } from "./resources/agents.js";
|
|
19
20
|
import { registerWalletResources } from "./resources/wallet.js";
|
|
@@ -77,6 +78,7 @@ export async function startMcpServer() {
|
|
|
77
78
|
registerPassTools(server);
|
|
78
79
|
registerUploadTools(server);
|
|
79
80
|
registerProbeTools(server);
|
|
81
|
+
registerProviderTools(server);
|
|
80
82
|
// Register resources
|
|
81
83
|
registerAgentResources(server);
|
|
82
84
|
registerWalletResources(server);
|
|
@@ -56,6 +56,7 @@ vi.mock("../../core/config.js", () => ({
|
|
|
56
56
|
}));
|
|
57
57
|
vi.mock("../../core/payments.js", () => ({
|
|
58
58
|
getWalletAddress: async () => null,
|
|
59
|
+
isCardPaymentEnabled: () => true,
|
|
59
60
|
}));
|
|
60
61
|
vi.mock("../../core/card-setup.js", () => ({
|
|
61
62
|
formatCardSetupBlocks: () => state.cardSetupBlocks,
|
package/dist/tools/agent-info.js
CHANGED
|
@@ -15,6 +15,8 @@ export function registerAgentInfoTools(server) {
|
|
|
15
15
|
const _pricing = (payment.pricing ?? {});
|
|
16
16
|
const creditPacks = payment.credit_packs;
|
|
17
17
|
const totalJobs = (s.completedJobs ?? a.totalExecutions ?? 0);
|
|
18
|
+
const provider = a.provider ?? null;
|
|
19
|
+
const urls = a.urls ?? {};
|
|
18
20
|
const acceptedPayments = payment.accepted_payments ?? [];
|
|
19
21
|
const paymentLabelMap = {
|
|
20
22
|
tempo_usdc: "tempo",
|
|
@@ -29,6 +31,7 @@ export function registerAgentInfoTools(server) {
|
|
|
29
31
|
"",
|
|
30
32
|
a.description ?? "",
|
|
31
33
|
"",
|
|
34
|
+
...(provider?.name ? [`Provider: ${provider.name}${provider.slug ? ` (${provider.slug})` : ""}`] : []),
|
|
32
35
|
`Pricing: ${formatPrice(a.pricePerRunUsd)}`,
|
|
33
36
|
...(paymentLabels.length > 0 ? [`Accepted payments: ${paymentLabels.join(", ")}`] : []),
|
|
34
37
|
...(totalJobs > 0 && a.successRate != null
|
|
@@ -91,7 +94,7 @@ export function registerAgentInfoTools(server) {
|
|
|
91
94
|
lines.push(` ${name}: ${def.type ?? "string"}${req}${desc}`);
|
|
92
95
|
}
|
|
93
96
|
}
|
|
94
|
-
lines.push("", `ID: ${a.id}`, `View: ${agentWebUrl(a.id)}`);
|
|
97
|
+
lines.push("", `ID: ${a.id}`, `View: ${urls.public_url ?? agentWebUrl(a.id)}`, ...(urls.mirrored_x402_url ? [`Mirrored x402: ${urls.mirrored_x402_url}`] : []), ...(urls.normalized_x402_url ? [`Agent x402: ${urls.normalized_x402_url}`] : []), ...(urls.agent_card_url ? [`AgentCard: ${urls.agent_card_url}`] : []));
|
|
95
98
|
return text(lines.join("\n"));
|
|
96
99
|
});
|
|
97
100
|
}
|
package/dist/tools/index.d.ts
CHANGED
package/dist/tools/index.js
CHANGED