@neus/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/server.json ADDED
@@ -0,0 +1,134 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.neus/neus-mcp",
4
+ "title": "NEUS MCP",
5
+ "description": "NEUS hosted MCP: verifiers, proofs, verify flows, agents. Always call neus_context first.",
6
+ "version": "1.0.0",
7
+ "repository": {
8
+ "url": "https://github.com/neus/network",
9
+ "source": "github"
10
+ },
11
+ "websiteUrl": "https://docs.neus.network/mcp/overview",
12
+ "icons": [
13
+ {
14
+ "src": "https://neus.network/images/neus-brand-pack/neus-mark-128.png",
15
+ "sizes": ["128x128"],
16
+ "mimeType": "image/png"
17
+ }
18
+ ],
19
+ "remotes": [
20
+ {
21
+ "type": "streamable-http",
22
+ "url": "https://mcp.neus.network/mcp"
23
+ }
24
+ ],
25
+ "capabilities": {
26
+ "tools": {
27
+ "supported": true,
28
+ "listChanged": false
29
+ },
30
+ "elicitation": {
31
+ "supported": true
32
+ }
33
+ },
34
+ "tools": [
35
+ {
36
+ "name": "neus_context",
37
+ "description": "NEUS MCP session home: product summary, setup, access-key mode, verifier summaries, reuse-first workflow, tools, and safety rules. Always call first."
38
+ },
39
+ {
40
+ "name": "neus_verifiers_catalog",
41
+ "description": "Full verifier directory with JSON schemas. Use after neus_context when payloads need exact fields beyond the summary."
42
+ },
43
+ {
44
+ "name": "neus_proofs_check",
45
+ "description": "Checks whether existing proofs satisfy verifier IDs. Eligibility only; never creates proofs."
46
+ },
47
+ {
48
+ "name": "neus_verify",
49
+ "description": "Creates or continues verification only when Profile access, wallet, and signing already satisfy NEUS for this verifier. Check existing receipts first."
50
+ },
51
+ {
52
+ "name": "neus_verify_or_guide",
53
+ "description": "Fallback orchestration after reuse checks: use only when proof, profile, payment, provider, wallet, or delegation setup is missing."
54
+ },
55
+ {
56
+ "name": "neus_proofs_get",
57
+ "description": "Reads proof records, tags, delegated agent reads, Profile summary fields, and live proof state. qHashes in the response are authoritative."
58
+ },
59
+ {
60
+ "name": "neus_me",
61
+ "description": "Returns the signed-in NEUS Profile when a personal access key from Profile → Account is configured on this MCP session."
62
+ },
63
+ {
64
+ "name": "neus_agent_link",
65
+ "description": "Readiness check for an agent wallet: returns linked when agent-identity and agent-delegation proofs exist; otherwise link_required with hostedVerifyUrl."
66
+ },
67
+ {
68
+ "name": "neus_agent_create",
69
+ "description": "Prepares agent-identity and agent-delegation payloads (signatures and/or hostedVerifyUrl). Does not finalize receipts; confirm with neus_agent_link after signing. Skills/services fields are metadata only, not secrets."
70
+ }
71
+ ],
72
+ "license": "Apache-2.0",
73
+ "author": {
74
+ "name": "NEUS",
75
+ "email": "info@neus.network",
76
+ "url": "https://neus.network"
77
+ },
78
+ "keywords": [
79
+ "neus",
80
+ "mcp",
81
+ "model-context-protocol",
82
+ "verification",
83
+ "trust-receipts",
84
+ "proofs",
85
+ "delegation",
86
+ "identity",
87
+ "agents",
88
+ "profile",
89
+ "wallet",
90
+ "ai-agents",
91
+ "ethereum",
92
+ "solana",
93
+ "did",
94
+ "x402"
95
+ ],
96
+ "examples": [
97
+ {
98
+ "title": "Bootstrap a session",
99
+ "description": "Load the MCP session home plus verifier summaries.",
100
+ "prompt": "Call neus_context first, then follow the returned goldenPath. If Bearer is configured, call neus_me before any account-aware work."
101
+ },
102
+ {
103
+ "title": "Register an AI agent",
104
+ "description": "Identity and delegation proofs for an agent wallet; confirm readiness before use.",
105
+ "prompt": "Use neus_agent_link first when an agent wallet already exists. Use neus_agent_create only when setup is missing. If a hosted URL is returned, share it exactly, then call neus_agent_link after completion."
106
+ },
107
+ {
108
+ "title": "Gate before a new verification",
109
+ "description": "Eligibility only—no new proof creation.",
110
+ "prompt": "Use neus_proofs_check for wallet 0x... and verifier IDs [\"ownership-basic\", \"proof-of-human\"]. Report only whether requirements are satisfied and which verifiers matched—do not start a new verification. Use qHash as the trust receipt reference; contentHash identifies proven content."
111
+ },
112
+ {
113
+ "title": "Reuse first, guide only when missing",
114
+ "description": "Check existing trust before any browser handoff.",
115
+ "prompt": "Call neus_proofs_check for wallet 0x... and verifier IDs [\"ownership-basic\"]. If eligible, continue without hosted verify. If not eligible, call neus_verify_or_guide, share any returned hosted URL exactly, then call neus_proofs_get after completion and report the qHash from the proof record."
116
+ },
117
+ {
118
+ "title": "Tag-filtered proof inventory (delegated context)",
119
+ "description": "Filter proof reads by tags—for memory, skills, jobs—same tool as listing proofs.",
120
+ "prompt": "For wallet 0x..., call neus_proofs_get with tags=memory,skill and optional agentWallet only when delegated reads apply. Summarize proof titles, tags, and qHashes only—no invented records."
121
+ },
122
+ {
123
+ "title": "Instant verification with Profile access key",
124
+ "description": "When MCP carries your NEUS access key and the profile wallet matches, some instant verifiers may submit without a wallet signature.",
125
+ "prompt": "My NEUS personal access key is configured on this MCP connection and walletAddress 0x... matches my NEUS profile wallet. Call neus_context, then neus_me, then neus_proofs_check. If the existing receipt satisfies the request, stop. If not, call neus_verify only when prerequisites match; otherwise explain the next returned setup step."
126
+ }
127
+ ],
128
+ "compatibility": {
129
+ "claudeDesktop": true,
130
+ "claudeCode": true,
131
+ "cursor": true,
132
+ "vscode": true
133
+ }
134
+ }
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Print a compact neus_context view for MCP connectivity checks.
5
+ * Usage: MCP_E2E_BASE_URL=http://127.0.0.1:3110 node test-agent-context.js
6
+ */
7
+
8
+ const BASE_URL = String(process.env.MCP_E2E_BASE_URL || 'https://mcp.neus.network').replace(/\/$/, '');
9
+
10
+ function parseSseJson(raw) {
11
+ const dataLine = String(raw || '')
12
+ .split(/\r?\n/)
13
+ .find((line) => line.startsWith('data: '));
14
+ if (!dataLine) throw new Error(`Unexpected MCP response: ${raw.slice(0, 2000)}`);
15
+ return JSON.parse(dataLine.slice('data: '.length));
16
+ }
17
+
18
+ async function initMcpSession(baseUrl) {
19
+ const headers = {
20
+ 'content-type': 'application/json',
21
+ 'mcp-protocol-version': '2025-11-25',
22
+ accept: 'application/json, text/event-stream'
23
+ };
24
+ const response = await fetch(`${baseUrl}/mcp`, {
25
+ method: 'POST',
26
+ headers,
27
+ body: JSON.stringify({
28
+ jsonrpc: '2.0',
29
+ id: 1,
30
+ method: 'initialize',
31
+ params: {
32
+ protocolVersion: '2025-11-25',
33
+ capabilities: {},
34
+ clientInfo: { name: 'agent-context-probe', version: '1.0.0' }
35
+ }
36
+ })
37
+ });
38
+ const raw = await response.text();
39
+ const sessionId = response.headers.get('mcp-session-id');
40
+ if (!sessionId) throw new Error(`initialize failed HTTP ${response.status}: ${raw.slice(0, 2000)}`);
41
+ parseSseJson(raw);
42
+ return { sessionId, headers };
43
+ }
44
+
45
+ async function callNeusContext(baseUrl, sessionId, headers) {
46
+ const response = await fetch(`${baseUrl}/mcp`, {
47
+ method: 'POST',
48
+ headers: { ...headers, 'mcp-session-id': sessionId },
49
+ body: JSON.stringify({
50
+ jsonrpc: '2.0',
51
+ id: 2,
52
+ method: 'tools/call',
53
+ params: { name: 'neus_context', arguments: {} }
54
+ })
55
+ });
56
+ const raw = await response.text();
57
+ const payload = parseSseJson(raw);
58
+ const text = payload?.result?.content?.[0]?.text;
59
+ if (!text) throw new Error(`no neus_context body: ${raw.slice(0, 2000)}`);
60
+ return JSON.parse(text);
61
+ }
62
+
63
+ async function close(baseUrl, sessionId, headers) {
64
+ try {
65
+ await fetch(`${baseUrl}/mcp`, {
66
+ method: 'DELETE',
67
+ headers: { ...headers, 'mcp-session-id': sessionId }
68
+ });
69
+ } catch {
70
+ /* ignore */
71
+ }
72
+ }
73
+
74
+ async function main() {
75
+ let sessionId;
76
+ let headers;
77
+ try {
78
+ ({ sessionId, headers } = await initMcpSession(BASE_URL));
79
+ const ctx = await callNeusContext(BASE_URL, sessionId, headers);
80
+ const summary = {
81
+ mode: ctx.mode,
82
+ headline: ctx.product?.headline,
83
+ goldenPath: ctx.goldenPath,
84
+ jobIntents: Array.isArray(ctx.jobs) ? ctx.jobs.map((j) => j.intent) : [],
85
+ verifierCount: Array.isArray(ctx.verifierSummary) ? ctx.verifierSummary.length : null,
86
+ urls: ctx.setup
87
+ ? {
88
+ mcp: ctx.setup.mcpEndpoint,
89
+ profile: ctx.setup.profileUrl,
90
+ accessKeys: ctx.setup.accessKeysUrl
91
+ }
92
+ : null
93
+ };
94
+ console.log(JSON.stringify(summary, null, 2));
95
+ } finally {
96
+ if (sessionId && headers) await close(BASE_URL, sessionId, headers);
97
+ }
98
+ }
99
+
100
+ main().catch((e) => {
101
+ console.error(e);
102
+ process.exit(1);
103
+ });
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Static contract guard for the hosted NEUS MCP surface.
5
+ * Fails on drift across server.json, README, docs, and core server framing.
6
+ */
7
+
8
+ import { readFileSync, existsSync } from 'node:fs';
9
+ import { dirname, join } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+
14
+ const MCP_DIR = __dirname;
15
+
16
+ /** Final public tool surface (order must match server.json and hosted registration). */
17
+ const PUBLIC_TOOLS = [
18
+ 'neus_context',
19
+ 'neus_verifiers_catalog',
20
+ 'neus_proofs_check',
21
+ 'neus_verify',
22
+ 'neus_verify_or_guide',
23
+ 'neus_proofs_get',
24
+ 'neus_me',
25
+ 'neus_agent_link',
26
+ 'neus_agent_create'
27
+ ];
28
+
29
+ const NEUS_CONTEXT_TOP_LEVEL = [
30
+ 'product',
31
+ 'setup',
32
+ 'mode',
33
+ 'goldenPath',
34
+ 'jobs',
35
+ 'tools',
36
+ 'proofModel',
37
+ 'agentModel',
38
+ 'safetyRules',
39
+ 'verifierSummary'
40
+ ];
41
+
42
+ /** Required words in MCP package README (minimal teaching surface). */
43
+ const README_REQUIRED = [
44
+ 'neus_context',
45
+ 'neus_verifiers_catalog',
46
+ 'neus_proofs_check',
47
+ 'neus_verify_or_guide',
48
+ 'neus_verify',
49
+ 'neus_proofs_get',
50
+ 'neus_me',
51
+ 'neus_agent_create',
52
+ 'neus_agent_link',
53
+ 'call neus_context first'
54
+ ];
55
+
56
+ /** Core teaching pages that must retain `neus_proofs_get` mention. */
57
+ const NETWORK_DOC_FILES = ['overview.mdx', 'tools.mdx', 'setup.mdx', 'proofs-get.mdx'];
58
+
59
+ /** Banned in public MCP-facing manifest + hosted package README (adapter boundary). */
60
+ const BANNED_PUBLIC_SUBSTR = ['Zeus', 'Hostinger', 'trust layer', 'Verification MCP', 'runtime ledger', 'receipt chain'];
61
+
62
+ function fail(msg) {
63
+ console.error(`\nPUBLIC MCP CONTRACT FAILURE: ${msg}\n`);
64
+ process.exit(1);
65
+ }
66
+
67
+ function readUtf8(rel) {
68
+ return readFileSync(join(MCP_DIR, rel), 'utf8');
69
+ }
70
+
71
+ /** Slice of server.js covering public TOOLS[] entries (exclusive of extended tools). */
72
+ function getPublicToolsDocumentationBlock(serverJs) {
73
+ const start = serverJs.indexOf('const TOOLS = [');
74
+ if (start < 0) return null;
75
+ const zeus = serverJs.indexOf("name: 'zeus_url_fetch'", start);
76
+ if (zeus < 0) return null;
77
+ return serverJs.slice(start, zeus);
78
+ }
79
+
80
+ /** First tool-level description string before inputSchema for a named TOOLS entry. */
81
+ function extractToolDescriptionFromToolsBlock(block, toolName) {
82
+ const needle = `name: '${toolName}'`;
83
+ const idx = block.indexOf(needle);
84
+ if (idx < 0) return null;
85
+ const fromName = block.slice(idx);
86
+ const schemaAt = fromName.indexOf('inputSchema:');
87
+ if (schemaAt < 0) return null;
88
+ const head = fromName.slice(0, schemaAt);
89
+ const multiline = head.match(/description:\s*\n\s*'((?:[^'\\]|\\.)*)'/);
90
+ const single = head.match(/description:\s*'((?:[^'\\]|\\.)*)'/);
91
+ const raw = multiline ? multiline[1] : single ? single[1] : null;
92
+ return raw;
93
+ }
94
+
95
+ function main() {
96
+ const serverJson = JSON.parse(readUtf8('server.json'));
97
+ const readme = readUtf8('README.md');
98
+ const serverJs = readUtf8('server.js');
99
+
100
+ const title = serverJson.title || '';
101
+ if (title.includes('Verification MCP')) fail('server.json title must not contain "Verification MCP".');
102
+
103
+ /** Official MCP Registry rejects longer server summaries (422). */
104
+ const REGISTRY_SERVER_DESCRIPTION_MAX = 100;
105
+ const serverDesc = String(serverJson.description || '');
106
+ if (serverDesc.length > REGISTRY_SERVER_DESCRIPTION_MAX) {
107
+ fail(
108
+ `server.json top-level description must be <= ${REGISTRY_SERVER_DESCRIPTION_MAX} chars for MCP Registry (got ${serverDesc.length})`
109
+ );
110
+ }
111
+
112
+ if (!Array.isArray(serverJson.tools)) fail('server.json tools must be an array');
113
+
114
+ const names = serverJson.tools.map((t) => t?.name).filter(Boolean);
115
+ if (names.length !== PUBLIC_TOOLS.length) fail(`server.json must expose exactly ${PUBLIC_TOOLS.length} tools`);
116
+ const setErr = PUBLIC_TOOLS.some((n, i) => names[i] !== n);
117
+ if (setErr) {
118
+ fail(
119
+ `server.json tool order/set mismatch.\nExpected: ${PUBLIC_TOOLS.join(', ')}\nActual: ${names.join(', ')}`
120
+ );
121
+ }
122
+
123
+ const extra = names.filter((n) => !PUBLIC_TOOLS.includes(n));
124
+ if (extra.length) fail(`server.json has unexpected tools: ${extra.join(', ')}`);
125
+
126
+ const sjPlain = JSON.stringify(serverJson);
127
+ if (/\b[Hh]ub\b/.test(sjPlain)) fail('server.json must not use public "Hub" language');
128
+
129
+ for (const banned of BANNED_PUBLIC_SUBSTR) {
130
+ if (banned !== 'Verification MCP' && sjPlain.includes(banned)) fail(`server.json must not contain: ${banned}`);
131
+ }
132
+
133
+ if (/\b[Hh]ub\b/.test(readme)) fail('mcp/README.md must not use public "Hub" language');
134
+
135
+ for (const needle of README_REQUIRED) {
136
+ if (!readme.includes(needle)) fail(`mcp/README.md must mention: ${needle}`);
137
+ }
138
+
139
+ const repoRoot = join(MCP_DIR, '..', '..');
140
+ const networkMcpDocs = join(repoRoot, 'network', 'docs', 'mcp');
141
+ if (!existsSync(networkMcpDocs)) {
142
+ fail(`network MCP docs directory missing (expected ${networkMcpDocs})`);
143
+ }
144
+
145
+ for (const doc of NETWORK_DOC_FILES) {
146
+ const p = join(networkMcpDocs, doc);
147
+ if (!existsSync(p)) fail(`Missing required doc: ${p}`);
148
+ const text = readFileSync(p, 'utf8');
149
+ if (!text.includes('neus_proofs_get')) fail(`${doc} must document neus_proofs_get`);
150
+ if (/\b[Hh]ub\b/.test(text)) fail(`${doc} must not use public "Hub" language`);
151
+ if (text.includes('Zeus')) fail(`${doc} must not expose "Zeus"`);
152
+ }
153
+
154
+ if (!/call neus_context first/i.test(serverJs)) {
155
+ fail('server.js MCP instructions must tell models to call neus_context first');
156
+ }
157
+
158
+ const bpStart = serverJs.indexOf('function buildPublicNeusContextPayload(');
159
+ if (bpStart < 0) fail('server.js must define buildPublicNeusContextPayload');
160
+ const bpDelim = '// ERROR REMEDIATION';
161
+ const bpEnd = serverJs.indexOf(bpDelim, bpStart);
162
+ if (bpEnd < 0) fail('cannot locate buildPublicNeusContextPayload boundary before ERROR_REMEDIATION');
163
+ const bpSlice = serverJs.slice(bpStart, bpEnd);
164
+
165
+ for (const key of NEUS_CONTEXT_TOP_LEVEL) {
166
+ if (key === 'verifierSummary') {
167
+ if (!/\bverifierSummary\b/.test(bpSlice)) fail('buildPublicNeusContextPayload must define verifierSummary');
168
+ } else if (!bpSlice.includes(`${key}:`)) {
169
+ fail(`buildPublicNeusContextPayload must define ${key}`);
170
+ }
171
+ }
172
+ const noise = ['defaultCallOrder:', 'routing:', 'toolSelection:', 'responseRules:', 'catalogMeta', 'billing:', 'integrationGuidelines'];
173
+ for (const bad of noise) {
174
+ if (bpSlice.includes(bad)) fail(`buildPublicNeusContextPayload must not carry legacy section: ${bad}`);
175
+ }
176
+ if (/\bzeus\b/i.test(bpSlice)) fail('buildPublicNeusContextPayload must not mention Zeus');
177
+ if (/\bhub\b/i.test(bpSlice)) fail('buildPublicNeusContextPayload must not mention Hub wording');
178
+
179
+ const toolsDoc = getPublicToolsDocumentationBlock(serverJs);
180
+ if (!toolsDoc) fail('Cannot slice public TOOLS block before zeus_url_fetch');
181
+ for (const toolName of PUBLIC_TOOLS) {
182
+ const fromJs = extractToolDescriptionFromToolsBlock(toolsDoc, toolName);
183
+ const fromJson = serverJson.tools.find((t) => t.name === toolName)?.description;
184
+ if (fromJs == null) fail(`Parse TOOLS[].description failed for ${toolName}`);
185
+ if (fromJs !== fromJson) {
186
+ fail(
187
+ `server.json tool "${toolName}" description must equal TOOLS in server.js.\nmanifest: ${JSON.stringify(fromJson)}\nserver.js: ${JSON.stringify(fromJs)}`
188
+ );
189
+ }
190
+ }
191
+
192
+ console.log('PUBLIC MCP CONTRACT OK');
193
+ }
194
+
195
+ main();