mcp-server-near-snapshot-taker 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,37 @@
1
+ # mcp-server-near-snapshot-taker
2
+
3
+ MCP Server - NEAR Snapshot taker
4
+
5
+ ## Install & Setup
6
+
7
+ Add to Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
8
+
9
+ ```json
10
+ {
11
+ "mcpServers": {
12
+ "mcp-server-near-snapshot-taker": {
13
+ "command": "npx",
14
+ "args": [
15
+ "-y",
16
+ "mcp-server-near-snapshot-taker"
17
+ ]
18
+ }
19
+ }
20
+ }
21
+ ```
22
+
23
+ ## Available Tools
24
+
25
+ - `take_account_snapshot`
26
+ - `take_token_balance_snapshot`
27
+ - `take_block_snapshot`
28
+ - `take_contract_state_snapshot`
29
+ - `take_validators_snapshot`
30
+ - `ft_balance_of`
31
+
32
+ ## Build from Source
33
+
34
+ ```bash
35
+ npm install && npm run build
36
+ node dist/index.js
37
+ ```
package/index.js ADDED
@@ -0,0 +1,30 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
+ import { tools, callTool } from './tools.js';
5
+
6
+ const server = new Server(
7
+ { name: 'mcp-server-near-snapshot-taker', version: '1.0.0' },
8
+ { capabilities: { tools: {} } }
9
+ );
10
+
11
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
12
+
13
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
14
+ const { name, arguments: args } = request.params;
15
+ try {
16
+ const result = await callTool(name, args ?? {});
17
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
18
+ } catch (err: unknown) {
19
+ const msg = err instanceof Error ? err.message : String(err);
20
+ return { content: [{ type: 'text', text: `Error: ${msg}` }], isError: true };
21
+ }
22
+ });
23
+
24
+ async function main() {
25
+ const transport = new StdioServerTransport();
26
+ await server.connect(transport);
27
+ console.error('mcp-server-near-snapshot-taker MCP server running on stdio');
28
+ }
29
+
30
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "mcp-server-near-snapshot-taker",
3
+ "version": "1.0.0",
4
+ "description": "MCP Server - NEAR Snapshot taker",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "mcp-server-near-snapshot-taker": "index.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node dist/index.js"
11
+ },
12
+ "engines": {
13
+ "node": ">=18"
14
+ },
15
+ "dependencies": {
16
+ "@modelcontextprotocol/sdk": "^1.0.0",
17
+ "near-api-js": "^4.0.0"
18
+ },
19
+ "devDependencies": {
20
+ "typescript": "^5.0.0",
21
+ "@types/node": "^20.0.0"
22
+ }
23
+ }
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
+ import { tools, callTool } from './tools.js';
5
+
6
+ const server = new Server(
7
+ { name: 'mcp-server-near-snapshot-taker', version: '1.0.0' },
8
+ { capabilities: { tools: {} } }
9
+ );
10
+
11
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
12
+
13
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
14
+ const { name, arguments: args } = request.params;
15
+ try {
16
+ const result = await callTool(name, args ?? {});
17
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
18
+ } catch (err: unknown) {
19
+ const msg = err instanceof Error ? err.message : String(err);
20
+ return { content: [{ type: 'text', text: `Error: ${msg}` }], isError: true };
21
+ }
22
+ });
23
+
24
+ async function main() {
25
+ const transport = new StdioServerTransport();
26
+ await server.connect(transport);
27
+ console.error('mcp-server-near-snapshot-taker MCP server running on stdio');
28
+ }
29
+
30
+ main().catch(console.error);
package/src/tools.ts ADDED
@@ -0,0 +1,207 @@
1
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+
3
+ const NEAR_RPC_URL = "https://rpc.mainnet.near.org";
4
+
5
+ async function nearRpc(method: string, params: unknown): Promise<unknown> {
6
+ const response = await fetch(NEAR_RPC_URL, {
7
+ method: "POST",
8
+ headers: { "Content-Type": "application/json" },
9
+ body: JSON.stringify({ jsonrpc: "2.0", id: "mcp", method, params }),
10
+ });
11
+ if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
12
+ const data = (await response.json()) as { result?: unknown; error?: { message: string } };
13
+ if (data.error) throw new Error(`RPC error: ${data.error.message}`);
14
+ return data.result;
15
+ }
16
+
17
+ export const tools: Tool[] = [
18
+ {
19
+ name: "take_account_snapshot",
20
+ description: "Take a snapshot of a NEAR account's state including balance, storage, and staking at a specific block.",
21
+ inputSchema: {
22
+ type: "object",
23
+ properties: {
24
+ account_id: { type: "string", description: "NEAR account ID (e.g. alice.near)" },
25
+ block_id: { type: ["string", "number"], description: "Block height or hash (optional, defaults to latest)" },
26
+ },
27
+ required: ["account_id"],
28
+ },
29
+ },
30
+ {
31
+ name: "take_token_balance_snapshot",
32
+ description: "Take a snapshot of a NEP-141 fungible token balance for an account at a specific block.",
33
+ inputSchema: {
34
+ type: "object",
35
+ properties: {
36
+ account_id: { type: "string", description: "NEAR account to check balance for" },
37
+ token_contract: { type: "string", description: "NEP-141 token contract account ID" },
38
+ block_id: { type: ["string", "number"], description: "Block height or hash (optional, defaults to latest)" },
39
+ },
40
+ required: ["account_id", "token_contract"],
41
+ },
42
+ },
43
+ {
44
+ name: "take_block_snapshot",
45
+ description: "Take a snapshot of a NEAR block including hash, height, timestamp, and gas information.",
46
+ inputSchema: {
47
+ type: "object",
48
+ properties: {
49
+ block_id: { type: ["string", "number"], description: "Block height or block hash (defaults to latest finalized)" },
50
+ },
51
+ required: [],
52
+ },
53
+ },
54
+ {
55
+ name: "take_contract_state_snapshot",
56
+ description: "Take a snapshot of a NEAR smart contract's on-chain state keys and values.",
57
+ inputSchema: {
58
+ type: "object",
59
+ properties: {
60
+ contract_id: { type: "string", description: "Contract account ID" },
61
+ prefix_base64: { type: "string", description: "Base64-encoded key prefix to filter state (optional)" },
62
+ block_id: { type: ["string", "number"], description: "Block height or hash (optional, defaults to latest)" },
63
+ },
64
+ required: ["contract_id"],
65
+ },
66
+ },
67
+ {
68
+ name: "take_validators_snapshot",
69
+ description: "Take a snapshot of current NEAR validators including stake, seats, and epoch information.",
70
+ inputSchema: {
71
+ type: "object",
72
+ properties: {
73
+ epoch_id: { type: "string", description: "Epoch ID to query validators for (optional, defaults to current)" },
74
+ },
75
+ required: [],
76
+ },
77
+ },
78
+ ];
79
+
80
+ function buildBlockRef(block_id?: unknown): { finality: string } | { block_id: string | number } {
81
+ if (block_id === undefined || block_id === null || block_id === "") {
82
+ return { finality: "final" };
83
+ }
84
+ return { block_id: block_id as string | number };
85
+ }
86
+
87
+ export async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
88
+ const timestamp = new Date().toISOString();
89
+
90
+ switch (name) {
91
+ case "take_account_snapshot": {
92
+ const { account_id, block_id } = args;
93
+ const result = await nearRpc("query", {
94
+ request_type: "view_account",
95
+ account_id,
96
+ ...buildBlockRef(block_id),
97
+ }) as Record<string, unknown>;
98
+ return {
99
+ snapshot_type: "account",
100
+ timestamp,
101
+ account_id,
102
+ block_height: result.block_height,
103
+ block_hash: result.block_hash,
104
+ data: {
105
+ amount_yocto: result.amount,
106
+ locked_yocto: result.locked,
107
+ storage_usage_bytes: result.storage_usage,
108
+ code_hash: result.code_hash,
109
+ },
110
+ };
111
+ }
112
+
113
+ case "take_token_balance_snapshot": {
114
+ const { account_id, token_contract, block_id } = args;
115
+ const argsBase64 = Buffer.from(JSON.stringify({ account_id })).toString("base64");
116
+ const result = await nearRpc("query", {
117
+ request_type: "call_function",
118
+ account_id: token_contract,
119
+ method_name: "ft_balance_of",
120
+ args_base64: argsBase64,
121
+ ...buildBlockRef(block_id),
122
+ }) as Record<string, unknown>;
123
+ const resultBytes = result.result as number[];
124
+ const balance = JSON.parse(Buffer.from(resultBytes).toString("utf8"));
125
+ return {
126
+ snapshot_type: "token_balance",
127
+ timestamp,
128
+ account_id,
129
+ token_contract,
130
+ block_height: result.block_height,
131
+ block_hash: result.block_hash,
132
+ data: { balance_raw: balance },
133
+ };
134
+ }
135
+
136
+ case "take_block_snapshot": {
137
+ const { block_id } = args;
138
+ const result = await nearRpc("block", buildBlockRef(block_id)) as Record<string, unknown>;
139
+ const header = result.header as Record<string, unknown>;
140
+ return {
141
+ snapshot_type: "block",
142
+ timestamp,
143
+ data: {
144
+ block_height: header.height,
145
+ block_hash: header.hash,
146
+ prev_hash: header.prev_hash,
147
+ timestamp_nanosec: header.timestamp_nanosec,
148
+ epoch_id: header.epoch_id,
149
+ gas_price: header.gas_price,
150
+ total_supply: header.total_supply,
151
+ validator_proposals: header.validator_proposals,
152
+ },
153
+ };
154
+ }
155
+
156
+ case "take_contract_state_snapshot": {
157
+ const { contract_id, prefix_base64, block_id } = args;
158
+ const result = await nearRpc("query", {
159
+ request_type: "view_state",
160
+ account_id: contract_id,
161
+ prefix_base64: prefix_base64 ?? "",
162
+ ...buildBlockRef(block_id),
163
+ }) as Record<string, unknown>;
164
+ const values = result.values as Array<{ key: string; value: string }>;
165
+ return {
166
+ snapshot_type: "contract_state",
167
+ timestamp,
168
+ contract_id,
169
+ block_height: result.block_height,
170
+ block_hash: result.block_hash,
171
+ data: {
172
+ entry_count: values.length,
173
+ entries: values.map((v) => ({
174
+ key_base64: v.key,
175
+ key_utf8: Buffer.from(v.key, "base64").toString("utf8"),
176
+ value_base64: v.value,
177
+ })),
178
+ },
179
+ };
180
+ }
181
+
182
+ case "take_validators_snapshot": {
183
+ const { epoch_id } = args;
184
+ const result = await nearRpc("validators", epoch_id ? [epoch_id] : [null]) as Record<string, unknown>;
185
+ const current = result.current_validators as Array<Record<string, unknown>>;
186
+ return {
187
+ snapshot_type: "validators",
188
+ timestamp,
189
+ data: {
190
+ epoch_height: result.epoch_height,
191
+ epoch_start_height: result.epoch_start_height,
192
+ validator_count: current.length,
193
+ validators: current.map((v) => ({
194
+ account_id: v.account_id,
195
+ stake_yocto: v.stake,
196
+ shards: v.shards,
197
+ num_produced_blocks: v.num_produced_blocks,
198
+ num_expected_blocks: v.num_expected_blocks,
199
+ })),
200
+ },
201
+ };
202
+ }
203
+
204
+ default:
205
+ throw new Error(`Unknown tool: ${name}`);
206
+ }
207
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "lib": [
7
+ "ES2020"
8
+ ],
9
+ "outDir": "./dist",
10
+ "rootDir": "./src",
11
+ "strict": true,
12
+ "esModuleInterop": true,
13
+ "declaration": true,
14
+ "skipLibCheck": true
15
+ },
16
+ "include": [
17
+ "src/**/*"
18
+ ],
19
+ "exclude": [
20
+ "node_modules",
21
+ "dist"
22
+ ]
23
+ }