@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 +108 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +290 -0
- package/package.json +24 -0
- package/src/index.ts +500 -0
- package/tsconfig.json +15 -0
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.
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|