wickedapi-mcp 0.3.1 → 0.4.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/README.md +14 -1
- package/dist/http.js +20 -2
- package/dist/landing.js +96 -0
- package/dist/prompts.js +85 -0
- package/dist/server.js +8 -3
- package/dist/usage.js +46 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ MCP server for the whole [Wicked](https://wickedapi.com) suite — trading data,
|
|
|
17
17
|
| [Wicked Memory](https://memory.wickedapi.com) | 8 (`memory_prepare`, `memory_store`, `memory_search`, …) | wallet signature on every call; only search is metered |
|
|
18
18
|
| [x402 Paywall](https://paywall.wickedapi.com) | 8 (`paywall_*`) | `paywall_signup` is open; the rest need the tenant key |
|
|
19
19
|
|
|
20
|
-
Plus **6 resources** (the platform directory, `llms.txt`, and each service's live endpoint catalog) and **
|
|
20
|
+
Plus **6 resources** (the platform directory, `llms.txt`, and each service's live endpoint catalog) and **7 prompts** (`rug_check`, `vet_dependency`, `morning_briefing`, `fact_check`, `verify_agent`, `find_reliable_tool`, `agent_memory_guide`) that chain tools together the way the [cookbook](../cookbooks/wickedapi-cookbook.md) does.
|
|
21
21
|
|
|
22
22
|
These tools never sign anything or hold a key. Identity, Registry registration and Memory take a signature you produce with your own wallet (`identity_get_nonce` / `memory_prepare` return the exact message to sign). Calls that need x402 payment return the real `402` challenge (decoded under `payment_required` for x402 v2) rather than auto-paying — `WICKEDAPI_PAYER_PRIVATE_KEY` auto-pay applies to the Trading, Protocol Health, Token Risk and Broker Sync tools.
|
|
23
23
|
|
|
@@ -75,6 +75,15 @@ Read without calling a tool — useful for a client that surfaces resources in i
|
|
|
75
75
|
- `wickedapi://platform/llms` — the platform's `llms.txt`
|
|
76
76
|
- `wickedapi://trading/status`, `wickedapi://protocol_health/status`, `wickedapi://token_risk/status`, `wickedapi://broker_sync/status` — each service's live endpoint catalog
|
|
77
77
|
|
|
78
|
+
## Quick start
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
claude mcp add --transport http wicked https://mcp.wickedapi.com/mcp # Claude Code, no install
|
|
82
|
+
npx -y wickedapi-mcp # local stdio
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Claude Desktop / Cursor / VS Code config and per-framework starters: see the [cookbooks](https://github.com/choaticpixels/api_services/tree/main/cookbooks). The remote also serves a landing page at `/`, `/llms.txt`, an MCP server card at `/.well-known/mcp/server-card.json`, and aggregate per-tool call counts at `/stats` (counts only; no arguments or IPs).
|
|
86
|
+
|
|
78
87
|
## Prompts
|
|
79
88
|
|
|
80
89
|
One-click workflows that chain real tool calls (Claude Desktop and other prompt-aware clients surface these directly):
|
|
@@ -82,6 +91,10 @@ One-click workflows that chain real tool calls (Claude Desktop and other prompt-
|
|
|
82
91
|
- **`rug_check`** (`address`, `chain`, optional `symbol`) — token risk check + market sentiment + optional momentum read, before buying.
|
|
83
92
|
- **`vet_dependency`** (`slug`, optional `token_address`, `chain`) — protocol health + optional contract-level risk check, before building on a DeFi protocol.
|
|
84
93
|
- **`morning_briefing`** (optional `symbol`) — macro calendar + earnings calendar + sentiment + momentum verdict, as one digest.
|
|
94
|
+
- **`fact_check`** (`output`, optional `source`) — split an answer into claims and verify each with Wicked Sanity.
|
|
95
|
+
- **`verify_agent`** (`wallet`) — Identity assertion + Reputation stake/tier before you transact with an agent.
|
|
96
|
+
- **`find_reliable_tool`** (`need`, optional `category`, `min_score`) — Registry search by real uptime/latency/schema scores.
|
|
97
|
+
- **`agent_memory_guide`** (`wallet`, `task`) — the two-step signed Memory flow, explained to the model.
|
|
85
98
|
|
|
86
99
|
## Environment variables
|
|
87
100
|
|
package/dist/http.js
CHANGED
|
@@ -8,6 +8,8 @@ import { createServer } from "node:http";
|
|
|
8
8
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
9
9
|
import { clientKey } from "./clientIp.js";
|
|
10
10
|
import { rateLimited } from "./rateLimit.js";
|
|
11
|
+
import { landingHtml, llmsTxt, serverCard } from "./landing.js";
|
|
12
|
+
import { knownTools, usageHooks, usageSnapshot } from "./usage.js";
|
|
11
13
|
import { buildServer, loadWorld, SERVER_VERSION } from "./server.js";
|
|
12
14
|
const PORT = Number(process.env.PORT || 8080);
|
|
13
15
|
const RATE_LIMIT_PER_MINUTE = Number(process.env.RATE_LIMIT_PER_MINUTE || 30);
|
|
@@ -20,6 +22,10 @@ function send(res, status, body, headers = {}) {
|
|
|
20
22
|
res.writeHead(status, { "content-type": "application/json", ...headers });
|
|
21
23
|
res.end(JSON.stringify(body));
|
|
22
24
|
}
|
|
25
|
+
function sendText(res, contentType, body) {
|
|
26
|
+
res.writeHead(200, { "content-type": `${contentType}; charset=utf-8`, "cache-control": "public, max-age=300" });
|
|
27
|
+
res.end(body);
|
|
28
|
+
}
|
|
23
29
|
async function readJson(req) {
|
|
24
30
|
const chunks = [];
|
|
25
31
|
let size = 0;
|
|
@@ -40,7 +46,8 @@ async function readJson(req) {
|
|
|
40
46
|
}
|
|
41
47
|
async function main() {
|
|
42
48
|
const world = await loadWorld();
|
|
43
|
-
const { summary } = buildServer(world);
|
|
49
|
+
const { summary } = buildServer(world, { onRegister: usageHooks.onRegister });
|
|
50
|
+
const tools = [...knownTools];
|
|
44
51
|
const http = createServer(async (req, res) => {
|
|
45
52
|
const url = new URL(req.url || "/", "http://x");
|
|
46
53
|
if (url.pathname === "/health")
|
|
@@ -48,6 +55,17 @@ async function main() {
|
|
|
48
55
|
const host = (req.headers.host || "").toLowerCase();
|
|
49
56
|
if (!ALLOWED_HOSTS.includes(host))
|
|
50
57
|
return send(res, 421, { error: "Invalid Host header" });
|
|
58
|
+
if (req.method === "GET" || req.method === "HEAD") {
|
|
59
|
+
if (url.pathname === "/")
|
|
60
|
+
return sendText(res, "text/html", landingHtml(tools, SERVER_VERSION));
|
|
61
|
+
if (url.pathname === "/llms.txt")
|
|
62
|
+
return sendText(res, "text/plain", llmsTxt(tools, SERVER_VERSION));
|
|
63
|
+
if (url.pathname === "/.well-known/mcp/server-card.json" || url.pathname === "/.well-known/mcp.json") {
|
|
64
|
+
return send(res, 200, serverCard(tools, SERVER_VERSION), { "cache-control": "public, max-age=300" });
|
|
65
|
+
}
|
|
66
|
+
if (url.pathname === "/stats")
|
|
67
|
+
return send(res, 200, usageSnapshot());
|
|
68
|
+
}
|
|
51
69
|
if (url.pathname !== "/mcp")
|
|
52
70
|
return send(res, 404, { error: "Not found. The MCP endpoint is /mcp." });
|
|
53
71
|
if (req.method !== "POST") {
|
|
@@ -58,7 +76,7 @@ async function main() {
|
|
|
58
76
|
}
|
|
59
77
|
try {
|
|
60
78
|
const body = await readJson(req);
|
|
61
|
-
const { server } = buildServer(world);
|
|
79
|
+
const { server } = buildServer(world, usageHooks);
|
|
62
80
|
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
63
81
|
res.on("close", () => {
|
|
64
82
|
void transport.close();
|
package/dist/landing.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Discovery surfaces served by the HTTP deployment: a human landing page,
|
|
2
|
+
// /llms.txt for crawlers and agents, and an MCP server card. All three are
|
|
3
|
+
// generated from the tools actually registered, so they cannot drift from
|
|
4
|
+
// what the server exposes.
|
|
5
|
+
const SITE = "https://mcp.wickedapi.com";
|
|
6
|
+
const GROUPS = [
|
|
7
|
+
{ id: "protocol_health", label: "Protocol Health Oracle", prefix: "protocol_health_", blurb: "Score any DeFi protocol before you integrate it." },
|
|
8
|
+
{ id: "token_risk", label: "Token Risk Oracle", prefix: "token_risk", blurb: "Free rug / honeypot check for EVM and Solana tokens." },
|
|
9
|
+
{ id: "broker_sync", label: "Broker Sync", prefix: "broker_sync_", blurb: "Positions and balances across 30+ brokerages." },
|
|
10
|
+
{ id: "reputation", label: "Wicked Reputation", prefix: "reputation_", blurb: "On-chain agent stake, tiers and slashing history. Free." },
|
|
11
|
+
{ id: "registry", label: "Wicked Registry", prefix: "registry_", blurb: "Real uptime, latency and schema scores for x402/MCP tools." },
|
|
12
|
+
{ id: "identity", label: "Wicked Identity", prefix: "identity_", blurb: "Know-Your-Agent verification with signed assertions." },
|
|
13
|
+
{ id: "sanity", label: "Wicked Sanity", prefix: "sanity_", blurb: "Hallucination check against your source or live web evidence." },
|
|
14
|
+
{ id: "memory", label: "Wicked Memory", prefix: "memory_", blurb: "Persistent, wallet-scoped agent memory with semantic search." },
|
|
15
|
+
{ id: "paywall", label: "x402 Paywall", prefix: "paywall_", blurb: "Put any endpoint behind pay-per-call USDC." },
|
|
16
|
+
];
|
|
17
|
+
export const PROMPT_NAMES = ["rug_check", "vet_dependency", "morning_briefing", "fact_check", "verify_agent", "find_reliable_tool", "agent_memory_guide"];
|
|
18
|
+
function grouped(tools) {
|
|
19
|
+
const claimed = new Set();
|
|
20
|
+
const out = GROUPS.map((g) => {
|
|
21
|
+
const names = tools.filter((t) => t.startsWith(g.prefix));
|
|
22
|
+
names.forEach((n) => claimed.add(n));
|
|
23
|
+
return { ...g, tools: names };
|
|
24
|
+
});
|
|
25
|
+
const trading = tools.filter((t) => !claimed.has(t));
|
|
26
|
+
return [{ id: "trading", label: "Trading Data API", blurb: "Prices, OHLCV, indicators, funding/OI, macro, prediction markets, momentum score.", tools: trading }, ...out];
|
|
27
|
+
}
|
|
28
|
+
export function llmsTxt(tools, version) {
|
|
29
|
+
const g = grouped(tools);
|
|
30
|
+
return [
|
|
31
|
+
"# Wicked MCP",
|
|
32
|
+
"",
|
|
33
|
+
`> One MCP server for the whole Wicked suite: ${tools.length} tools across ${g.length} services - market data, DeFi and token risk, brokerage sync, agent identity, reputation, tool registry, hallucination checks, persistent memory and x402 paywalls. Version ${version}.`,
|
|
34
|
+
"",
|
|
35
|
+
"## Connect",
|
|
36
|
+
`- Remote (streamable HTTP, no install): ${SITE}/mcp`,
|
|
37
|
+
"- Local (stdio): `npx -y wickedapi-mcp`",
|
|
38
|
+
"- Claude Code: `claude mcp add --transport http wicked " + SITE + "/mcp`",
|
|
39
|
+
"- No signup needed. Priced calls return a real x402 payment challenge (USDC on Base); set WICKEDAPI_API_KEY or a payer wallet locally for free-tier / auto-pay.",
|
|
40
|
+
"- The public remote is rate limited per IP; run it locally with `npx` for unlimited use.",
|
|
41
|
+
"",
|
|
42
|
+
"## Services and tools",
|
|
43
|
+
...g.map((s) => `- ${s.label} (${s.tools.length}): ${s.blurb} Tools: ${s.tools.join(", ")}`),
|
|
44
|
+
"",
|
|
45
|
+
"## Workflow prompts",
|
|
46
|
+
...PROMPT_NAMES.map((p) => `- ${p}`),
|
|
47
|
+
"",
|
|
48
|
+
"## Links",
|
|
49
|
+
"- Platform: https://wickedapi.com (service directory at https://wickedapi.com/services.json)",
|
|
50
|
+
"- Source: https://github.com/choaticpixels/wicked-mcp",
|
|
51
|
+
"- npm: https://www.npmjs.com/package/wickedapi-mcp",
|
|
52
|
+
"- Registry: io.github.choaticpixels/wicked-mcp on https://registry.modelcontextprotocol.io",
|
|
53
|
+
"- Server card: " + SITE + "/.well-known/mcp/server-card.json",
|
|
54
|
+
"",
|
|
55
|
+
"Wallet-signed tools (identity, memory, registry registration) never sign for you: you supply the signature from your own wallet.",
|
|
56
|
+
"",
|
|
57
|
+
].join("\n");
|
|
58
|
+
}
|
|
59
|
+
export function serverCard(tools, version) {
|
|
60
|
+
return {
|
|
61
|
+
$schema: "https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json",
|
|
62
|
+
version: "1.0",
|
|
63
|
+
protocolVersion: "2025-06-18",
|
|
64
|
+
serverInfo: { name: "io.github.choaticpixels/wicked-mcp", title: "Wicked MCP", version },
|
|
65
|
+
description: `The whole Wicked suite for agents: ${tools.length} tools across market data, DeFi/token risk, identity, reputation, registry, sanity checks, memory and paywalls. x402-native.`,
|
|
66
|
+
documentationUrl: "https://github.com/choaticpixels/wicked-mcp",
|
|
67
|
+
transport: { type: "streamable-http", endpoint: "/mcp" },
|
|
68
|
+
capabilities: { tools: { listChanged: false }, resources: {}, prompts: {} },
|
|
69
|
+
authentication: { required: false, note: "Optional keys unlock free tiers; otherwise paid calls return an x402 challenge." },
|
|
70
|
+
packages: [{ registry: "npm", name: "wickedapi-mcp", transport: "stdio" }],
|
|
71
|
+
tools: tools.map((name) => ({ name })),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const esc = (s) => s.replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c]);
|
|
75
|
+
export function landingHtml(tools, version) {
|
|
76
|
+
const g = grouped(tools);
|
|
77
|
+
const rows = g
|
|
78
|
+
.map((s) => `<details><summary><b>${esc(s.label)}</b> <span>${s.tools.length} tools</span><em>${esc(s.blurb)}</em></summary><code>${s.tools.map(esc).join(" · ")}</code></details>`)
|
|
79
|
+
.join("\n");
|
|
80
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
81
|
+
<title>Wicked MCP</title><meta name="description" content="One MCP server for the whole Wicked suite: ${tools.length} tools for agents.">
|
|
82
|
+
<style>:root{color-scheme:light dark;--bg:#fff;--fg:#14171f;--mut:#5b6472;--card:#f4f6fa;--acc:#6d28d9}@media(prefers-color-scheme:dark){:root{--bg:#0e1117;--fg:#e8ebf2;--mut:#98a2b3;--card:#181d27;--acc:#a78bfa}}
|
|
83
|
+
body{margin:0;background:var(--bg);color:var(--fg);font:16px/1.55 system-ui,sans-serif}main{max-width:760px;margin:0 auto;padding:32px 16px}h1{margin:0 0 4px}p.l{color:var(--mut);margin-top:0}
|
|
84
|
+
pre{background:var(--card);padding:12px 14px;border-radius:8px;overflow:auto;font-size:14px}details{background:var(--card);border-radius:8px;padding:10px 14px;margin:8px 0}summary{cursor:pointer}summary span{color:var(--acc);margin:0 8px;font-size:14px}summary em{display:block;color:var(--mut);font-style:normal;font-size:14px}
|
|
85
|
+
code{display:block;margin-top:8px;font-size:13px;color:var(--mut);word-break:break-word}a{color:var(--acc)}footer{color:var(--mut);font-size:14px;margin-top:24px}</style></head><body><main>
|
|
86
|
+
<h1>Wicked MCP</h1><p class="l">${tools.length} tools. ${g.length} services. One server for agents. v${esc(version)}</p>
|
|
87
|
+
<h3>Connect in one line</h3>
|
|
88
|
+
<pre>claude mcp add --transport http wicked ${SITE}/mcp</pre>
|
|
89
|
+
<pre>{ "mcpServers": { "wicked": { "url": "${SITE}/mcp" } } }</pre>
|
|
90
|
+
<pre>npx -y wickedapi-mcp # local, stdio - unlimited, supports your API key or payer wallet</pre>
|
|
91
|
+
<p>No signup. Priced calls return a real x402 payment challenge (USDC on Base). The public endpoint is rate limited per IP.</p>
|
|
92
|
+
<h3>What's inside</h3>
|
|
93
|
+
${rows}
|
|
94
|
+
<footer><a href="https://wickedapi.com">wickedapi.com</a> · <a href="https://github.com/choaticpixels/wicked-mcp">GitHub</a> · <a href="https://www.npmjs.com/package/wickedapi-mcp">npm</a> · <a href="/llms.txt">llms.txt</a> · <a href="/.well-known/mcp/server-card.json">server card</a> · <a href="/stats">usage</a></footer>
|
|
95
|
+
</main></body></html>`;
|
|
96
|
+
}
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const text = (t) => ({ role: "user", content: { type: "text", text: t } });
|
|
3
|
+
export function registerSuitePrompts(server) {
|
|
4
|
+
server.registerPrompt("fact_check", {
|
|
5
|
+
title: "Fact-check before acting",
|
|
6
|
+
description: "Split an answer into claims and verify each against your source text (or live web evidence) with Wicked Sanity before you rely on it.",
|
|
7
|
+
argsSchema: {
|
|
8
|
+
output: z.string().describe("The text to check - an AI answer, summary or report."),
|
|
9
|
+
source: z.string().optional().describe("The source text the output must be faithful to. Omit to check against live web evidence instead."),
|
|
10
|
+
},
|
|
11
|
+
}, ({ output, source }) => ({
|
|
12
|
+
description: "Fact-check workflow",
|
|
13
|
+
messages: [
|
|
14
|
+
text(`Verify the following output before I act on it. Don't judge it yourself - use the tools.\n\n` +
|
|
15
|
+
`1. Split it into individual factual claims (one sentence each).\n` +
|
|
16
|
+
`2. Call \`sanity_check_batch\` once with all claims, ` +
|
|
17
|
+
(source
|
|
18
|
+
? `mode="grounded" and context set to the source text below.\n`
|
|
19
|
+
: `mode="open" (no source supplied, so check against live web evidence).\n`) +
|
|
20
|
+
`3. Report every claim with its exact verdict (supported / contradicted / unsupported / insufficient_evidence) ` +
|
|
21
|
+
`and the evidence returned. Treat unsupported and insufficient_evidence as UNVERIFIED, and contradicted as wrong.\n` +
|
|
22
|
+
`4. If the response has http_status 402, 429 or another error, say so plainly - do not guess verdicts.\n\n` +
|
|
23
|
+
`OUTPUT TO CHECK:\n${output}\n` +
|
|
24
|
+
(source ? `\nSOURCE:\n${source}\n` : "")),
|
|
25
|
+
],
|
|
26
|
+
}));
|
|
27
|
+
server.registerPrompt("verify_agent", {
|
|
28
|
+
title: "Check an agent's trust",
|
|
29
|
+
description: "Combine Wicked Identity (is this wallet a verified autonomous agent?) and Wicked Reputation (what does it have at stake?) before you transact with it.",
|
|
30
|
+
argsSchema: { wallet: z.string().describe("The agent's 0x wallet address.") },
|
|
31
|
+
}, ({ wallet }) => ({
|
|
32
|
+
description: "Agent trust check",
|
|
33
|
+
messages: [
|
|
34
|
+
text(`Before I deal with the agent at ${wallet}, check its real trust signals - don't assume anything:\n\n` +
|
|
35
|
+
`1. Call \`identity_status\` with wallet="${wallet}": does it hold a valid, unexpired Know-Your-Agent assertion? Report the exact fields.\n` +
|
|
36
|
+
`2. Call \`reputation_status\` with wallet="${wallet}": tier, active stake, reputation score, slash count. ` +
|
|
37
|
+
`An http_status 404 means the wallet never registered - report that as "unranked", not an error.\n` +
|
|
38
|
+
`3. Call \`reputation_statement\` only if it has stake or slashes and I need the event history.\n\n` +
|
|
39
|
+
`Summarize plainly: verified or not, stake and tier, any slashes, and what is unknown. State that this is ` +
|
|
40
|
+
`a set of signals, not a guarantee. If a call returns a 402, show the payment instructions rather than guessing.`),
|
|
41
|
+
],
|
|
42
|
+
}));
|
|
43
|
+
server.registerPrompt("find_reliable_tool", {
|
|
44
|
+
title: "Find a reliable tool",
|
|
45
|
+
description: "Search the Wicked Registry for x402/MCP tools by real uptime, latency and schema-conformance scores before you pay for one.",
|
|
46
|
+
argsSchema: {
|
|
47
|
+
need: z.string().describe('What you need the tool for, e.g. "token price data".'),
|
|
48
|
+
category: z.string().optional().describe('Registry category filter, e.g. "data" or "crypto".'),
|
|
49
|
+
min_score: z.string().optional().describe("Minimum composite score 0-100."),
|
|
50
|
+
},
|
|
51
|
+
}, ({ need, category, min_score }) => ({
|
|
52
|
+
description: "Reliable tool discovery",
|
|
53
|
+
messages: [
|
|
54
|
+
text(`I need a tool for: ${need}. Find options with real reliability data - never recommend from memory.\n\n` +
|
|
55
|
+
`1. Call \`registry_featured_tools\` (free) for the top scored tools.\n` +
|
|
56
|
+
`2. Call \`registry_search_tools\`` +
|
|
57
|
+
(category ? ` with category="${category}"` : "") +
|
|
58
|
+
(min_score ? ` and min_score=${min_score}` : "") +
|
|
59
|
+
`. If it returns a 402, show me the payment instructions and continue with the free results.\n` +
|
|
60
|
+
`3. For the best 1-3 candidates call \`registry_tool_detail\` (or \`registry_tool_badge\` for a free score) and report ` +
|
|
61
|
+
`uptime, p95 latency and schema conformance exactly as returned. Tools with no check history have no score - say so.\n\n` +
|
|
62
|
+
`Recommend one, with the numbers that justify it.`),
|
|
63
|
+
],
|
|
64
|
+
}));
|
|
65
|
+
server.registerPrompt("agent_memory_guide", {
|
|
66
|
+
title: "Use Wicked Memory",
|
|
67
|
+
description: "Walk through storing and searching persistent, wallet-scoped memory - including the two-step signing flow.",
|
|
68
|
+
argsSchema: {
|
|
69
|
+
wallet: z.string().describe("Your agent's 0x wallet address (memories are scoped to it)."),
|
|
70
|
+
task: z.string().describe('What to do, e.g. "remember that I prefer concise answers" or "find what I stored about the Q3 launch".'),
|
|
71
|
+
},
|
|
72
|
+
}, ({ wallet, task }) => ({
|
|
73
|
+
description: "Wicked Memory walkthrough",
|
|
74
|
+
messages: [
|
|
75
|
+
text(`Use Wicked Memory for wallet ${wallet} to: ${task}\n\n` +
|
|
76
|
+
`Every memory call needs a fresh wallet signature, and this server never signs or holds a key:\n` +
|
|
77
|
+
`1. Call \`memory_prepare\` with the operation ("store" or "search") and the exact params you will use. It returns \`message_to_sign\`, \`timestamp\` and \`nonce\`.\n` +
|
|
78
|
+
`2. Ask ME to sign \`message_to_sign\` with the wallet (EIP-191 personal_sign) and give you the signature. Never invent one.\n` +
|
|
79
|
+
`3. Call the matching tool (\`memory_store\` / \`memory_search\`) with the SAME params plus timestamp, nonce and signature.\n` +
|
|
80
|
+
`4. A nonce is single-use and the timestamp must be within 5 minutes - prepare again for every call. ` +
|
|
81
|
+
`\`memory_search\` is metered: if it returns http_status 402, show me the payment instructions; the same signed request can be retried with a payment_signature.\n\n` +
|
|
82
|
+
`Report exactly what the tools returned.`),
|
|
83
|
+
],
|
|
84
|
+
}));
|
|
85
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { registerSuiteTools } from "./suite.js";
|
|
4
|
+
import { registerSuitePrompts } from "./prompts.js";
|
|
5
|
+
import { instrument } from "./usage.js";
|
|
4
6
|
function envUrl(name, fallback) {
|
|
5
7
|
return (process.env[name] || fallback).replace(/\/$/, "");
|
|
6
8
|
}
|
|
@@ -297,7 +299,7 @@ function registerPrompts(server) {
|
|
|
297
299
|
};
|
|
298
300
|
});
|
|
299
301
|
}
|
|
300
|
-
export const SERVER_VERSION = "0.
|
|
302
|
+
export const SERVER_VERSION = "0.4.0";
|
|
301
303
|
export async function loadWorld() {
|
|
302
304
|
const doFetch = await buildFetcher();
|
|
303
305
|
const specs = [];
|
|
@@ -311,8 +313,10 @@ export async function loadWorld() {
|
|
|
311
313
|
}
|
|
312
314
|
return { doFetch, specs };
|
|
313
315
|
}
|
|
314
|
-
export function buildServer(world) {
|
|
316
|
+
export function buildServer(world, hooks) {
|
|
315
317
|
const server = new McpServer({ name: "wickedapi", version: SERVER_VERSION });
|
|
318
|
+
if (hooks)
|
|
319
|
+
instrument(server, hooks);
|
|
316
320
|
let totalTools = 0;
|
|
317
321
|
const loaded = [];
|
|
318
322
|
for (const { service, doc } of world.specs) {
|
|
@@ -325,8 +329,9 @@ export function buildServer(world) {
|
|
|
325
329
|
loaded.push("Reputation, Registry, Identity, Sanity, Memory, Paywall");
|
|
326
330
|
registerResources(server);
|
|
327
331
|
registerPrompts(server);
|
|
332
|
+
registerSuitePrompts(server);
|
|
328
333
|
return {
|
|
329
334
|
server,
|
|
330
|
-
summary: `${totalTools} tools across ${loaded.length} group(s) (${loaded.join(", ")}), ${2 + SERVICES.length} resources,
|
|
335
|
+
summary: `${totalTools} tools across ${loaded.length} group(s) (${loaded.join(", ")}), ${2 + SERVICES.length} resources, 7 prompts`,
|
|
331
336
|
};
|
|
332
337
|
}
|
package/dist/usage.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const startedAt = Date.now();
|
|
2
|
+
const counts = new Map();
|
|
3
|
+
export const knownTools = new Set();
|
|
4
|
+
export const usageHooks = {
|
|
5
|
+
onRegister: (name) => knownTools.add(name),
|
|
6
|
+
onToolCall: (name, ms, isError) => {
|
|
7
|
+
const c = counts.get(name) ?? { calls: 0, errors: 0, totalMs: 0 };
|
|
8
|
+
c.calls++;
|
|
9
|
+
c.totalMs += ms;
|
|
10
|
+
if (isError)
|
|
11
|
+
c.errors++;
|
|
12
|
+
counts.set(name, c);
|
|
13
|
+
console.log(JSON.stringify({ evt: "tool_call", tool: name, error: isError, ms }));
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
export function usageSnapshot() {
|
|
17
|
+
const tools = [...counts.entries()]
|
|
18
|
+
.map(([tool, c]) => ({ tool, calls: c.calls, errors: c.errors, avg_ms: Math.round(c.totalMs / c.calls) }))
|
|
19
|
+
.sort((a, b) => b.calls - a.calls);
|
|
20
|
+
return {
|
|
21
|
+
since: new Date(startedAt).toISOString(),
|
|
22
|
+
note: "In-memory since last deploy; durable history is in the service logs.",
|
|
23
|
+
total_calls: tools.reduce((n, t) => n + t.calls, 0),
|
|
24
|
+
tools,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// Wrap server.registerTool so every handler reports to the hooks. Must run
|
|
28
|
+
// before any tools are registered.
|
|
29
|
+
export function instrument(server, hooks) {
|
|
30
|
+
const original = server.registerTool.bind(server);
|
|
31
|
+
server.registerTool = (name, config, handler) => {
|
|
32
|
+
hooks.onRegister?.(name);
|
|
33
|
+
return original(name, config, async (...args) => {
|
|
34
|
+
const t0 = Date.now();
|
|
35
|
+
let isError = true;
|
|
36
|
+
try {
|
|
37
|
+
const result = await handler(...args);
|
|
38
|
+
isError = Boolean(result?.isError);
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
hooks.onToolCall?.(name, Date.now() - t0, isError);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wickedapi-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "MCP server for the whole Wicked suite — trading data, DeFi protocol health, token safety, brokerage sync, agent identity, reputation, tool registry, hallucination checks, persistent memory, and x402 paywalls. 70 tools, 6 resources, 3 prompts. No signup required: pay per call in USDC via x402, or use a partner API key.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|