@true402.dev/mcp-server 0.3.0 → 0.5.0

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
@@ -17,20 +17,29 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
17
17
  import { registerModelsTool } from "./tools/models.js";
18
18
  import { registerChatTool } from "./tools/chat.js";
19
19
  import { registerStallTools } from "./tools/stalls.js";
20
+ import { registerDiscoveredStalls } from "./tools/discover.js";
20
21
  const SERVER_URL = process.env.SERVER_URL ?? "https://true402.dev/api";
21
22
  const WALLET_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY;
22
23
  const server = new McpServer({
23
24
  name: "true402",
24
- version: "0.3.0",
25
+ version: "0.5.0",
25
26
  });
26
- // Register tools
27
- registerModelsTool(server, SERVER_URL);
28
- registerChatTool(server, SERVER_URL, WALLET_PRIVATE_KEY);
29
- // Non-LLM paid stalls: seo_audit, web_extract, link_preview, robots_check,
30
- // headers_check, token_safety (all x402-gated, USDC on Base).
31
- registerStallTools(server, SERVER_URL, WALLET_PRIVATE_KEY);
32
27
  // Start server with stdio transport
33
28
  async function main() {
29
+ // Always-present built-in tools (LLM router — chat uses a $ref body + streaming, so it stays
30
+ // hand-written rather than discovered).
31
+ registerModelsTool(server, SERVER_URL);
32
+ registerChatTool(server, SERVER_URL, WALLET_PRIVATE_KEY);
33
+ // Paid stalls: ASK THE SERVER. Fetch its OpenAPI spec and register an MCP tool for every paid
34
+ // POST endpoint (x-payment-info), so a new marketplace stall surfaces automatically with no MCP
35
+ // release. Falls back to the built-in stall list only if the server is unreachable at startup.
36
+ const discovered = await registerDiscoveredStalls(server, SERVER_URL, WALLET_PRIVATE_KEY);
37
+ if (discovered) {
38
+ console.error(`true402 MCP: discovered ${discovered.length} paid stalls from ${SERVER_URL} (${discovered.join(", ")})`);
39
+ }
40
+ else {
41
+ registerStallTools(server, SERVER_URL, WALLET_PRIVATE_KEY);
42
+ }
34
43
  const transport = new StdioServerTransport();
35
44
  await server.connect(transport);
36
45
  }
@@ -0,0 +1,6 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ /**
3
+ * Fetch `${baseUrl}/openapi.json` and register an MCP tool for each paid POST stall.
4
+ * Returns the registered tool names, or null if discovery failed (caller should fall back).
5
+ */
6
+ export declare function registerDiscoveredStalls(server: McpServer, baseUrl: string, walletPrivateKey: string | undefined): Promise<string[] | null>;
@@ -0,0 +1,93 @@
1
+ import { z } from "zod";
2
+ import { registerStall } from "./stalls.js";
3
+ /** Map one JSON-Schema property to a zod validator (covers the property kinds the stalls use). */
4
+ function toZod(prop) {
5
+ let base;
6
+ if (Array.isArray(prop.enum) && prop.enum.length > 0) {
7
+ base = z.enum(prop.enum.map(String));
8
+ }
9
+ else if (prop.type === "number" || prop.type === "integer") {
10
+ base = z.number();
11
+ }
12
+ else if (prop.type === "boolean") {
13
+ base = z.boolean();
14
+ }
15
+ else {
16
+ base = z.string();
17
+ }
18
+ return prop.description ? base.describe(prop.description) : base;
19
+ }
20
+ /** Build a zod raw shape from a request-body JSON Schema's `properties` + `required`. */
21
+ function bodyToShape(schema) {
22
+ const shape = {};
23
+ const required = new Set(schema?.required ?? []);
24
+ for (const [key, prop] of Object.entries(schema?.properties ?? {})) {
25
+ const v = toZod(prop);
26
+ shape[key] = required.has(key) ? v : v.optional();
27
+ }
28
+ return shape;
29
+ }
30
+ /**
31
+ * Fetch `${baseUrl}/openapi.json` and register an MCP tool for each paid POST stall.
32
+ * Returns the registered tool names, or null if discovery failed (caller should fall back).
33
+ */
34
+ export async function registerDiscoveredStalls(server, baseUrl, walletPrivateKey) {
35
+ let spec;
36
+ try {
37
+ const controller = new AbortController();
38
+ const timer = setTimeout(() => controller.abort(), 10_000);
39
+ const res = await fetch(`${baseUrl}/openapi.json`, {
40
+ headers: { accept: "application/json" },
41
+ signal: controller.signal,
42
+ });
43
+ clearTimeout(timer);
44
+ if (!res.ok)
45
+ throw new Error(`HTTP ${res.status}`);
46
+ spec = (await res.json());
47
+ }
48
+ catch (err) {
49
+ const msg = err instanceof Error ? err.message : String(err);
50
+ console.error(`true402 MCP: stall discovery from ${baseUrl}/openapi.json failed (${msg}); using built-in stall list`);
51
+ return null;
52
+ }
53
+ // OpenAPI path keys carry the base path (e.g. /api/v1/x); strip it so the tool path is relative
54
+ // to baseUrl (which already includes /api), matching how payAndFetch joins baseUrl + path.
55
+ let basePath = "";
56
+ try {
57
+ basePath = new URL(baseUrl).pathname.replace(/\/+$/, "");
58
+ }
59
+ catch {
60
+ /* baseUrl is relative — keys are already root-relative */
61
+ }
62
+ const registered = [];
63
+ for (const [key, ops] of Object.entries(spec.paths ?? {})) {
64
+ const op = ops.post;
65
+ if (!op || op["x-payment-info"] === undefined)
66
+ continue; // paid stalls only
67
+ if (key.endsWith("/chat/completions"))
68
+ continue; // chat is special-cased (hardcoded)
69
+ const rel = basePath && key.startsWith(basePath) ? key.slice(basePath.length) : key;
70
+ const toolName = (rel.replace(/\/+$/, "").split("/").pop() ?? "").replace(/-/g, "_");
71
+ if (!toolName)
72
+ continue;
73
+ const bodySchema = op.requestBody?.content?.["application/json"]?.schema;
74
+ const description = (op.description ?? op.summary ?? `Paid x402 stall ${rel}`) +
75
+ " (PAID x402 service — USDC on Base; the MCP server needs a funded wallet to settle.)";
76
+ registerStall(server, baseUrl, walletPrivateKey, {
77
+ toolName,
78
+ path: rel,
79
+ description,
80
+ inputSchema: bodyToShape(bodySchema),
81
+ // The discovered zod shape already mirrors the body, so pass validated args straight through.
82
+ buildBody: (args) => {
83
+ const body = {};
84
+ for (const [k, v] of Object.entries(args ?? {}))
85
+ if (v !== undefined)
86
+ body[k] = v;
87
+ return body;
88
+ },
89
+ });
90
+ registered.push(toolName);
91
+ }
92
+ return registered;
93
+ }
@@ -1,6 +1,40 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ZodRawShapeCompat, ShapeOutput } from "@modelcontextprotocol/sdk/server/zod-compat.js";
2
3
  /**
3
- * Register all six non-LLM paid stalls (seo_audit, web_extract, link_preview,
4
- * robots_check, headers_check, token_safety) on the given MCP server.
4
+ * Paid, non-LLM stalls exposed by the true402 marketplace.
5
+ *
6
+ * Each of these is an x402-gated POST endpoint that takes a small JSON body and
7
+ * returns a structured JSON report. They are all PAID services (USDC on Base):
8
+ * the MCP server must have a funded wallet (WALLET_PRIVATE_KEY) configured for
9
+ * the calls to settle. With no wallet, the tools surface the 402 payment
10
+ * requirements instead of crashing.
11
+ */
12
+ /**
13
+ * A generic paid stall: an MCP tool that POSTs a body to an x402-gated path and
14
+ * returns the parsed JSON report. The input type is inferred from the zod raw
15
+ * shape `Schema`, and the body is built from the validated tool args via
16
+ * `buildBody`.
17
+ */
18
+ export interface StallSpec<Schema extends ZodRawShapeCompat> {
19
+ /** MCP tool name (e.g. "seo_audit"). */
20
+ toolName: string;
21
+ /** Server-side endpoint path (e.g. "/v1/seo-audit"). */
22
+ path: string;
23
+ /** Agent-facing description. Should make clear this is a PAID x402 service. */
24
+ description: string;
25
+ /** Zod raw shape (plain object of validators) describing the tool inputs. */
26
+ inputSchema: Schema;
27
+ /** Map validated args to the JSON request body sent to the endpoint. */
28
+ buildBody: (args: ShapeOutput<Schema>) => Record<string, unknown>;
29
+ }
30
+ /**
31
+ * Register a single paid stall as an MCP tool. The handler runs the shared
32
+ * x402 pay-and-retry flow and maps the result into the MCP content shape.
33
+ */
34
+ export declare function registerStall<Schema extends ZodRawShapeCompat>(server: McpServer, baseUrl: string, walletPrivateKey: string | undefined, spec: StallSpec<Schema>): void;
35
+ /**
36
+ * Register all nine non-LLM paid stalls on the given MCP server: the web utilities
37
+ * (seo_audit, web_extract, link_preview, robots_check, headers_check) and the Base
38
+ * on-chain trading signals (token_safety, new_pairs, liquidity_pulls, whale_swaps).
5
39
  */
6
40
  export declare function registerStallTools(server: McpServer, baseUrl: string, walletPrivateKey: string | undefined): void;
@@ -4,7 +4,7 @@ import { payAndFetch } from "../x402-pay.js";
4
4
  * Register a single paid stall as an MCP tool. The handler runs the shared
5
5
  * x402 pay-and-retry flow and maps the result into the MCP content shape.
6
6
  */
7
- function registerStall(server, baseUrl, walletPrivateKey, spec) {
7
+ export function registerStall(server, baseUrl, walletPrivateKey, spec) {
8
8
  const handler = async (args) => {
9
9
  const body = spec.buildBody(args);
10
10
  const result = await payAndFetch(baseUrl, spec.path, body, walletPrivateKey);
@@ -31,8 +31,9 @@ const urlField = z
31
31
  .url()
32
32
  .describe("Absolute http(s) URL of the page to process");
33
33
  /**
34
- * Register all six non-LLM paid stalls (seo_audit, web_extract, link_preview,
35
- * robots_check, headers_check, token_safety) on the given MCP server.
34
+ * Register all nine non-LLM paid stalls on the given MCP server: the web utilities
35
+ * (seo_audit, web_extract, link_preview, robots_check, headers_check) and the Base
36
+ * on-chain trading signals (token_safety, new_pairs, liquidity_pulls, whale_swaps).
36
37
  */
37
38
  export function registerStallTools(server, baseUrl, walletPrivateKey) {
38
39
  // 1. SEO + GEO audit.
@@ -131,4 +132,91 @@ export function registerStallTools(server, baseUrl, walletPrivateKey) {
131
132
  return body;
132
133
  },
133
134
  });
135
+ // 7. New token / new pair detection — recently-created Base DEX pairs.
136
+ registerStall(server, baseUrl, walletPrivateKey, {
137
+ toolName: "new_pairs",
138
+ path: "/v1/base/new-pairs",
139
+ description: "Recently-created Base DEX pairs (Uniswap V3 + Aerodrome) — fresh token launches for " +
140
+ "trading/sniper agents. Returns each new token, its quote (WETH/USDC), pool, fee|stable, " +
141
+ "block and approx age, newest first. Bundle with token_safety for a pre-trade rug/honeypot " +
142
+ "check. On-chain log indexing, no API key. PAID x402 service (USDC on Base) — needs a funded " +
143
+ "wallet on the MCP server.",
144
+ inputSchema: {
145
+ since: z.number().int().nonnegative().optional().describe("Only pairs first seen at or after this block"),
146
+ limit: z.number().int().min(1).max(200).optional().describe("Max pairs to return (1–200, default 50)"),
147
+ dex: z.enum(["uniswap-v3", "aerodrome"]).optional().describe("Filter by DEX"),
148
+ withToken: z.boolean().optional().describe("Only token launches (vs all pools); default true"),
149
+ },
150
+ buildBody: ({ since, limit, dex, withToken }) => {
151
+ const body = {};
152
+ if (since !== undefined)
153
+ body.since = since;
154
+ if (limit !== undefined)
155
+ body.limit = limit;
156
+ if (dex !== undefined)
157
+ body.dex = dex;
158
+ if (withToken !== undefined)
159
+ body.withToken = withToken;
160
+ return body;
161
+ },
162
+ });
163
+ // 8. Liquidity-pull / rug alerts — liquidity-removal (Burn) events on tracked Base pools.
164
+ registerStall(server, baseUrl, walletPrivateKey, {
165
+ toolName: "liquidity_pulls",
166
+ path: "/v1/base/liquidity-pulls",
167
+ description: "Liquidity-pull / rug alerts on Base — liquidity-removal (Burn) events on recently-launched " +
168
+ "DEX pools (Uniswap V3 + Aerodrome). Returns the pool, token, and WETH/USDC amount removed " +
169
+ "(the rug magnitude), newest first — an early rug warning. Cross-check the token with " +
170
+ "token_safety. On-chain log indexing, no API key. PAID x402 service (USDC on Base) — needs a " +
171
+ "funded wallet on the MCP server.",
172
+ inputSchema: {
173
+ since: z.number().int().nonnegative().optional().describe("Only pulls first seen at or after this block"),
174
+ limit: z.number().int().min(1).max(200).optional().describe("Max pulls to return (1–200, default 50)"),
175
+ dex: z.enum(["uniswap-v3", "aerodrome"]).optional().describe("Filter by DEX"),
176
+ minQuote: z.number().nonnegative().optional().describe("Only removals of at least this much WETH/USDC"),
177
+ },
178
+ buildBody: ({ since, limit, dex, minQuote }) => {
179
+ const body = {};
180
+ if (since !== undefined)
181
+ body.since = since;
182
+ if (limit !== undefined)
183
+ body.limit = limit;
184
+ if (dex !== undefined)
185
+ body.dex = dex;
186
+ if (minQuote !== undefined)
187
+ body.minQuote = minQuote;
188
+ return body;
189
+ },
190
+ });
191
+ // 9. Whale swaps — large ($-value) Base DEX Swap events (whale-follow / copy-trade signal).
192
+ registerStall(server, baseUrl, walletPrivateKey, {
193
+ toolName: "whale_swaps",
194
+ path: "/v1/base/whale-swaps",
195
+ description: "Recent large ($-value) Base DEX Swap events (whale trades) on tracked pools — a " +
196
+ "whale-following / copy-trade signal. Returns the pool, dex, token, quote (WETH/USDC), USD " +
197
+ "size, direction (buy/sell of the non-quote token), block and approx age, newest first. " +
198
+ "On-chain log indexing, no API key. PAID x402 service (USDC on Base) — needs a funded wallet " +
199
+ "on the MCP server.",
200
+ inputSchema: {
201
+ min: z.number().nonnegative().optional().describe("Only swaps of at least this USD size (default 10000)"),
202
+ dex: z.enum(["uniswap-v3", "aerodrome"]).optional().describe("Filter by DEX"),
203
+ since: z.number().int().nonnegative().optional().describe("Only swaps at or after this block"),
204
+ limit: z.number().int().min(1).max(200).optional().describe("Max swaps to return (1–200, default 50)"),
205
+ direction: z.enum(["buy", "sell"]).optional().describe("Filter by trade direction (of the non-quote token)"),
206
+ },
207
+ buildBody: ({ min, dex, since, limit, direction }) => {
208
+ const body = {};
209
+ if (min !== undefined)
210
+ body.min = min;
211
+ if (dex !== undefined)
212
+ body.dex = dex;
213
+ if (since !== undefined)
214
+ body.since = since;
215
+ if (limit !== undefined)
216
+ body.limit = limit;
217
+ if (direction !== undefined)
218
+ body.direction = direction;
219
+ return body;
220
+ },
221
+ });
134
222
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@true402.dev/mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "mcpName": "dev.true402/mcp-server",
5
- "description": "MCP server for the true402 machine-native marketplace — pay-per-call AI + web tools over x402 (USDC on Base): LLM inference, SEO/GEO audit, web extract, link preview, robots/AI-crawler check, security headers, on-chain token safety (rug/honeypot check).",
5
+ "description": "MCP server for the true402 machine-native marketplace — pay-per-call AI + web + Base on-chain tools over x402 (USDC on Base): LLM inference, SEO/GEO audit, web extract, link preview, robots/AI-crawler check, security headers, and on-chain DeFi trading signals (token rug/honeypot safety, new token pairs, liquidity-pull/rug alerts, whale swaps).",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "bin": {
@@ -31,7 +31,12 @@
31
31
  "seo",
32
32
  "geo",
33
33
  "web-scraping",
34
- "tools"
34
+ "tools",
35
+ "defi",
36
+ "trading",
37
+ "onchain",
38
+ "rug-check",
39
+ "honeypot"
35
40
  ],
36
41
  "homepage": "https://true402.dev",
37
42
  "license": "MIT",