@magpieloans/magpie-mcp 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Magpie Capital
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # magpie-mcp
2
+
3
+ **MCP server exposing the Magpie Capital x402 API as native tools for Claude Desktop, Cursor, Windsurf, ChatGPT desktop, and any other MCP-aware agent host.**
4
+
5
+ Drop one config block into your host and your agent can query Magpie's protocol state, simulate borrows, fetch credit scores, build deposit/withdraw transactions, and post conditional borrow intents — all as first-class tool calls. No bespoke client code, no API keys.
6
+
7
+ ## What it exposes
8
+
9
+ 19 tools wrapping the x402 endpoints:
10
+
11
+ **Free reads (work out of the box):**
12
+ - `magpie_pool_state` — live LendingPool account
13
+ - `magpie_protocol_pulse` — 24h aggregates (active loans, volume, liquidations)
14
+ - `magpie_recent_activity` — anonymized borrow/repay/liquidate stream
15
+ - `magpie_loan` — single loan by ID
16
+ - `magpie_wallet_loans` — all loans for a wallet
17
+ - `magpie_tiers` — protocol tier constants
18
+ - `magpie_simulate_borrow` — quote a loan without submitting
19
+ - `magpie_collateral_eligible` — full collateral catalog
20
+ - `magpie_liquidatable` — loans currently liquidatable
21
+ - `magpie_credit_leaderboard` — top wallets by credit score
22
+ - `magpie_lp_state` — depositor position + pool context
23
+
24
+ **Paid (require a configured Solana keypair):**
25
+ - `magpie_credit_score` — 0.001 SOL
26
+ - `magpie_token_risk` — 0.001 SOL (per-token risk profile)
27
+ - `magpie_build_borrow` — 0.005 SOL
28
+ - `magpie_build_repay` — 0.002 SOL
29
+ - `magpie_build_deposit` — 0.002 SOL
30
+ - `magpie_build_withdraw` — 0.002 SOL
31
+ - `magpie_build_liquidate` — 0.003 SOL (liquidate a past-due loan, receive keeper bounty)
32
+ - `magpie_create_intent` — 0.01 SOL (conditional borrow)
33
+ - `magpie_get_intent` — 0.0005 SOL (poll)
34
+
35
+ When a paid tool fires, the server signs an x402 payment tx locally with your configured keypair and forwards the signature to magpie-x402. The keypair never leaves your machine.
36
+
37
+ ## Install
38
+
39
+ Two paths. Both end at the same place.
40
+
41
+ ### Path A — npm (recommended once published)
42
+
43
+ No clone, no build, no absolute paths in your host config:
44
+
45
+ ```bash
46
+ # Verify it runs once before wiring into your host:
47
+ npx -y @magpieloans/magpie-mcp --help
48
+ ```
49
+
50
+ Then in your host config:
51
+
52
+ ```json
53
+ {
54
+ "mcpServers": {
55
+ "magpie": {
56
+ "command": "npx",
57
+ "args": ["-y", "@magpieloans/magpie-mcp"],
58
+ "env": {
59
+ "SOLANA_RPC_URL": "https://api.mainnet-beta.solana.com",
60
+ "MAGPIE_MCP_PAYER_KEYPAIR": "/path/to/payer-id.json"
61
+ }
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ ### Path B — from source
68
+
69
+ If the npm package isn't published yet, or you want to hack on the server:
70
+
71
+ ```bash
72
+ git clone git@github.com:magpiecapital/magpie-x402.git
73
+ cd magpie-x402/mcp
74
+ npm install
75
+ npm run build
76
+ ```
77
+
78
+ Built executable lands at `mcp/dist/index.js`. Use the absolute path in your host config below.
79
+
80
+ ## Configure your host
81
+
82
+ Pick the snippet for your host. The `command` + `args` shape changes between npm-install vs source-build; the `env` block is identical.
83
+
84
+ ### Claude Desktop
85
+
86
+ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
87
+
88
+ ```json
89
+ {
90
+ "mcpServers": {
91
+ "magpie": {
92
+ "command": "node",
93
+ "args": ["/ABS/PATH/TO/magpie-x402/mcp/dist/index.js"],
94
+ "env": {
95
+ "SOLANA_RPC_URL": "https://api.mainnet-beta.solana.com",
96
+ "MAGPIE_MCP_PAYER_KEYPAIR": "/ABS/PATH/TO/payer-id.json"
97
+ }
98
+ }
99
+ }
100
+ }
101
+ ```
102
+
103
+ Restart Claude Desktop. The Magpie tools appear in the tool picker.
104
+
105
+ ### Cursor
106
+
107
+ Edit `~/.cursor/mcp.json`. Same shape as Claude Desktop above.
108
+
109
+ ### Windsurf
110
+
111
+ Edit `~/.codeium/windsurf/mcp_config.json`. Same shape.
112
+
113
+ ### ChatGPT desktop / any other MCP-aware host
114
+
115
+ Use the same shape — point the host at `node /ABS/PATH/.../mcp/dist/index.js`, pass env vars through the host's MCP config interface.
116
+
117
+ ## Free-only mode (no keypair)
118
+
119
+ Omit `MAGPIE_MCP_PAYER_KEYPAIR` and all 11 free tools still work. Paid tools return a clear error explaining that no payer is configured. Useful for read-only research agents or as a no-friction first install.
120
+
121
+ ## Environment variables
122
+
123
+ | Variable | Default | Purpose |
124
+ |---|---|---|
125
+ | `MAGPIE_X402_BASE_URL` | `https://x402.magpie.capital` | Override for self-hosted or testnet deployments |
126
+ | `SOLANA_RPC_URL` | `https://api.mainnet-beta.solana.com` | RPC used to send x402 payment txs. Public RPC rate-limits aggressively — use Helius/Triton/QuickNode for any real volume. |
127
+ | `MAGPIE_MCP_PAYER_KEYPAIR` | _(unset)_ | Path to a Solana keypair JSON in the standard `solana-keygen` format. Enables paid tools. |
128
+
129
+ ## Cost ceiling
130
+
131
+ The keypair you configure is the only place SOL can leave. Per-call costs are exact (the server doesn't ever pay more than the 402 challenge demands), but if you're paranoid about runaway tool-calls during agent experimentation, fund the payer wallet with a small float — say 0.05 SOL — and let it run dry rather than top it up unattended.
132
+
133
+ ## Security model
134
+
135
+ - The MCP server reads but never modifies your keypair file. The keypair is loaded into memory once at startup; the signature for each paid call happens locally; only the resulting tx signature ever leaves your machine (sent to magpie-x402 in the `X-Payment` header).
136
+ - The Magpie x402 service has no way to sign anything on your behalf. Even with a complete compromise of x402.magpie.capital, the worst case is that you pay for tool calls you didn't get useful results from. Your keypair is not exposed.
137
+ - For high-stakes write paths (`build-borrow`, `build-deposit`, etc.) the server returns an *unsigned* tx. Your agent receives it as plain text; you decide whether to sign + submit it. The MCP server itself never signs anything besides x402 payment transfers.
138
+
139
+ ## Troubleshooting
140
+
141
+ **Tool doesn't appear in host UI** — restart the host after editing config. Most hosts only re-read the MCP config on launch.
142
+
143
+ **402 error on a paid tool** — `MAGPIE_MCP_PAYER_KEYPAIR` is unset or the file doesn't exist. Run `solana-keygen new -o ~/.config/solana/magpie-mcp.json` and point the env var at the result, then `solana transfer …` a small SOL float in.
144
+
145
+ **`fetch failed` on x402.magpie.capital** — check the URL is reachable from your machine. If you're behind a corporate proxy, set `HTTPS_PROXY` in the same `env` block.
146
+
147
+ **RPC rate-limit during a paid call** — switch `SOLANA_RPC_URL` to a paid Helius/Triton/QuickNode URL. The default `api.mainnet-beta.solana.com` will throttle under any sustained use.
148
+
149
+ ## For maintainers — publishing to npm
150
+
151
+ The package is set up for `npm publish` with no additional configuration. From `mcp/`:
152
+
153
+ ```bash
154
+ # One-time per machine:
155
+ npm login --scope=@magpieloans
156
+
157
+ # Each release:
158
+ # 1. Bump version in package.json (semver)
159
+ # 2. Publish — prepublishOnly rebuilds dist/ and chmod +x's the binary
160
+ npm publish
161
+ ```
162
+
163
+ The package is scoped (`@magpieloans/magpie-mcp`) and `publishConfig.access: "public"` is set, so publish doesn't require any extra flags. The `files` field whitelists what ships — only `dist/`, `README.md`, `LICENSE` end up in the tarball (~9 KB).
164
+
165
+ Verify the contents before publish:
166
+
167
+ ```bash
168
+ npm pack --dry-run
169
+ ```
170
+
171
+ ## License
172
+
173
+ MIT.
package/dist/index.js ADDED
@@ -0,0 +1,407 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Magpie MCP server — exposes the magpie-x402 API as native MCP tools
4
+ * for Claude Desktop, Cursor, Windsurf, ChatGPT desktop, and any other
5
+ * MCP-aware agent host.
6
+ *
7
+ * Transport: stdio (the standard for desktop hosts).
8
+ *
9
+ * Free tools work out of the box. Paid tools require a configured
10
+ * Solana keypair (env var) that's funded with a small SOL balance for
11
+ * the per-call payments. The server signs payment txs locally; nothing
12
+ * is sent to magpie-x402 except the public X-Payment header.
13
+ *
14
+ * Env vars:
15
+ * MAGPIE_X402_BASE_URL default https://x402.magpie.capital
16
+ * SOLANA_RPC_URL default https://api.mainnet-beta.solana.com
17
+ * MAGPIE_MCP_PAYER_KEYPAIR path to a Solana keypair JSON (~/.config/solana/id.json format)
18
+ *
19
+ * Run from this directory:
20
+ * npm install && npm run build && node dist/index.js
21
+ *
22
+ * Or via tsx without build step:
23
+ * npx tsx src/index.ts
24
+ *
25
+ * Wire into Claude Desktop / Cursor / Windsurf via their `mcpServers`
26
+ * config — see ../README.md in this directory for snippets.
27
+ */
28
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
29
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
30
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
31
+ import { call, loadKeypairFromEnv } from "./x402-client.js";
32
+ const baseUrl = process.env.MAGPIE_X402_BASE_URL ?? "https://x402.magpie.capital";
33
+ const rpcUrl = process.env.SOLANA_RPC_URL ?? "https://api.mainnet-beta.solana.com";
34
+ const payer = loadKeypairFromEnv();
35
+ const ctx = { baseUrl, rpcUrl, payer };
36
+ // Schema-only tool registry. Each entry maps to a thin handler below
37
+ // that turns the args into a magpie-x402 HTTP call. Keeping the tool
38
+ // list in one declarative block makes ListTools cheap + always in sync
39
+ // with CallTool dispatch.
40
+ const TOOLS = [
41
+ // ── Free reads ──────────────────────────────────────────────────
42
+ {
43
+ name: "magpie_pool_state",
44
+ description: "Get live on-chain Magpie LendingPool state — totalDeposits, totalBorrowed, totalShares, utilization, fees earned. Free, 15s cache.",
45
+ inputSchema: { type: "object", properties: {} },
46
+ },
47
+ {
48
+ name: "magpie_protocol_pulse",
49
+ description: "24h protocol aggregates: active loans, active borrowers, borrows in last 1h + 24h, borrow volume in SOL, repays, liquidations. Pure aggregates, no per-wallet data. Free, 30s cache.",
50
+ inputSchema: { type: "object", properties: {} },
51
+ },
52
+ {
53
+ name: "magpie_recent_activity",
54
+ description: "Anonymized recent borrow/repay/liquidate events across the protocol. Wallets reduced to Xxxx…Yyyy. Useful as 'is this protocol active right now' signal. Free, 15s cache.",
55
+ inputSchema: {
56
+ type: "object",
57
+ properties: {
58
+ limit: {
59
+ type: "integer",
60
+ minimum: 1,
61
+ maximum: 200,
62
+ default: 50,
63
+ },
64
+ },
65
+ },
66
+ },
67
+ {
68
+ name: "magpie_loan",
69
+ description: "Fetch a single Magpie loan by its u64 ID. Free.",
70
+ inputSchema: {
71
+ type: "object",
72
+ properties: { loan_id: { type: "string", pattern: "^[0-9]+$" } },
73
+ required: ["loan_id"],
74
+ },
75
+ },
76
+ {
77
+ name: "magpie_wallet_loans",
78
+ description: "All loans owned by a wallet. Optional ?status filter. Free, 8s cache.",
79
+ inputSchema: {
80
+ type: "object",
81
+ properties: {
82
+ wallet: { type: "string" },
83
+ status: { type: "string", enum: ["active", "repaid", "liquidated"] },
84
+ },
85
+ required: ["wallet"],
86
+ },
87
+ },
88
+ {
89
+ name: "magpie_tiers",
90
+ description: "Magpie loan tier constants — three fixed tiers (Express / Quick / Standard) with their LTV, duration, and fee parameters. Free, 1h cache.",
91
+ inputSchema: { type: "object", properties: {} },
92
+ },
93
+ {
94
+ name: "magpie_simulate_borrow",
95
+ description: "Preview a loan WITHOUT submitting. Pure math from caller-supplied prices + the public tier constants. Use tier='all' to compare all three tiers. Free.",
96
+ inputSchema: {
97
+ type: "object",
98
+ properties: {
99
+ mint: { type: "string" },
100
+ amount: { type: "string", pattern: "^[0-9]+$" },
101
+ decimals: { type: "string", pattern: "^[0-9]{1,2}$" },
102
+ pricePerTokenUsd: { type: "string" },
103
+ solPriceUsd: { type: "string" },
104
+ tier: {
105
+ type: "string",
106
+ enum: ["express", "quick", "standard", "all"],
107
+ default: "all",
108
+ },
109
+ },
110
+ required: ["mint", "amount", "decimals", "pricePerTokenUsd", "solPriceUsd"],
111
+ },
112
+ },
113
+ {
114
+ name: "magpie_collateral_eligible",
115
+ description: "Catalog of every token currently approved as Magpie collateral. Mint, symbol, decimals, category. First-touch endpoint for new agent integrations. Free, 1h cache.",
116
+ inputSchema: { type: "object", properties: {} },
117
+ },
118
+ {
119
+ name: "magpie_liquidatable",
120
+ description: "Active loans at or past their on-chain due timestamp — the canonical liquidation-bot data feed. Sorted most-past-due-first. Use within_seconds for pre-positioning. Free, 8s cache.",
121
+ inputSchema: {
122
+ type: "object",
123
+ properties: {
124
+ within_seconds: { type: "integer", minimum: 0, maximum: 604800 },
125
+ limit: { type: "integer", minimum: 1, maximum: 500 },
126
+ },
127
+ },
128
+ },
129
+ {
130
+ name: "magpie_credit_leaderboard",
131
+ description: "Top wallets ranked by Magpie credit score (anonymized to Xxxx…Yyyy). Free, 60s cache.",
132
+ inputSchema: { type: "object", properties: {} },
133
+ },
134
+ {
135
+ name: "magpie_lp_state",
136
+ description: "Read a wallet's LP position in the main LendingPool — shares, deposited lamports, current value, yield earned, share-of-pool, plus pool context. Free, 10s cache.",
137
+ inputSchema: {
138
+ type: "object",
139
+ properties: { wallet: { type: "string" } },
140
+ required: ["wallet"],
141
+ },
142
+ },
143
+ // ── Paid endpoints ──────────────────────────────────────────────
144
+ {
145
+ name: "magpie_credit_score",
146
+ description: "Magpie credit score (300-850) + tier benefits for a wallet. Paid: 0.001 SOL per lookup.",
147
+ inputSchema: {
148
+ type: "object",
149
+ properties: { wallet: { type: "string" } },
150
+ required: ["wallet"],
151
+ },
152
+ },
153
+ {
154
+ name: "magpie_token_risk",
155
+ description: "Per-token risk profile from Magpie's internal risk engine — risk score (0-100), dimension breakdown (volatility, liquidity, concentration, volume, rug_pull), market data, max allowed LTV the program will enforce, operator flags. Useful pre-borrow collateral check. Paid: 0.001 SOL per lookup.",
156
+ inputSchema: {
157
+ type: "object",
158
+ properties: { mint: { type: "string" } },
159
+ required: ["mint"],
160
+ },
161
+ },
162
+ {
163
+ name: "magpie_build_borrow",
164
+ description: "Build an UNSIGNED borrow transaction. Server runs the full anti-exploit gate eval (ban registry, TWAP, pool floor, cross-source price, RWA-only guard, etc.) and returns a partial-signed tx. Agent signs locally and submits to magpie.capital/api/v1/cosign-borrow. Paid: 0.005 SOL.",
165
+ inputSchema: {
166
+ type: "object",
167
+ properties: {
168
+ borrower_wallet: { type: "string" },
169
+ collateral_mint: { type: "string" },
170
+ collateral_amount: { type: "string", pattern: "^[0-9]+$" },
171
+ tier: { type: "integer", enum: [0, 1, 2] },
172
+ },
173
+ required: ["borrower_wallet", "collateral_mint", "collateral_amount", "tier"],
174
+ },
175
+ },
176
+ {
177
+ name: "magpie_build_repay",
178
+ description: "Build an unsigned repay tx for an existing loan. Paid: 0.002 SOL.",
179
+ inputSchema: {
180
+ type: "object",
181
+ properties: {
182
+ borrower_wallet: { type: "string" },
183
+ loan_id: { type: "string", pattern: "^[0-9]+$" },
184
+ },
185
+ required: ["borrower_wallet", "loan_id"],
186
+ },
187
+ },
188
+ {
189
+ name: "magpie_build_deposit",
190
+ description: "Build an UNSIGNED LP-deposit transaction (wraps SOL → wSOL → deposits into the main LendingPool → closes wSOL ATA). Agent signs and submits. Paid: 0.002 SOL.",
191
+ inputSchema: {
192
+ type: "object",
193
+ properties: {
194
+ depositor: { type: "string" },
195
+ lamports: { type: "string", pattern: "^[0-9]+$" },
196
+ },
197
+ required: ["depositor", "lamports"],
198
+ },
199
+ },
200
+ {
201
+ name: "magpie_build_withdraw",
202
+ description: "Build an unsigned LP-withdraw tx for the requested shares. Server pre-validates the on-chain position and refuses chunks larger than max_safe_shares. Paid: 0.002 SOL.",
203
+ inputSchema: {
204
+ type: "object",
205
+ properties: {
206
+ depositor: { type: "string" },
207
+ shares: { type: "string", pattern: "^[0-9]+$" },
208
+ },
209
+ required: ["depositor", "shares"],
210
+ },
211
+ },
212
+ {
213
+ name: "magpie_build_liquidate",
214
+ description: "Build an unsigned liquidate-loan tx for a past-due active loan. The keeper (your wallet) receives keeper_reward_bps share of the seized collateral as bounty; the rest goes to the lender authority for pool recovery. Permissionless — any wallet can liquidate. Server pre-validates the loan exists, is active, and is past due. Paid: 0.003 SOL. Workflow: poll magpie_liquidatable to find candidates, then call this on the loan_pda.",
215
+ inputSchema: {
216
+ type: "object",
217
+ properties: {
218
+ keeper: { type: "string" },
219
+ loan_pda: { type: "string" },
220
+ },
221
+ required: ["keeper", "loan_pda"],
222
+ },
223
+ },
224
+ {
225
+ name: "magpie_create_intent",
226
+ description: "Post a CONDITIONAL borrow intent. Bot watches the trigger condition (price_above / price_below / time_after / pool_liq_above) and builds the unsigned tx when matched. Single payment covers entire intent lifecycle. Paid: 0.01 SOL. Optional webhook_url — server POSTs an HMAC-SHA256-signed payload on match instead of forcing you to poll. The webhook_secret is returned ONCE in the response; persist it to verify signatures on receive.",
227
+ inputSchema: {
228
+ type: "object",
229
+ properties: {
230
+ borrower_wallet: { type: "string" },
231
+ collateral_mint: { type: "string" },
232
+ collateral_amount: { type: "string" },
233
+ tier: { type: "integer", enum: [0, 1, 2] },
234
+ condition_type: {
235
+ type: "string",
236
+ enum: ["price_above", "price_below", "time_after", "pool_liq_above"],
237
+ },
238
+ condition_params: { type: "object" },
239
+ expires_in_seconds: { type: "integer", minimum: 60, maximum: 2592000 },
240
+ webhook_url: {
241
+ type: "string",
242
+ format: "uri",
243
+ description: "Optional HTTPS endpoint to receive the match notification. Must be HTTPS; private IPs blocked.",
244
+ },
245
+ },
246
+ required: [
247
+ "borrower_wallet",
248
+ "collateral_mint",
249
+ "collateral_amount",
250
+ "tier",
251
+ "condition_type",
252
+ "condition_params",
253
+ ],
254
+ },
255
+ },
256
+ {
257
+ name: "magpie_get_intent",
258
+ description: "Poll the status of a conditional borrow intent. When status='matched', the response includes partial_signed_tx_b64 ready to sign + submit. Paid: 0.0005 SOL per poll.",
259
+ inputSchema: {
260
+ type: "object",
261
+ properties: { id: { type: "string" } },
262
+ required: ["id"],
263
+ },
264
+ },
265
+ ];
266
+ // ── Server wiring ─────────────────────────────────────────────────
267
+ const server = new Server({
268
+ name: "magpie-mcp",
269
+ version: "0.1.0",
270
+ }, {
271
+ capabilities: { tools: {} },
272
+ });
273
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
274
+ tools: TOOLS,
275
+ }));
276
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
277
+ const { name, arguments: args = {} } = req.params;
278
+ const a = args;
279
+ try {
280
+ let result;
281
+ switch (name) {
282
+ case "magpie_pool_state":
283
+ result = await call(ctx, "GET", "/api/v1/pool");
284
+ break;
285
+ case "magpie_protocol_pulse":
286
+ result = await call(ctx, "GET", "/api/v1/agent/protocol-pulse");
287
+ break;
288
+ case "magpie_recent_activity":
289
+ result = await call(ctx, "GET", "/api/v1/agent/activity", {
290
+ query: a.limit !== undefined ? { limit: String(a.limit) } : {},
291
+ });
292
+ break;
293
+ case "magpie_loan":
294
+ result = await call(ctx, "GET", `/api/v1/loan/${encodeURIComponent(String(a.loan_id))}`);
295
+ break;
296
+ case "magpie_wallet_loans": {
297
+ const q = {};
298
+ if (a.status)
299
+ q.status = String(a.status);
300
+ result = await call(ctx, "GET", `/api/v1/wallet/${encodeURIComponent(String(a.wallet))}/loans`, { query: q });
301
+ break;
302
+ }
303
+ case "magpie_tiers":
304
+ result = await call(ctx, "GET", "/api/v1/tiers");
305
+ break;
306
+ case "magpie_simulate_borrow": {
307
+ const q = {
308
+ mint: String(a.mint),
309
+ amount: String(a.amount),
310
+ decimals: String(a.decimals),
311
+ pricePerTokenUsd: String(a.pricePerTokenUsd),
312
+ solPriceUsd: String(a.solPriceUsd),
313
+ };
314
+ if (a.tier)
315
+ q.tier = String(a.tier);
316
+ result = await call(ctx, "GET", "/api/v1/simulate-borrow", { query: q });
317
+ break;
318
+ }
319
+ case "magpie_collateral_eligible":
320
+ result = await call(ctx, "GET", "/api/v1/collateral/eligible");
321
+ break;
322
+ case "magpie_liquidatable": {
323
+ const q = {};
324
+ if (a.within_seconds !== undefined)
325
+ q.within_seconds = String(a.within_seconds);
326
+ if (a.limit !== undefined)
327
+ q.limit = String(a.limit);
328
+ result = await call(ctx, "GET", "/api/v1/markets/liquidatable", { query: q });
329
+ break;
330
+ }
331
+ case "magpie_credit_leaderboard":
332
+ result = await call(ctx, "GET", "/api/v1/agent/leaderboard");
333
+ break;
334
+ case "magpie_lp_state":
335
+ result = await call(ctx, "GET", "/api/v1/agent/lp-state", {
336
+ query: { wallet: String(a.wallet) },
337
+ });
338
+ break;
339
+ case "magpie_credit_score":
340
+ result = await call(ctx, "GET", "/api/v1/credit-score", {
341
+ query: { wallet: String(a.wallet) },
342
+ });
343
+ break;
344
+ case "magpie_token_risk":
345
+ result = await call(ctx, "GET", "/api/v1/agent/token-risk", {
346
+ query: { mint: String(a.mint) },
347
+ });
348
+ break;
349
+ case "magpie_build_borrow":
350
+ result = await call(ctx, "POST", "/api/v1/agent/build-borrow", { body: a });
351
+ break;
352
+ case "magpie_build_repay":
353
+ result = await call(ctx, "POST", "/api/v1/agent/build-repay", { body: a });
354
+ break;
355
+ case "magpie_build_deposit":
356
+ result = await call(ctx, "POST", "/api/v1/agent/build-deposit", { body: a });
357
+ break;
358
+ case "magpie_build_withdraw":
359
+ result = await call(ctx, "POST", "/api/v1/agent/build-withdraw", { body: a });
360
+ break;
361
+ case "magpie_build_liquidate":
362
+ result = await call(ctx, "POST", "/api/v1/agent/build-liquidate", { body: a });
363
+ break;
364
+ case "magpie_create_intent":
365
+ result = await call(ctx, "POST", "/api/v1/agent/intent", { body: a });
366
+ break;
367
+ case "magpie_get_intent":
368
+ result = await call(ctx, "GET", "/api/v1/agent/intent", {
369
+ query: { id: String(a.id) },
370
+ });
371
+ break;
372
+ default:
373
+ return {
374
+ isError: true,
375
+ content: [{ type: "text", text: `unknown tool: ${name}` }],
376
+ };
377
+ }
378
+ return {
379
+ content: [
380
+ {
381
+ type: "text",
382
+ text: JSON.stringify({ data: result.data, paid: result.paid }, null, 2),
383
+ },
384
+ ],
385
+ };
386
+ }
387
+ catch (err) {
388
+ return {
389
+ isError: true,
390
+ content: [
391
+ {
392
+ type: "text",
393
+ text: `error calling ${name}: ${err.message}`,
394
+ },
395
+ ],
396
+ };
397
+ }
398
+ });
399
+ const transport = new StdioServerTransport();
400
+ await server.connect(transport);
401
+ // MCP servers communicate over stdio so anything written to stdout
402
+ // gets parsed as protocol JSON. Use stderr for any human-readable
403
+ // startup logs.
404
+ const payerNote = payer
405
+ ? `paid endpoints enabled — payer: ${payer.publicKey.toBase58()}`
406
+ : "free endpoints only — set MAGPIE_MCP_PAYER_KEYPAIR to enable paid tools";
407
+ process.stderr.write(`magpie-mcp ready (${baseUrl}) — ${payerNote}\n`);
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Minimal x402 client for the MCP server. Same logic as the
3
+ * examples/lib/x402-client.ts but typed for the MCP context (no
4
+ * keypair-loading helpers; the MCP server loads its keypair once
5
+ * at startup from env vars).
6
+ */
7
+ import { Connection, Keypair, PublicKey, SystemProgram, Transaction, TransactionInstruction, sendAndConfirmTransaction, } from "@solana/web3.js";
8
+ const MEMO_PROGRAM_ID = new PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");
9
+ export async function call(ctx, method, path, init = {}) {
10
+ const url = new URL(path, ctx.baseUrl);
11
+ for (const [k, v] of Object.entries(init.query ?? {})) {
12
+ url.searchParams.set(k, v);
13
+ }
14
+ const headers = { Accept: "application/json" };
15
+ if (init.body !== undefined)
16
+ headers["Content-Type"] = "application/json";
17
+ const first = await fetch(url, {
18
+ method,
19
+ headers,
20
+ body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
21
+ });
22
+ if (first.status !== 402) {
23
+ const data = (await first.json());
24
+ if (!first.ok) {
25
+ throw new Error(`magpie ${first.status} ${path}: ${JSON.stringify(data)}`);
26
+ }
27
+ return { data, paid: null };
28
+ }
29
+ if (!ctx.payer) {
30
+ throw new Error(`${path} is a paid endpoint (402) but no payer keypair was configured. Set MAGPIE_MCP_PAYER_KEYPAIR.`);
31
+ }
32
+ const payTo = first.headers.get("X-Payment-Required-Recipient");
33
+ const amountStr = first.headers.get("X-Payment-Required-Amount");
34
+ const nonce = first.headers.get("X-Payment-Required-Nonce");
35
+ const memo = first.headers.get("X-Payment-Required-Memo");
36
+ if (!payTo || !amountStr || !nonce || !memo) {
37
+ throw new Error(`${path} 402 missing required headers`);
38
+ }
39
+ const connection = new Connection(ctx.rpcUrl, "confirmed");
40
+ const tx = new Transaction();
41
+ tx.add(SystemProgram.transfer({
42
+ fromPubkey: ctx.payer.publicKey,
43
+ toPubkey: new PublicKey(payTo),
44
+ lamports: BigInt(amountStr),
45
+ }));
46
+ tx.add(new TransactionInstruction({
47
+ keys: [],
48
+ programId: MEMO_PROGRAM_ID,
49
+ data: Buffer.from(memo, "utf8"),
50
+ }));
51
+ const signature = await sendAndConfirmTransaction(connection, tx, [ctx.payer], { commitment: "confirmed" });
52
+ const retry = await fetch(url, {
53
+ method,
54
+ headers: { ...headers, "X-Payment": signature },
55
+ body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
56
+ });
57
+ const data = (await retry.json());
58
+ if (!retry.ok) {
59
+ throw new Error(`magpie retry ${retry.status} ${path}: ${JSON.stringify(data)}`);
60
+ }
61
+ return {
62
+ data,
63
+ paid: { amountLamports: amountStr, txSignature: signature, nonce },
64
+ };
65
+ }
66
+ export function loadKeypairFromEnv() {
67
+ // Two supported forms:
68
+ // MAGPIE_MCP_PAYER_KEYPAIR=/abs/path/to/id.json (Solana CLI format)
69
+ // MAGPIE_MCP_PAYER_SECRET=base58_encoded_secret_key
70
+ const path = process.env.MAGPIE_MCP_PAYER_KEYPAIR;
71
+ if (path) {
72
+ const fs = require("node:fs");
73
+ const raw = JSON.parse(fs.readFileSync(path.replace(/^~/, process.env.HOME || ""), "utf8"));
74
+ return Keypair.fromSecretKey(new Uint8Array(raw));
75
+ }
76
+ return undefined;
77
+ }
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@magpieloans/magpie-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server exposing Magpie Capital's x402 API as native tools for Claude Desktop, Cursor, Windsurf, ChatGPT desktop, and any MCP-aware agent host.",
5
+ "type": "module",
6
+ "bin": {
7
+ "magpie-mcp": "dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "dev": "tsx src/index.ts",
18
+ "typecheck": "tsc --noEmit",
19
+ "start": "node dist/index.js",
20
+ "prepublishOnly": "rm -rf dist && tsc && chmod +x dist/index.js"
21
+ },
22
+ "engines": {
23
+ "node": ">=20"
24
+ },
25
+ "keywords": [
26
+ "mcp",
27
+ "model-context-protocol",
28
+ "magpie",
29
+ "solana",
30
+ "defi",
31
+ "lending",
32
+ "ai-agents",
33
+ "x402",
34
+ "claude-desktop",
35
+ "cursor",
36
+ "windsurf"
37
+ ],
38
+ "author": "Magpie Capital",
39
+ "license": "MIT",
40
+ "homepage": "https://magpie.capital/x402",
41
+ "bugs": {
42
+ "url": "https://github.com/magpiecapital/magpie-x402/issues"
43
+ },
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/magpiecapital/magpie-x402.git",
47
+ "directory": "mcp"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public",
51
+ "registry": "https://registry.npmjs.org/"
52
+ },
53
+ "dependencies": {
54
+ "@modelcontextprotocol/sdk": "^1.29.0",
55
+ "@solana/web3.js": "^1.95.0"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^20.0.0",
59
+ "tsx": "^4.0.0",
60
+ "typescript": "^5.5.0"
61
+ }
62
+ }