wickedapi-mcp 0.1.0 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +40 -9
  2. package/dist/index.js +289 -54
  3. package/package.json +4 -2
package/README.md CHANGED
@@ -1,16 +1,25 @@
1
1
  # WickedAPI MCP Server
2
2
 
3
- MCP server for [WickedAPI](https://api.wickedapi.com) — an agent-ready trading & market-data API covering crypto/stock prices, OHLCV, technical indicators (ATR, Fair Value Gaps, ICT liquidity levels), perp funding/OI, order books, Kalshi prediction markets, DeFi TVL, volatility, a verified macro calendar, an earnings calendar, and an AI momentum score.
3
+ MCP server for the whole [WickedAPI](https://wickedapi.com) platform — trading data, DeFi protocol health, token safety, and brokerage sync, in one server.
4
4
 
5
- Tools are generated automatically from the live [`openapi.json`](https://api.wickedapi.com/openapi.json), so the tool list always matches what's actually deployed.
5
+ **35 tools** across 4 live services, generated automatically from each service's own live `openapi.json` — the tool list always matches what's actually deployed:
6
+
7
+ | Service | Tools | Auth |
8
+ | --- | --- | --- |
9
+ | [Trading Data API](https://api.wickedapi.com) | 22 (unprefixed: `price`, `ohlcv`, `fvg`, `momentum_score`, `sentiment`, `macro_calendar`, …) | API key or x402, 5 free |
10
+ | [Protocol Health Oracle](https://protocol-health-oracle.wickedapi.com) | 6 (`protocol_health_*`) | API key or x402, scoreboard free |
11
+ | [Token Risk Oracle](https://token-risk-oracle.wickedapi.com) | 1 (`token_risk`) | 100% free, no key |
12
+ | [Broker Sync API](https://broker-sync-api.wickedapi.com) | 6 (`broker_sync_*`) | API key or x402 |
13
+
14
+ Plus **6 resources** (the platform directory, `llms.txt`, and each service's live endpoint catalog) and **3 prompts** (`rug_check`, `vet_dependency`, `morning_briefing`) that chain tools together the way the [cookbook](../cookbooks/wickedapi-cookbook.md) does.
6
15
 
7
16
  ## No signup required
8
17
 
9
- Every tool call hits a real `/v1/*` endpoint that's gated by [x402](https://x402.org) (HTTP-native pay-per-call, USDC on Base) with an API-key fallback:
18
+ Every tool hits a real endpoint gated by [x402](https://x402.org) (HTTP-native pay-per-call, USDC on Base) with an API-key fallback — and the Token Risk Oracle tool is free no matter what:
10
19
 
11
- - **Zero config** — calls without credentials still work; you'll get back the real `402` payment challenge (price, network, `payTo`) as the tool result.
20
+ - **Zero config** — calls without credentials still work; paid endpoints return the real `402` payment challenge (price, network, `payTo`) as the tool result.
12
21
  - **Autonomous pay-per-call** — set `WICKEDAPI_PAYER_PRIVATE_KEY` to a funded Base-mainnet wallet's private key, and every call auto-pays ~$0.001 in USDC and returns real data. No account, no subscription.
13
- - **Partner API key** — set `WICKEDAPI_API_KEY` for free, rate-limited access if you have one.
22
+ - **Partner API key** — set `WICKEDAPI_API_KEY` for free, rate-limited access across all four services (same key works everywhere).
14
23
 
15
24
  ## Install
16
25
 
@@ -44,15 +53,37 @@ Or for autonomous pay-per-call instead of a key:
44
53
  }
45
54
  ```
46
55
 
56
+ Nothing configured? The Token Risk Oracle tool and the 5 free Trading Data API endpoints (`sentiment`, `macro_calendar`, `market_overview`, `gas`, `kalshi_probability`) still work with zero setup.
57
+
58
+ ## Resources
59
+
60
+ Read without calling a tool — useful for a client that surfaces resources in its UI:
61
+
62
+ - `wickedapi://platform/services` — the full service directory (JSON)
63
+ - `wickedapi://platform/llms` — the platform's `llms.txt`
64
+ - `wickedapi://trading/status`, `wickedapi://protocol_health/status`, `wickedapi://token_risk/status`, `wickedapi://broker_sync/status` — each service's live endpoint catalog
65
+
66
+ ## Prompts
67
+
68
+ One-click workflows that chain real tool calls (Claude Desktop and other prompt-aware clients surface these directly):
69
+
70
+ - **`rug_check`** (`address`, `chain`, optional `symbol`) — token risk check + market sentiment + optional momentum read, before buying.
71
+ - **`vet_dependency`** (`slug`, optional `token_address`, `chain`) — protocol health + optional contract-level risk check, before building on a DeFi protocol.
72
+ - **`morning_briefing`** (optional `symbol`) — macro calendar + earnings calendar + sentiment + momentum verdict, as one digest.
73
+
47
74
  ## Environment variables
48
75
 
49
76
  | Var | Required | Notes |
50
77
  | --- | --- | --- |
51
- | `WICKEDAPI_API_KEY` | no | Free, rate-limited access if you have a partner key. |
78
+ | `WICKEDAPI_API_KEY` | no | Free, rate-limited access across all four services (same key works everywhere). |
52
79
  | `WICKEDAPI_PAYER_PRIVATE_KEY` | no | Base-mainnet wallet private key. If set, every call auto-pays the x402 challenge in USDC — no key needed. |
53
- | `WICKEDAPI_BASE_URL` | no | Override the API base (default `https://api.wickedapi.com`). |
80
+ | `WICKEDAPI_BASE_URL` | no | Override the Trading Data API base (default `https://api.wickedapi.com`). |
81
+ | `WICKEDAPI_PROTOCOL_HEALTH_URL` | no | Override the Protocol Health Oracle base (default `https://protocol-health-oracle.wickedapi.com`). |
82
+ | `WICKEDAPI_TOKEN_RISK_URL` | no | Override the Token Risk Oracle base (default `https://token-risk-oracle.wickedapi.com`). |
83
+ | `WICKEDAPI_BROKER_SYNC_URL` | no | Override the Broker Sync API base (default `https://broker-sync-api.wickedapi.com`). |
84
+ | `WICKEDAPI_SHOWCASE_URL` | no | Override the platform directory base used by resources (default `https://wickedapi.com`). |
54
85
 
55
- If neither `WICKEDAPI_API_KEY` nor `WICKEDAPI_PAYER_PRIVATE_KEY` is set, calls still succeed at the protocol level — you'll get the real `402` challenge back as the tool result instead of data.
86
+ If neither `WICKEDAPI_API_KEY` nor `WICKEDAPI_PAYER_PRIVATE_KEY` is set, paid calls still succeed at the protocol level — you'll get the real `402` challenge back as the tool result instead of data. If one service's spec fails to load (network hiccup, that service is down), its tools are simply skipped for the session — the rest still register normally.
56
87
 
57
88
  ## Local development
58
89
 
@@ -64,4 +95,4 @@ node dist/index.js
64
95
 
65
96
  ## What this is not
66
97
 
67
- This server doesn't reimplement any data logic — it's a thin, auto-generated bridge from MCP tool calls to the live WickedAPI HTTP endpoints. All the actual market data, indicators, and scoring live in the API itself.
98
+ This server doesn't reimplement any data logic — it's a thin, auto-generated bridge from MCP tool calls to the live WickedAPI HTTP endpoints across all four services. All the actual market data, indicators, scoring, and risk signals live in the APIs themselves.
package/dist/index.js CHANGED
@@ -2,21 +2,51 @@
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { z } from "zod";
5
- const BASE_URL = (process.env.WICKEDAPI_BASE_URL || "https://api.wickedapi.com").replace(/\/$/, "");
5
+ function envUrl(name, fallback) {
6
+ return (process.env[name] || fallback).replace(/\/$/, "");
7
+ }
8
+ const SERVICES = [
9
+ { id: "trading", name: "Trading Data API", baseUrl: envUrl("WICKEDAPI_BASE_URL", "https://api.wickedapi.com") },
10
+ { id: "protocol_health", name: "Protocol Health Oracle", baseUrl: envUrl("WICKEDAPI_PROTOCOL_HEALTH_URL", "https://protocol-health-oracle.wickedapi.com") },
11
+ { id: "token_risk", name: "Token Risk Oracle", baseUrl: envUrl("WICKEDAPI_TOKEN_RISK_URL", "https://token-risk-oracle.wickedapi.com") },
12
+ { id: "broker_sync", name: "Broker Sync API", baseUrl: envUrl("WICKEDAPI_BROKER_SYNC_URL", "https://broker-sync-api.wickedapi.com") },
13
+ ];
14
+ const SHOWCASE_URL = envUrl("WICKEDAPI_SHOWCASE_URL", "https://wickedapi.com");
6
15
  const API_KEY = process.env.WICKEDAPI_API_KEY;
7
16
  const PAYER_KEY = process.env.WICKEDAPI_PAYER_PRIVATE_KEY;
17
+ // A few auto-generated tool names collide or read badly (e.g. GET and POST
18
+ // both hitting /v1/protocols) — override just those, keyed by
19
+ // "METHOD /openapi/path/{param}".
20
+ const TOOL_NAME_OVERRIDES = {
21
+ protocol_health: {
22
+ "GET /v1/protocols": "protocol_health_list_protocols",
23
+ "POST /v1/protocols": "protocol_health_track_protocol",
24
+ "GET /v1/health/{slug}": "protocol_health_score",
25
+ "POST /v1/refresh": "protocol_health_refresh_all",
26
+ "POST /v1/ingest": "protocol_health_sync_universe",
27
+ },
28
+ broker_sync: {
29
+ "POST /v1/users": "broker_sync_register_user",
30
+ },
31
+ };
8
32
  // If a funded Base-mainnet wallet key is provided, every call auto-pays the
9
33
  // x402 402 challenge in USDC — no API key or signup needed at all. Loaded
10
34
  // lazily so a plain API-key setup never has to pull in viem/x402-fetch.
11
35
  async function buildFetcher() {
12
36
  if (!PAYER_KEY)
13
37
  return fetch;
14
- const [{ createWalletClient, http }, { privateKeyToAccount }, { base }, { wrapFetchWithPayment }] = await Promise.all([
15
- import("viem"),
16
- import("viem/accounts"),
17
- import("viem/chains"),
18
- import("x402-fetch"),
19
- ]);
38
+ let deps;
39
+ try {
40
+ deps = await Promise.all([import("viem"), import("viem/accounts"), import("viem/chains"), import("x402-fetch")]);
41
+ }
42
+ catch {
43
+ console.error("[wickedapi-mcp] WICKEDAPI_PAYER_PRIVATE_KEY is set, but the wallet-payment dependencies " +
44
+ "(viem, x402-fetch) aren't installed in this distribution. Falling back to plain fetch — " +
45
+ "calls without an API key will surface the 402 challenge instead of auto-paying. " +
46
+ "Use `npx wickedapi-mcp` for full auto-pay support.");
47
+ return fetch;
48
+ }
49
+ const [{ createWalletClient, http }, { privateKeyToAccount }, { base }, { wrapFetchWithPayment }] = deps;
20
50
  const account = privateKeyToAccount(PAYER_KEY);
21
51
  const wallet = createWalletClient({ account, chain: base, transport: http() });
22
52
  console.error(`[wickedapi-mcp] x402 auto-pay enabled — wallet ${account.address}`);
@@ -25,65 +55,270 @@ async function buildFetcher() {
25
55
  // wallet client the same way — cast rather than pull in publicActions.
26
56
  return wrapFetchWithPayment(fetch, wallet);
27
57
  }
28
- function toolNameFromPath(path) {
29
- return path.replace(/^\/v1\//, "").replace(/-/g, "_");
58
+ function pathSlug(openapiPath) {
59
+ return openapiPath
60
+ .replace(/^\/v1\//, "")
61
+ .replace(/[{}]/g, "")
62
+ .replace(/[\/\-]+/g, "_");
63
+ }
64
+ function toolNameFor(service, method, openapiPath) {
65
+ const override = TOOL_NAME_OVERRIDES[service.id]?.[`${method.toUpperCase()} ${openapiPath}`];
66
+ if (override)
67
+ return override;
68
+ const slug = pathSlug(openapiPath);
69
+ if (service.id === "trading")
70
+ return slug; // legacy naming, already published
71
+ return slug === service.id ? service.id : `${service.id}_${slug}`;
30
72
  }
31
- function zodForParam(p) {
32
- const type = p.schema?.type;
33
- const base = type === "number" ? z.coerce.number() : type === "boolean" ? z.coerce.boolean() : z.string();
73
+ function zodFor(p) {
74
+ const base = p.type === "number" || p.type === "integer"
75
+ ? z.coerce.number()
76
+ : p.type === "boolean"
77
+ ? z.coerce.boolean()
78
+ : z.string();
34
79
  const described = p.description ? base.describe(p.description) : base;
35
80
  return p.required ? described : described.optional();
36
81
  }
37
- async function main() {
38
- const specRes = await fetch(`${BASE_URL}/openapi.json`);
39
- if (!specRes.ok) {
40
- throw new Error(`Failed to load ${BASE_URL}/openapi.json: HTTP ${specRes.status}`);
82
+ async function loadServiceSpec(service) {
83
+ try {
84
+ const res = await fetch(`${service.baseUrl}/openapi.json`);
85
+ if (!res.ok)
86
+ throw new Error(`HTTP ${res.status}`);
87
+ return (await res.json());
41
88
  }
42
- const doc = (await specRes.json());
43
- const doFetch = await buildFetcher();
44
- const server = new McpServer({ name: "wickedapi", version: "0.1.0" });
89
+ catch (err) {
90
+ console.error(`[wickedapi-mcp] WARNING: couldn't load ${service.name} spec (${service.baseUrl}/openapi.json): ` +
91
+ `${err.message} — its tools are unavailable this session, the rest load normally.`);
92
+ return null;
93
+ }
94
+ }
95
+ function registerServiceTools(server, service, doc, doFetch) {
45
96
  let registered = 0;
46
- for (const [path, methods] of Object.entries(doc.paths)) {
47
- const op = methods.get;
48
- if (!op || !path.startsWith("/v1/") || path === "/v1/status")
49
- continue;
50
- const shape = {};
51
- for (const p of op.parameters ?? []) {
52
- if (p.in !== "query")
53
- continue;
54
- shape[p.name] = zodForParam(p);
55
- }
56
- server.registerTool(toolNameFromPath(path), {
57
- description: [op.summary, op.description].filter(Boolean).join(" — ").slice(0, 1000),
58
- inputSchema: shape,
59
- }, async (args) => {
60
- const url = new URL(BASE_URL + path);
61
- for (const [k, v] of Object.entries(args ?? {})) {
62
- if (v !== undefined && v !== null)
63
- url.searchParams.set(k, String(v));
97
+ for (const [openapiPath, methods] of Object.entries(doc.paths)) {
98
+ if (!openapiPath.startsWith("/v1/") || openapiPath === "/v1/status")
99
+ continue; // /v1/status is a resource instead
100
+ if (openapiPath.includes("webhook"))
101
+ continue; // called by a third party, never by an agent
102
+ for (const [method, op] of Object.entries(methods)) {
103
+ const pathParamNames = [];
104
+ const queryParamNames = [];
105
+ const bodyPropNames = [];
106
+ const shape = {};
107
+ for (const p of op.parameters ?? []) {
108
+ if (p.in === "path") {
109
+ pathParamNames.push(p.name);
110
+ shape[p.name] = zodFor({ type: p.schema?.type, required: true, description: p.description });
111
+ }
112
+ else if (p.in === "query") {
113
+ queryParamNames.push(p.name);
114
+ shape[p.name] = zodFor({ type: p.schema?.type, required: p.required, description: p.description });
115
+ }
64
116
  }
65
- const headers = {};
66
- if (API_KEY)
67
- headers["x-api-key"] = API_KEY;
68
- const r = await doFetch(url.toString(), { headers });
69
- const text = await r.text();
70
- let body;
71
- try {
72
- body = JSON.parse(text);
117
+ const bodySchema = op.requestBody?.content?.["application/json"]?.schema;
118
+ if (bodySchema) {
119
+ const required = new Set(bodySchema.required ?? []);
120
+ for (const [name, prop] of Object.entries(bodySchema.properties ?? {})) {
121
+ bodyPropNames.push(name);
122
+ shape[name] = zodFor({ type: prop.type, required: required.has(name), description: prop.description });
123
+ }
73
124
  }
74
- catch {
75
- body = text;
76
- }
77
- return {
78
- isError: !r.ok,
79
- content: [{ type: "text", text: JSON.stringify(body, null, 2) }],
80
- };
125
+ const name = toolNameFor(service, method, openapiPath);
126
+ server.registerTool(name, {
127
+ description: `[${service.name}] ${[op.summary, op.description].filter(Boolean).join(" — ")}`.slice(0, 1000),
128
+ inputSchema: shape,
129
+ }, async (args) => {
130
+ let urlPath = openapiPath;
131
+ for (const p of pathParamNames)
132
+ urlPath = urlPath.replace(`{${p}}`, encodeURIComponent(String(args[p])));
133
+ const url = new URL(service.baseUrl + urlPath);
134
+ for (const p of queryParamNames) {
135
+ const v = args[p];
136
+ if (v !== undefined && v !== null)
137
+ url.searchParams.set(p, String(v));
138
+ }
139
+ const headers = {};
140
+ if (API_KEY)
141
+ headers["x-api-key"] = API_KEY;
142
+ const init = { method: method.toUpperCase(), headers };
143
+ if (bodyPropNames.length) {
144
+ const bodyObj = {};
145
+ for (const p of bodyPropNames)
146
+ if (args[p] !== undefined)
147
+ bodyObj[p] = args[p];
148
+ headers["content-type"] = "application/json";
149
+ init.body = JSON.stringify(bodyObj);
150
+ }
151
+ const r = await doFetch(url.toString(), init);
152
+ const text = await r.text();
153
+ let body;
154
+ try {
155
+ body = JSON.parse(text);
156
+ }
157
+ catch {
158
+ body = text;
159
+ }
160
+ return {
161
+ isError: !r.ok,
162
+ content: [{ type: "text", text: JSON.stringify(body, null, 2) }],
163
+ };
164
+ });
165
+ registered++;
166
+ }
167
+ }
168
+ return registered;
169
+ }
170
+ // --- Resources -----------------------------------------------------------
171
+ // Reference material an agent can read without invoking a tool: the
172
+ // platform-wide directory/llms.txt, and each service's live endpoint
173
+ // catalog (the /v1/status we deliberately excluded from becoming a tool).
174
+ function registerResources(server) {
175
+ server.registerResource("platform-services", "wickedapi://platform/services", {
176
+ title: "WickedAPI service directory",
177
+ description: "Every live WickedAPI service — URL, category, one-line description, auth modes, and links to each one's own OpenAPI spec/docs/llms.txt.",
178
+ mimeType: "application/json",
179
+ }, async (uri) => {
180
+ const r = await fetch(`${SHOWCASE_URL}/services.json`);
181
+ if (!r.ok)
182
+ throw new Error(`Failed to load service directory: HTTP ${r.status}`);
183
+ return { contents: [{ uri: uri.toString(), mimeType: "application/json", text: await r.text() }] };
184
+ });
185
+ server.registerResource("platform-llms-txt", "wickedapi://platform/llms", {
186
+ title: "WickedAPI llms.txt",
187
+ description: "Terse, platform-wide summary for agents deciding what to call — auth modes, free vs. paid endpoints.",
188
+ mimeType: "text/plain",
189
+ }, async (uri) => {
190
+ const r = await fetch(`${SHOWCASE_URL}/llms.txt`);
191
+ if (!r.ok)
192
+ throw new Error(`Failed to load llms.txt: HTTP ${r.status}`);
193
+ return { contents: [{ uri: uri.toString(), mimeType: "text/plain", text: await r.text() }] };
194
+ });
195
+ for (const service of SERVICES) {
196
+ server.registerResource(`${service.id}-status`, `wickedapi://${service.id}/status`, {
197
+ title: `${service.name} — live catalog`,
198
+ description: `Live endpoint catalog for ${service.name} (${service.baseUrl}), fetched fresh on every read.`,
199
+ mimeType: "application/json",
200
+ }, async (uri) => {
201
+ const r = await fetch(`${service.baseUrl}/v1/status`);
202
+ if (!r.ok)
203
+ throw new Error(`Failed to load ${service.name} status: HTTP ${r.status}`);
204
+ return { contents: [{ uri: uri.toString(), mimeType: "application/json", text: await r.text() }] };
81
205
  });
82
- registered++;
83
206
  }
207
+ }
208
+ // --- Prompts ---------------------------------------------------------------
209
+ // The "compound recipes" from the WickedAPI cookbook, turned into one-click
210
+ // prompts: each just tells the assistant which real tools to call and how
211
+ // to read the results — no fabricated synthesis, every number cited comes
212
+ // from an actual tool call made during the conversation.
213
+ function registerPrompts(server) {
214
+ server.registerPrompt("rug_check", {
215
+ title: "Rug-check before buying",
216
+ description: "Real on-chain risk signals for a token contract, plus market sentiment (and momentum, if it's a covered asset) before you decide whether to buy.",
217
+ argsSchema: {
218
+ address: z.string().describe("The token contract address (EVM) or mint address (Solana)."),
219
+ chain: z.string().describe("ethereum, bsc, base, polygon, arbitrum, optimism, avalanche, fantom, zksync, linea, scroll, or solana."),
220
+ symbol: z.string().optional().describe("If this token maps to a covered momentum-score asset (BTC, ETH, SOL, SUI, NVDA, IBIT, COIN, QQQ, TQQQ, DIA), pass it to also pull a real momentum verdict."),
221
+ },
222
+ }, ({ address, chain, symbol }) => ({
223
+ description: "Rug-check workflow",
224
+ messages: [
225
+ {
226
+ role: "user",
227
+ content: {
228
+ type: "text",
229
+ text: `Before I consider buying this token, run a real risk check — don't guess or assume anything:\n\n` +
230
+ `1. Call the \`token_risk\` tool with address="${address}" and chain="${chain}". Report the exact ` +
231
+ `risk_score, risk_tier, and every real flag it returns (honeypot, mint/ownership authority, holder ` +
232
+ `and LP concentration, buy/sell tax, trust-list status) — cite the actual values, don't summarize ` +
233
+ `away the specifics or invent ones that weren't returned.\n` +
234
+ `2. Call the \`sentiment\` tool for the current overall crypto market mood.\n` +
235
+ (symbol
236
+ ? `3. Call the \`momentum_score\` tool with symbol="${symbol}" and timeframe="1h" for a real technical read.\n`
237
+ : "") +
238
+ `\nThen give a plain verdict: the real risk signals found (listed, not paraphrased), current market ` +
239
+ `mood, and — only if applicable — the momentum read. State clearly this is aggregated real signals, ` +
240
+ `never a safety guarantee.`,
241
+ },
242
+ },
243
+ ],
244
+ }));
245
+ server.registerPrompt("vet_dependency", {
246
+ title: "Vet a DeFi dependency",
247
+ description: "Protocol activity/treasury health plus an optional contract-level risk check, before you build on a DeFi protocol.",
248
+ argsSchema: {
249
+ slug: z.string().describe('The protocol\'s DefiLlama slug, e.g. "lido".'),
250
+ token_address: z.string().optional().describe("The protocol's token contract address, if you also want a contract-level risk check."),
251
+ chain: z.string().optional().describe("Chain for token_address (default ethereum)."),
252
+ },
253
+ }, ({ slug, token_address, chain }) => ({
254
+ description: "Dependency-vetting workflow",
255
+ messages: [
256
+ {
257
+ role: "user",
258
+ content: {
259
+ type: "text",
260
+ text: `Before I integrate this protocol as a dependency, check its real health — don't guess:\n\n` +
261
+ `1. Call the \`protocol_health_score\` tool with slug="${slug}". Report the components (GitHub ` +
262
+ `activity, TVL trend, treasury) exactly as returned, including any that came back null (never ` +
263
+ `filled in).\n` +
264
+ (token_address
265
+ ? `2. Call the \`token_risk\` tool with address="${token_address}" and chain="${chain || "ethereum"}" ` +
266
+ `for a contract-level check on its token.\n`
267
+ : "") +
268
+ `\nSummarize: is this protocol actively maintained, is its TVL trend healthy, and (if checked) does ` +
269
+ `its token carry any real contract-level risk flags. Cite the real numbers.`,
270
+ },
271
+ },
272
+ ],
273
+ }));
274
+ server.registerPrompt("morning_briefing", {
275
+ title: "Morning briefing",
276
+ description: "One daily digest from macro calendar, earnings calendar, sentiment, and a momentum verdict — all free or a fraction of a cent.",
277
+ argsSchema: {
278
+ symbol: z.string().optional().describe("Asset for the momentum verdict (default BTC). One of BTC, ETH, SOL, SUI, NVDA, IBIT, COIN, QQQ, TQQQ, DIA."),
279
+ },
280
+ }, ({ symbol }) => {
281
+ const sym = symbol || "BTC";
282
+ return {
283
+ description: "Morning briefing workflow",
284
+ messages: [
285
+ {
286
+ role: "user",
287
+ content: {
288
+ type: "text",
289
+ text: `Compile my morning briefing from real, current data — call each tool, don't estimate:\n\n` +
290
+ `1. \`macro_calendar\` with days=7 — upcoming FOMC/CPI/jobs events.\n` +
291
+ `2. \`earnings_calendar\` with days=3 — upcoming earnings reports.\n` +
292
+ `3. \`sentiment\` — current crypto Fear & Greed reading.\n` +
293
+ `4. \`momentum_score\` with symbol="${sym}" and timeframe="1D" — today's technical verdict.\n\n` +
294
+ `Then write one concise digest combining all four, citing the real values from each call.`,
295
+ },
296
+ },
297
+ ],
298
+ };
299
+ });
300
+ }
301
+ async function main() {
302
+ const server = new McpServer({ name: "wickedapi", version: "0.2.0" });
303
+ const doFetch = await buildFetcher();
304
+ let totalTools = 0;
305
+ const loaded = [];
306
+ for (const service of SERVICES) {
307
+ const doc = await loadServiceSpec(service);
308
+ if (!doc)
309
+ continue;
310
+ totalTools += registerServiceTools(server, service, doc, doFetch);
311
+ loaded.push(service.name);
312
+ }
313
+ if (totalTools === 0) {
314
+ throw new Error("No service specs loaded — check network access and WICKEDAPI_*_URL overrides.");
315
+ }
316
+ registerResources(server);
317
+ registerPrompts(server);
84
318
  const transport = new StdioServerTransport();
85
319
  await server.connect(transport);
86
- console.error(`[wickedapi-mcp] ready — ${registered} tools loaded from ${BASE_URL}/openapi.json`);
320
+ console.error(`[wickedapi-mcp] ready — ${totalTools} tools across ${loaded.length} service(s) (${loaded.join(", ")}), ` +
321
+ `${2 + SERVICES.length} resources, 3 prompts.`);
87
322
  }
88
323
  main().catch((err) => {
89
324
  console.error("[wickedapi-mcp] fatal:", err);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "wickedapi-mcp",
3
- "version": "0.1.0",
4
- "description": "MCP server for WickedAPI — agent-ready trading & market-data API (crypto/stock prices, indicators, prediction markets, DeFi, macro, AI momentum score). No signup required: pay per call in USDC via x402, or use a partner API key.",
3
+ "version": "0.2.0",
4
+ "description": "MCP server for the whole WickedAPI platform — trading data, DeFi protocol health, token safety, and brokerage sync. 35 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": {
7
7
  "wickedapi-mcp": "dist/index.js"
@@ -31,6 +31,8 @@
31
31
  "crypto",
32
32
  "stocks",
33
33
  "market-data",
34
+ "defi",
35
+ "brokerage",
34
36
  "x402",
35
37
  "agent"
36
38
  ],