Skip to content

Commit 373e2d0

Browse files
committed
Merge branch 'feature/mcp-server' of github.com:sei-protocol/sei-js into feature/mcp-server
2 parents 7ef4e88 + 7a6e741 commit 373e2d0

13 files changed

Lines changed: 4112 additions & 158 deletions

packages/mcp-server/coverage.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

packages/mcp-server/package.json

Lines changed: 56 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,58 @@
11
{
2-
"name": "@sei-js/mcp-server",
3-
"description": "Model Context Protocol (MCP) server for interacting with EVM-compatible networks",
4-
"type": "module",
5-
"version": "0.1.0",
6-
"bin": "./bin/cli.js",
7-
"main": "./dist/esm/index.js",
8-
"module": "./dist/esm/index.js",
9-
"files": ["dist/", "bin/", "README.md", "LICENSE"],
10-
"scripts": {
11-
"build": "rm -rf dist && tsc --outDir dist/esm --module esnext",
12-
"start": "node dist/esm/index.js",
13-
"start:http": "node dist/esm/server/http-server.js",
14-
"dev": "npx tsx src/index.ts",
15-
"dev:http": "npx tsx src/server/http-server.ts",
16-
"test": "jest"
17-
},
18-
"devDependencies": {
19-
"@types/cors": "^2.8.17",
20-
"@types/express": "^5.0.0",
21-
"typescript": "^5.8.3"
22-
},
23-
"dependencies": {
24-
"@modelcontextprotocol/sdk": "^1.7.0",
25-
"cors": "^2.8.5",
26-
"dotenv": "^16.5.0",
27-
"express": "^4.21.2",
28-
"viem": "^2.30.5",
29-
"zod": "^3.24.2"
30-
},
31-
"keywords": ["mcp", "model-context-protocol", "evm", "blockchain", "sei", "web3", "smart-contracts", "ai", "agent"],
32-
"author": "Etheral <etheral.eth.dev@gmail.com>",
33-
"license": "MIT",
34-
"engines": {
35-
"node": ">=18.0.0"
36-
},
37-
"repository": {
38-
"type": "git",
39-
"url": "https://github.com/sei-protocol/sei-mcp-server"
40-
},
41-
"bugs": {
42-
"url": "https://github.com/sei-protocol/sei-mcp-server/issues"
43-
},
44-
"homepage": "https://github.com/sei-protocol/sei-mcp-server#readme",
45-
"publishConfig": {
46-
"access": "public"
47-
}
2+
"name": "@sei-js/mcp-server",
3+
"description": "Model Context Protocol (MCP) server for interacting with EVM-compatible networks",
4+
"type": "module",
5+
"version": "0.1.0",
6+
"bin": "./bin/cli.js",
7+
"main": "./dist/esm/index.js",
8+
"module": "./dist/esm/index.js",
9+
"files": [
10+
"dist/",
11+
"bin/",
12+
"README.md",
13+
"LICENSE"
14+
],
15+
"scripts": {
16+
"build": "rimraf dist && tsc --outDir dist/esm --module esnext",
17+
"test": "NODE_ENV=test jest"
18+
},
19+
"devDependencies": {
20+
"@types/cors": "^2.8.17",
21+
"@types/express": "^5.0.0"
22+
},
23+
"dependencies": {
24+
"@modelcontextprotocol/sdk": "^1.7.0",
25+
"cors": "^2.8.5",
26+
"dotenv": "^16.5.0",
27+
"express": "^4.21.2",
28+
"viem": "^2.23.9",
29+
"zod": "^3.24.2"
30+
},
31+
"keywords": [
32+
"mcp",
33+
"model-context-protocol",
34+
"evm",
35+
"blockchain",
36+
"sei",
37+
"web3",
38+
"smart-contracts",
39+
"ai",
40+
"agent"
41+
],
42+
"author": "Etheral <etheral.eth.dev@gmail.com>",
43+
"license": "MIT",
44+
"engines": {
45+
"node": ">=18.0.0"
46+
},
47+
"repository": {
48+
"type": "git",
49+
"url": "https://github.com/sei-protocol/sei-mcp-server"
50+
},
51+
"bugs": {
52+
"url": "https://github.com/sei-protocol/sei-mcp-server/issues"
53+
},
54+
"homepage": "https://github.com/sei-protocol/sei-mcp-server#readme",
55+
"publishConfig": {
56+
"access": "public"
57+
}
4858
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import { describe, expect, test } from '@jest/globals';
2+
import type { Chain } from 'viem';
3+
import { sei, seiTestnet, seiDevnet } from 'viem/chains';
4+
import {
5+
DEFAULT_NETWORK,
6+
DEFAULT_RPC_URL,
7+
DEFAULT_CHAIN_ID,
8+
chainMap,
9+
networkNameMap,
10+
rpcUrlMap,
11+
resolveChainId,
12+
getChain,
13+
getRpcUrl,
14+
getSupportedNetworks
15+
} from '../../core/chains.js';
16+
17+
describe('chains module', () => {
18+
// Test constants
19+
describe('constants', () => {
20+
test('DEFAULT_NETWORK is set correctly', () => {
21+
expect(DEFAULT_NETWORK).toBe('sei');
22+
});
23+
24+
test('DEFAULT_RPC_URL is set correctly', () => {
25+
expect(DEFAULT_RPC_URL).toBe('https://evm-rpc.sei-apis.com');
26+
});
27+
28+
test('DEFAULT_CHAIN_ID is set correctly', () => {
29+
expect(DEFAULT_CHAIN_ID).toBe(1329);
30+
});
31+
32+
test('chainMap contains expected chains', () => {
33+
expect(chainMap[1329]).toBe(sei);
34+
expect(chainMap[1328]).toBe(seiTestnet);
35+
expect(chainMap[713715]).toBe(seiDevnet);
36+
});
37+
38+
test('networkNameMap contains expected mappings', () => {
39+
expect(networkNameMap.sei).toBe(1329);
40+
expect(networkNameMap['sei-testnet']).toBe(1328);
41+
expect(networkNameMap['sei-devnet']).toBe(713715);
42+
});
43+
44+
test('rpcUrlMap contains expected URLs', () => {
45+
expect(rpcUrlMap[1329]).toBe('https://evm-rpc.sei-apis.com');
46+
expect(rpcUrlMap[1328]).toBe('https://evm-rpc-testnet.sei-apis.com');
47+
expect(rpcUrlMap[713715]).toBe('https://evm-rpc-arctic-1.sei-apis.com');
48+
});
49+
});
50+
51+
// Test resolveChainId function
52+
describe('resolveChainId', () => {
53+
test('resolves number chain IDs directly', () => {
54+
expect(resolveChainId(1329)).toBe(1329);
55+
expect(resolveChainId(1328)).toBe(1328);
56+
expect(resolveChainId(713715)).toBe(713715);
57+
});
58+
59+
test('resolves network names to chain IDs', () => {
60+
expect(resolveChainId('sei')).toBe(1329);
61+
expect(resolveChainId('sei-testnet')).toBe(1328);
62+
expect(resolveChainId('sei-devnet')).toBe(713715);
63+
});
64+
65+
test('resolves case-insensitive network names', () => {
66+
expect(resolveChainId('SEI')).toBe(1329);
67+
expect(resolveChainId('Sei-Testnet')).toBe(1328);
68+
expect(resolveChainId('SEI-DEVNET')).toBe(713715);
69+
});
70+
71+
test('resolves string numbers to chain IDs', () => {
72+
expect(resolveChainId('1329')).toBe(1329);
73+
expect(resolveChainId('1328')).toBe(1328);
74+
expect(resolveChainId('713715')).toBe(713715);
75+
});
76+
77+
test('defaults to DEFAULT_CHAIN_ID for unknown network names', () => {
78+
expect(resolveChainId('unknown-network')).toBe(DEFAULT_CHAIN_ID);
79+
});
80+
});
81+
82+
// Test getChain function
83+
describe('getChain', () => {
84+
test('returns chain for numeric chain ID', () => {
85+
expect(getChain(1329)).toBe(sei);
86+
expect(getChain(1328)).toBe(seiTestnet);
87+
expect(getChain(713715)).toBe(seiDevnet);
88+
});
89+
90+
test('returns chain for network name', () => {
91+
expect(getChain('sei')).toBe(sei);
92+
expect(getChain('sei-testnet')).toBe(seiTestnet);
93+
expect(getChain('sei-devnet')).toBe(seiDevnet);
94+
});
95+
96+
test('returns sei chain when network name exists but chain mapping is missing', () => {
97+
// Create a temporary entry in networkNameMap for a non-existent chain ID
98+
const originalNetworkNameMap = { ...networkNameMap };
99+
// @ts-ignore - Intentionally modifying for test
100+
networkNameMap['test-network'] = 9999;
101+
102+
try {
103+
// This should return sei as fallback since chainMap[9999] doesn't exist
104+
expect(getChain('test-network')).toBe(sei);
105+
} finally {
106+
// Restore the original map
107+
// @ts-ignore - Restoring original state
108+
for (const key of Object.keys(networkNameMap)) {
109+
if (key !== 'sei' && key !== 'sei-testnet' && key !== 'sei-devnet') {
110+
// @ts-ignore - Cleanup
111+
delete networkNameMap[key];
112+
}
113+
}
114+
}
115+
});
116+
117+
test('returns chain for case-insensitive network name', () => {
118+
expect(getChain('SEI')).toBe(sei);
119+
expect(getChain('Sei-Testnet')).toBe(seiTestnet);
120+
expect(getChain('SEI-DEVNET')).toBe(seiDevnet);
121+
});
122+
123+
test('returns default chain when no parameter is provided', () => {
124+
expect(getChain()).toBe(sei);
125+
});
126+
127+
test('returns sei chain for unknown numeric chain ID', () => {
128+
expect(getChain(9999)).toBe(sei);
129+
});
130+
131+
test('throws error for numeric string that is not in networkNameMap', () => {
132+
// This should throw an error just like other unknown network names
133+
expect(() => getChain('9999')).toThrow('Unsupported network: 9999');
134+
});
135+
136+
test('throws error for unknown network name', () => {
137+
expect(() => getChain('unknown-network')).toThrow('Unsupported network: unknown-network');
138+
});
139+
});
140+
141+
// Test getRpcUrl function
142+
describe('getRpcUrl', () => {
143+
test('returns correct RPC URL for numeric chain ID', () => {
144+
expect(getRpcUrl(1329)).toBe('https://evm-rpc.sei-apis.com');
145+
expect(getRpcUrl(1328)).toBe('https://evm-rpc-testnet.sei-apis.com');
146+
expect(getRpcUrl(713715)).toBe('https://evm-rpc-arctic-1.sei-apis.com');
147+
});
148+
149+
test('returns correct RPC URL for network name', () => {
150+
expect(getRpcUrl('sei')).toBe('https://evm-rpc.sei-apis.com');
151+
expect(getRpcUrl('sei-testnet')).toBe('https://evm-rpc-testnet.sei-apis.com');
152+
expect(getRpcUrl('sei-devnet')).toBe('https://evm-rpc-arctic-1.sei-apis.com');
153+
});
154+
155+
test('returns default RPC URL for unknown chain ID', () => {
156+
expect(getRpcUrl(9999)).toBe(DEFAULT_RPC_URL);
157+
});
158+
159+
test('returns default RPC URL when no parameter is provided', () => {
160+
expect(getRpcUrl()).toBe(DEFAULT_RPC_URL);
161+
});
162+
});
163+
164+
// Test getSupportedNetworks function
165+
describe('getSupportedNetworks', () => {
166+
test('returns sorted list of supported networks', () => {
167+
const networks = getSupportedNetworks();
168+
169+
// Check that all expected networks are included
170+
expect(networks).toContain('sei');
171+
expect(networks).toContain('sei-testnet');
172+
expect(networks).toContain('sei-devnet');
173+
174+
// Check that the list is sorted
175+
expect(networks).toEqual([...networks].sort());
176+
177+
// Check that the length matches the expected number of networks
178+
expect(networks.length).toBe(Object.keys(networkNameMap).length);
179+
});
180+
});
181+
});

0 commit comments

Comments
 (0)