@neus/mcp-server 1.0.0 → 1.0.2
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 +23 -46
- package/index.mjs +21 -0
- package/package.json +21 -50
- package/server.json +1 -1
- package/CHANGELOG.md +0 -18
- package/e2e-mcp-agent-local.test.js +0 -220
- package/e2e-mcp-live.test.js +0 -235
- package/e2e-mcp-local.test.js +0 -263
- package/server.js +0 -4420
- package/test-agent-context.js +0 -103
- package/test-public-mcp-contract.js +0 -195
package/README.md
CHANGED
|
@@ -1,63 +1,40 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @neus/mcp-server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Model Context Protocol server for NEUS trust receipts, verification, and agent flows.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Prefer the hosted endpoint
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
For almost all integrations, connect to **`https://mcp.neus.network/mcp`** and follow **[MCP setup](https://docs.neus.network/mcp/setup)** (transport, auth, and access keys).
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
This npm package ships **public discovery metadata** (for example `server.json`) for registries and installers. The hosted MCP server runs at **`https://mcp.neus.network/mcp`** — see the [MCP docs](https://docs.neus.network/mcp/overview) for setup and usage.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## Golden path (nine tools)
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
## Public Tools And Golden Path
|
|
16
|
-
|
|
17
|
-
By default, **`server.js`** registers **nine** hosted tools. Models should **call neus_context first** each session. The **`server.json`** top-level `description` matches the MCP Registry length limit (≤100 chars) and stays aligned with this README.
|
|
18
|
-
|
|
19
|
-
1. Call **`neus_context`** first every session.
|
|
20
|
-
2. If a Profile access key is configured, call **`neus_me`**.
|
|
21
|
-
3. For agents, call **`neus_agent_link`** before assuming identity or delegation is ready.
|
|
22
|
-
4. Use **`neus_proofs_check`** to check access without creating anything.
|
|
23
|
-
5. If requirements are satisfied, continue without hosted verify.
|
|
24
|
-
6. Use **`neus_proofs_get`** when you need authoritative proof records, tags, delegated reads, or qHashes.
|
|
25
|
-
7. Use **`neus_verify`** only when your Profile personal access key, signing, and wallet identifiers already match NEUS guidance for this verifier path.
|
|
26
|
-
8. Use **`neus_verify_or_guide`** only when proof, profile, payment, provider, wallet, or delegation setup is missing.
|
|
27
|
-
9. Use **`neus_agent_create`** only when agent setup is missing, then confirm with **`neus_agent_link`**.
|
|
28
|
-
|
|
29
|
-
Also use **`neus_verifiers_catalog`** when **`neus_context`** is not enough for JSON schemas. After **`neus_context`**, when a Profile access key is configured on this MCP session, call **`neus_me`** and expect **`status: ok`** before relying on profile-scoped behavior.
|
|
30
|
-
|
|
31
|
-
Inputs are validated with **Zod** at runtime. When your MCP client attaches **`Authorization: Bearer`** plus the **personal access key** minted under Profile → Account, NEUS resolves the signed-in Profile ([auth docs](https://docs.neus.network/mcp/setup)).
|
|
32
|
-
|
|
33
|
-
### Tool list
|
|
13
|
+
Models should **call neus_context first** every session, then follow the tool order your client needs:
|
|
34
14
|
|
|
35
15
|
| Tool | Role |
|
|
36
16
|
| --- | --- |
|
|
37
|
-
| `neus_context` | Session home —
|
|
38
|
-
| `neus_verifiers_catalog` |
|
|
39
|
-
| `neus_proofs_check` | Eligibility only (
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `neus_proofs_get` |
|
|
43
|
-
| `neus_me` | Signed-in Profile
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
|
|
47
|
-
Deploy-only helpers may appear in extended developer builds; hosted production stays on the nine-tool surface above.
|
|
48
|
-
|
|
49
|
-
## Checks
|
|
17
|
+
| `neus_context` | Session home — setup, verifier summaries, golden path |
|
|
18
|
+
| `neus_verifiers_catalog` | Verifier metadata and JSON schemas |
|
|
19
|
+
| `neus_proofs_check` | Eligibility only (does not create proofs) |
|
|
20
|
+
| `neus_verify` | Start or continue verification when prerequisites already match |
|
|
21
|
+
| `neus_verify_or_guide` | Guided flow when setup or consent is still missing |
|
|
22
|
+
| `neus_proofs_get` | Read proof records, tags, and delegated context |
|
|
23
|
+
| `neus_me` | Signed-in Profile when a personal access key is configured on the session |
|
|
24
|
+
| `neus_agent_link` | Confirm agent identity and delegation readiness |
|
|
25
|
+
| `neus_agent_create` | Prepare agent identity and delegation when setup is missing |
|
|
50
26
|
|
|
51
|
-
|
|
27
|
+
With a Profile access key on the MCP session, call **`neus_me`** and expect a successful status before relying on account-scoped behavior.
|
|
52
28
|
|
|
53
|
-
|
|
29
|
+
## Auth and safety
|
|
54
30
|
|
|
55
|
-
|
|
31
|
+
Use **`Authorization: Bearer`** with the **personal access key** from the NEUS app (Profile → Account). Do not embed secrets in public repos or client-side bundles. Inputs are validated at runtime.
|
|
56
32
|
|
|
57
|
-
|
|
33
|
+
## Docs and support
|
|
58
34
|
|
|
59
|
-
|
|
35
|
+
- **Product MCP guides:** [docs.neus.network/mcp](https://docs.neus.network/mcp/overview)
|
|
36
|
+
- **Issues (public):** [github.com/neus/network/issues](https://github.com/neus/network/issues)
|
|
60
37
|
|
|
61
38
|
## License
|
|
62
39
|
|
|
63
|
-
Apache-2.0
|
|
40
|
+
Apache-2.0 (see the `license` field in `package.json`).
|
package/index.mjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @neus/mcp-server — discovery metadata only.
|
|
3
|
+
* The hosted NEUS MCP server runs at https://mcp.neus.network/mcp, not inside this npm package.
|
|
4
|
+
*
|
|
5
|
+
* @see https://mcp.neus.network/mcp
|
|
6
|
+
* @see https://docs.neus.network/mcp/setup
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { dirname, join } from 'node:path';
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
|
|
15
|
+
/** @returns {Record<string, unknown>} Parsed MCP registry / installer manifest (`server.json`). */
|
|
16
|
+
export function loadServerManifest() {
|
|
17
|
+
const raw = readFileSync(join(__dirname, 'server.json'), 'utf8');
|
|
18
|
+
return JSON.parse(raw);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const serverManifest = loadServerManifest();
|
package/package.json
CHANGED
|
@@ -1,47 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neus/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"
|
|
5
|
-
"description": "NEUS MCP over HTTP: verifiers, proof checks, guided verify, reads, agent identity/delegation.",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Public discovery metadata for the hosted NEUS MCP (streamable HTTP). Runtime is not shipped in this package.",
|
|
6
5
|
"type": "module",
|
|
7
|
-
"main": "
|
|
8
|
-
"
|
|
9
|
-
"
|
|
6
|
+
"main": "./index.mjs",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.mjs"
|
|
10
9
|
},
|
|
11
|
-
"scripts": {
|
|
12
|
-
"start": "node server.js",
|
|
13
|
-
"dev": "node --watch server.js",
|
|
14
|
-
"test:e2e:local": "node e2e-mcp-local.test.js",
|
|
15
|
-
"test:e2e:live": "node e2e-mcp-live.test.js",
|
|
16
|
-
"test:e2e:agent": "node e2e-mcp-agent-local.test.js",
|
|
17
|
-
"test:agent-context": "node test-agent-context.js",
|
|
18
|
-
"test:public-contract": "node test-public-mcp-contract.js"
|
|
19
|
-
},
|
|
20
|
-
"engines": {
|
|
21
|
-
"node": ">=22.13.0"
|
|
22
|
-
},
|
|
23
|
-
"dependencies": {
|
|
24
|
-
"@azure/monitor-opentelemetry": "^1.11.0",
|
|
25
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
26
|
-
"@opentelemetry/api": "^1.9.0",
|
|
27
|
-
"@opentelemetry/resources": "^2.7.1",
|
|
28
|
-
"viem": "^2.47.6",
|
|
29
|
-
"zod": "^4.3.6"
|
|
30
|
-
},
|
|
31
|
-
"keywords": [
|
|
32
|
-
"neus",
|
|
33
|
-
"mcp",
|
|
34
|
-
"model-context-protocol",
|
|
35
|
-
"verification",
|
|
36
|
-
"proofs",
|
|
37
|
-
"agents",
|
|
38
|
-
"gate-check",
|
|
39
|
-
"ai-agents",
|
|
40
|
-
"identity",
|
|
41
|
-
"web3",
|
|
42
|
-
"wallet-verification",
|
|
43
|
-
"sybil-resistance"
|
|
44
|
-
],
|
|
45
10
|
"license": "Apache-2.0",
|
|
46
11
|
"author": "NEUS Network <info@neus.network>",
|
|
47
12
|
"homepage": "https://neus.network",
|
|
@@ -50,18 +15,24 @@
|
|
|
50
15
|
},
|
|
51
16
|
"repository": {
|
|
52
17
|
"type": "git",
|
|
53
|
-
"url": "git+https://github.com/neus/
|
|
18
|
+
"url": "git+https://github.com/neus/network.git",
|
|
54
19
|
"directory": "mcp"
|
|
55
20
|
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"neus",
|
|
23
|
+
"mcp",
|
|
24
|
+
"model-context-protocol",
|
|
25
|
+
"verification",
|
|
26
|
+
"trust-receipts",
|
|
27
|
+
"proofs",
|
|
28
|
+
"agents"
|
|
29
|
+
],
|
|
56
30
|
"files": [
|
|
57
|
-
"
|
|
31
|
+
"index.mjs",
|
|
58
32
|
"README.md",
|
|
59
|
-
"server.json"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
"test-agent-context.js",
|
|
65
|
-
"test-public-mcp-contract.js"
|
|
66
|
-
]
|
|
33
|
+
"server.json"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20.0.0"
|
|
37
|
+
}
|
|
67
38
|
}
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.neus/neus-mcp",
|
|
4
4
|
"title": "NEUS MCP",
|
|
5
5
|
"description": "NEUS hosted MCP: verifiers, proofs, verify flows, agents. Always call neus_context first.",
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.1",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/neus/network",
|
|
9
9
|
"source": "github"
|
package/CHANGELOG.md
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to `@neus/mcp-server` are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
|
-
|
|
5
|
-
## [Unreleased]
|
|
6
|
-
|
|
7
|
-
- **SSOT:** MCP process env is built in `../src/config/mcpConfig.js` (`buildMcpConfig`); Zeus index tags live in `../src/utils/zeusProofIndexTags.cjs`. The container image is built with `docker build -f mcp/Dockerfile` from the protocol repo root.
|
|
8
|
-
- This package is **not yet published** to the public npm registry. A manual publish workflow exists; when a release is published, it will be listed below with a version and date.
|
|
9
|
-
|
|
10
|
-
### Current tree (1.0.0)
|
|
11
|
-
|
|
12
|
-
- **Transport:** Streamable HTTP (default); `MCP_TRANSPORT=stdio` for local CLI. Hosted URL: `https://mcp.neus.network/mcp` (see [MCP docs](https://docs.neus.network/mcp/overview)).
|
|
13
|
-
- **Registry:** `server.json` follows the MCP server registry schema. Its **`version`** field is **manifest / implementation semver** for registry consumers — **not** MCP wire **`protocolVersion`** (see **Runtime** below). **`$schema`** date is the registry JSON schema revision, not the wire revision.
|
|
14
|
-
- **Tools:** `neus_context`, `neus_verifiers_catalog`, `neus_proofs_check`, `neus_verify`, `neus_verify_or_guide`, `neus_proofs_get`, `neus_me`, `neus_agent_link`, `neus_agent_create`.
|
|
15
|
-
- **Proof reads:** `neus_proofs_get` supports optional `tags` and `agentWallet` aligned with the HTTP proof-by-wallet contract; consolidated read path (no separate index/content tools).
|
|
16
|
-
- **Context / copy:** Server `instructions` match public doc session order (context → optional Bearer + `neus_me` → work → `neus_proofs_get`); Bearer framed as optional. Tool descriptions verb-first; `neus_context` includes structured `proofCreation` where applicable.
|
|
17
|
-
- **neus_verify:** `data` optional and defaults to `{}` when verifiers have no `requiredFields` (Zod + API aligned).
|
|
18
|
-
- **Runtime:** Node `>=22.13` (see `package.json` `engines`). MCP wire `protocolVersion` / `mcp-protocol-version`: **`2025-11-25`**. Issues: [github.com/neus/network/issues](https://github.com/neus/network/issues). Implementation is maintained in the NEUS API repository; sources are not vendored into the public `neus/network` tree.
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { spawn } from 'node:child_process';
|
|
4
|
-
import { privateKeyToAccount } from 'viem/accounts';
|
|
5
|
-
|
|
6
|
-
const PORT = 3105;
|
|
7
|
-
const BASE_URL = `http://127.0.0.1:${PORT}`;
|
|
8
|
-
const CONTROLLER_WALLET = '0x0000000000000000000000000000000000000001';
|
|
9
|
-
|
|
10
|
-
function wait(ms) {
|
|
11
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async function waitForHealth(url, timeoutMs = 15000) {
|
|
15
|
-
const startedAt = Date.now();
|
|
16
|
-
while (Date.now() - startedAt < timeoutMs) {
|
|
17
|
-
try {
|
|
18
|
-
const res = await fetch(`${url}/health`);
|
|
19
|
-
if (res.ok) return true;
|
|
20
|
-
} catch {
|
|
21
|
-
// Ignore until ready.
|
|
22
|
-
}
|
|
23
|
-
await wait(250);
|
|
24
|
-
}
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function parseSseJson(raw) {
|
|
29
|
-
const trimmed = String(raw || '').trim();
|
|
30
|
-
if (trimmed.startsWith('{')) {
|
|
31
|
-
return JSON.parse(trimmed);
|
|
32
|
-
}
|
|
33
|
-
const dataLine = String(raw || '')
|
|
34
|
-
.split(/\r?\n/)
|
|
35
|
-
.find((line) => line.startsWith('data: '));
|
|
36
|
-
if (!dataLine) {
|
|
37
|
-
throw new Error(`Unexpected MCP response: ${raw}`);
|
|
38
|
-
}
|
|
39
|
-
return JSON.parse(dataLine.slice('data: '.length));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function initMcpSession(baseUrl) {
|
|
43
|
-
const headers = {
|
|
44
|
-
'content-type': 'application/json',
|
|
45
|
-
'mcp-protocol-version': '2025-11-25',
|
|
46
|
-
accept: 'application/json, text/event-stream',
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const response = await fetch(`${baseUrl}/mcp`, {
|
|
50
|
-
method: 'POST',
|
|
51
|
-
headers,
|
|
52
|
-
body: JSON.stringify({
|
|
53
|
-
jsonrpc: '2.0',
|
|
54
|
-
id: 1,
|
|
55
|
-
method: 'initialize',
|
|
56
|
-
params: {
|
|
57
|
-
protocolVersion: '2025-11-25',
|
|
58
|
-
capabilities: {},
|
|
59
|
-
clientInfo: { name: 'agent-smoke', version: '1.0.0' },
|
|
60
|
-
},
|
|
61
|
-
}),
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const raw = await response.text();
|
|
65
|
-
const sessionId = response.headers.get('mcp-session-id');
|
|
66
|
-
if (sessionId) {
|
|
67
|
-
let detail = raw.slice(0, 2000);
|
|
68
|
-
try {
|
|
69
|
-
const j = parseSseJson(raw);
|
|
70
|
-
if (j?.error) detail = JSON.stringify(j.error);
|
|
71
|
-
} catch {
|
|
72
|
-
// keep truncated raw
|
|
73
|
-
}
|
|
74
|
-
throw new Error(`MCP initialize should be stateless and must not return mcp-session-id ${sessionId}: ${detail}`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
parseSseJson(raw);
|
|
78
|
-
return { headers };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function listTools(baseUrl, headers, id) {
|
|
82
|
-
const response = await fetch(`${baseUrl}/mcp`, {
|
|
83
|
-
method: 'POST',
|
|
84
|
-
headers,
|
|
85
|
-
body: JSON.stringify({
|
|
86
|
-
jsonrpc: '2.0',
|
|
87
|
-
id,
|
|
88
|
-
method: 'tools/list',
|
|
89
|
-
params: {},
|
|
90
|
-
}),
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
const raw = await response.text();
|
|
94
|
-
const payload = parseSseJson(raw);
|
|
95
|
-
return payload?.result?.tools || [];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async function callTool(baseUrl, headers, id, name, args) {
|
|
99
|
-
const response = await fetch(`${baseUrl}/mcp`, {
|
|
100
|
-
method: 'POST',
|
|
101
|
-
headers,
|
|
102
|
-
body: JSON.stringify({
|
|
103
|
-
jsonrpc: '2.0',
|
|
104
|
-
id,
|
|
105
|
-
method: 'tools/call',
|
|
106
|
-
params: {
|
|
107
|
-
name,
|
|
108
|
-
arguments: args,
|
|
109
|
-
},
|
|
110
|
-
}),
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
const raw = await response.text();
|
|
114
|
-
const payload = parseSseJson(raw);
|
|
115
|
-
const text = payload?.result?.content?.[0]?.text;
|
|
116
|
-
if (!text) {
|
|
117
|
-
throw new Error(`Missing tool payload for ${name}: ${raw}`);
|
|
118
|
-
}
|
|
119
|
-
return JSON.parse(text);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async function main() {
|
|
123
|
-
console.log('\n=== NEUS MCP Agent Local E2E ===');
|
|
124
|
-
console.log(`Spawning MCP on ${BASE_URL}`);
|
|
125
|
-
|
|
126
|
-
const child = spawn(process.execPath, ['server.js'], {
|
|
127
|
-
cwd: new URL('.', import.meta.url),
|
|
128
|
-
env: {
|
|
129
|
-
...process.env,
|
|
130
|
-
MCP_TRANSPORT: 'http',
|
|
131
|
-
HOST: '127.0.0.1',
|
|
132
|
-
PORT: String(PORT),
|
|
133
|
-
},
|
|
134
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
child.stdout.on('data', () => {});
|
|
138
|
-
child.stderr.on('data', () => {});
|
|
139
|
-
|
|
140
|
-
let headers = null;
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
const ready = await waitForHealth(BASE_URL);
|
|
144
|
-
if (!ready) {
|
|
145
|
-
throw new Error('MCP server did not become healthy in time');
|
|
146
|
-
}
|
|
147
|
-
console.log(' ✓ Health endpoint');
|
|
148
|
-
|
|
149
|
-
({ headers } = await initMcpSession(BASE_URL));
|
|
150
|
-
console.log(' ✓ MCP stateless initialize');
|
|
151
|
-
|
|
152
|
-
const tools = await listTools(BASE_URL, headers, 99);
|
|
153
|
-
const verifyTool = tools.find((t) => t.name === 'neus_verify');
|
|
154
|
-
const props = Object.keys(verifyTool?.inputSchema?.properties || {});
|
|
155
|
-
if (!props.includes('walletAddress') || !props.includes('verifierIds') || !props.includes('data')) {
|
|
156
|
-
throw new Error(`tools/list must expose neus_verify args; got properties: ${props.join(',')}`);
|
|
157
|
-
}
|
|
158
|
-
console.log(' ✓ tools/list exposes neus_verify parameter schema');
|
|
159
|
-
|
|
160
|
-
const proofsCheckTool = tools.find((t) => t.name === 'neus_proofs_check');
|
|
161
|
-
const proofsCheckProps = Object.keys(proofsCheckTool?.inputSchema?.properties || {});
|
|
162
|
-
if (proofsCheckProps.includes('sponsorGrant')) {
|
|
163
|
-
throw new Error(`tools/list must not expose legacy sponsorGrant; got properties: ${proofsCheckProps.join(',')}`);
|
|
164
|
-
}
|
|
165
|
-
console.log(' legacy sponsorGrant input hidden from tools/list');
|
|
166
|
-
|
|
167
|
-
const createResult = await callTool(BASE_URL, headers, 2, 'neus_agent_create', {
|
|
168
|
-
agentId: 'smoke-agent',
|
|
169
|
-
agentWallet: 'generate',
|
|
170
|
-
controllerWallet: CONTROLLER_WALLET,
|
|
171
|
-
});
|
|
172
|
-
if (createResult?.status !== 'signatures_required') {
|
|
173
|
-
throw new Error(`Unexpected create status: ${JSON.stringify(createResult)}`);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const generatedWallet = createResult?.agent?.generatedWallet;
|
|
177
|
-
if (!generatedWallet?.privateKey || !generatedWallet?.address) {
|
|
178
|
-
throw new Error(`Missing generated wallet payload: ${JSON.stringify(createResult)}`);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const derivedAddress = privateKeyToAccount(generatedWallet.privateKey).address.toLowerCase();
|
|
182
|
-
if (derivedAddress !== String(generatedWallet.address).toLowerCase()) {
|
|
183
|
-
throw new Error(`Generated wallet mismatch: ${generatedWallet.address} !== ${derivedAddress}`);
|
|
184
|
-
}
|
|
185
|
-
console.log(' ✓ Generated wallet key/address pair matches');
|
|
186
|
-
|
|
187
|
-
if (!String(createResult.hostedVerifyUrl || '').includes('agentWallet=')) {
|
|
188
|
-
throw new Error(`Missing agentWallet in hostedVerifyUrl: ${createResult.hostedVerifyUrl}`);
|
|
189
|
-
}
|
|
190
|
-
if (!String(createResult.hostedVerifyUrl || '').includes('controllerWallet=')) {
|
|
191
|
-
throw new Error(`Missing controllerWallet in hostedVerifyUrl: ${createResult.hostedVerifyUrl}`);
|
|
192
|
-
}
|
|
193
|
-
console.log(' ✓ Agent create hostedVerifyUrl includes agent + controller context');
|
|
194
|
-
|
|
195
|
-
const linkResult = await callTool(BASE_URL, headers, 3, 'neus_agent_link', {
|
|
196
|
-
agentWallet: createResult.agent.agentWallet,
|
|
197
|
-
principal: CONTROLLER_WALLET,
|
|
198
|
-
});
|
|
199
|
-
if (linkResult?.status !== 'link_required') {
|
|
200
|
-
throw new Error(`Unexpected link status: ${JSON.stringify(linkResult)}`);
|
|
201
|
-
}
|
|
202
|
-
if (linkResult?.nextSteps?.action !== 'open_hosted_verify') {
|
|
203
|
-
throw new Error(`Unexpected next step action: ${JSON.stringify(linkResult)}`);
|
|
204
|
-
}
|
|
205
|
-
if (linkResult?.principal?.toLowerCase() !== CONTROLLER_WALLET.toLowerCase()) {
|
|
206
|
-
throw new Error(`Principal was not preserved: ${JSON.stringify(linkResult)}`);
|
|
207
|
-
}
|
|
208
|
-
console.log(' ✓ Agent link returns browser-first guidance with principal context');
|
|
209
|
-
|
|
210
|
-
console.log('\n--- Summary ---');
|
|
211
|
-
console.log('Passed: 5 Failed: 0');
|
|
212
|
-
} finally {
|
|
213
|
-
child.kill('SIGTERM');
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
main().catch((error) => {
|
|
218
|
-
console.error(error);
|
|
219
|
-
process.exit(1);
|
|
220
|
-
});
|