mcp-server-madeonsol 1.27.0 → 2.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.
- package/README.md +26 -2
- package/dist/http-security.d.ts +14 -0
- package/dist/http-security.js +180 -0
- package/dist/index.js +38 -50
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/glama.json +27 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,8 +11,14 @@
|
|
|
11
11
|
|
|
12
12
|
MCP server for [MadeOnSol](https://madeonsol.com) Solana KOL intelligence API. Use from Claude Desktop, Cursor, or any MCP-compatible client.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
<!-- Stats below are deliberate conservative floors kept in sync with the site's canonical labels (src/lib/constants.ts KOL_COUNT_LABEL / DEPLOYERS_PROFILED_LABEL / ALPHA_WALLETS_LABEL), rounded down from a live count measured on a known date and bumped only when the real count crosses the next threshold -- never the exact live number, which changes every minute. Do not replace with a live/volatile count. -->
|
|
15
15
|
|
|
16
|
+
> Real-time Solana trading intelligence: track 2,000+ KOL wallets with <3s latency on paid keys and x402 pay-per-call (free-tier live feeds are 5-min delayed), score 85K+ Pump.fun deployers, surface deshred deploy signals **~500ms before on-chain confirmation**, detect multi-KOL coordination, surface bundle-cohort holdings (which same-slot wallets still hold a token's supply), verify any wallet's CURRENT on-chain holdings straight from its token accounts, and stream every DEX trade across 9+ programs. Free tier: 200 requests/day across 40+ endpoints (live feeds 5-min delayed) — no signup payment. Get a key at [madeonsol.com/pricing](https://madeonsol.com/pricing).
|
|
17
|
+
|
|
18
|
+
> **New in 2.0.0 — BREAKING: HTTP mode now requires a separate token (security fix, SEC-02).** If you run this server with `MCP_TRANSPORT=http`, you must now also set `MCP_HTTP_TOKEN` (a random secret you generate) and send it as `Authorization: Bearer <token>` on **every** request, including `GET /health` and the server-card endpoint — previously, anything that could reach the bound port could call every tool using the operator's own API key with no authentication at all. HTTP mode is now hard-restricted to literal loopback (`127.0.0.1` / `::1`), rejects `Origin`/`X-Forwarded-*` headers and duplicate `Authorization`/`Host` headers outright, exposes only `POST /mcp` plus the two GET routes, and **refuses to start** if a wallet/payment signer is configured — HTTP mode can never carry a payer key. **If you use the default `stdio` transport (Claude Desktop, Cursor, most MCP clients), nothing changes — no action needed.** This is a single shared-operator-token fix, not a new multi-user or OAuth system; each HTTP caller still shares the operator's own MadeOnSol API key. Full writeup: `docs/audit/SEC02_PRIVATE_HTTP_MCP.md`.
|
|
19
|
+
>
|
|
20
|
+
> **New in 1.27.1 — the server now reports MCP `instructions`.** The `initialize` response's `instructions` field (operational guidance for the calling agent — distinct from this README/package description) was never set; directories that introspect the live server (Glama) reported "no recorded MCP instructions." No new tools; still 104 total.
|
|
21
|
+
>
|
|
16
22
|
> **New in 1.27.0 — top traders, sniper watchlist management, and two update tools (104 tools total).** Six additions found by an internal agentic-infra coverage audit. `madeonsol_token_top_traders` (`GET /tokens/{mint}/top-traders`, PRO/ULTRA) ranks a token's wallets by realized PnL or ROI, enriched with KOL/alpha-wallet identity — this endpoint already existed on the REST API and x402 rail but had no MCP tool. `madeonsol_sniper_watchlist_list` / `_add` / `_remove` (PRO+/ULTRA) manage your custom sniper-deploy watchlist (`madeonsol_sniper_recent(watchlist: true)` narrows to it) — previously only reachable via raw HTTP. `madeonsol_update_webhook` (PATCH) changes a webhook's URL, events, or active state without deleting and recreating it. `madeonsol_wallet_tracker_relabel` (PATCH) renames or clears a tracked wallet's label.
|
|
17
23
|
>
|
|
18
24
|
> **New in 1.26.0 — deployer reputation as-of a date, and creator-fee rewards.** Two new tools (PRO+, keyed `msk_` API only — no x402 route). `madeonsol_deployer_as_of` binds `GET /deployer-hunter/{wallet}/as-of`: the deployer's reputation exactly as it stood on `date` (default today, UTC) — the latest write-on-change snapshot at or before it, so an agent backtests without look-ahead bias. `snapshot.snapshot_date` can predate `date` (write-on-change); `snapshot.carried: true` marks that. No snapshot at or before `date` → `as_of: false, snapshot: null` — nothing is ever synthesized. `date` must be ≥ 2026-04-07 and not in the future. `madeonsol_deployer_rewards` binds `GET /deployer-hunter/{wallet}/rewards`: pump.fun creator-fee rewards, answered two ways that are never merged — `collected` (what actually reached the wallet: direct vault claims kept 90 days, social-handle claims, shareholder payouts on **any** token) and `attributed` (every payout on the tokens it **deployed**, split `to_self`/`to_others` + `redirected_pct`). Every money field is `{sol, usdc, usd}`; `usd` is `null` (never a silent 0) when a SOL amount exists and no SOL price was available. `top_tokens`/`top_recipients` (≤10, USD-sorted) show where attributed fees went. Works for non-deployers too (`is_deployer: false`, `attributed` empty).
|
|
@@ -165,6 +171,24 @@ Add to `claude_desktop_config.json`:
|
|
|
165
171
|
|
|
166
172
|
Add to MCP settings with the same command and env vars.
|
|
167
173
|
|
|
174
|
+
|
|
175
|
+
## Private HTTP transport (SEC-02)
|
|
176
|
+
|
|
177
|
+
Stdio remains the default for Claude Desktop, Cursor and other local MCP clients. HTTP now requires an explicit private-operator configuration; previously unauthenticated HTTP launch settings will fail closed.
|
|
178
|
+
|
|
179
|
+
1. Set `MADEONSOL_API_KEY` to the operator's `msk_` key.
|
|
180
|
+
2. Generate a separate random access token, for example `node -e "console.log(require('node:crypto').randomBytes(32).toString('base64url'))"`, and store it as `MCP_HTTP_TOKEN`. Do not reuse the upstream API key or put either credential in a URL.
|
|
181
|
+
3. Set `MCP_TRANSPORT=http`, `HOST=127.0.0.1` (default, or `::1`) and optionally `PORT` (default `3100`). Non-loopback bindings, missing credentials and any `SVM_PRIVATE_KEY` or `RHC_PAYER_KEY` are refused before startup. Solana wallet/x402 mode remains available through stdio.
|
|
182
|
+
4. Connect to `http://127.0.0.1:3100/mcp` with `Authorization: Bearer <MCP_HTTP_TOKEN>` on **every request**, plus the normal MCP `Content-Type` and `Accept` headers. Use stdio if the client cannot attach headers. The local token is checked by the MCP adapter; only `MADEONSOL_API_KEY` is sent upstream.
|
|
183
|
+
|
|
184
|
+
Only `POST /mcp`, `GET /health` and `GET /.well-known/mcp/server-card.json` are exposed, all authenticated. `/` and arbitrary paths are no longer MCP endpoints. The adapter is stateless: it does not issue session IDs, rejects supplied `Mcp-Session-Id`, and returns 405 for GET/DELETE on `/mcp`. Host must be the selected loopback literal with its port, or `localhost` with that exact port. Browser Origin headers, forwarded/proxy headers and cross-origin requests are rejected; no CORS access is granted.
|
|
185
|
+
|
|
186
|
+
Limits: 256 KiB uncompressed JSON bodies, 8 KiB headers, a 10-second body-upload deadline, and 16 active authenticated requests. Oversized or malformed inputs are rejected before tool dispatch. This is not an overall tool-execution deadline; an upstream action already submitted may continue after a disconnect.
|
|
187
|
+
|
|
188
|
+
Everyone holding the local token acts as the **same operator**, including access to that operator's mutation tools. This is not a multi-user/OAuth server: do not put it behind a public proxy, share it with untrusted users or expose a funded signer. Separate users require isolated processes/credentials or a future transport that authenticates each principal and maps their own credentials. Restart with a new `MCP_HTTP_TOKEN` to rotate access.
|
|
189
|
+
|
|
190
|
+
The Docker image uses the same loopback restriction and requires both environment credentials. Its healthcheck authenticates without putting the token in the URL. It does not support a publicly published Docker port; use stdio or a client in the same trusted network namespace.
|
|
191
|
+
|
|
168
192
|
## Tools
|
|
169
193
|
|
|
170
194
|
### KOL Intelligence
|
|
@@ -223,7 +247,7 @@ Cached server-side with dynamic TTL (5min / 1h / 24h based on last activity). Co
|
|
|
223
247
|
|
|
224
248
|
### Alpha Wallet Intelligence
|
|
225
249
|
|
|
226
|
-
Scored from
|
|
250
|
+
Scored from 1.5M+ early-buyer records (wallets seen in the first 20 buyers of Pump.fun tokens).
|
|
227
251
|
|
|
228
252
|
| Tool | Tier | Description |
|
|
229
253
|
|---|---|---|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
2
|
+
export declare const MAX_BODY_BYTES: number;
|
|
3
|
+
export declare const BODY_TIMEOUT_MS = 10000;
|
|
4
|
+
export declare const MAX_ACTIVE_REQUESTS = 16;
|
|
5
|
+
export interface HttpConfig {
|
|
6
|
+
host: "127.0.0.1" | "::1";
|
|
7
|
+
port: number;
|
|
8
|
+
allowedHosts: string[];
|
|
9
|
+
tokenDigest: Buffer;
|
|
10
|
+
}
|
|
11
|
+
export declare function readHttpConfig(env?: NodeJS.ProcessEnv): HttpConfig;
|
|
12
|
+
/** Bounded body ingestion; callers must authenticate before invoking this. */
|
|
13
|
+
export declare function readJsonBody(req: IncomingMessage, maxBytes?: number, timeoutMs?: number): Promise<unknown>;
|
|
14
|
+
export declare function createPrivateHttpServer(config: HttpConfig, handle: (req: IncomingMessage, res: ServerResponse, body?: unknown) => Promise<void>): import("http").Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// Keep this standalone-package helper identical in both MCP packages.
|
|
2
|
+
// HTTP is a private, single-operator adapter. Public/multi-user hosting is
|
|
3
|
+
// intentionally unsupported until credentials can be scoped per principal.
|
|
4
|
+
import { createServer } from "node:http";
|
|
5
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
6
|
+
export const MAX_BODY_BYTES = 256 * 1024;
|
|
7
|
+
export const BODY_TIMEOUT_MS = 10_000;
|
|
8
|
+
export const MAX_ACTIVE_REQUESTS = 16;
|
|
9
|
+
export function readHttpConfig(env = process.env) {
|
|
10
|
+
const host = env.HOST || "127.0.0.1";
|
|
11
|
+
if (host !== "127.0.0.1" && host !== "::1") {
|
|
12
|
+
throw new Error("HTTP MCP requires HOST=127.0.0.1 or ::1; public binding is unsupported. Use stdio for local clients.");
|
|
13
|
+
}
|
|
14
|
+
const portText = env.PORT || "3100";
|
|
15
|
+
const port = Number(portText);
|
|
16
|
+
if (!/^\d+$/.test(portText) || !Number.isInteger(port) || port < 1 || port > 65535) {
|
|
17
|
+
throw new Error("HTTP MCP requires a valid PORT between 1 and 65535.");
|
|
18
|
+
}
|
|
19
|
+
if (env.SVM_PRIVATE_KEY || env.RHC_PAYER_KEY) {
|
|
20
|
+
throw new Error("HTTP MCP cannot expose a payment signer. Remove wallet credentials or use stdio.");
|
|
21
|
+
}
|
|
22
|
+
if (!env.MADEONSOL_API_KEY?.startsWith("msk_")) {
|
|
23
|
+
throw new Error("HTTP MCP requires the operator's MADEONSOL_API_KEY.");
|
|
24
|
+
}
|
|
25
|
+
const token = env.MCP_HTTP_TOKEN || "";
|
|
26
|
+
if (!/^[A-Za-z0-9_-]{32,256}$/.test(token) || token === env.MADEONSOL_API_KEY) {
|
|
27
|
+
throw new Error("HTTP MCP requires a separate random MCP_HTTP_TOKEN (32–256 base64url characters).");
|
|
28
|
+
}
|
|
29
|
+
const authority = host === "::1" ? `[::1]:${port}` : `${host}:${port}`;
|
|
30
|
+
return {
|
|
31
|
+
host, port, allowedHosts: [authority, `localhost:${port}`],
|
|
32
|
+
tokenDigest: createHash("sha256").update(token).digest(),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
class HttpFailure extends Error {
|
|
36
|
+
status;
|
|
37
|
+
constructor(status, message) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.status = status;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function fail(res, status, message) {
|
|
43
|
+
if (res.headersSent) {
|
|
44
|
+
res.destroy();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
res.writeHead(status, {
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
"Cache-Control": "no-store",
|
|
50
|
+
"Connection": "close",
|
|
51
|
+
...(status === 401 ? { "WWW-Authenticate": 'Bearer realm="private-mcp"' } : {}),
|
|
52
|
+
});
|
|
53
|
+
res.end(JSON.stringify({ error: message }));
|
|
54
|
+
}
|
|
55
|
+
function headerCount(req, name) {
|
|
56
|
+
let count = 0;
|
|
57
|
+
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
58
|
+
if (req.rawHeaders[i].toLowerCase() === name)
|
|
59
|
+
count++;
|
|
60
|
+
}
|
|
61
|
+
return count;
|
|
62
|
+
}
|
|
63
|
+
function authorize(req, res, config) {
|
|
64
|
+
const remote = req.socket.remoteAddress;
|
|
65
|
+
if ((remote !== "127.0.0.1" && remote !== "::1") ||
|
|
66
|
+
headerCount(req, "host") !== 1 || !config.allowedHosts.includes(req.headers.host || "")) {
|
|
67
|
+
fail(res, 403, "Forbidden host");
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
// Native local MCP clients do not need browser origins, CORS or proxy headers.
|
|
71
|
+
if (req.headers.origin !== undefined || Object.keys(req.headers).some(h => h === "forwarded" || h.startsWith("x-forwarded-"))) {
|
|
72
|
+
fail(res, 403, "Browser origins and forwarded requests are unsupported");
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const auth = req.headers.authorization;
|
|
76
|
+
const match = typeof auth === "string" ? /^Bearer ([A-Za-z0-9_-]{32,256})$/i.exec(auth) : null;
|
|
77
|
+
if (headerCount(req, "authorization") !== 1 || !match || !timingSafeEqual(createHash("sha256").update(match[1]).digest(), config.tokenDigest)) {
|
|
78
|
+
fail(res, 401, "Unauthorized");
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
/** Bounded body ingestion; callers must authenticate before invoking this. */
|
|
84
|
+
export function readJsonBody(req, maxBytes = MAX_BODY_BYTES, timeoutMs = BODY_TIMEOUT_MS) {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const chunks = [];
|
|
87
|
+
let size = 0;
|
|
88
|
+
const cleanup = () => {
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
req.off("data", data);
|
|
91
|
+
req.off("end", end);
|
|
92
|
+
req.off("aborted", aborted);
|
|
93
|
+
req.off("error", error);
|
|
94
|
+
};
|
|
95
|
+
const stop = (reason) => { cleanup(); req.pause(); reject(reason); };
|
|
96
|
+
const timer = setTimeout(() => stop(new HttpFailure(408, "Request body timed out")), timeoutMs);
|
|
97
|
+
const data = (chunk) => {
|
|
98
|
+
size += chunk.length;
|
|
99
|
+
if (size > maxBytes) {
|
|
100
|
+
stop(new HttpFailure(413, "Request body too large"));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
chunks.push(chunk);
|
|
104
|
+
};
|
|
105
|
+
const end = () => {
|
|
106
|
+
cleanup();
|
|
107
|
+
try {
|
|
108
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
reject(new HttpFailure(400, "Invalid JSON"));
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const aborted = () => stop(new HttpFailure(400, "Request aborted"));
|
|
115
|
+
const error = () => stop(new HttpFailure(400, "Request failed"));
|
|
116
|
+
req.on("data", data);
|
|
117
|
+
req.once("end", end);
|
|
118
|
+
req.once("aborted", aborted);
|
|
119
|
+
req.once("error", error);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
export function createPrivateHttpServer(config, handle) {
|
|
123
|
+
let active = 0;
|
|
124
|
+
const server = createServer({ maxHeaderSize: 8192 }, (req, res) => {
|
|
125
|
+
void (async () => {
|
|
126
|
+
if (!authorize(req, res, config))
|
|
127
|
+
return;
|
|
128
|
+
const isMcp = req.url === "/mcp";
|
|
129
|
+
const isInfo = req.url === "/health" || req.url === "/.well-known/mcp/server-card.json";
|
|
130
|
+
if (!isMcp && !isInfo) {
|
|
131
|
+
fail(res, 404, "Not found");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const method = isMcp ? "POST" : "GET";
|
|
135
|
+
if (req.method !== method) {
|
|
136
|
+
res.setHeader("Allow", method);
|
|
137
|
+
fail(res, 405, "Method not allowed");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (isMcp && (req.headers["content-type"]?.split(";")[0].trim().toLowerCase() !== "application/json" ||
|
|
141
|
+
(req.headers["content-encoding"] !== undefined && req.headers["content-encoding"] !== "identity"))) {
|
|
142
|
+
fail(res, 415, "Only uncompressed application/json is supported");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (Number(req.headers["content-length"] || 0) > MAX_BODY_BYTES) {
|
|
146
|
+
fail(res, 413, "Request body too large");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// No sessions are issued: a supplied session id must never become an auth bypass.
|
|
150
|
+
if (req.headers["mcp-session-id"] !== undefined) {
|
|
151
|
+
fail(res, 400, "HTTP MCP is stateless; session identifiers are unsupported");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (active >= MAX_ACTIVE_REQUESTS) {
|
|
155
|
+
fail(res, 503, "Too many active requests");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
active++;
|
|
159
|
+
let released = false;
|
|
160
|
+
const release = () => { if (!released) {
|
|
161
|
+
released = true;
|
|
162
|
+
active--;
|
|
163
|
+
} };
|
|
164
|
+
res.once("close", release);
|
|
165
|
+
res.once("finish", release);
|
|
166
|
+
res.setHeader("Cache-Control", "no-store");
|
|
167
|
+
const body = isMcp ? await readJsonBody(req) : undefined;
|
|
168
|
+
if (!res.destroyed)
|
|
169
|
+
await handle(req, res, body);
|
|
170
|
+
})().catch(error => {
|
|
171
|
+
if (!res.destroyed)
|
|
172
|
+
fail(res, error instanceof HttpFailure ? error.status : 500, error instanceof HttpFailure ? error.message : "Internal server error");
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
server.headersTimeout = 10_000;
|
|
176
|
+
server.requestTimeout = 15_000;
|
|
177
|
+
server.keepAliveTimeout = 5_000;
|
|
178
|
+
server.maxRequestsPerSocket = 100;
|
|
179
|
+
return server;
|
|
180
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -4,11 +4,16 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { VERSION } from "./version.js";
|
|
7
|
-
import {
|
|
7
|
+
import { createPrivateHttpServer, readHttpConfig } from "./http-security.js";
|
|
8
|
+
// MCP `initialize` response `instructions` field (ServerOptions.instructions in
|
|
9
|
+
// the SDK) — operational guidance for the calling agent, distinct from the
|
|
10
|
+
// package/marketing description in package.json and glama.json. Left unset
|
|
11
|
+
// until 2026-09-18, which is why directories that introspect the live server
|
|
12
|
+
// (e.g. Glama) reported "no recorded MCP instructions."
|
|
13
|
+
const SERVER_INSTRUCTIONS = "Real-time Solana on-chain intelligence: KOL wallet trades, Pump.fun deployer reputation, token risk/holders/buyer-quality, wallet PnL, and the all-DEX trade tape. Call madeonsol_discovery first to see every endpoint and its x402 price. Auth is either a msk_ API key (MADEONSOL_API_KEY env var, madeonsol.com/pricing) or pay-per-call x402 micropayments (SVM_PRIVATE_KEY env var) — no signup required for x402. Prefer a single-item lookup (madeonsol_token_get, madeonsol_wallet_stats) before a heavier call (madeonsol_wallet_pnl, madeonsol_token_risk, madeonsol_token_trades) on the same mint/wallet. Free-tier live feeds (KOL/deployer alerts) are delayed 5 minutes; paid keys and x402 calls are real-time.";
|
|
8
14
|
const BASE_URL = process.env.MADEONSOL_API_URL || "https://madeonsol.com";
|
|
9
15
|
const MADEONSOL_API_KEY = process.env.MADEONSOL_API_KEY; // Native key from madeonsol.com/pricing
|
|
10
16
|
const PRIVATE_KEY = process.env.SVM_PRIVATE_KEY; // x402 micropayments (for AI agents)
|
|
11
|
-
const PORT = parseInt(process.env.PORT || "3100", 10);
|
|
12
17
|
const MODE = process.env.MCP_TRANSPORT || "stdio"; // "stdio" or "http"
|
|
13
18
|
let authMode = "none";
|
|
14
19
|
let paidFetch = fetch;
|
|
@@ -1269,12 +1274,11 @@ function registerTools(server) {
|
|
|
1269
1274
|
});
|
|
1270
1275
|
}
|
|
1271
1276
|
async function main() {
|
|
1277
|
+
const httpConfig = MODE === "http" ? readHttpConfig() : undefined;
|
|
1272
1278
|
await initAuth();
|
|
1273
1279
|
if (MODE === "http") {
|
|
1274
|
-
//
|
|
1275
|
-
const httpServer =
|
|
1276
|
-
const transports = new Map();
|
|
1277
|
-
httpServer.on("request", async (req, res) => {
|
|
1280
|
+
// Configuration is validated before authentication can initialize a signer.
|
|
1281
|
+
const httpServer = createPrivateHttpServer(httpConfig, async (req, res, body) => {
|
|
1278
1282
|
// Health check
|
|
1279
1283
|
if (req.method === "GET" && req.url === "/health") {
|
|
1280
1284
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -1286,7 +1290,7 @@ async function main() {
|
|
|
1286
1290
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1287
1291
|
res.end(JSON.stringify({
|
|
1288
1292
|
name: "madeonsol",
|
|
1289
|
-
description: "Solana KOL trading intelligence and deployer analytics. Real-time data from 1,000+ KOL wallets, 15,500+ Pump.fun deployers, 25,000+ scored alpha wallets, copy-trade rules, and wallet tracker.
|
|
1293
|
+
description: "Solana KOL trading intelligence and deployer analytics. Real-time data from 1,000+ KOL wallets, 15,500+ Pump.fun deployers, 25,000+ scored alpha wallets, copy-trade rules, and wallet tracker. Private HTTP access with the operator's MadeOnSol API key (msk_).",
|
|
1290
1294
|
version: VERSION,
|
|
1291
1295
|
tools: [
|
|
1292
1296
|
{ name: "madeonsol_kol_feed", description: "Get real-time Solana KOL trades from 1,000+ tracked wallets." },
|
|
@@ -1384,54 +1388,35 @@ async function main() {
|
|
|
1384
1388
|
}));
|
|
1385
1389
|
return;
|
|
1386
1390
|
}
|
|
1387
|
-
//
|
|
1388
|
-
const
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
}
|
|
1403
|
-
if (req.method === "GET" && sessionId) {
|
|
1404
|
-
const transport = transports.get(sessionId);
|
|
1405
|
-
if (transport) {
|
|
1406
|
-
await transport.handleRequest(req, res);
|
|
1407
|
-
return;
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
if (req.method === "DELETE" && sessionId) {
|
|
1411
|
-
const transport = transports.get(sessionId);
|
|
1412
|
-
if (transport) {
|
|
1413
|
-
await transport.handleRequest(req, res);
|
|
1414
|
-
transports.delete(sessionId);
|
|
1415
|
-
return;
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1391
|
+
// One server/transport per authenticated stateless POST /mcp.
|
|
1392
|
+
const transport = new StreamableHTTPServerTransport({
|
|
1393
|
+
sessionIdGenerator: undefined,
|
|
1394
|
+
enableJsonResponse: true,
|
|
1395
|
+
enableDnsRebindingProtection: true,
|
|
1396
|
+
allowedHosts: httpConfig.allowedHosts,
|
|
1397
|
+
allowedOrigins: [],
|
|
1398
|
+
});
|
|
1399
|
+
const server = new McpServer({ name: "madeonsol", version: VERSION }, { instructions: SERVER_INSTRUCTIONS });
|
|
1400
|
+
res.once("close", () => { void server.close().catch(() => { }); });
|
|
1401
|
+
registerTools(server);
|
|
1402
|
+
await server.connect(transport);
|
|
1403
|
+
if (res.destroyed) {
|
|
1404
|
+
await server.close();
|
|
1405
|
+
return;
|
|
1418
1406
|
}
|
|
1419
|
-
|
|
1420
|
-
res.end("Not found");
|
|
1407
|
+
await transport.handleRequest(req, res, body);
|
|
1421
1408
|
});
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
httpServer.listen(PORT, HOST, () => {
|
|
1429
|
-
console.error(`[madeonsol-mcp] HTTP server listening on ${HOST}:${PORT}`);
|
|
1409
|
+
httpServer.on("error", () => {
|
|
1410
|
+
console.error("[madeonsol-mcp] HTTP listener failed");
|
|
1411
|
+
process.exitCode = 1;
|
|
1412
|
+
});
|
|
1413
|
+
httpServer.listen(httpConfig.port, httpConfig.host, () => {
|
|
1414
|
+
console.error(`[madeonsol-mcp] HTTP server listening on ${httpConfig.host}:${httpConfig.port}/mcp (private operator only)`);
|
|
1430
1415
|
});
|
|
1431
1416
|
}
|
|
1432
1417
|
else {
|
|
1433
1418
|
// Stdio transport for local use (Claude Desktop, Cursor, Claude Code)
|
|
1434
|
-
const server = new McpServer({ name: "madeonsol", version: VERSION });
|
|
1419
|
+
const server = new McpServer({ name: "madeonsol", version: VERSION }, { instructions: SERVER_INSTRUCTIONS });
|
|
1435
1420
|
registerTools(server);
|
|
1436
1421
|
const transport = new StdioServerTransport();
|
|
1437
1422
|
await server.connect(transport);
|
|
@@ -1440,5 +1425,8 @@ async function main() {
|
|
|
1440
1425
|
// Only auto-run when executed as a program (CLI / spawned process), not when
|
|
1441
1426
|
// the module is imported by a test for its exported pure helpers.
|
|
1442
1427
|
if (process.env.MADEONSOL_MCP_NO_AUTORUN !== "1") {
|
|
1443
|
-
main().catch(
|
|
1428
|
+
main().catch(error => {
|
|
1429
|
+
console.error(error instanceof Error ? error.message : "MCP startup failed");
|
|
1430
|
+
process.exitCode = 1;
|
|
1431
|
+
});
|
|
1444
1432
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "
|
|
1
|
+
export declare const VERSION = "2.0.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// AUTO-GENERATED by ../gen-version.mjs (npm prebuild) from package.json. Do not edit.
|
|
2
|
-
export const VERSION = "
|
|
2
|
+
export const VERSION = "2.0.0";
|
package/glama.json
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"madeonsol"
|
|
5
5
|
],
|
|
6
6
|
"display_name": "MadeOnSol — Solana memecoin intelligence",
|
|
7
|
-
"description": "Real-time Solana memecoin trading intelligence — track
|
|
8
|
-
"version": "1.
|
|
7
|
+
"description": "Real-time Solana memecoin trading intelligence — track 2,000+ KOL wallets with sub-second latency on paid keys and x402 (free-tier live feeds are 5-min delayed), score 85,000+ reputation-profiled Pump.fun deployers out of 1.2M+ indexed, surface multi-KOL coordination signals, run server-side copy-trade rules, and stream every DEX trade across 14 programs. Backtested first-touch scout signal (S-tier scouts attract ≥3 follow-on KOLs ~50% of the time vs 14% baseline). Free tier 200 requests per day (live feeds 5-min delayed), no payment to start; auth via msk_ API key or x402 micropayments.",
|
|
8
|
+
"version": "1.27.1",
|
|
9
9
|
"homepage": "https://madeonsol.com/solana-api",
|
|
10
10
|
"repository": "https://github.com/madeonsol/mcp-server-madeonsol",
|
|
11
11
|
"license": "MIT",
|
|
@@ -427,11 +427,32 @@
|
|
|
427
427
|
{
|
|
428
428
|
"name": "madeonsol_signal_performance",
|
|
429
429
|
"description": "Signal performance stats for a named signal — hit rate, sample size, median outcome, and confidence window."
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
"name": "madeonsol_sniper_watchlist_list",
|
|
433
|
+
"description": "List your custom sniper watchlist (tracked deployer wallets, any tier). PRO+/ULTRA only."
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
"name": "madeonsol_sniper_watchlist_add",
|
|
437
|
+
"description": "Add one or many deployer wallets to your sniper watchlist. PRO+/ULTRA only."
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
"name": "madeonsol_sniper_watchlist_remove",
|
|
441
|
+
"description": "Remove a deployer wallet from your sniper watchlist. PRO+/ULTRA only."
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
"name": "madeonsol_wallet_tracker_relabel",
|
|
445
|
+
"description": "Rename (or clear) the label on a wallet already in your watchlist."
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
"name": "madeonsol_update_webhook",
|
|
449
|
+
"description": "Update a webhook's URL, subscribed events, or active state."
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
"name": "madeonsol_token_top_traders",
|
|
453
|
+
"description": "Wallets that made (or lost) the most on a token, ranked by realized PnL or ROI, enriched with KOL/alpha-wallet identity."
|
|
430
454
|
}
|
|
431
455
|
],
|
|
432
|
-
"transports": [
|
|
433
|
-
"stdio",
|
|
434
|
-
"http"
|
|
435
|
-
],
|
|
456
|
+
"transports": ["stdio"],
|
|
436
457
|
"runtime": "node"
|
|
437
458
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-server-madeonsol",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"mcpName": "io.github.MadeOnSol/madeonsol",
|
|
5
5
|
"description": "MCP server for MadeOnSol Solana KOL intelligence API — use from Claude, Cursor, or any MCP client. Free tier 200 req/day (live feeds 5-min delayed; paid is real-time), no payment to start.",
|
|
6
6
|
"type": "module",
|