@neus/mcp-server 1.0.0 → 1.0.1

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.
@@ -1,235 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Hosted NEUS MCP smoke (production by default). Set MCP_E2E_BASE_URL to override.
5
- *
6
- * Hosted MCP may be stateful or stateless during rollout. If initialize returns
7
- * mcp-session-id, reuse it and send notifications/initialized before tools/list.
8
- */
9
-
10
- const BASE_URL = String(process.env.MCP_E2E_BASE_URL || 'https://mcp.neus.network').replace(/\/$/, '');
11
-
12
- const EXPECTED_TOOLS = [
13
- 'neus_context',
14
- 'neus_verifiers_catalog',
15
- 'neus_proofs_check',
16
- 'neus_verify',
17
- 'neus_verify_or_guide',
18
- 'neus_proofs_get',
19
- 'neus_me',
20
- 'neus_agent_link',
21
- 'neus_agent_create'
22
- ];
23
-
24
- const NEUS_CONTEXT_KEYS = [
25
- 'product',
26
- 'setup',
27
- 'mode',
28
- 'goldenPath',
29
- 'jobs',
30
- 'tools',
31
- 'proofModel',
32
- 'agentModel',
33
- 'safetyRules',
34
- 'verifierSummary'
35
- ];
36
-
37
- function unwrapDataArray(value, label) {
38
- if (Array.isArray(value)) return value;
39
- if (value?.success === true && Array.isArray(value.data)) return value.data;
40
- throw new Error(`${label} must return an array or { success: true, data: array }, got: ${JSON.stringify(value).slice(0, 400)}`);
41
- }
42
-
43
- function parseSseJson(raw) {
44
- const trimmed = String(raw || '').trim();
45
- if (!trimmed) return null;
46
- if (trimmed.startsWith('{')) {
47
- return JSON.parse(trimmed);
48
- }
49
- const dataLine = String(raw || '')
50
- .split(/\r?\n/)
51
- .find((line) => line.startsWith('data: '));
52
- if (!dataLine) {
53
- throw new Error(`Unexpected MCP response: ${raw.slice(0, 500)}`);
54
- }
55
- return JSON.parse(dataLine.slice('data: '.length));
56
- }
57
-
58
- const COMMON_HEADERS = {
59
- 'content-type': 'application/json',
60
- 'mcp-protocol-version': '2025-11-25',
61
- accept: 'application/json, text/event-stream'
62
- };
63
-
64
- async function post(baseUrl, body, extraHeaders = {}) {
65
- const response = await fetch(`${baseUrl}/mcp`, {
66
- method: 'POST',
67
- headers: { ...COMMON_HEADERS, ...extraHeaders },
68
- body: JSON.stringify(body)
69
- });
70
- const raw = await response.text();
71
- return {
72
- status: response.status,
73
- payload: parseSseJson(raw),
74
- sessionId: response.headers.get('mcp-session-id')
75
- };
76
- }
77
-
78
- async function callTool(baseUrl, headers, id, name, args) {
79
- const { payload } = await post(baseUrl, {
80
- jsonrpc: '2.0',
81
- id,
82
- method: 'tools/call',
83
- params: { name, arguments: args }
84
- }, headers);
85
- const text = payload?.result?.content?.[0]?.text;
86
- if (!text) {
87
- throw new Error(`Missing tool payload for ${name}: ${JSON.stringify(payload).slice(0, 400)}`);
88
- }
89
- return JSON.parse(text);
90
- }
91
-
92
- async function main() {
93
- console.log('\n=== NEUS MCP hosted E2E ===');
94
- console.log(`BASE_URL=${BASE_URL}`);
95
-
96
- // initialize
97
- const { status: initStatus, payload: initPayload, sessionId } = await post(BASE_URL, {
98
- jsonrpc: '2.0',
99
- id: 1,
100
- method: 'initialize',
101
- params: {
102
- protocolVersion: '2025-11-25',
103
- capabilities: {},
104
- clientInfo: { name: 'mcp-live-e2e', version: '1.0.0' }
105
- }
106
- });
107
- if (initStatus !== 200) {
108
- throw new Error(`Initialize HTTP ${initStatus}: ${JSON.stringify(initPayload).slice(0, 400)}`);
109
- }
110
- if (!initPayload?.result?.protocolVersion) {
111
- throw new Error(`Initialize missing protocolVersion: ${JSON.stringify(initPayload).slice(0, 400)}`);
112
- }
113
- const sessionHeaders = sessionId ? { 'mcp-session-id': sessionId } : {};
114
- if (sessionId) {
115
- const { status: initializedStatus } = await post(BASE_URL, {
116
- jsonrpc: '2.0',
117
- method: 'notifications/initialized',
118
- params: {}
119
- }, sessionHeaders);
120
- if (initializedStatus !== 202 && initializedStatus !== 200) {
121
- throw new Error(`notifications/initialized HTTP ${initializedStatus}`);
122
- }
123
- }
124
- console.log(sessionId ? ' ✓ initialize (stateful streamable HTTP)' : ' ✓ initialize (stateless HTTP)');
125
-
126
- // tools/list
127
- const { payload: listPayload } = await post(BASE_URL, {
128
- jsonrpc: '2.0',
129
- id: 2,
130
- method: 'tools/list',
131
- params: {}
132
- }, sessionHeaders);
133
- if (listPayload?.error) {
134
- throw new Error(`tools/list failed: ${JSON.stringify(listPayload.error).slice(0, 400)}`);
135
- }
136
- const tools = listPayload?.result?.tools || [];
137
- const names = tools.map((t) => t.name);
138
- if (names.join(',') !== EXPECTED_TOOLS.join(',')) {
139
- throw new Error(`Unexpected hosted tool list.\nExpected: ${EXPECTED_TOOLS.join(', ')}\nGot: ${names.join(', ')}`);
140
- }
141
- console.log(' ✓ tools/list');
142
-
143
- // neus_context
144
- const ctx = await callTool(BASE_URL, sessionHeaders, 3, 'neus_context', {});
145
- const keys = Object.keys(ctx).sort();
146
- const expectedKeys = [...NEUS_CONTEXT_KEYS].sort();
147
- if (keys.join(',') !== expectedKeys.join(',')) {
148
- throw new Error(`Unexpected neus_context shape.\nExpected keys: ${expectedKeys.join(', ')}\nGot: ${keys.join(', ')}`);
149
- }
150
- if (!Array.isArray(ctx.goldenPath) || ctx.goldenPath.length === 0) {
151
- throw new Error('neus_context.goldenPath must be a non-empty array');
152
- }
153
- if (!Array.isArray(ctx.verifierSummary)) {
154
- throw new Error('neus_context.verifierSummary must be an array');
155
- }
156
- console.log(' ✓ neus_context contract (ten keys)');
157
-
158
- // neus_verifiers_catalog
159
- const cat = unwrapDataArray(await callTool(BASE_URL, sessionHeaders, 4, 'neus_verifiers_catalog', {}), 'neus_verifiers_catalog');
160
- console.log(` ✓ neus_verifiers_catalog (array, length=${cat.length})`);
161
-
162
- // neus_proofs_check
163
- const chk = await callTool(BASE_URL, sessionHeaders, 5, 'neus_proofs_check', {
164
- wallet: '0x0000000000000000000000000000000000000001',
165
- verifiers: ['ownership-basic']
166
- });
167
- if (typeof chk?.eligible !== 'boolean') {
168
- throw new Error(`neus_proofs_check missing eligible boolean: ${JSON.stringify(chk).slice(0, 400)}`);
169
- }
170
- console.log(' ✓ neus_proofs_check');
171
-
172
- // neus_verify_or_guide
173
- const vog = await callTool(BASE_URL, sessionHeaders, 6, 'neus_verify_or_guide', {
174
- walletAddress: '0x0000000000000000000000000000000000000001',
175
- verifierIds: ['ownership-basic']
176
- });
177
- if (typeof vog?.eligible !== 'boolean') {
178
- throw new Error(`neus_verify_or_guide missing eligible boolean: ${JSON.stringify(vog).slice(0, 400)}`);
179
- }
180
- console.log(' ✓ neus_verify_or_guide');
181
-
182
- // neus_proofs_get
183
- const pg = await callTool(BASE_URL, sessionHeaders, 7, 'neus_proofs_get', {
184
- identifier: '0x0000000000000000000000000000000000000001',
185
- limit: 5
186
- });
187
- if (!pg || typeof pg !== 'object' || !Array.isArray(pg.data?.proofs)) {
188
- throw new Error(`neus_proofs_get missing data.proofs array: ${JSON.stringify(pg).slice(0, 400)}`);
189
- }
190
- console.log(' ✓ neus_proofs_get');
191
-
192
- // neus_me
193
- const me = await callTool(BASE_URL, sessionHeaders, 8, 'neus_me', {});
194
- if (typeof me?.status !== 'string') {
195
- throw new Error(`neus_me missing status string: ${JSON.stringify(me).slice(0, 400)}`);
196
- }
197
- console.log(' ✓ neus_me');
198
-
199
- // neus_agent_link
200
- const al = await callTool(BASE_URL, sessionHeaders, 9, 'neus_agent_link', {
201
- agentWallet: '0x0000000000000000000000000000000000000001'
202
- });
203
- if (typeof al?.status !== 'string') {
204
- throw new Error(`neus_agent_link missing status string: ${JSON.stringify(al).slice(0, 400)}`);
205
- }
206
- console.log(' ✓ neus_agent_link');
207
-
208
- // neus_agent_create
209
- const ac = await callTool(BASE_URL, sessionHeaders, 10, 'neus_agent_create', {
210
- agentId: 'e2e-test-agent',
211
- agentWallet: 'generate'
212
- });
213
- if (typeof ac?.status !== 'string' && typeof ac?.error !== 'string') {
214
- throw new Error(`neus_agent_create missing status or error: ${JSON.stringify(ac).slice(0, 400)}`);
215
- }
216
- console.log(' ✓ neus_agent_create');
217
-
218
- // neus_verify (preparation step)
219
- const v = await callTool(BASE_URL, sessionHeaders, 11, 'neus_verify', {
220
- walletAddress: '0x0000000000000000000000000000000000000001',
221
- verifierIds: ['ownership-basic']
222
- });
223
- if (typeof v?.status !== 'string' && typeof v?.error !== 'string') {
224
- throw new Error(`neus_verify missing status or error: ${JSON.stringify(v).slice(0, 400)}`);
225
- }
226
- console.log(' ✓ neus_verify');
227
-
228
- console.log('\n--- Summary ---');
229
- console.log('Passed');
230
- }
231
-
232
- main().catch((err) => {
233
- console.error(err);
234
- process.exit(1);
235
- });
@@ -1,263 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Local NEUS MCP smoke: health, discovery, ten-key neus_context contract.
5
- */
6
-
7
- import { spawn } from 'node:child_process';
8
-
9
- const PORT = 3110;
10
- const BASE_URL = `http://127.0.0.1:${PORT}`;
11
-
12
- const EXPECTED_TOOLS = [
13
- 'neus_context',
14
- 'neus_verifiers_catalog',
15
- 'neus_proofs_check',
16
- 'neus_verify',
17
- 'neus_verify_or_guide',
18
- 'neus_proofs_get',
19
- 'neus_me',
20
- 'neus_agent_link',
21
- 'neus_agent_create'
22
- ];
23
-
24
- const NEUS_CONTEXT_KEYS = [
25
- 'product',
26
- 'setup',
27
- 'mode',
28
- 'goldenPath',
29
- 'jobs',
30
- 'tools',
31
- 'proofModel',
32
- 'agentModel',
33
- 'safetyRules',
34
- 'verifierSummary'
35
- ];
36
-
37
- function wait(ms) {
38
- return new Promise((resolve) => setTimeout(resolve, ms));
39
- }
40
-
41
- async function waitForHealth(url, timeoutMs = 20000) {
42
- const startedAt = Date.now();
43
- while (Date.now() - startedAt < timeoutMs) {
44
- try {
45
- const res = await fetch(`${url}/health`);
46
- if (res.ok) return true;
47
- } catch {
48
- // ignore
49
- }
50
- await wait(250);
51
- }
52
- return false;
53
- }
54
-
55
- function parseSseJson(raw) {
56
- const trimmed = String(raw || '').trim();
57
- if (trimmed.startsWith('{')) {
58
- return JSON.parse(trimmed);
59
- }
60
- const dataLine = String(raw || '')
61
- .split(/\r?\n/)
62
- .find((line) => line.startsWith('data: '));
63
- if (!dataLine) {
64
- throw new Error(`Unexpected MCP response: ${raw}`);
65
- }
66
- return JSON.parse(dataLine.slice('data: '.length));
67
- }
68
-
69
- async function initMcpSession(baseUrl) {
70
- const headers = {
71
- 'content-type': 'application/json',
72
- 'mcp-protocol-version': '2025-11-25',
73
- accept: 'application/json, text/event-stream'
74
- };
75
-
76
- const response = await fetch(`${baseUrl}/mcp`, {
77
- method: 'POST',
78
- headers,
79
- body: JSON.stringify({
80
- jsonrpc: '2.0',
81
- id: 1,
82
- method: 'initialize',
83
- params: {
84
- protocolVersion: '2025-11-25',
85
- capabilities: {},
86
- clientInfo: { name: 'mcp-local-e2e', version: '1.0.0' }
87
- }
88
- })
89
- });
90
-
91
- const raw = await response.text();
92
- const sessionId = response.headers.get('mcp-session-id');
93
- if (sessionId) {
94
- throw new Error(`MCP initialize should be stateless and must not return mcp-session-id: ${sessionId}`);
95
- }
96
- parseSseJson(raw);
97
- return { headers };
98
- }
99
-
100
- async function listTools(baseUrl, headers, id) {
101
- const response = await fetch(`${baseUrl}/mcp`, {
102
- method: 'POST',
103
- headers,
104
- body: JSON.stringify({
105
- jsonrpc: '2.0',
106
- id,
107
- method: 'tools/list',
108
- params: {}
109
- })
110
- });
111
-
112
- const raw = await response.text();
113
- const payload = parseSseJson(raw);
114
- return payload?.result?.tools || [];
115
- }
116
-
117
- async function callTool(baseUrl, headers, id, name, args) {
118
- const response = await fetch(`${baseUrl}/mcp`, {
119
- method: 'POST',
120
- headers,
121
- body: JSON.stringify({
122
- jsonrpc: '2.0',
123
- id,
124
- method: 'tools/call',
125
- params: {
126
- name,
127
- arguments: args
128
- }
129
- })
130
- });
131
-
132
- const raw = await response.text();
133
- const payload = parseSseJson(raw);
134
- const text = payload?.result?.content?.[0]?.text;
135
- if (!text) {
136
- throw new Error(`Missing tool payload for ${name}: ${raw}`);
137
- }
138
- return JSON.parse(text);
139
- }
140
-
141
- async function main() {
142
- console.log('\n=== NEUS MCP local E2E ===');
143
- console.log(`Spawning MCP on ${BASE_URL}`);
144
-
145
- const child = spawn(process.execPath, ['server.js'], {
146
- cwd: new URL('.', import.meta.url),
147
- env: {
148
- ...process.env,
149
- MCP_TRANSPORT: 'http',
150
- HOST: '127.0.0.1',
151
- PORT: String(PORT)
152
- },
153
- stdio: ['ignore', 'pipe', 'pipe']
154
- });
155
-
156
- child.stdout.on('data', () => {});
157
- child.stderr.on('data', () => {});
158
-
159
- let headers = null;
160
-
161
- try {
162
- const ready = await waitForHealth(BASE_URL);
163
- if (!ready) throw new Error('MCP server did not become healthy in time');
164
- console.log(' ✓ Health endpoint');
165
-
166
- ({ headers } = await initMcpSession(BASE_URL));
167
- console.log(' ✓ MCP stateless initialize');
168
-
169
- const tools = await listTools(BASE_URL, headers, 10);
170
- const names = tools.map((t) => t.name);
171
- if (names.join(',') !== EXPECTED_TOOLS.join(',')) {
172
- throw new Error(`Unexpected public tool list.\nExpected: ${EXPECTED_TOOLS.join(', ')}\nGot: ${names.join(', ')}`);
173
- }
174
- console.log(' ✓ tools/list matches nine-tool public surface');
175
-
176
- const ctx = await callTool(BASE_URL, headers, 11, 'neus_context', {});
177
- const keys = Object.keys(ctx).sort();
178
- const expectedKeys = [...NEUS_CONTEXT_KEYS].sort();
179
- if (keys.join(',') !== expectedKeys.join(',')) {
180
- throw new Error(`Unexpected neus_context shape.\nExpected keys: ${expectedKeys.join(', ')}\nGot: ${keys.join(', ')}`);
181
- }
182
- if (!Array.isArray(ctx.goldenPath) || ctx.goldenPath.length === 0) {
183
- throw new Error('neus_context.goldenPath must be a non-empty array');
184
- }
185
- if (!Array.isArray(ctx.verifierSummary)) {
186
- throw new Error('neus_context.verifierSummary must be an array');
187
- }
188
-
189
- const cat = await callTool(BASE_URL, headers, 11, 'neus_verifiers_catalog', {});
190
- if (!Array.isArray(cat)) {
191
- throw new Error(`neus_verifiers_catalog must return an array, got: ${JSON.stringify(cat).slice(0, 400)}`);
192
- }
193
- console.log(` ✓ neus_verifiers_catalog (array, length=${cat.length})`);
194
-
195
- const chk = await callTool(BASE_URL, headers, 12, 'neus_proofs_check', {
196
- wallet: '0x0000000000000000000000000000000000000001',
197
- verifiers: ['ownership-basic']
198
- });
199
- if (typeof chk?.eligible !== 'boolean') {
200
- throw new Error(`neus_proofs_check missing eligible boolean: ${JSON.stringify(chk).slice(0, 400)}`);
201
- }
202
- console.log(' ✓ neus_proofs_check');
203
-
204
- const vog = await callTool(BASE_URL, headers, 13, 'neus_verify_or_guide', {
205
- walletAddress: '0x0000000000000000000000000000000000000001',
206
- verifierIds: ['ownership-basic']
207
- });
208
- if (typeof vog?.eligible !== 'boolean') {
209
- throw new Error(`neus_verify_or_guide missing eligible boolean: ${JSON.stringify(vog).slice(0, 400)}`);
210
- }
211
- console.log(' ✓ neus_verify_or_guide');
212
-
213
- const pg = await callTool(BASE_URL, headers, 14, 'neus_proofs_get', {
214
- identifier: '0x0000000000000000000000000000000000000001',
215
- limit: 5
216
- });
217
- if (!pg || typeof pg !== 'object' || !Array.isArray(pg.data?.proofs)) {
218
- throw new Error(`neus_proofs_get missing data.proofs array: ${JSON.stringify(pg).slice(0, 400)}`);
219
- }
220
- console.log(' ✓ neus_proofs_get');
221
-
222
- const me = await callTool(BASE_URL, headers, 15, 'neus_me', {});
223
- if (typeof me?.status !== 'string') {
224
- throw new Error(`neus_me missing status string: ${JSON.stringify(me).slice(0, 400)}`);
225
- }
226
- console.log(' ✓ neus_me');
227
-
228
- const al = await callTool(BASE_URL, headers, 16, 'neus_agent_link', {
229
- agentWallet: '0x0000000000000000000000000000000000000001'
230
- });
231
- if (typeof al?.status !== 'string') {
232
- throw new Error(`neus_agent_link missing status string: ${JSON.stringify(al).slice(0, 400)}`);
233
- }
234
- console.log(' ✓ neus_agent_link');
235
-
236
- const ac = await callTool(BASE_URL, headers, 17, 'neus_agent_create', {
237
- agentId: 'e2e-test-agent',
238
- agentWallet: 'generate'
239
- });
240
- if (typeof ac?.status !== 'string' && typeof ac?.error !== 'string') {
241
- throw new Error(`neus_agent_create missing status or error: ${JSON.stringify(ac).slice(0, 400)}`);
242
- }
243
- console.log(' ✓ neus_agent_create');
244
-
245
- const v = await callTool(BASE_URL, headers, 18, 'neus_verify', {
246
- walletAddress: '0x0000000000000000000000000000000000000001',
247
- verifierIds: ['ownership-basic']
248
- });
249
- if (typeof v?.status !== 'string' && typeof v?.error !== 'string') {
250
- throw new Error(`neus_verify missing status or error: ${JSON.stringify(v).slice(0, 400)}`);
251
- }
252
- console.log(' ✓ neus_verify');
253
- console.log('\n--- Summary ---');
254
- console.log('Passed');
255
- } finally {
256
- child.kill('SIGTERM');
257
- }
258
- }
259
-
260
- main().catch((error) => {
261
- console.error(error);
262
- process.exit(1);
263
- });