@paidfor/mcp 0.3.0 → 0.3.2

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/index.js CHANGED
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { createServer as createHttpServer } from "node:http";
12
12
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
13
- import { createServer, DEFAULT_BASE, TOOL_NAMES, VERSION } from "./server.js";
13
+ import { createServer, DEFAULT_BASE, HOSTED_CLIENT_HEADER, HOSTED_INSTRUCTIONS, TOOL_NAMES, VERSION } from "./server.js";
14
14
  const base = (process.env.PAIDFOR_API_BASE ?? process.env.PAIDFOR_URL ?? DEFAULT_BASE).replace(/\/$/, "");
15
15
  const port = Number(process.env.PORT ?? 3000);
16
16
  const host = process.env.HOST ?? "0.0.0.0";
@@ -21,7 +21,7 @@ const noteGeneratedAt = (g) => {
21
21
  if (g)
22
22
  dataGeneratedAt = g;
23
23
  };
24
- fetch(`${base}/v1/summary`, { headers: { Accept: "application/json", "User-Agent": `paidfor-mcp/${VERSION}` } })
24
+ fetch(`${base}/v1/summary`, { headers: { Accept: "application/json", "User-Agent": `paidfor-mcp/${VERSION}`, "X-Paidfor-Client": HOSTED_CLIENT_HEADER } })
25
25
  .then((r) => (r.ok ? r.json() : null))
26
26
  .then((j) => noteGeneratedAt(j?.generated_at))
27
27
  .catch(() => undefined);
@@ -62,7 +62,7 @@ createHttpServer(async (req, res) => {
62
62
  }
63
63
  for (const [k, v] of Object.entries(CORS))
64
64
  res.setHeader(k, v);
65
- const server = createServer({ baseUrl: base, onResponse: noteGeneratedAt });
65
+ const server = createServer({ baseUrl: base, onResponse: noteGeneratedAt, clientHeader: HOSTED_CLIENT_HEADER, instructions: HOSTED_INSTRUCTIONS });
66
66
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
67
67
  res.on("close", () => {
68
68
  void transport.close();
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The one copy of the rules an agent follows: when to check, what each verdict means, and never paying twice.
3
+ * The server instructions and the tool descriptions (server.ts) and the skill at paidfor.dev/skill.md (lib/skill.ts on
4
+ * the site) are all generated from these constants, so the three copies cannot drift.
5
+ *
6
+ * Changing any text here changes the skill: bump RULES_REVISION (the skill's `skill-revision`) and update RULES_SHA;
7
+ * lib/skill.test.ts fails until both move together.
8
+ */
9
+ export declare const RULES_REVISION = "2026-10-29";
10
+ /** When to call check_x402_seller (GET /v1/check), in order. */
11
+ export declare const CHECK_TRIGGERS: readonly ["before any x402 payment", "before retrying a request that answered HTTP 402", "whenever the user is about to buy an API call with USDC on Base or names an API they intend to pay per call"];
12
+ /** When to call list_reliable_sellers (GET /v1/sellers?zero_failures=1). */
13
+ export declare const CHOOSE_TRIGGER = "when choosing among sellers for a task";
14
+ /** When to call recent_changes (GET /v1/changes). */
15
+ export declare const CHANGES_TRIGGER = "when the user asks what changed, or before relying on a verdict older than a day";
16
+ /** What to do for each verdict. */
17
+ export declare const VERDICT_RULES: {
18
+ readonly avoid: "do not pay an \"avoid\" seller and say why with the evidence link";
19
+ readonly caution: "before paying a \"caution\" seller tell the user the failure rate";
20
+ readonly ok: "\"ok\" needs no comment";
21
+ readonly unknown: "\"unknown\" means no data, say so and proceed only on the user's say-so";
22
+ };
23
+ export declare const NEVER_PAY_TWICE = "Never pay the same seller twice for one task because of a timeout or error without checking first.";
24
+ export declare const SENDS_ONLY_URL = "These tools read a public index, need no key, and send nothing but the URL being checked.";
25
+ /** sha256 of the JSON of every constant above (lib/skill.test.ts recomputes it). */
26
+ export declare const RULES_SHA = "7258b901f6577a02dfe997416d3de61e122f5da21476eb686dd1c1a9379bffc6";
27
+ /** "a, b, and c" */
28
+ export declare function listAnd(xs: readonly string[]): string;
29
+ /** "a, b, or c" */
30
+ export declare function listOr(xs: readonly string[]): string;
31
+ export declare const cap: (s: string) => string;
32
+ /** The verdict rules as one sentence (server instructions, skill). */
33
+ export declare const VERDICT_SENTENCE: string;
package/dist/rules.js ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The one copy of the rules an agent follows: when to check, what each verdict means, and never paying twice.
3
+ * The server instructions and the tool descriptions (server.ts) and the skill at paidfor.dev/skill.md (lib/skill.ts on
4
+ * the site) are all generated from these constants, so the three copies cannot drift.
5
+ *
6
+ * Changing any text here changes the skill: bump RULES_REVISION (the skill's `skill-revision`) and update RULES_SHA;
7
+ * lib/skill.test.ts fails until both move together.
8
+ */
9
+ export const RULES_REVISION = "2026-10-29";
10
+ /** When to call check_x402_seller (GET /v1/check), in order. */
11
+ export const CHECK_TRIGGERS = [
12
+ "before any x402 payment",
13
+ "before retrying a request that answered HTTP 402",
14
+ "whenever the user is about to buy an API call with USDC on Base or names an API they intend to pay per call",
15
+ ];
16
+ /** When to call list_reliable_sellers (GET /v1/sellers?zero_failures=1). */
17
+ export const CHOOSE_TRIGGER = "when choosing among sellers for a task";
18
+ /** When to call recent_changes (GET /v1/changes). */
19
+ export const CHANGES_TRIGGER = "when the user asks what changed, or before relying on a verdict older than a day";
20
+ /** What to do for each verdict. */
21
+ export const VERDICT_RULES = {
22
+ avoid: 'do not pay an "avoid" seller and say why with the evidence link',
23
+ caution: 'before paying a "caution" seller tell the user the failure rate',
24
+ ok: '"ok" needs no comment',
25
+ unknown: '"unknown" means no data, say so and proceed only on the user\'s say-so',
26
+ };
27
+ export const NEVER_PAY_TWICE = "Never pay the same seller twice for one task because of a timeout or error without checking first.";
28
+ export const SENDS_ONLY_URL = "These tools read a public index, need no key, and send nothing but the URL being checked.";
29
+ /** sha256 of the JSON of every constant above (lib/skill.test.ts recomputes it). */
30
+ export const RULES_SHA = "7258b901f6577a02dfe997416d3de61e122f5da21476eb686dd1c1a9379bffc6";
31
+ /** "a, b, and c" */
32
+ export function listAnd(xs) {
33
+ return xs.length < 2 ? (xs[0] ?? "") : `${xs.slice(0, -1).join(", ")}, and ${xs.at(-1)}`;
34
+ }
35
+ /** "a, b, or c" */
36
+ export function listOr(xs) {
37
+ return xs.length < 2 ? (xs[0] ?? "") : `${xs.slice(0, -1).join(", ")}, or ${xs.at(-1)}`;
38
+ }
39
+ export const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
40
+ /** The verdict rules as one sentence (server instructions, skill). */
41
+ export const VERDICT_SENTENCE = `Follow the verdict: ${[VERDICT_RULES.avoid, VERDICT_RULES.caution, VERDICT_RULES.ok, VERDICT_RULES.unknown].join("; ")}.`;
package/dist/server.d.ts CHANGED
@@ -5,7 +5,12 @@
5
5
  * The paying tools (pay.ts) are added by stdio.ts alone, and only when a wallet is configured.
6
6
  */
7
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
- export declare const VERSION = "0.3.0";
8
+ export * from "./rules.js";
9
+ export declare const VERSION = "0.3.2";
10
+ /** Sent on every request to the index so usage can be counted by channel (never anything else). */
11
+ export declare const CLIENT_HEADER = "mcp/0.3.2";
12
+ /** What the hosted service (index.ts) sends instead, so the index can count hosted and stdio use apart. */
13
+ export declare const HOSTED_CLIENT_HEADER = "mcp-hosted/0.3.2";
9
14
  export declare const DEFAULT_BASE = "https://paidfor.dev";
10
15
  export declare const TOOL_NAMES: readonly ["check_x402_seller", "list_reliable_sellers", "recent_changes"];
11
16
  /**
@@ -13,8 +18,15 @@ export declare const TOOL_NAMES: readonly ["check_x402_seller", "list_reliable_s
13
18
  * this package's README, /methodology, /install.md and /privacy on the site.
14
19
  */
15
20
  export declare const NO_ACCOUNT_PROMISE: string;
16
- /** Sent on initialize by every transport (stdio, the hosted service, the site's /mcp route), so clients call the tools unprompted. */
21
+ /** Sent on initialize by every transport (stdio, the hosted service, the site's /mcp route), so clients call the tools unprompted.
22
+ * Generated from rules.ts, like the tool descriptions below and the skill at paidfor.dev/skill.md. */
17
23
  export declare const INSTRUCTIONS: string;
24
+ /**
25
+ * What the hosted transports (index.ts, the site's /mcp route) send instead of INSTRUCTIONS: the same facts, described
26
+ * rather than directed, because the Connectors Directory review rejects text that tells Claude how to behave or to call
27
+ * tools unprompted. stdio and the skill keep INSTRUCTIONS; the tool descriptions are shared by both.
28
+ */
29
+ export declare const HOSTED_INSTRUCTIONS: string;
18
30
  export declare const CHECK_DESCRIPTION: string;
19
31
  export declare const LIST_DESCRIPTION: string;
20
32
  export declare const CHANGES_DESCRIPTION: string;
@@ -25,7 +37,22 @@ export type ServerOptions = {
25
37
  onToolCall?: (tool: string) => void;
26
38
  /** Called with the export's generated_at from every successful response (the hosted service shows it on /health). */
27
39
  onResponse?: (generated_at: string) => void;
28
- /** Replaces INSTRUCTIONS; stdio.ts passes INSTRUCTIONS_WITH_PAY when it registers pay_x402. */
40
+ /** The X-Paidfor-Client header on every index call; defaults to CLIENT_HEADER (stdio). */
41
+ clientHeader?: string;
42
+ /** Replaces INSTRUCTIONS; stdio.ts passes INSTRUCTIONS_WITH_PAY when it registers pay_x402, the hosted transports HOSTED_INSTRUCTIONS. */
29
43
  instructions?: string;
30
44
  };
45
+ /** Advertised on initialize (Implementation in the MCP schema) for clients and directories. */
46
+ export declare const SERVER_INFO: {
47
+ name: string;
48
+ title: string;
49
+ version: string;
50
+ description: string;
51
+ websiteUrl: string;
52
+ icons: {
53
+ src: string;
54
+ mimeType: string;
55
+ sizes: string[];
56
+ }[];
57
+ };
31
58
  export declare function createServer(opts?: ServerOptions): McpServer;
package/dist/server.js CHANGED
@@ -6,7 +6,13 @@
6
6
  */
7
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
8
  import { z } from "zod";
9
- export const VERSION = "0.3.0";
9
+ import { CHANGES_TRIGGER, CHECK_TRIGGERS, CHOOSE_TRIGGER, NEVER_PAY_TWICE, SENDS_ONLY_URL, VERDICT_SENTENCE, cap, listAnd, listOr } from "./rules.js";
10
+ export * from "./rules.js";
11
+ export const VERSION = "0.3.2";
12
+ /** Sent on every request to the index so usage can be counted by channel (never anything else). */
13
+ export const CLIENT_HEADER = `mcp/${VERSION}`;
14
+ /** What the hosted service (index.ts) sends instead, so the index can count hosted and stdio use apart. */
15
+ export const HOSTED_CLIENT_HEADER = `mcp-hosted/${VERSION}`;
10
16
  export const DEFAULT_BASE = "https://paidfor.dev";
11
17
  export const TOOL_NAMES = ["check_x402_seller", "list_reliable_sellers", "recent_changes"];
12
18
  const CATEGORIES = ["inference", "data", "scraping", "compute", "other"];
@@ -16,41 +22,114 @@ const CATEGORIES = ["inference", "data", "scraping", "compute", "other"];
16
22
  */
17
23
  export const NO_ACCOUNT_PROMISE = "Everything that reads the index or contributes data (the site, the API, the MCP tools, the client, telemetry and the changes feed) needs no account and no key, now and later. " +
18
24
  "An email address is asked for only to deliver alerts or to join a waitlist, as a confirmation link, never a profile.";
19
- /** Sent on initialize by every transport (stdio, the hosted service, the site's /mcp route), so clients call the tools unprompted. */
25
+ /** Sent on initialize by every transport (stdio, the hosted service, the site's /mcp route), so clients call the tools unprompted.
26
+ * Generated from rules.ts, like the tool descriptions below and the skill at paidfor.dev/skill.md. */
20
27
  export const INSTRUCTIONS = "paidfor rates x402 sellers by paying them and verifying delivery. " +
21
- "Before any x402 payment, before retrying a request that answered HTTP 402, and whenever the user is about to buy an API call with USDC on Base, " +
22
- "call check_x402_seller on the URL or seller domain first, without being asked. " +
23
- 'Follow the verdict: do not pay an "avoid" seller and say why with the evidence link; before paying a "caution" seller tell the user the failure rate; ' +
24
- '"ok" needs no comment; "unknown" means no data, say so and proceed only on the user\'s say-so. ' +
25
- "When choosing among sellers for a task, prefer results from list_reliable_sellers. " +
26
- "When the user asks what changed, or before relying on a verdict older than a day, call recent_changes. " +
27
- "Never pay the same seller twice for one task because of a timeout or error without checking first. " +
28
- "These tools read a public index, need no key, and send nothing but the URL being checked.\n" +
28
+ `${cap(listAnd(CHECK_TRIGGERS))}, call check_x402_seller on the URL or seller domain first, without being asked. ` +
29
+ `${VERDICT_SENTENCE} ` +
30
+ `${cap(CHOOSE_TRIGGER)}, prefer results from list_reliable_sellers. ` +
31
+ `${cap(CHANGES_TRIGGER)}, call recent_changes. ` +
32
+ `${NEVER_PAY_TWICE} ` +
33
+ SENDS_ONLY_URL +
34
+ "\n" +
29
35
  NO_ACCOUNT_PROMISE;
30
- export const CHECK_DESCRIPTION = "Call this before paying any x402 seller, before retrying a request that returned 402, or when the user names an API they intend to pay per call. " +
36
+ /**
37
+ * What the hosted transports (index.ts, the site's /mcp route) send instead of INSTRUCTIONS: the same facts, described
38
+ * rather than directed, because the Connectors Directory review rejects text that tells Claude how to behave or to call
39
+ * tools unprompted. stdio and the skill keep INSTRUCTIONS; the tool descriptions are shared by both.
40
+ */
41
+ export const HOSTED_INSTRUCTIONS = "paidfor rates x402 sellers by paying them and verifying delivery. " +
42
+ "check_x402_seller returns a verdict for an x402 resource URL or seller domain, useful before an x402 payment or before retrying a request that answered HTTP 402: " +
43
+ '"avoid" means the seller failed paid calls (the evidence link shows how), "caution" carries the failure rate, "ok" means no failures across its paid calls, and "unknown" means the index has no data on it. ' +
44
+ "list_reliable_sellers returns sellers with zero failures across at least 30 paid calls, for choosing among sellers. " +
45
+ "recent_changes returns what changed in the index, which is diffed hourly. " +
46
+ `${SENDS_ONLY_URL}\n` +
47
+ NO_ACCOUNT_PROMISE;
48
+ export const CHECK_DESCRIPTION = `Call this ${listOr(CHECK_TRIGGERS)}. ` +
31
49
  "Returns a verdict (avoid, caution, ok, unknown), one sentence to act on, the seller's paid-call count and failure rate with a 95% interval, " +
32
50
  "listed price, modal quote and max overcharge ratio when known, and links to the seller page and raw evidence.";
33
- export const LIST_DESCRIPTION = "Call this when choosing which x402 seller to use for a task, or when the user asks for a safe or reliable API. " +
34
- "Returns sellers with zero failures across at least 30 paid calls, most-called first; optional category filter (inference, data, scraping, compute, other).";
35
- export const CHANGES_DESCRIPTION = "Call this when the user asks what changed for an x402 seller or in the index, or before relying on a verdict older than a day. " +
51
+ export const LIST_DESCRIPTION = `Call this ${CHOOSE_TRIGGER}, or when the user asks for a safe or reliable API. ` +
52
+ "Returns sellers with zero failures across at least 30 paid calls, most-called first. For each: domain, category, paid-call count, " +
53
+ "the 95% upper bound on its failure rate, median quoted price in USDC, USDC paid to it in the last 30 days, when it was last seen, and its paidfor seller page.";
54
+ export const CHANGES_DESCRIPTION = `Call this ${CHANGES_TRIGGER}. ` +
36
55
  "Returns recent changes, newest first: verdict changes, new failures by type, moved quotes, changed pay-to addresses, sellers added or reaching 30 paid calls, published seller replies, " +
37
- "the operator-reported series maturing or disagreeing with ours; optional host filter and since (ISO time).";
56
+ "the operator-reported series maturing or disagreeing with ours. Each change has its time, type, seller domain, seller page and type-specific details (for example the old and new quote).";
57
+ /** Advertised on initialize (Implementation in the MCP schema) for clients and directories. */
58
+ export const SERVER_INFO = {
59
+ name: "paidfor",
60
+ title: "paidfor: x402 seller reliability",
61
+ version: VERSION,
62
+ description: "Helps an agent decide whether to pay an x402 API seller on Base: verdicts, failure rates and evidence from real paid calls, " +
63
+ "a list of sellers that have never failed, and a feed of recent changes. No account or API key needed.",
64
+ websiteUrl: DEFAULT_BASE,
65
+ icons: [{ src: `${DEFAULT_BASE}/icon.png`, mimeType: "image/png", sizes: ["512x512"] }],
66
+ };
67
+ const BY_TYPE = z.record(z.string(), z.number()).describe("Failure counts by type (non_delivery, overcharge, duplicate, wrong_goods)");
68
+ /** The structured result of check_x402_seller: the same object the text content carries after its lead sentence. */
69
+ const CHECK_OUTPUT = {
70
+ verdict: z.enum(["avoid", "caution", "ok", "unknown"]),
71
+ verdict_text: z.string().describe("One sentence to act on, with the evidence link"),
72
+ host: z.string().nullable(),
73
+ seller: z
74
+ .object({
75
+ domain: z.string(), calls: z.number(), failures: z.number(), failure_rate: z.number(),
76
+ wilson_low: z.number().describe("95% interval lower bound on the failure rate"),
77
+ wilson_high: z.number().describe("95% interval upper bound on the failure rate"),
78
+ by_type: BY_TYPE, low_sample: z.boolean(), last_seen: z.string(),
79
+ })
80
+ .nullable()
81
+ .describe("Measured paid calls to this seller; null when the study never paid it"),
82
+ resource: z
83
+ .object({
84
+ url: z.string(), method: z.string(), listed_price_usdc: z.string().nullable(), modal_quote_usdc: z.string().nullable(),
85
+ max_quote_usdc: z.string().nullable(), max_overcharge_ratio: z.number().nullable(), calls: z.number(), failure_rate: z.number(),
86
+ })
87
+ .nullable()
88
+ .describe("Measured paid calls to this exact URL; null when not measured"),
89
+ probe: z.looseObject({}).nullable().describe("Hourly unpaid probes of the seller's 402 quote; null when not probed"),
90
+ candidate: z.looseObject({}).nullable().describe("Queue state for a seller not yet measured; null otherwise"),
91
+ seller_url: z.string().nullable(),
92
+ evidence_url: z.string().nullable(),
93
+ methodology_url: z.string(),
94
+ generated_at: z.string().describe("When the index was generated"),
95
+ };
96
+ const LIST_OUTPUT = {
97
+ generated_at: z.string().describe("When the index was generated"),
98
+ sellers: z.array(z.object({
99
+ domain: z.string(), category: z.string(), calls: z.number(),
100
+ failure_rate_upper_bound: z.number().describe("95% interval upper bound on the failure rate"),
101
+ median_quote_usdc: z.string().nullable(), inflow_30d_usdc: z.string().nullable(), last_seen: z.string(),
102
+ url: z.string().describe("The seller's paidfor page"),
103
+ })),
104
+ };
105
+ const CHANGES_OUTPUT = {
106
+ count: z.number().describe("Matching changes the index returned (it caps the feed at the newest 500); events holds at most the newest 100"),
107
+ events: z.array(z.object({
108
+ id: z.string(), at: z.string(), type: z.string(), host: z.string(), url: z.string().describe("The seller's paidfor page"),
109
+ data: z.record(z.string(), z.unknown()).describe("Type-specific details"),
110
+ })),
111
+ };
38
112
  /** The read tools only read the public index; the directories ask for these hints. */
39
113
  const READ_ONLY = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
40
- async function getJson(url) {
41
- const res = await fetch(url, { headers: { Accept: "application/json", "User-Agent": `paidfor-mcp/${VERSION}` } });
42
- if (!res.ok)
43
- throw new Error(`${url} returned HTTP ${res.status}`);
114
+ async function getJsonAs(url, client = CLIENT_HEADER) {
115
+ const res = await fetch(url, { headers: { Accept: "application/json", "User-Agent": `paidfor-mcp/${VERSION}`, "X-Paidfor-Client": client } });
116
+ if (!res.ok) {
117
+ // Pass the API's own reason through (e.g. "since must be an ISO 8601 time"), so the caller can fix the input.
118
+ const reason = await res.json().then((j) => j.error, () => undefined);
119
+ throw new Error(`${url} returned HTTP ${res.status}${reason ? `: ${reason}` : ""}`);
120
+ }
44
121
  return (await res.json());
45
122
  }
46
123
  export function createServer(opts = {}) {
47
124
  const base = (opts.baseUrl ?? process.env.PAIDFOR_API_BASE ?? process.env.PAIDFOR_URL ?? DEFAULT_BASE).replace(/\/$/, "");
48
- const server = new McpServer({ name: "paidfor", version: VERSION }, { instructions: opts.instructions ?? INSTRUCTIONS });
125
+ const getJson = (url) => getJsonAs(url, opts.clientHeader);
126
+ const server = new McpServer(SERVER_INFO, { instructions: opts.instructions ?? INSTRUCTIONS });
49
127
  server.registerTool("check_x402_seller", {
50
128
  title: "Check an x402 seller before paying",
51
129
  description: CHECK_DESCRIPTION,
52
130
  annotations: READ_ONLY,
53
131
  inputSchema: { url: z.string().min(1).describe("x402 resource URL, or a bare seller domain such as api.example.com") },
132
+ outputSchema: CHECK_OUTPUT,
54
133
  }, async ({ url }) => {
55
134
  opts.onToolCall?.("check_x402_seller");
56
135
  const r = await getJson(`${base}/v1/check?url=${encodeURIComponent(url)}`);
@@ -66,21 +145,24 @@ export function createServer(opts = {}) {
66
145
  ? { url: r.resource.url, method: r.resource.method, listed_price_usdc: r.resource.listed_price_usdc, modal_quote_usdc: r.resource.modal_quote_usdc, max_quote_usdc: r.resource.max_quote_usdc, max_overcharge_ratio: r.resource.overcharge_ratio, calls: r.resource.calls, failure_rate: r.resource.failure_rate }
67
146
  : null,
68
147
  probe: r.probe,
148
+ candidate: r.candidate ?? null,
69
149
  seller_url: r.seller_url,
70
150
  evidence_url: r.evidence_url,
71
151
  methodology_url: r.methodology_url,
72
152
  generated_at: r.generated_at,
73
153
  };
74
- return { content: [{ type: "text", text: `${r.verdict_text}\n\n${JSON.stringify(short, null, 2)}` }] };
154
+ const lead = r.candidate ? `${r.verdict_text} ${r.candidate.note}` : r.verdict_text;
155
+ return { content: [{ type: "text", text: `${lead}\n\n${JSON.stringify(short, null, 2)}` }], structuredContent: short };
75
156
  });
76
157
  server.registerTool("list_reliable_sellers", {
77
- title: "List reliable x402 sellers",
158
+ title: "List reliable pay-per-call API sellers",
78
159
  description: LIST_DESCRIPTION,
79
160
  annotations: READ_ONLY,
80
161
  inputSchema: {
81
- category: z.enum(CATEGORIES).optional().describe("Restrict to one category"),
82
- limit: z.number().int().min(1).max(500).optional().describe("Max rows, default 50"),
162
+ category: z.enum(CATEGORIES).optional().describe("Kind of service to list sellers for; omit to list every kind"),
163
+ limit: z.number().int().min(1).max(500).optional().describe("How many sellers to return, most-called first; 50 when omitted"),
83
164
  },
165
+ outputSchema: LIST_OUTPUT,
84
166
  }, async ({ category, limit }) => {
85
167
  opts.onToolCall?.("list_reliable_sellers");
86
168
  const q = new URLSearchParams({ min_calls: "30", zero_failures: "1", sort: "calls", limit: String(limit ?? 50) });
@@ -90,16 +172,17 @@ export function createServer(opts = {}) {
90
172
  opts.onResponse?.(r.generated_at);
91
173
  const rows = r.sellers.map((s) => ({ domain: s.domain, category: s.category, calls: s.calls, failure_rate_upper_bound: s.wilson_high, median_quote_usdc: s.median_quote_usdc, inflow_30d_usdc: s.inflow_30d_usdc, last_seen: s.last_seen, url: s.url }));
92
174
  const head = `${rows.length} sellers with zero failures across at least 30 paid calls${category ? ` in ${category}` : ""} (index generated ${r.generated_at}).`;
93
- return { content: [{ type: "text", text: `${head}\n\n${JSON.stringify(rows, null, 2)}` }] };
175
+ return { content: [{ type: "text", text: `${head}\n\n${JSON.stringify(rows, null, 2)}` }], structuredContent: { generated_at: r.generated_at, sellers: rows } };
94
176
  });
95
177
  server.registerTool("recent_changes", {
96
178
  title: "Recent changes to x402 sellers",
97
179
  description: CHANGES_DESCRIPTION,
98
180
  annotations: READ_ONLY,
99
181
  inputSchema: {
100
- host: z.string().min(1).optional().describe("Only this seller, a bare domain such as api.example.com"),
101
- since: z.string().min(1).optional().describe("Only changes after this ISO 8601 time"),
182
+ host: z.string().min(1).optional().describe("Seller to show changes for, as a bare domain such as api.example.com; omit for every seller"),
183
+ since: z.string().min(1).optional().describe("Show only changes after this time, in ISO 8601 (such as the generated_at of a verdict you already have); omit for the latest changes"),
102
184
  },
185
+ outputSchema: CHANGES_OUTPUT,
103
186
  }, async ({ host, since }) => {
104
187
  opts.onToolCall?.("recent_changes");
105
188
  const q = new URLSearchParams();
@@ -112,7 +195,7 @@ export function createServer(opts = {}) {
112
195
  const head = r.count
113
196
  ? `${r.count} changes${host ? ` for ${host}` : ""}${since ? ` since ${since}` : ""}, newest first${r.count > rows.length ? ` (first ${rows.length} shown)` : ""}.`
114
197
  : `No changes${host ? ` for ${host}` : ""}${since ? ` since ${since}` : ""}. The index is diffed hourly.`;
115
- return { content: [{ type: "text", text: `${head}\n\n${JSON.stringify(rows, null, 2)}` }] };
198
+ return { content: [{ type: "text", text: `${head}\n\n${JSON.stringify(rows, null, 2)}` }], structuredContent: { count: r.count, events: rows } };
116
199
  });
117
200
  return server;
118
201
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paidfor/mcp",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "mcpName": "io.github.sarmadahsan/paidfor",
5
5
  "description": "MCP server for the paidfor.dev x402 seller index: check_x402_seller, list_reliable_sellers and recent_changes, plus pay_x402 (stdio, your own wallet)",
6
6
  "license": "MIT",