agentcanary-mcp 1.0.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.
@@ -0,0 +1,20 @@
1
+ name: Publish to npm
2
+ on:
3
+ push:
4
+ tags: ['v*']
5
+ jobs:
6
+ publish:
7
+ runs-on: ubuntu-latest
8
+ permissions:
9
+ contents: read
10
+ id-token: write
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-node@v4
14
+ with:
15
+ node-version: '20'
16
+ registry-url: 'https://registry.npmjs.org'
17
+ - run: npm install
18
+ - run: npm publish --provenance --access public
19
+ env:
20
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # AgentCanary MCP Server
2
+
3
+ Connect any MCP-compatible AI client to [AgentCanary](https://agentcanary.ai) market intelligence.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npx agentcanary-mcp
9
+ ```
10
+
11
+ ## Claude Desktop Config
12
+
13
+ ```json
14
+ {
15
+ "mcpServers": {
16
+ "agentcanary": {
17
+ "command": "npx",
18
+ "args": ["agentcanary-mcp"],
19
+ "env": { "AC_API_KEY": "ac_your_key_here" }
20
+ }
21
+ }
22
+ }
23
+ ```
24
+
25
+ ## Get an API Key
26
+
27
+ ```bash
28
+ curl -X POST https://api.agentcanary.ai/api/keys/create \
29
+ -H "Content-Type: application/json" \
30
+ -d '{"walletAddress": "0xYourWallet"}'
31
+ ```
32
+
33
+ Deposit USDC/USDT on any major EVM chain (Base, Ethereum, Arbitrum, Optimism, Polygon). $5 minimum. Credits never expire.
34
+
35
+ ## Tools
36
+
37
+ | Tool | Tier | Description |
38
+ |------|------|-------------|
39
+ | `get_briefs` | Explorer | Latest AI market briefs (4x daily) |
40
+ | `get_regime` | Explorer | Current macro regime + risk gauge |
41
+ | `get_indicator` | Builder | Any of 36 proprietary indicators |
42
+ | `get_narratives` | Signal | Narrative momentum scores |
43
+ | `get_scenarios` | Signal | Forward scenario probabilities |
44
+ | `get_whale_alerts` | Explorer | Large crypto transactions |
45
+ | `get_fear_greed` | Explorer | Crypto Fear & Greed Index |
46
+ | `get_signals` | Signal | Multi-factor trading signals |
47
+
48
+ ## Links
49
+
50
+ - [API Docs](https://api.agentcanary.ai/api/docs)
51
+ - [Website](https://agentcanary.ai)
52
+ - [Telegram](https://t.me/AgentCanary)
53
+
54
+ ## License
55
+
56
+ MIT
package/index.js ADDED
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentCanary MCP Server
4
+ *
5
+ * Connect any MCP-compatible AI client to AgentCanary market intelligence.
6
+ *
7
+ * Setup:
8
+ * AC_API_KEY=ac_your_key_here node index.js
9
+ *
10
+ * Or in Claude Desktop config:
11
+ * { "command": "npx", "args": ["agentcanary-mcp"], "env": { "AC_API_KEY": "ac_..." } }
12
+ *
13
+ * Tools available depend on your tier:
14
+ * Explorer: briefs, scores
15
+ * Builder: + indicators, regime, narratives, news, predictions
16
+ * Signal: + scenario analysis, full content
17
+ */
18
+
19
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
20
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
21
+ import { z } from "zod";
22
+
23
+ const API_BASE = process.env.AC_API_BASE || "https://api.agentcanary.ai/api";
24
+ const API_KEY = process.env.AC_API_KEY;
25
+
26
+ if (!API_KEY) {
27
+ console.error("Error: AC_API_KEY environment variable is required.");
28
+ console.error("Get your key at https://agentcanary.ai or POST https://api.agentcanary.ai/api/keys/create");
29
+ process.exit(1);
30
+ }
31
+
32
+ // ─── Helpers ─────────────────────────────────────────────────────
33
+
34
+ async function acFetch(endpoint, params = {}) {
35
+ const url = new URL(`${API_BASE}/${endpoint}`);
36
+ for (const [k, v] of Object.entries(params)) {
37
+ if (v != null) url.searchParams.set(k, String(v));
38
+ }
39
+ const res = await fetch(url.toString(), {
40
+ headers: { "x-api-key": API_KEY, "User-Agent": "AgentCanary-MCP/1.0" },
41
+ });
42
+ if (res.status === 401) throw new Error("Invalid API key. Check your AC_API_KEY.");
43
+ if (res.status === 403) throw new Error("Endpoint not available on your tier. Upgrade at agentcanary.ai");
44
+ if (res.status === 429) throw new Error("Rate limit exceeded. Wait and try again.");
45
+ if (!res.ok) {
46
+ const text = await res.text();
47
+ throw new Error(`AC API ${res.status}: ${text.slice(0, 200)}`);
48
+ }
49
+ return res.json();
50
+ }
51
+
52
+ function truncate(obj, maxChars = 8000) {
53
+ const str = JSON.stringify(obj, null, 2);
54
+ if (str.length <= maxChars) return str;
55
+ return str.slice(0, maxChars) + "\n... [truncated]";
56
+ }
57
+
58
+ function stripHtml(s) {
59
+ return (s || "").replace(/<[^>]+>/g, "");
60
+ }
61
+
62
+ // ─── Tier detection (best-effort) ────────────────────────────────
63
+
64
+ let detectedTier = "explorer";
65
+
66
+ async function detectTier() {
67
+ try {
68
+ const res = await fetch(`${API_BASE}/keys/info`, {
69
+ method: "POST",
70
+ headers: { "Content-Type": "application/json" },
71
+ body: JSON.stringify({ apiKey: API_KEY }),
72
+ });
73
+ if (res.ok) {
74
+ const data = await res.json();
75
+ detectedTier = data.tier || "explorer";
76
+ }
77
+ } catch { /* default to explorer */ }
78
+ }
79
+
80
+ // ─── Server ──────────────────────────────────────────────────────
81
+
82
+ const server = new McpServer({
83
+ name: "agentcanary",
84
+ version: "1.0.0",
85
+ });
86
+
87
+ // --- Tool: get_briefs (all tiers) ---
88
+ server.tool(
89
+ "get_briefs",
90
+ "Get AgentCanary market intelligence briefs (Morning Brief, Market Pulse, Signal Scan, Evening Wrap, Cycle Check). Returns headlines, tags, panels, and content.",
91
+ {
92
+ limit: z.number().min(1).max(50).default(10).describe("Number of briefs to return"),
93
+ date: z.string().optional().describe("Filter by date (YYYY-MM-DD)"),
94
+ session: z.string().optional().describe("Filter by session type: morning, midday, intelligence, evening, cycle"),
95
+ },
96
+ async ({ limit, date, session }) => {
97
+ const data = await acFetch("briefs/archive", { limit });
98
+ let briefs = data.briefs || [];
99
+ if (date) briefs = briefs.filter(b => b.date === date);
100
+ if (session) briefs = briefs.filter(b => b.session === session);
101
+
102
+ const clean = briefs.map(b => ({
103
+ date: b.date,
104
+ session: b.session,
105
+ time: b.time,
106
+ headline: b.headline,
107
+ desc: b.desc,
108
+ tags: (b.tags || []).map(t => t.t),
109
+ panels: b.panels,
110
+ content: stripHtml(b.content).slice(0, 2000),
111
+ }));
112
+ return { content: [{ type: "text", text: truncate(clean) }] };
113
+ }
114
+ );
115
+
116
+ // --- Tool: get_indicators (builder+) ---
117
+ server.tool(
118
+ "get_indicators",
119
+ "Get market indicators. 36 proprietary indicators including Bull Market Support Band, Pi Cycle, Wyckoff Structure, Stablecoin Composite, Composite Risk Score, and more. Pass a name for a single indicator, or list all. Requires Builder tier or above.",
120
+ {
121
+ name: z.string().optional().describe("Specific indicator name, e.g. 'bull-market-support-band', 'btc-pi-cycle', 'wyckoff-structure'"),
122
+ category: z.string().optional().describe("Filter by category: crypto, macro, sentiment, technical, liquidity"),
123
+ },
124
+ async ({ name, category }) => {
125
+ if (name) {
126
+ const data = await acFetch(`indicators/${name}`);
127
+ return { content: [{ type: "text", text: truncate(data) }] };
128
+ }
129
+ const data = await acFetch("indicators");
130
+ let indicators = data.indicators || [];
131
+ if (category) {
132
+ indicators = indicators.filter(i =>
133
+ (i.category || "").toLowerCase().includes(category.toLowerCase())
134
+ );
135
+ }
136
+ return { content: [{ type: "text", text: truncate({ date: data.date, count: indicators.length, indicators }) }] };
137
+ }
138
+ );
139
+
140
+ // --- Tool: get_regime (builder+) ---
141
+ server.tool(
142
+ "get_regime",
143
+ "Get current macro regime classification (expansion, stagflation, late-cycle, recession, etc.) with transition probabilities and favored/unfavored assets. Requires Builder tier or above.",
144
+ {},
145
+ async () => {
146
+ const data = await acFetch("regime");
147
+ return { content: [{ type: "text", text: truncate(data) }] };
148
+ }
149
+ );
150
+
151
+ // --- Tool: get_predictions (builder+) ---
152
+ server.tool(
153
+ "get_predictions",
154
+ "Get prediction market data (Polymarket, Kalshi, etc.) with probabilities for geopolitical, economic, and market events. Requires Builder tier or above.",
155
+ {},
156
+ async () => {
157
+ const data = await acFetch("predictions");
158
+ return { content: [{ type: "text", text: truncate(data) }] };
159
+ }
160
+ );
161
+
162
+ // --- Tool: get_narratives (builder+) ---
163
+ server.tool(
164
+ "get_narratives",
165
+ "Get narrative momentum scores — which market themes are heating up or cooling down (AI, energy, defense, crypto, healthcare, etc.). Requires Builder tier or above.",
166
+ {},
167
+ async () => {
168
+ const data = await acFetch("narratives");
169
+ return { content: [{ type: "text", text: truncate(data) }] };
170
+ }
171
+ );
172
+
173
+ // --- Tool: get_news (builder+) ---
174
+ server.tool(
175
+ "get_news",
176
+ "Get latest market news articles aggregated by AgentCanary. Filterable by ticker. Requires Builder tier or above.",
177
+ {
178
+ limit: z.number().min(1).max(30).default(10).describe("Number of articles"),
179
+ ticker: z.string().optional().describe("Filter by ticker symbol (e.g. BTC, NVDA)"),
180
+ },
181
+ async ({ limit, ticker }) => {
182
+ const params = { limit };
183
+ if (ticker) params.ticker = ticker;
184
+ const data = await acFetch("news", params);
185
+ return { content: [{ type: "text", text: truncate(data) }] };
186
+ }
187
+ );
188
+
189
+ // --- Tool: get_scores (all tiers) ---
190
+ server.tool(
191
+ "get_scores",
192
+ "Get prediction scoring results from The Record — how accurate AgentCanary's scenario price targets have been (hit/miss/partial rates with details).",
193
+ {},
194
+ async () => {
195
+ try {
196
+ const res = await fetch("https://agentcanary.ai/record/data/predictions.json");
197
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
198
+ const data = await res.json();
199
+ const preds = data.predictions || [];
200
+
201
+ const scored = preds.filter(p => p.result && p.result !== "pending" && p.result !== "no_data");
202
+ const hits = scored.filter(p => p.result === "hit").length;
203
+ const partials = scored.filter(p => p.result === "partial").length;
204
+ const misses = scored.filter(p => p.result === "miss").length;
205
+ const pending = preds.filter(p => p.result === "pending").length;
206
+
207
+ const summary = {
208
+ total: preds.length,
209
+ scored: scored.length,
210
+ hits,
211
+ partials,
212
+ misses,
213
+ pending,
214
+ hitRate: scored.length > 0 ? Math.round((hits / scored.length) * 100) + "%" : "N/A",
215
+ lastScored: data.lastScored,
216
+ };
217
+
218
+ const byDate = {};
219
+ for (const p of scored) {
220
+ if (!byDate[p.date]) byDate[p.date] = [];
221
+ byDate[p.date].push({
222
+ scenario: `${p.scenario} — ${p.scenarioName}`,
223
+ ticker: p.ticker,
224
+ range: `$${p.rangeMin}-$${p.rangeMax}`,
225
+ result: p.result,
226
+ actualClose: p.actualClose,
227
+ });
228
+ }
229
+
230
+ return { content: [{ type: "text", text: truncate({ summary, results: byDate }) }] };
231
+ } catch (e) {
232
+ return { content: [{ type: "text", text: `Scoring data not available: ${e.message}` }] };
233
+ }
234
+ }
235
+ );
236
+
237
+ // --- Tool: get_scenario_analysis (signal+) ---
238
+ server.tool(
239
+ "get_scenario_analysis",
240
+ "Get forward scenario analysis from Signal Scan — price targets for different market scenarios (geopolitical, stagflation, risk-on, etc.) with implication. Requires Signal tier.",
241
+ {
242
+ date: z.string().optional().describe("Specific date (YYYY-MM-DD). Defaults to latest."),
243
+ },
244
+ async ({ date }) => {
245
+ const data = await acFetch("briefs/archive", { limit: 50 });
246
+ let briefs = (data.briefs || []).filter(b => b.session === "intelligence" || b.session === "signal");
247
+ if (date) briefs = briefs.filter(b => b.date === date);
248
+
249
+ const results = briefs.slice(0, 3).map(b => {
250
+ const content = stripHtml(b.content);
251
+ const scenarioSection = content.split("FORWARD SCENARIOS")[1]?.split("SECTOR STRENGTH")[0] || "";
252
+ const implication = content.split("IMPLICATION")[1]?.split("\n\n")[0] || "";
253
+ const positioning = content.split("POSITIONING")[1]?.split("IMPLICATION")[0] || "";
254
+ return {
255
+ date: b.date,
256
+ headline: b.headline,
257
+ scenarios: scenarioSection.trim(),
258
+ positioning: positioning.trim().slice(0, 500),
259
+ implication: implication.trim(),
260
+ };
261
+ });
262
+ return { content: [{ type: "text", text: truncate(results) }] };
263
+ }
264
+ );
265
+
266
+ // ─── Start ───────────────────────────────────────────────────────
267
+
268
+ await detectTier();
269
+ const transport = new StdioServerTransport();
270
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "agentcanary-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for AgentCanary market intelligence — briefs, indicators, regime, narratives, predictions, scoring",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "agentcanary-mcp": "./index.js"
9
+ },
10
+ "keywords": ["mcp", "market-intelligence", "agentcanary", "trading", "macro", "crypto"],
11
+ "author": "AgentCanary",
12
+ "license": "MIT",
13
+ "homepage": "https://agentcanary.ai",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/MrCerq/agentcanary-mcp"
17
+ },
18
+ "dependencies": {
19
+ "@modelcontextprotocol/sdk": "^1.27.1"
20
+ },
21
+ "engines": {
22
+ "node": ">=18"
23
+ }
24
+ }