@rougechain/mcp-server 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.
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # RougeChain MCP Server
2
+
3
+ > AI agents can now interact with a post-quantum blockchain.
4
+
5
+ The **first MCP-native blockchain integration** — lets AI agents (Claude, ChatGPT, custom agents) read chain state, query tokens, check balances, deploy WASM smart contracts, and more using the [Model Context Protocol](https://modelcontextprotocol.io/).
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ cd mcp-server
11
+ npm install
12
+ npm run build
13
+ ```
14
+
15
+ ### Claude Desktop Config
16
+
17
+ Add to `~/.config/claude/claude_desktop_config.json`:
18
+
19
+ ```json
20
+ {
21
+ "mcpServers": {
22
+ "rougechain": {
23
+ "command": "node",
24
+ "args": ["/path/to/quantum-vault/mcp-server/dist/index.js"],
25
+ "env": {
26
+ "ROUGECHAIN_URL": "https://api.rougechain.io"
27
+ }
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ ### Environment Variables
34
+
35
+ | Variable | Default | Description |
36
+ |----------|---------|-------------|
37
+ | `ROUGECHAIN_URL` | `https://api.rougechain.io` | RougeChain API host (the `api.` subdomain — **not** the `rougechain.io` frontend, which serves the web app) |
38
+ | `ROUGECHAIN_API_KEY` | (none) | Optional API key |
39
+
40
+ ## Available Tools (29)
41
+
42
+ ### Chain Info
43
+ - `get_chain_stats` — Network stats (height, peers, validators, supply)
44
+ - `get_block` — Get block by height
45
+ - `get_latest_blocks` — Recent blocks
46
+
47
+ ### Wallet & Balance
48
+ - `get_balance` — Check XRGE or token balance
49
+ - `get_transaction` — Look up a transaction
50
+
51
+ ### Tokens
52
+ - `list_tokens` — All custom tokens
53
+ - `get_token` — Token metadata
54
+ - `get_token_holders` — Top holders
55
+
56
+ ### DeFi / AMM
57
+ - `list_pools` — Liquidity pools
58
+ - `get_swap_quote` — AMM swap quote
59
+
60
+ ### NFTs
61
+ - `list_nft_collections` — All NFT collections
62
+ - `get_nft_collection` — Collection details + tokens
63
+
64
+ ### Validators
65
+ - `list_validators` — Network validators
66
+
67
+ ### WASM Smart Contracts
68
+ - `list_contracts` — All deployed contracts
69
+ - `get_contract` — Contract metadata
70
+ - `get_contract_state` — Read contract storage
71
+ - `get_contract_events` — Contract event log
72
+ - `deploy_contract` — Deploy WASM bytecode
73
+ - `call_contract` — Execute contract method
74
+
75
+ ### Social
76
+ - `get_global_timeline` — Global post timeline (newest first)
77
+ - `get_post` — Get a single post with engagement stats
78
+ - `get_user_posts` — Get posts by a specific user
79
+ - `get_post_replies` — Get threaded replies to a post
80
+ - `get_track_stats` — Get play/like/comment stats for a track
81
+ - `get_artist_stats` — Get follower/following counts for an artist
82
+
83
+ ### Mail & Messaging
84
+ - `resolve_name` — Resolve a mail name to wallet info and encryption keys
85
+ - `reverse_lookup_name` — Look up the registered mail name for a wallet ID
86
+ - `list_messenger_wallets` — List registered messenger wallets with display names
87
+
88
+ ### Other
89
+ - `list_proposals` — Governance proposals
90
+ - `get_fee_info` — Dynamic fee info (EIP-1559)
91
+
92
+ ## Resources
93
+
94
+ - `rougechain://info` — Static context about RougeChain's tech stack, features, and API
95
+
96
+ ## Architecture
97
+
98
+ ```
99
+ AI Agent (Claude/GPT/GLTCH)
100
+ ↕ stdio (MCP protocol)
101
+ RougeChain MCP Server
102
+ ↕ HTTPS
103
+ RougeChain Node API
104
+ ↕ PQC-signed transactions
105
+ RougeChain L1 (ML-DSA + ML-KEM)
106
+ ```
107
+
108
+ All operations maintain post-quantum security. WASM contract execution runs in a fuel-metered sandbox. Transactions are ML-DSA-65 signed.
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * RougeChain MCP Server
4
+ *
5
+ * Exposes RougeChain blockchain operations as MCP tools for AI agents.
6
+ * The first post-quantum, AI-agent-native programmable blockchain.
7
+ *
8
+ * Usage:
9
+ * ROUGECHAIN_URL=https://api.rougechain.io npx @rougechain/mcp-server
10
+ */
11
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,290 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * RougeChain MCP Server
4
+ *
5
+ * Exposes RougeChain blockchain operations as MCP tools for AI agents.
6
+ * The first post-quantum, AI-agent-native programmable blockchain.
7
+ *
8
+ * Usage:
9
+ * ROUGECHAIN_URL=https://api.rougechain.io npx @rougechain/mcp-server
10
+ */
11
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
+ import { z } from "zod";
14
+ // ─── Configuration ────────────────────────────────────────────────────────────
15
+ // NOTE: this must be the API host (api.rougechain.io), NOT the frontend host
16
+ // (rougechain.io), which serves the SPA index.html for every /api/* path.
17
+ const BASE_URL = process.env.ROUGECHAIN_URL || "https://api.rougechain.io";
18
+ const API = `${BASE_URL}/api`;
19
+ const API_KEY = process.env.ROUGECHAIN_API_KEY || "";
20
+ // ─── HTTP helpers ─────────────────────────────────────────────────────────────
21
+ const headers = {
22
+ "Content-Type": "application/json",
23
+ ...(API_KEY ? { "X-API-Key": API_KEY } : {}),
24
+ };
25
+ async function apiGet(path) {
26
+ const res = await fetch(`${API}${path}`, { headers });
27
+ return res.json();
28
+ }
29
+ async function apiPost(path, body) {
30
+ const res = await fetch(`${API}${path}`, {
31
+ method: "POST",
32
+ headers,
33
+ body: JSON.stringify(body),
34
+ });
35
+ return res.json();
36
+ }
37
+ // ─── MCP Server ───────────────────────────────────────────────────────────────
38
+ const server = new McpServer({
39
+ name: "rougechain",
40
+ version: "1.0.0",
41
+ });
42
+ // ══════════════════════════════════════════════════════════════════════════════
43
+ // TOOLS — actions that AI agents can perform on RougeChain
44
+ // ══════════════════════════════════════════════════════════════════════════════
45
+ // ── Chain Info ────────────────────────────────────────────────────────────────
46
+ server.tool("get_chain_stats", "Get RougeChain network statistics: block height, peer count, validator count, total supply", {}, async () => {
47
+ const data = await apiGet("/stats");
48
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
49
+ });
50
+ server.tool("get_block", "Get a block by height from the RougeChain blockchain", { height: z.number().describe("Block height to retrieve") }, async ({ height }) => {
51
+ const data = await apiGet(`/block/${height}`);
52
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
53
+ });
54
+ server.tool("get_latest_blocks", "Get the most recent blocks from the chain", { limit: z.number().optional().default(10).describe("Number of blocks to return (max 100)") }, async ({ limit }) => {
55
+ const data = await apiGet(`/blocks?limit=${limit}`);
56
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
57
+ });
58
+ // ── Wallet & Balance ─────────────────────────────────────────────────────────
59
+ server.tool("get_balance", "Check XRGE or token balance for a wallet address or public key", {
60
+ address: z.string().describe("Wallet address (rouge1...) or public key hex"),
61
+ token: z.string().optional().describe("Token symbol (omit for XRGE native balance)"),
62
+ }, async ({ address, token }) => {
63
+ const path = token
64
+ ? `/balance/${address}/${token}`
65
+ : `/balance/${address}`;
66
+ const data = await apiGet(path);
67
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
68
+ });
69
+ server.tool("get_transaction", "Look up a specific transaction by hash", { hash: z.string().describe("Transaction hash") }, async ({ hash }) => {
70
+ const data = await apiGet(`/tx/${hash}`);
71
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
72
+ });
73
+ // ── Token Operations ─────────────────────────────────────────────────────────
74
+ server.tool("list_tokens", "List all custom tokens on RougeChain with their metadata", {}, async () => {
75
+ const data = await apiGet("/tokens");
76
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
77
+ });
78
+ server.tool("get_token", "Get detailed metadata for a specific token by symbol", { symbol: z.string().describe("Token symbol (e.g. ROUGE)") }, async ({ symbol }) => {
79
+ const data = await apiGet(`/token/${symbol}/metadata`);
80
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
81
+ });
82
+ server.tool("get_token_holders", "Get the top holders of a specific token", { symbol: z.string().describe("Token symbol") }, async ({ symbol }) => {
83
+ const data = await apiGet(`/token/${symbol}/holders`);
84
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
85
+ });
86
+ // ── DeFi / AMM ───────────────────────────────────────────────────────────────
87
+ server.tool("list_pools", "List all liquidity pools on RougeChain DEX", {}, async () => {
88
+ const data = await apiGet("/pools");
89
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
90
+ });
91
+ server.tool("get_swap_quote", "Get a swap quote from the AMM (price, slippage, route)", {
92
+ from: z.string().describe("Source token symbol"),
93
+ to: z.string().describe("Destination token symbol"),
94
+ amount: z.number().describe("Amount of source token to swap"),
95
+ }, async ({ from, to, amount }) => {
96
+ const data = await apiPost("/swap/quote", {
97
+ token_in: from,
98
+ token_out: to,
99
+ amount_in: amount,
100
+ });
101
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
102
+ });
103
+ // ── NFTs ──────────────────────────────────────────────────────────────────────
104
+ server.tool("list_nft_collections", "List all NFT collections on RougeChain", {}, async () => {
105
+ const data = await apiGet("/nft/collections");
106
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
107
+ });
108
+ server.tool("get_nft_collection", "Get details and tokens for an NFT collection", { symbol: z.string().describe("Collection symbol") }, async ({ symbol }) => {
109
+ const data = await apiGet(`/nft/collection/${symbol}`);
110
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
111
+ });
112
+ // ── Validators & Staking ─────────────────────────────────────────────────────
113
+ server.tool("list_validators", "List all validators on the RougeChain network with their stake and status", {}, async () => {
114
+ const data = await apiGet("/validators");
115
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
116
+ });
117
+ // ── WASM Smart Contracts ─────────────────────────────────────────────────────
118
+ server.tool("list_contracts", "List all deployed WASM smart contracts on RougeChain", {}, async () => {
119
+ const data = await apiGet("/contracts");
120
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
121
+ });
122
+ server.tool("get_contract", "Get metadata for a deployed smart contract", { address: z.string().describe("Contract address (hex)") }, async ({ address }) => {
123
+ const data = await apiGet(`/contract/${address}`);
124
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
125
+ });
126
+ server.tool("get_contract_state", "Read contract storage. Omit key to dump all state; provide key for single-value lookup.", {
127
+ address: z.string().describe("Contract address"),
128
+ key: z.string().optional().describe("Storage key (hex or string). Omit to dump all state."),
129
+ }, async ({ address, key }) => {
130
+ const path = key
131
+ ? `/contract/${address}/state?key=${encodeURIComponent(key)}`
132
+ : `/contract/${address}/state`;
133
+ const data = await apiGet(path);
134
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
135
+ });
136
+ server.tool("get_contract_events", "Get the event log for a smart contract", {
137
+ address: z.string().describe("Contract address"),
138
+ limit: z.number().optional().default(50).describe("Max events to return"),
139
+ }, async ({ address, limit }) => {
140
+ const data = await apiGet(`/contract/${address}/events?limit=${limit}`);
141
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
142
+ });
143
+ server.tool("deploy_contract", "Deploy a WASM smart contract to RougeChain. Requires base64-encoded WASM bytecode.", {
144
+ wasm: z.string().describe("Base64-encoded WASM bytecode"),
145
+ deployer: z.string().describe("Deployer's public key hex"),
146
+ nonce: z.number().optional().default(0).describe("Nonce for deterministic address"),
147
+ }, async ({ wasm, deployer, nonce }) => {
148
+ const data = await apiPost("/v2/contract/deploy", { wasm, deployer, nonce });
149
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
150
+ });
151
+ server.tool("call_contract", "Call a method on a deployed WASM smart contract", {
152
+ contractAddr: z.string().describe("Contract address (hex)"),
153
+ method: z.string().describe("Method name to call"),
154
+ caller: z.string().optional().describe("Caller's public key"),
155
+ args: z.record(z.unknown()).optional().describe("JSON arguments for the method"),
156
+ gasLimit: z.number().optional().describe("Gas limit (default 10M)"),
157
+ }, async ({ contractAddr, method, caller, args, gasLimit }) => {
158
+ const data = await apiPost("/v2/contract/call", {
159
+ contractAddr,
160
+ method,
161
+ caller,
162
+ args: args || {},
163
+ gasLimit,
164
+ });
165
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
166
+ });
167
+ // ── Governance ───────────────────────────────────────────────────────────────
168
+ server.tool("list_proposals", "List governance proposals on RougeChain", {}, async () => {
169
+ const data = await apiGet("/governance/proposals");
170
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
171
+ });
172
+ // ── Fee Info ──────────────────────────────────────────────────────────────────
173
+ server.tool("get_fee_info", "Get current EIP-1559 dynamic fee information (base fee, priority fee, burned fees)", {}, async () => {
174
+ const data = await apiGet("/fee-info");
175
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
176
+ });
177
+ // ── Name Service ─────────────────────────────────────────────────────────────
178
+ server.tool("resolve_name", "Resolve a mail name (e.g. 'alice') to the wallet's public keys and encryption key. Names are registered as alice@rouge.quant or alice@qwalla.mail", { name: z.string().describe("Name to resolve (e.g. 'alice', without the @domain)") }, async ({ name }) => {
179
+ const data = await apiGet(`/names/resolve/${encodeURIComponent(name.toLowerCase())}`);
180
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
181
+ });
182
+ server.tool("reverse_lookup_name", "Look up the registered mail name for a wallet ID or public key", { walletId: z.string().describe("Wallet ID or public key hex") }, async ({ walletId }) => {
183
+ const data = await apiGet(`/names/reverse/${encodeURIComponent(walletId)}`);
184
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
185
+ });
186
+ server.tool("list_messenger_wallets", "List all registered messenger wallets with their display names and encryption keys", {}, async () => {
187
+ const data = await apiGet("/messenger/wallets");
188
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
189
+ });
190
+ // ── Social ───────────────────────────────────────────────────────────────────
191
+ server.tool("get_global_timeline", "Get the global social timeline — all posts, newest first", {
192
+ limit: z.number().optional().default(50).describe("Max posts to return"),
193
+ offset: z.number().optional().default(0).describe("Offset for pagination"),
194
+ }, async ({ limit, offset }) => {
195
+ const data = await apiGet(`/social/timeline?limit=${limit}&offset=${offset}`);
196
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
197
+ });
198
+ server.tool("get_post", "Get a single social post by ID with engagement stats", {
199
+ postId: z.string().describe("Post ID (UUID)"),
200
+ viewer: z.string().optional().describe("Viewer public key to check liked/reposted state"),
201
+ }, async ({ postId, viewer }) => {
202
+ const q = viewer ? `?viewer=${encodeURIComponent(viewer)}` : "";
203
+ const data = await apiGet(`/social/post/${encodeURIComponent(postId)}${q}`);
204
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
205
+ });
206
+ server.tool("get_user_posts", "Get posts by a specific user", {
207
+ pubkey: z.string().describe("User's public key"),
208
+ limit: z.number().optional().default(50).describe("Max posts to return"),
209
+ }, async ({ pubkey, limit }) => {
210
+ const data = await apiGet(`/social/user/${encodeURIComponent(pubkey)}/posts?limit=${limit}`);
211
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
212
+ });
213
+ server.tool("get_post_replies", "Get threaded replies to a post", {
214
+ postId: z.string().describe("Parent post ID"),
215
+ limit: z.number().optional().default(50).describe("Max replies to return"),
216
+ }, async ({ postId, limit }) => {
217
+ const data = await apiGet(`/social/post/${encodeURIComponent(postId)}/replies?limit=${limit}`);
218
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
219
+ });
220
+ server.tool("get_track_stats", "Get social stats for a music track (plays, likes, comments)", {
221
+ trackId: z.string().describe("Track/NFT token ID"),
222
+ viewer: z.string().optional().describe("Viewer public key to check liked state"),
223
+ }, async ({ trackId, viewer }) => {
224
+ const q = viewer ? `?viewer=${encodeURIComponent(viewer)}` : "";
225
+ const data = await apiGet(`/social/track/${encodeURIComponent(trackId)}/stats${q}`);
226
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
227
+ });
228
+ server.tool("get_artist_stats", "Get social stats for an artist (followers, following count)", {
229
+ pubkey: z.string().describe("Artist's public key"),
230
+ viewer: z.string().optional().describe("Viewer public key to check follow state"),
231
+ }, async ({ pubkey, viewer }) => {
232
+ const q = viewer ? `?viewer=${encodeURIComponent(viewer)}` : "";
233
+ const data = await apiGet(`/social/artist/${encodeURIComponent(pubkey)}/stats${q}`);
234
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
235
+ });
236
+ // ══════════════════════════════════════════════════════════════════════════════
237
+ // RESOURCES — static context about RougeChain for AI agents
238
+ // ══════════════════════════════════════════════════════════════════════════════
239
+ server.resource("chain-info", "rougechain://info", async (uri) => ({
240
+ contents: [
241
+ {
242
+ uri: uri.href,
243
+ mimeType: "text/plain",
244
+ text: `RougeChain — The First Post-Quantum Programmable Blockchain
245
+
246
+ Core Technology:
247
+ - ML-DSA-65 (FIPS 204) digital signatures
248
+ - ML-KEM-768 (FIPS 203) key encapsulation
249
+ - Bech32m addresses (rouge1...)
250
+ - ZK-STARK proofs (winterfell) for shielded transactions
251
+ - WASM smart contracts (wasmi runtime)
252
+ - EIP-1559 dynamic fees with fee burning
253
+
254
+ Native Token: XRGE
255
+ Address Format: rouge1... (Bech32m)
256
+ Consensus: Proof of Stake with BFT finality
257
+ API Base: ${API}
258
+
259
+ Features:
260
+ - Custom tokens with mint authority
261
+ - NFT collections with royalties
262
+ - AMM DEX with multi-hop routing
263
+ - End-to-end encrypted messaging (ML-KEM-768 + AES-GCM)
264
+ - Encrypted mail with @rouge.quant / @qwalla.mail addresses (CEK multi-recipient encryption)
265
+ - Social layer: posts, timeline, threaded replies, reposts, likes, follows, comments, tips
266
+ - Real-time notifications: unread badges, native browser notifications, push notifications
267
+ - EVM bridge (Base Sepolia)
268
+ - Name service (mail + wallet name registry)
269
+ - Governance proposals
270
+ - WASM smart contracts with fuel-metered execution
271
+ - WebSocket real-time event streaming
272
+
273
+ SDK: @rougechain/sdk (npm)
274
+ Docs: ${BASE_URL}/docs`,
275
+ },
276
+ ],
277
+ }));
278
+ // ══════════════════════════════════════════════════════════════════════════════
279
+ // Start the server
280
+ // ══════════════════════════════════════════════════════════════════════════════
281
+ async function main() {
282
+ const transport = new StdioServerTransport();
283
+ await server.connect(transport);
284
+ console.error("[rougechain-mcp] Server started — connected via stdio");
285
+ console.error(`[rougechain-mcp] API endpoint: ${API}`);
286
+ }
287
+ main().catch((err) => {
288
+ console.error("[rougechain-mcp] Fatal error:", err);
289
+ process.exit(1);
290
+ });
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@rougechain/mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for RougeChain — AI agents interact with a post-quantum blockchain",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "rougechain-mcp": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "tsc && node dist/index.js"
14
+ },
15
+ "keywords": ["mcp", "rougechain", "blockchain", "post-quantum", "ai-agent"],
16
+ "dependencies": {
17
+ "@modelcontextprotocol/sdk": "^1.12.0",
18
+ "zod": "^3.23.0"
19
+ },
20
+ "devDependencies": {
21
+ "typescript": "^5.4.0",
22
+ "@types/node": "^20.0.0"
23
+ }
24
+ }
package/src/index.ts ADDED
@@ -0,0 +1,500 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * RougeChain MCP Server
4
+ *
5
+ * Exposes RougeChain blockchain operations as MCP tools for AI agents.
6
+ * The first post-quantum, AI-agent-native programmable blockchain.
7
+ *
8
+ * Usage:
9
+ * ROUGECHAIN_URL=https://api.rougechain.io npx @rougechain/mcp-server
10
+ */
11
+
12
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
+ import { z } from "zod";
15
+
16
+ // ─── Configuration ────────────────────────────────────────────────────────────
17
+
18
+ // NOTE: this must be the API host (api.rougechain.io), NOT the frontend host
19
+ // (rougechain.io), which serves the SPA index.html for every /api/* path.
20
+ const BASE_URL = process.env.ROUGECHAIN_URL || "https://api.rougechain.io";
21
+ const API = `${BASE_URL}/api`;
22
+ const API_KEY = process.env.ROUGECHAIN_API_KEY || "";
23
+
24
+ // ─── HTTP helpers ─────────────────────────────────────────────────────────────
25
+
26
+ const headers: Record<string, string> = {
27
+ "Content-Type": "application/json",
28
+ ...(API_KEY ? { "X-API-Key": API_KEY } : {}),
29
+ };
30
+
31
+ async function apiGet(path: string): Promise<unknown> {
32
+ const res = await fetch(`${API}${path}`, { headers });
33
+ return res.json();
34
+ }
35
+
36
+ async function apiPost(path: string, body: unknown): Promise<unknown> {
37
+ const res = await fetch(`${API}${path}`, {
38
+ method: "POST",
39
+ headers,
40
+ body: JSON.stringify(body),
41
+ });
42
+ return res.json();
43
+ }
44
+
45
+ // ─── MCP Server ───────────────────────────────────────────────────────────────
46
+
47
+ const server = new McpServer({
48
+ name: "rougechain",
49
+ version: "1.0.0",
50
+ });
51
+
52
+ // ══════════════════════════════════════════════════════════════════════════════
53
+ // TOOLS — actions that AI agents can perform on RougeChain
54
+ // ══════════════════════════════════════════════════════════════════════════════
55
+
56
+ // ── Chain Info ────────────────────────────────────────────────────────────────
57
+
58
+ server.tool(
59
+ "get_chain_stats",
60
+ "Get RougeChain network statistics: block height, peer count, validator count, total supply",
61
+ {},
62
+ async () => {
63
+ const data = await apiGet("/stats");
64
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
65
+ }
66
+ );
67
+
68
+ server.tool(
69
+ "get_block",
70
+ "Get a block by height from the RougeChain blockchain",
71
+ { height: z.number().describe("Block height to retrieve") },
72
+ async ({ height }) => {
73
+ const data = await apiGet(`/block/${height}`);
74
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
75
+ }
76
+ );
77
+
78
+ server.tool(
79
+ "get_latest_blocks",
80
+ "Get the most recent blocks from the chain",
81
+ { limit: z.number().optional().default(10).describe("Number of blocks to return (max 100)") },
82
+ async ({ limit }) => {
83
+ const data = await apiGet(`/blocks?limit=${limit}`);
84
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
85
+ }
86
+ );
87
+
88
+ // ── Wallet & Balance ─────────────────────────────────────────────────────────
89
+
90
+ server.tool(
91
+ "get_balance",
92
+ "Check XRGE or token balance for a wallet address or public key",
93
+ {
94
+ address: z.string().describe("Wallet address (rouge1...) or public key hex"),
95
+ token: z.string().optional().describe("Token symbol (omit for XRGE native balance)"),
96
+ },
97
+ async ({ address, token }) => {
98
+ const path = token
99
+ ? `/balance/${address}/${token}`
100
+ : `/balance/${address}`;
101
+ const data = await apiGet(path);
102
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
103
+ }
104
+ );
105
+
106
+ server.tool(
107
+ "get_transaction",
108
+ "Look up a specific transaction by hash",
109
+ { hash: z.string().describe("Transaction hash") },
110
+ async ({ hash }) => {
111
+ const data = await apiGet(`/tx/${hash}`);
112
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
113
+ }
114
+ );
115
+
116
+ // ── Token Operations ─────────────────────────────────────────────────────────
117
+
118
+ server.tool(
119
+ "list_tokens",
120
+ "List all custom tokens on RougeChain with their metadata",
121
+ {},
122
+ async () => {
123
+ const data = await apiGet("/tokens");
124
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
125
+ }
126
+ );
127
+
128
+ server.tool(
129
+ "get_token",
130
+ "Get detailed metadata for a specific token by symbol",
131
+ { symbol: z.string().describe("Token symbol (e.g. ROUGE)") },
132
+ async ({ symbol }) => {
133
+ const data = await apiGet(`/token/${symbol}/metadata`);
134
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
135
+ }
136
+ );
137
+
138
+ server.tool(
139
+ "get_token_holders",
140
+ "Get the top holders of a specific token",
141
+ { symbol: z.string().describe("Token symbol") },
142
+ async ({ symbol }) => {
143
+ const data = await apiGet(`/token/${symbol}/holders`);
144
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
145
+ }
146
+ );
147
+
148
+ // ── DeFi / AMM ───────────────────────────────────────────────────────────────
149
+
150
+ server.tool(
151
+ "list_pools",
152
+ "List all liquidity pools on RougeChain DEX",
153
+ {},
154
+ async () => {
155
+ const data = await apiGet("/pools");
156
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
157
+ }
158
+ );
159
+
160
+ server.tool(
161
+ "get_swap_quote",
162
+ "Get a swap quote from the AMM (price, slippage, route)",
163
+ {
164
+ from: z.string().describe("Source token symbol"),
165
+ to: z.string().describe("Destination token symbol"),
166
+ amount: z.number().describe("Amount of source token to swap"),
167
+ },
168
+ async ({ from, to, amount }) => {
169
+ const data = await apiPost("/swap/quote", {
170
+ token_in: from,
171
+ token_out: to,
172
+ amount_in: amount,
173
+ });
174
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
175
+ }
176
+ );
177
+
178
+ // ── NFTs ──────────────────────────────────────────────────────────────────────
179
+
180
+ server.tool(
181
+ "list_nft_collections",
182
+ "List all NFT collections on RougeChain",
183
+ {},
184
+ async () => {
185
+ const data = await apiGet("/nft/collections");
186
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
187
+ }
188
+ );
189
+
190
+ server.tool(
191
+ "get_nft_collection",
192
+ "Get details and tokens for an NFT collection",
193
+ { symbol: z.string().describe("Collection symbol") },
194
+ async ({ symbol }) => {
195
+ const data = await apiGet(`/nft/collection/${symbol}`);
196
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
197
+ }
198
+ );
199
+
200
+ // ── Validators & Staking ─────────────────────────────────────────────────────
201
+
202
+ server.tool(
203
+ "list_validators",
204
+ "List all validators on the RougeChain network with their stake and status",
205
+ {},
206
+ async () => {
207
+ const data = await apiGet("/validators");
208
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
209
+ }
210
+ );
211
+
212
+ // ── WASM Smart Contracts ─────────────────────────────────────────────────────
213
+
214
+ server.tool(
215
+ "list_contracts",
216
+ "List all deployed WASM smart contracts on RougeChain",
217
+ {},
218
+ async () => {
219
+ const data = await apiGet("/contracts");
220
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
221
+ }
222
+ );
223
+
224
+ server.tool(
225
+ "get_contract",
226
+ "Get metadata for a deployed smart contract",
227
+ { address: z.string().describe("Contract address (hex)") },
228
+ async ({ address }) => {
229
+ const data = await apiGet(`/contract/${address}`);
230
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
231
+ }
232
+ );
233
+
234
+ server.tool(
235
+ "get_contract_state",
236
+ "Read contract storage. Omit key to dump all state; provide key for single-value lookup.",
237
+ {
238
+ address: z.string().describe("Contract address"),
239
+ key: z.string().optional().describe("Storage key (hex or string). Omit to dump all state."),
240
+ },
241
+ async ({ address, key }) => {
242
+ const path = key
243
+ ? `/contract/${address}/state?key=${encodeURIComponent(key)}`
244
+ : `/contract/${address}/state`;
245
+ const data = await apiGet(path);
246
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
247
+ }
248
+ );
249
+
250
+ server.tool(
251
+ "get_contract_events",
252
+ "Get the event log for a smart contract",
253
+ {
254
+ address: z.string().describe("Contract address"),
255
+ limit: z.number().optional().default(50).describe("Max events to return"),
256
+ },
257
+ async ({ address, limit }) => {
258
+ const data = await apiGet(`/contract/${address}/events?limit=${limit}`);
259
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
260
+ }
261
+ );
262
+
263
+ server.tool(
264
+ "deploy_contract",
265
+ "Deploy a WASM smart contract to RougeChain. Requires base64-encoded WASM bytecode.",
266
+ {
267
+ wasm: z.string().describe("Base64-encoded WASM bytecode"),
268
+ deployer: z.string().describe("Deployer's public key hex"),
269
+ nonce: z.number().optional().default(0).describe("Nonce for deterministic address"),
270
+ },
271
+ async ({ wasm, deployer, nonce }) => {
272
+ const data = await apiPost("/v2/contract/deploy", { wasm, deployer, nonce });
273
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
274
+ }
275
+ );
276
+
277
+ server.tool(
278
+ "call_contract",
279
+ "Call a method on a deployed WASM smart contract",
280
+ {
281
+ contractAddr: z.string().describe("Contract address (hex)"),
282
+ method: z.string().describe("Method name to call"),
283
+ caller: z.string().optional().describe("Caller's public key"),
284
+ args: z.record(z.unknown()).optional().describe("JSON arguments for the method"),
285
+ gasLimit: z.number().optional().describe("Gas limit (default 10M)"),
286
+ },
287
+ async ({ contractAddr, method, caller, args, gasLimit }) => {
288
+ const data = await apiPost("/v2/contract/call", {
289
+ contractAddr,
290
+ method,
291
+ caller,
292
+ args: args || {},
293
+ gasLimit,
294
+ });
295
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
296
+ }
297
+ );
298
+
299
+ // ── Governance ───────────────────────────────────────────────────────────────
300
+
301
+ server.tool(
302
+ "list_proposals",
303
+ "List governance proposals on RougeChain",
304
+ {},
305
+ async () => {
306
+ const data = await apiGet("/governance/proposals");
307
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
308
+ }
309
+ );
310
+
311
+ // ── Fee Info ──────────────────────────────────────────────────────────────────
312
+
313
+ server.tool(
314
+ "get_fee_info",
315
+ "Get current EIP-1559 dynamic fee information (base fee, priority fee, burned fees)",
316
+ {},
317
+ async () => {
318
+ const data = await apiGet("/fee-info");
319
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
320
+ }
321
+ );
322
+
323
+ // ── Name Service ─────────────────────────────────────────────────────────────
324
+
325
+ server.tool(
326
+ "resolve_name",
327
+ "Resolve a mail name (e.g. 'alice') to the wallet's public keys and encryption key. Names are registered as alice@rouge.quant or alice@qwalla.mail",
328
+ { name: z.string().describe("Name to resolve (e.g. 'alice', without the @domain)") },
329
+ async ({ name }) => {
330
+ const data = await apiGet(`/names/resolve/${encodeURIComponent(name.toLowerCase())}`);
331
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
332
+ }
333
+ );
334
+
335
+ server.tool(
336
+ "reverse_lookup_name",
337
+ "Look up the registered mail name for a wallet ID or public key",
338
+ { walletId: z.string().describe("Wallet ID or public key hex") },
339
+ async ({ walletId }) => {
340
+ const data = await apiGet(`/names/reverse/${encodeURIComponent(walletId)}`);
341
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
342
+ }
343
+ );
344
+
345
+ server.tool(
346
+ "list_messenger_wallets",
347
+ "List all registered messenger wallets with their display names and encryption keys",
348
+ {},
349
+ async () => {
350
+ const data = await apiGet("/messenger/wallets");
351
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
352
+ }
353
+ );
354
+
355
+ // ── Social ───────────────────────────────────────────────────────────────────
356
+
357
+ server.tool(
358
+ "get_global_timeline",
359
+ "Get the global social timeline — all posts, newest first",
360
+ {
361
+ limit: z.number().optional().default(50).describe("Max posts to return"),
362
+ offset: z.number().optional().default(0).describe("Offset for pagination"),
363
+ },
364
+ async ({ limit, offset }) => {
365
+ const data = await apiGet(`/social/timeline?limit=${limit}&offset=${offset}`);
366
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
367
+ }
368
+ );
369
+
370
+ server.tool(
371
+ "get_post",
372
+ "Get a single social post by ID with engagement stats",
373
+ {
374
+ postId: z.string().describe("Post ID (UUID)"),
375
+ viewer: z.string().optional().describe("Viewer public key to check liked/reposted state"),
376
+ },
377
+ async ({ postId, viewer }) => {
378
+ const q = viewer ? `?viewer=${encodeURIComponent(viewer)}` : "";
379
+ const data = await apiGet(`/social/post/${encodeURIComponent(postId)}${q}`);
380
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
381
+ }
382
+ );
383
+
384
+ server.tool(
385
+ "get_user_posts",
386
+ "Get posts by a specific user",
387
+ {
388
+ pubkey: z.string().describe("User's public key"),
389
+ limit: z.number().optional().default(50).describe("Max posts to return"),
390
+ },
391
+ async ({ pubkey, limit }) => {
392
+ const data = await apiGet(`/social/user/${encodeURIComponent(pubkey)}/posts?limit=${limit}`);
393
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
394
+ }
395
+ );
396
+
397
+ server.tool(
398
+ "get_post_replies",
399
+ "Get threaded replies to a post",
400
+ {
401
+ postId: z.string().describe("Parent post ID"),
402
+ limit: z.number().optional().default(50).describe("Max replies to return"),
403
+ },
404
+ async ({ postId, limit }) => {
405
+ const data = await apiGet(`/social/post/${encodeURIComponent(postId)}/replies?limit=${limit}`);
406
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
407
+ }
408
+ );
409
+
410
+ server.tool(
411
+ "get_track_stats",
412
+ "Get social stats for a music track (plays, likes, comments)",
413
+ {
414
+ trackId: z.string().describe("Track/NFT token ID"),
415
+ viewer: z.string().optional().describe("Viewer public key to check liked state"),
416
+ },
417
+ async ({ trackId, viewer }) => {
418
+ const q = viewer ? `?viewer=${encodeURIComponent(viewer)}` : "";
419
+ const data = await apiGet(`/social/track/${encodeURIComponent(trackId)}/stats${q}`);
420
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
421
+ }
422
+ );
423
+
424
+ server.tool(
425
+ "get_artist_stats",
426
+ "Get social stats for an artist (followers, following count)",
427
+ {
428
+ pubkey: z.string().describe("Artist's public key"),
429
+ viewer: z.string().optional().describe("Viewer public key to check follow state"),
430
+ },
431
+ async ({ pubkey, viewer }) => {
432
+ const q = viewer ? `?viewer=${encodeURIComponent(viewer)}` : "";
433
+ const data = await apiGet(`/social/artist/${encodeURIComponent(pubkey)}/stats${q}`);
434
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
435
+ }
436
+ );
437
+
438
+ // ══════════════════════════════════════════════════════════════════════════════
439
+ // RESOURCES — static context about RougeChain for AI agents
440
+ // ══════════════════════════════════════════════════════════════════════════════
441
+
442
+ server.resource(
443
+ "chain-info",
444
+ "rougechain://info",
445
+ async (uri) => ({
446
+ contents: [
447
+ {
448
+ uri: uri.href,
449
+ mimeType: "text/plain",
450
+ text: `RougeChain — The First Post-Quantum Programmable Blockchain
451
+
452
+ Core Technology:
453
+ - ML-DSA-65 (FIPS 204) digital signatures
454
+ - ML-KEM-768 (FIPS 203) key encapsulation
455
+ - Bech32m addresses (rouge1...)
456
+ - ZK-STARK proofs (winterfell) for shielded transactions
457
+ - WASM smart contracts (wasmi runtime)
458
+ - EIP-1559 dynamic fees with fee burning
459
+
460
+ Native Token: XRGE
461
+ Address Format: rouge1... (Bech32m)
462
+ Consensus: Proof of Stake with BFT finality
463
+ API Base: ${API}
464
+
465
+ Features:
466
+ - Custom tokens with mint authority
467
+ - NFT collections with royalties
468
+ - AMM DEX with multi-hop routing
469
+ - End-to-end encrypted messaging (ML-KEM-768 + AES-GCM)
470
+ - Encrypted mail with @rouge.quant / @qwalla.mail addresses (CEK multi-recipient encryption)
471
+ - Social layer: posts, timeline, threaded replies, reposts, likes, follows, comments, tips
472
+ - Real-time notifications: unread badges, native browser notifications, push notifications
473
+ - EVM bridge (Base Sepolia)
474
+ - Name service (mail + wallet name registry)
475
+ - Governance proposals
476
+ - WASM smart contracts with fuel-metered execution
477
+ - WebSocket real-time event streaming
478
+
479
+ SDK: @rougechain/sdk (npm)
480
+ Docs: ${BASE_URL}/docs`,
481
+ },
482
+ ],
483
+ })
484
+ );
485
+
486
+ // ══════════════════════════════════════════════════════════════════════════════
487
+ // Start the server
488
+ // ══════════════════════════════════════════════════════════════════════════════
489
+
490
+ async function main() {
491
+ const transport = new StdioServerTransport();
492
+ await server.connect(transport);
493
+ console.error("[rougechain-mcp] Server started — connected via stdio");
494
+ console.error(`[rougechain-mcp] API endpoint: ${API}`);
495
+ }
496
+
497
+ main().catch((err) => {
498
+ console.error("[rougechain-mcp] Fatal error:", err);
499
+ process.exit(1);
500
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true,
12
+ "resolveJsonModule": true
13
+ },
14
+ "include": ["src/**/*"]
15
+ }