@paidfor/mcp 0.1.0 → 0.2.1

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/README.md CHANGED
@@ -2,14 +2,30 @@
2
2
 
3
3
  MCP server for [paidfor.dev](https://paidfor.dev), the x402 seller failure index on Base mainnet.
4
4
 
5
+ **No account, no key.** 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. An email address is asked for only to deliver alerts or to join a waitlist, as a confirmation link, never a profile.
6
+
5
7
  Two tools:
6
8
 
7
9
  - `check_x402_seller({ url })`: verdict (`avoid`, `caution`, `ok`, `unknown`) and one sentence for an x402 resource URL or seller domain, with the seller's paid-call count, failure rate and 95% interval, the resource's listed price, modal quote and max overcharge ratio when known, and links to the seller page and raw evidence.
8
10
  - `list_reliable_sellers({ category?, limit? })`: zero-failure sellers with at least 30 paid calls.
9
11
 
12
+ On initialize the server sends `instructions` telling the client to call `check_x402_seller` before any x402 payment or retry, unprompted, and how to act on each verdict; the text is `INSTRUCTIONS` in `src/server.ts`, shared by every transport. Per-client setup: https://paidfor.dev/install.
13
+
10
14
  Both read the public JSON API (`GET https://paidfor.dev/v1/check`, `GET https://paidfor.dev/v1/sellers`). No keys, no rate limit for now.
11
15
 
12
- ## Claude Desktop
16
+ ## Hosted (Streamable HTTP)
17
+
18
+ `https://mcp.paidfor.dev/mcp`; a plain `GET` on it (or on `/status`) returns the service status with the data `generated_at` it last saw. `/health` on that host is answered by Manufact's gateway. Cursor, Claude Code and any Streamable HTTP client take the URL directly; Claude Desktop takes it under Settings, Connectors, or through `mcp-remote`:
19
+
20
+ ```json
21
+ { "mcpServers": { "paidfor": { "url": "https://mcp.paidfor.dev/mcp" } } }
22
+ ```
23
+
24
+ ```json
25
+ { "mcpServers": { "paidfor": { "command": "npx", "args": ["-y", "mcp-remote", "https://mcp.paidfor.dev/mcp"] } } }
26
+ ```
27
+
28
+ ## Stdio (Claude Desktop, Cursor, anything that runs a command)
13
29
 
14
30
  ```json
15
31
  {
@@ -19,16 +35,34 @@ Both read the public JSON API (`GET https://paidfor.dev/v1/check`, `GET https://
19
35
  }
20
36
  ```
21
37
 
22
- ## Cursor / any Streamable HTTP client
23
-
24
- ```json
25
- { "mcpServers": { "paidfor": { "url": "https://paidfor.dev/mcp" } } }
26
- ```
27
-
28
38
  ## Run the HTTP service yourself
29
39
 
40
+ This folder is a standalone Node project (own `package.json` and `pnpm-lock.yaml`):
41
+
30
42
  ```bash
31
- npm install && npm run build && PORT=3402 node dist/http.js
43
+ pnpm install && pnpm build && pnpm start # node dist/index.js on 0.0.0.0:$PORT (default 3000)
32
44
  ```
33
45
 
34
- `PAIDFOR_URL` overrides the site origin it reads from. `railway up` or `fly launch` in this folder deploys it with the included Dockerfile.
46
+ - `PORT` (default `3000`), `HOST` (default `0.0.0.0`).
47
+ - `PAIDFOR_API_BASE` (default `https://paidfor.dev`): the site it reads from. The older `PAIDFOR_URL` still works.
48
+ - `GET /mcp` without `Accept: text/event-stream`, `GET /status` and `GET /health` all return `{ ok, site, data_generated_at, uptime_s, tools, ... }`; `POST /mcp` is the MCP endpoint. Behind Manufact's gateway `/health` is answered by the gateway, so use `/mcp` or `/status` there.
49
+
50
+ The server is plain `@modelcontextprotocol/sdk`; there is no platform SDK in the dependencies, so the same folder runs on any Node host.
51
+
52
+ ## Where it is deployed
53
+
54
+ Manufact Cloud, from the `mcp/` subfolder of `sarmadahsan/paidfor-index` (root directory `mcp`), on the **Node auto-detect path**: `package.json` plus `pnpm-lock.yaml` select pnpm, the pinned `packageManager` version is used, and the `build` then `start` scripts run. There is deliberately no `Dockerfile` at this level, since Manufact would use one as-is; the container build lives in `deploy/Dockerfile` for the exit path (Fly, Railway, any VM), see `deploy/README.md`. The custom domain `mcp.paidfor.dev` is a CNAME to Manufact; the slug URL Manufact assigns keeps working too.
55
+
56
+ Usage is counted by Manufact's gateway (every `tools/call` with client and session); the service itself keeps no counters. The site's own `/mcp` route serves the same server for the case where the hosted one is down.
57
+
58
+ ## Trigger eval
59
+
60
+ `pnpm build && pnpm eval` runs ten user messages that never mention paidfor through the Anthropic API (`ANTHROPIC_API_KEY` from the environment or an untracked `eval/.env`; model `claude-sonnet-5-5` or `EVAL_MODEL`) with the server's tools and instructions attached, by default with the tools deferred as Claude Code presents them, and prints which ones called the right tool first. Results by date in `eval/RESULTS.md`.
61
+
62
+ ## Claude Desktop extension
63
+
64
+ `pnpm mcpb` bundles the stdio server with esbuild, writes `manifest.json` at the package version, validates and packs it with `@anthropic-ai/mcpb`, and copies it to the site's `public/paidfor.mcpb` (served at https://paidfor.dev/paidfor.mcpb).
65
+
66
+ ## Registry
67
+
68
+ `server.json` is the Official MCP Registry entry (`io.github.sarmadahsan/paidfor`, matching `mcpName` in `package.json`). Publish npm first, then `mcp-publisher login github && mcp-publisher publish`.
package/dist/index.js ADDED
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The hosted Streamable HTTP service: `node dist/index.js`. Plain @modelcontextprotocol/sdk,
3
+ * no platform SDK, so the same folder runs on Manufact Cloud, Fly, Railway or a bare VM.
4
+ * Stateless: one transport per request. GET /mcp (without Accept: text/event-stream) and
5
+ * GET /status describe the service, including the site it reads and the export's
6
+ * generated_at it last saw; the warm-up ping uses GET /mcp. GET /health is the plain
7
+ * platform check (Manufact's gateway answers /health itself, Fly and Railway forward it).
8
+ *
9
+ * Env: PORT (default 3000), HOST (default 0.0.0.0), PAIDFOR_API_BASE (default https://paidfor.dev).
10
+ */
11
+ import { createServer as createHttpServer } from "node:http";
12
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
13
+ import { createServer, DEFAULT_BASE, TOOL_NAMES, VERSION } from "./server.js";
14
+ const base = (process.env.PAIDFOR_API_BASE ?? process.env.PAIDFOR_URL ?? DEFAULT_BASE).replace(/\/$/, "");
15
+ const port = Number(process.env.PORT ?? 3000);
16
+ const host = process.env.HOST ?? "0.0.0.0";
17
+ const started = Date.now();
18
+ /** generated_at of the paidfor-data export as last seen in a tool response (or the boot-time summary). */
19
+ let dataGeneratedAt = null;
20
+ const noteGeneratedAt = (g) => {
21
+ if (g)
22
+ dataGeneratedAt = g;
23
+ };
24
+ fetch(`${base}/v1/summary`, { headers: { Accept: "application/json", "User-Agent": `paidfor-mcp/${VERSION}` } })
25
+ .then((r) => (r.ok ? r.json() : null))
26
+ .then((j) => noteGeneratedAt(j?.generated_at))
27
+ .catch(() => undefined);
28
+ const CORS = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS", "Access-Control-Allow-Headers": "*" };
29
+ const sendJson = (res, status, body) => {
30
+ res.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store", ...CORS });
31
+ res.end(JSON.stringify(body, null, 2));
32
+ };
33
+ createHttpServer(async (req, res) => {
34
+ const path = (req.url ?? "/").split("?")[0].replace(/\/+$/, "") || "/";
35
+ if (req.method === "OPTIONS") {
36
+ res.writeHead(204, CORS);
37
+ res.end();
38
+ return;
39
+ }
40
+ const status = () => ({
41
+ ok: true,
42
+ service: "paidfor-mcp",
43
+ name: "paidfor",
44
+ version: VERSION,
45
+ transport: "streamable-http",
46
+ endpoint: "/mcp",
47
+ site: base,
48
+ data_generated_at: dataGeneratedAt,
49
+ uptime_s: Math.round((Date.now() - started) / 1000),
50
+ tools: TOOL_NAMES,
51
+ usage: "POST JSON-RPC 2.0 to /mcp with Accept: application/json, text/event-stream",
52
+ docs: `${base}/api`,
53
+ });
54
+ if (path === "/health" || path === "/status") {
55
+ sendJson(res, 200, status());
56
+ return;
57
+ }
58
+ if (path === "/" || path === "/mcp") {
59
+ if (req.method === "GET" && !(req.headers.accept ?? "").includes("text/event-stream")) {
60
+ sendJson(res, 200, status());
61
+ return;
62
+ }
63
+ for (const [k, v] of Object.entries(CORS))
64
+ res.setHeader(k, v);
65
+ const server = createServer({ baseUrl: base, onResponse: noteGeneratedAt });
66
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
67
+ res.on("close", () => {
68
+ void transport.close();
69
+ void server.close();
70
+ });
71
+ await server.connect(transport);
72
+ await transport.handleRequest(req, res);
73
+ return;
74
+ }
75
+ sendJson(res, 404, { error: "not found", endpoints: ["/mcp", "/status", "/health"] });
76
+ }).listen(port, host, () => {
77
+ console.log(`paidfor MCP ${VERSION} (streamable http) listening on ${host}:${port}, MCP at /mcp, reading ${base}`);
78
+ });
package/dist/server.d.ts CHANGED
@@ -1,16 +1,27 @@
1
1
  /**
2
2
  * The paidfor MCP server. One factory, three transports: stdio (npx @paidfor/mcp),
3
- * the standalone Streamable HTTP service (http.ts) and the Next.js route at /mcp.
3
+ * the hosted Streamable HTTP service (index.ts) and the Next.js route at /mcp on the site.
4
4
  * Both tools are thin readers of the public JSON API; the verdict logic lives on the site.
5
5
  */
6
6
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
- export declare const VERSION = "0.1.0";
7
+ export declare const VERSION = "0.2.1";
8
8
  export declare const DEFAULT_BASE = "https://paidfor.dev";
9
9
  export declare const TOOL_NAMES: readonly ["check_x402_seller", "list_reliable_sellers"];
10
+ /**
11
+ * The no-account promise, word for word wherever it appears: the server instructions (last line),
12
+ * this package's README, /methodology, /install.md and /privacy on the site.
13
+ */
14
+ export declare const NO_ACCOUNT_PROMISE: string;
15
+ /** Sent on initialize by every transport (stdio, the hosted service, the site's /mcp route), so clients call the tools unprompted. */
16
+ export declare const INSTRUCTIONS: string;
17
+ export declare const CHECK_DESCRIPTION: string;
18
+ export declare const LIST_DESCRIPTION: string;
10
19
  export type ServerOptions = {
11
- /** Site origin to read from. Defaults to PAIDFOR_URL or https://paidfor.dev. */
20
+ /** Site origin to read from. Defaults to PAIDFOR_API_BASE (or the older PAIDFOR_URL) or https://paidfor.dev. */
12
21
  baseUrl?: string;
13
- /** Called with the tool name on every call, for usage counting. */
22
+ /** Called with the tool name on every call, for usage counting (the site's /mcp route uses it). */
14
23
  onToolCall?: (tool: string) => void;
24
+ /** Called with the export's generated_at from every successful response (the hosted service shows it on /health). */
25
+ onResponse?: (generated_at: string) => void;
15
26
  };
16
27
  export declare function createServer(opts?: ServerOptions): McpServer;
package/dist/server.js CHANGED
@@ -1,14 +1,37 @@
1
1
  /**
2
2
  * The paidfor MCP server. One factory, three transports: stdio (npx @paidfor/mcp),
3
- * the standalone Streamable HTTP service (http.ts) and the Next.js route at /mcp.
3
+ * the hosted Streamable HTTP service (index.ts) and the Next.js route at /mcp on the site.
4
4
  * Both tools are thin readers of the public JSON API; the verdict logic lives on the site.
5
5
  */
6
6
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
7
  import { z } from "zod";
8
- export const VERSION = "0.1.0";
8
+ export const VERSION = "0.2.1";
9
9
  export const DEFAULT_BASE = "https://paidfor.dev";
10
10
  export const TOOL_NAMES = ["check_x402_seller", "list_reliable_sellers"];
11
11
  const CATEGORIES = ["inference", "data", "scraping", "compute", "other"];
12
+ /**
13
+ * The no-account promise, word for word wherever it appears: the server instructions (last line),
14
+ * this package's README, /methodology, /install.md and /privacy on the site.
15
+ */
16
+ 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. " +
17
+ "An email address is asked for only to deliver alerts or to join a waitlist, as a confirmation link, never a profile.";
18
+ /** Sent on initialize by every transport (stdio, the hosted service, the site's /mcp route), so clients call the tools unprompted. */
19
+ export const INSTRUCTIONS = "paidfor rates x402 sellers by paying them and verifying delivery. " +
20
+ "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, " +
21
+ "call check_x402_seller on the URL or seller domain first, without being asked. " +
22
+ '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; ' +
23
+ '"ok" needs no comment; "unknown" means no data, say so and proceed only on the user\'s say-so. ' +
24
+ "When choosing among sellers for a task, prefer results from list_reliable_sellers. " +
25
+ "Never pay the same seller twice for one task because of a timeout or error without checking first. " +
26
+ "These tools read a public index, need no key, and send nothing but the URL being checked.\n" +
27
+ NO_ACCOUNT_PROMISE;
28
+ 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. " +
29
+ "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, " +
30
+ "listed price, modal quote and max overcharge ratio when known, and links to the seller page and raw evidence.";
31
+ 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. " +
32
+ "Returns sellers with zero failures across at least 30 paid calls, most-called first; optional category filter (inference, data, scraping, compute, other).";
33
+ /** Both tools only read the public index; the directories ask for these hints. */
34
+ const READ_ONLY = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
12
35
  async function getJson(url) {
13
36
  const res = await fetch(url, { headers: { Accept: "application/json", "User-Agent": `paidfor-mcp/${VERSION}` } });
14
37
  if (!res.ok)
@@ -16,22 +39,17 @@ async function getJson(url) {
16
39
  return (await res.json());
17
40
  }
18
41
  export function createServer(opts = {}) {
19
- const base = (opts.baseUrl ?? process.env.PAIDFOR_URL ?? DEFAULT_BASE).replace(/\/$/, "");
20
- const server = new McpServer({ name: "paidfor", version: VERSION }, {
21
- instructions: "paidfor.dev pays x402 sellers on Base mainnet and records which ones fail the buyer. " +
22
- "Call check_x402_seller with the resource URL or seller domain before paying an x402 402 quote; " +
23
- "treat verdict=avoid as a reason not to pay, caution as pay only if the price is small, ok as safe, unknown as no data. " +
24
- "list_reliable_sellers returns zero-failure sellers with at least 30 paid calls.",
25
- });
42
+ const base = (opts.baseUrl ?? process.env.PAIDFOR_API_BASE ?? process.env.PAIDFOR_URL ?? DEFAULT_BASE).replace(/\/$/, "");
43
+ const server = new McpServer({ name: "paidfor", version: VERSION }, { instructions: INSTRUCTIONS });
26
44
  server.registerTool("check_x402_seller", {
27
45
  title: "Check an x402 seller before paying",
28
- description: "Look up an x402 resource URL or seller domain in the paidfor.dev index. Returns a verdict (avoid, caution, ok, unknown), " +
29
- "one sentence to act on, the seller's paid-call count and failure rate with a 95% interval, the resource's listed price, " +
30
- "modal quote and max overcharge ratio when known, and links to the seller page and raw evidence.",
46
+ description: CHECK_DESCRIPTION,
47
+ annotations: READ_ONLY,
31
48
  inputSchema: { url: z.string().min(1).describe("x402 resource URL, or a bare seller domain such as api.example.com") },
32
49
  }, async ({ url }) => {
33
50
  opts.onToolCall?.("check_x402_seller");
34
51
  const r = await getJson(`${base}/v1/check?url=${encodeURIComponent(url)}`);
52
+ opts.onResponse?.(r.generated_at);
35
53
  const short = {
36
54
  verdict: r.verdict,
37
55
  verdict_text: r.verdict_text,
@@ -52,7 +70,8 @@ export function createServer(opts = {}) {
52
70
  });
53
71
  server.registerTool("list_reliable_sellers", {
54
72
  title: "List reliable x402 sellers",
55
- description: "Sellers with zero failures across at least 30 paid calls in the paidfor.dev index, most-called first. Optional category filter: inference, data, scraping, compute, other.",
73
+ description: LIST_DESCRIPTION,
74
+ annotations: READ_ONLY,
56
75
  inputSchema: {
57
76
  category: z.enum(CATEGORIES).optional().describe("Restrict to one category"),
58
77
  limit: z.number().int().min(1).max(500).optional().describe("Max rows, default 50"),
@@ -63,6 +82,7 @@ export function createServer(opts = {}) {
63
82
  if (category)
64
83
  q.set("category", category);
65
84
  const r = await getJson(`${base}/v1/sellers?${q}`);
85
+ opts.onResponse?.(r.generated_at);
66
86
  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 }));
67
87
  const head = `${rows.length} sellers with zero failures across at least 30 paid calls${category ? ` in ${category}` : ""} (index generated ${r.generated_at}).`;
68
88
  return { content: [{ type: "text", text: `${head}\n\n${JSON.stringify(rows, null, 2)}` }] };
package/package.json CHANGED
@@ -1,29 +1,53 @@
1
1
  {
2
2
  "name": "@paidfor/mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
+ "mcpName": "io.github.sarmadahsan/paidfor",
4
5
  "description": "MCP server for the paidfor.dev x402 seller index: check_x402_seller and list_reliable_sellers",
5
6
  "license": "MIT",
6
7
  "type": "module",
7
- "bin": { "paidfor-mcp": "./dist/stdio.js" },
8
- "publishConfig": { "access": "public" },
8
+ "bin": {
9
+ "paidfor-mcp": "dist/stdio.js"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
9
14
  "main": "./dist/server.js",
10
- "exports": { ".": "./dist/server.js" },
11
- "files": ["dist", "README.md"],
12
- "engines": { "node": ">=20" },
13
- "scripts": {
14
- "build": "tsc -p tsconfig.json",
15
- "start": "node dist/http.js",
16
- "dev": "tsc -p tsconfig.json && node dist/http.js",
17
- "prepublishOnly": "pnpm build"
15
+ "exports": {
16
+ ".": "./dist/server.js"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "engines": {
23
+ "node": ">=20"
18
24
  },
19
25
  "dependencies": {
20
26
  "@modelcontextprotocol/sdk": "^1.32.2",
21
27
  "zod": "^4.0.0"
22
28
  },
23
29
  "devDependencies": {
30
+ "@anthropic-ai/sdk": "^1.3.0",
24
31
  "@types/node": "^22",
25
32
  "typescript": "^5.9"
26
33
  },
27
- "repository": { "type": "git", "url": "https://github.com/sarmadahsan/paidfor-index", "directory": "mcp" },
28
- "keywords": ["mcp", "x402", "paidfor", "base", "usdc"]
29
- }
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/sarmadahsan/paidfor-index.git",
37
+ "directory": "mcp"
38
+ },
39
+ "keywords": [
40
+ "mcp",
41
+ "x402",
42
+ "paidfor",
43
+ "base",
44
+ "usdc"
45
+ ],
46
+ "scripts": {
47
+ "build": "tsc -p tsconfig.json",
48
+ "start": "node dist/index.js",
49
+ "dev": "tsc -p tsconfig.json && node dist/index.js",
50
+ "eval": "node --env-file-if-exists=eval/.env eval/run.mjs",
51
+ "mcpb": "node mcpb/build.mjs"
52
+ }
53
+ }
package/dist/http.js DELETED
@@ -1,49 +0,0 @@
1
- /**
2
- * Standalone Streamable HTTP service (Railway / Fly / any Node host). Stateless: one
3
- * transport per request. GET /health for the platform health check.
4
- */
5
- import { createServer as createHttpServer } from "node:http";
6
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
7
- import { createServer, TOOL_NAMES, VERSION } from "./server.js";
8
- const port = Number(process.env.PORT ?? 3402);
9
- const counts = new Map();
10
- const bump = (k) => counts.set(k, (counts.get(k) ?? 0) + 1);
11
- const CORS = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS", "Access-Control-Allow-Headers": "*" };
12
- createHttpServer(async (req, res) => {
13
- const path = (req.url ?? "/").split("?")[0];
14
- if (req.method === "OPTIONS") {
15
- res.writeHead(204, CORS);
16
- res.end();
17
- return;
18
- }
19
- if (path === "/health") {
20
- res.writeHead(200, { "Content-Type": "text/plain", ...CORS });
21
- res.end("ok");
22
- return;
23
- }
24
- if (path === "/usage") {
25
- res.writeHead(200, { "Content-Type": "application/json", ...CORS });
26
- res.end(JSON.stringify(Object.fromEntries(counts)));
27
- return;
28
- }
29
- if (path === "/" || path === "/mcp") {
30
- if (req.method === "GET" && !(req.headers.accept ?? "").includes("text/event-stream")) {
31
- res.writeHead(200, { "Content-Type": "application/json", ...CORS });
32
- res.end(JSON.stringify({ name: "paidfor", version: VERSION, transport: "streamable-http", tools: TOOL_NAMES, usage: "POST JSON-RPC 2.0 to this URL" }));
33
- return;
34
- }
35
- bump("request");
36
- for (const [k, v] of Object.entries(CORS))
37
- res.setHeader(k, v);
38
- const server = createServer({ onToolCall: bump });
39
- const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
40
- res.on("close", () => { void transport.close(); void server.close(); });
41
- await server.connect(transport);
42
- await transport.handleRequest(req, res);
43
- return;
44
- }
45
- res.writeHead(404, CORS);
46
- res.end();
47
- }).listen(port, () => {
48
- console.log(`paidfor MCP (streamable http) listening on :${port}, reading ${process.env.PAIDFOR_URL ?? "https://paidfor.dev"}`);
49
- });
File without changes