nexarch 0.1.4 → 0.1.6
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 +2 -0
- package/dist/commands/init-agent.js +305 -0
- package/dist/index.js +5 -0
- package/dist/lib/mcp.js +20 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,8 @@ npx nexarch setup
|
|
|
19
19
|
| `nexarch setup` | Auto-configure detected MCP clients |
|
|
20
20
|
| `nexarch mcp-config [client]` | Print config block for manual setup (`claude-desktop`, `cursor`, `windsurf`) |
|
|
21
21
|
| `nexarch mcp-proxy` | stdio→HTTP bridge used internally by MCP clients |
|
|
22
|
+
| `nexarch init-agent [--json] [--strict] [--agent-id <id>]` | Run onboarding handshake and mandatory `agent` entity registration in graph |
|
|
23
|
+
| `nexarch init-agent --bind-to-external-key <key> [--bind-relationship-type <code>]` | Optionally bind the agent node to an existing graph external key |
|
|
22
24
|
|
|
23
25
|
## Requirements
|
|
24
26
|
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { hostname, userInfo } from "os";
|
|
2
|
+
import { requireCredentials } from "../lib/credentials.js";
|
|
3
|
+
import { callMcpTool, mcpInitialize, mcpListTools } from "../lib/mcp.js";
|
|
4
|
+
const CLI_VERSION = "0.1.6";
|
|
5
|
+
function parseFlag(args, flag) {
|
|
6
|
+
return args.includes(flag);
|
|
7
|
+
}
|
|
8
|
+
function parseOptionValue(args, option) {
|
|
9
|
+
const idx = args.indexOf(option);
|
|
10
|
+
if (idx === -1)
|
|
11
|
+
return null;
|
|
12
|
+
const value = args[idx + 1];
|
|
13
|
+
if (!value || value.startsWith("--"))
|
|
14
|
+
return null;
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
function parseToolText(result) {
|
|
18
|
+
const text = result.content?.[0]?.text ?? "{}";
|
|
19
|
+
return JSON.parse(text);
|
|
20
|
+
}
|
|
21
|
+
function getDefaultAgentId() {
|
|
22
|
+
const osUser = process.env.USERNAME || process.env.USER || userInfo().username || "unknown";
|
|
23
|
+
const host = hostname() || "unknown-host";
|
|
24
|
+
return `nexarch-cli:${osUser}@${host}`;
|
|
25
|
+
}
|
|
26
|
+
async function emitInitObservation(params) {
|
|
27
|
+
if (!params.policyBundleHash)
|
|
28
|
+
return;
|
|
29
|
+
const nowIso = new Date().toISOString();
|
|
30
|
+
try {
|
|
31
|
+
await callMcpTool("nexarch_emit_observations", {
|
|
32
|
+
observations: [
|
|
33
|
+
{
|
|
34
|
+
kind: "entity",
|
|
35
|
+
externalKey: `agent:${params.agentId}`,
|
|
36
|
+
message: params.message,
|
|
37
|
+
confidence: params.confidence ?? 0.6,
|
|
38
|
+
evidence: { source: "nexarch init-agent", timestamp: nowIso },
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
agentContext: {
|
|
42
|
+
agentId: params.agentId,
|
|
43
|
+
agentRunId: `init-agent-${Date.now()}`,
|
|
44
|
+
repoRef: "nexarch-cli/init-agent",
|
|
45
|
+
observedAt: nowIso,
|
|
46
|
+
source: "nexarch-cli",
|
|
47
|
+
model: "n/a",
|
|
48
|
+
provider: "n/a",
|
|
49
|
+
},
|
|
50
|
+
policyContext: {
|
|
51
|
+
policyBundleHash: params.policyBundleHash,
|
|
52
|
+
alignmentSummary: {
|
|
53
|
+
score: 1,
|
|
54
|
+
violations: [],
|
|
55
|
+
waivers: [],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// non-fatal telemetry path
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export async function initAgent(args) {
|
|
65
|
+
const asJson = parseFlag(args, "--json");
|
|
66
|
+
const strict = parseFlag(args, "--strict");
|
|
67
|
+
const explicitAgentId = parseOptionValue(args, "--agent-id");
|
|
68
|
+
const bindToExternalKey = parseOptionValue(args, "--bind-to-external-key");
|
|
69
|
+
const bindRelationshipType = parseOptionValue(args, "--bind-relationship-type") ?? "depends_on";
|
|
70
|
+
const agentId = explicitAgentId ?? getDefaultAgentId();
|
|
71
|
+
const checks = [];
|
|
72
|
+
const creds = requireCredentials();
|
|
73
|
+
const init = await mcpInitialize();
|
|
74
|
+
checks.push({
|
|
75
|
+
name: "mcp.initialize",
|
|
76
|
+
ok: true,
|
|
77
|
+
detail: `${init.serverInfo?.name ?? "unknown"}@${init.serverInfo?.version ?? "unknown"}`,
|
|
78
|
+
});
|
|
79
|
+
const tools = await mcpListTools();
|
|
80
|
+
const toolNames = new Set(tools.map((t) => t.name));
|
|
81
|
+
const required = [
|
|
82
|
+
"nexarch_get_applied_policies",
|
|
83
|
+
"nexarch_get_ingest_contract",
|
|
84
|
+
"nexarch_upsert_entities",
|
|
85
|
+
"nexarch_upsert_relationships",
|
|
86
|
+
"nexarch_emit_observations",
|
|
87
|
+
];
|
|
88
|
+
const missing = required.filter((name) => !toolNames.has(name));
|
|
89
|
+
checks.push({
|
|
90
|
+
name: "mcp.tools.list",
|
|
91
|
+
ok: missing.length === 0,
|
|
92
|
+
detail: missing.length ? `missing: ${missing.join(", ")}` : `required tools present (${required.length})`,
|
|
93
|
+
});
|
|
94
|
+
const policiesRaw = await callMcpTool("nexarch_get_applied_policies", {});
|
|
95
|
+
const policies = parseToolText(policiesRaw);
|
|
96
|
+
checks.push({
|
|
97
|
+
name: "governance.bootstrap",
|
|
98
|
+
ok: Boolean(policies.policyBundleHash),
|
|
99
|
+
detail: policies.policyBundleHash
|
|
100
|
+
? `policyBundleHash=${policies.policyBundleHash.slice(0, 12)}… policyCount=${policies.policyCount ?? 0}`
|
|
101
|
+
: "missing policyBundleHash",
|
|
102
|
+
});
|
|
103
|
+
const contractRaw = await callMcpTool("nexarch_get_ingest_contract", {});
|
|
104
|
+
const contract = parseToolText(contractRaw);
|
|
105
|
+
checks.push({
|
|
106
|
+
name: "ingest.contract",
|
|
107
|
+
ok: Boolean(contract.contractVersion),
|
|
108
|
+
detail: `version=${contract.contractVersion ?? "unknown"}; entities=${contract.ontology?.entityCodes?.length ?? 0}; relationships=${contract.ontology?.relationshipCodes?.length ?? 0}`,
|
|
109
|
+
});
|
|
110
|
+
const hasAgentType = (contract.ontology?.entityCodes ?? []).some((code) => code.toLowerCase() === "agent");
|
|
111
|
+
checks.push({
|
|
112
|
+
name: "ontology.agent-type",
|
|
113
|
+
ok: hasAgentType,
|
|
114
|
+
detail: hasAgentType
|
|
115
|
+
? "entity type 'agent' is enabled"
|
|
116
|
+
: "entity type 'agent' is not enabled for this workspace ontology",
|
|
117
|
+
});
|
|
118
|
+
const preflightPassed = checks.every((c) => c.ok);
|
|
119
|
+
let registration = {
|
|
120
|
+
ok: false,
|
|
121
|
+
detail: "registration not attempted",
|
|
122
|
+
};
|
|
123
|
+
let binding = {
|
|
124
|
+
attempted: false,
|
|
125
|
+
ok: true,
|
|
126
|
+
detail: "not requested",
|
|
127
|
+
};
|
|
128
|
+
if (preflightPassed && policies.policyBundleHash) {
|
|
129
|
+
const nowIso = new Date().toISOString();
|
|
130
|
+
const upsertRaw = await callMcpTool("nexarch_upsert_entities", {
|
|
131
|
+
entities: [
|
|
132
|
+
{
|
|
133
|
+
externalKey: `agent:${agentId}`,
|
|
134
|
+
entityTypeCode: "agent",
|
|
135
|
+
name: `Agent ${agentId}`,
|
|
136
|
+
description: "Agent identity registered from nexarch init-agent handshake",
|
|
137
|
+
confidence: 1,
|
|
138
|
+
attributes: {
|
|
139
|
+
kind: "ai_agent",
|
|
140
|
+
client: "nexarch-cli",
|
|
141
|
+
version: CLI_VERSION,
|
|
142
|
+
companyId: creds.companyId,
|
|
143
|
+
handshake: {
|
|
144
|
+
completedAt: nowIso,
|
|
145
|
+
source: "init-agent",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
agentContext: {
|
|
151
|
+
agentId,
|
|
152
|
+
agentRunId: `init-agent-${Date.now()}`,
|
|
153
|
+
repoRef: "nexarch-cli/init-agent",
|
|
154
|
+
observedAt: nowIso,
|
|
155
|
+
source: "nexarch-cli",
|
|
156
|
+
model: "n/a",
|
|
157
|
+
provider: "n/a",
|
|
158
|
+
},
|
|
159
|
+
policyContext: {
|
|
160
|
+
policyBundleHash: policies.policyBundleHash,
|
|
161
|
+
alignmentSummary: {
|
|
162
|
+
score: 1,
|
|
163
|
+
violations: [],
|
|
164
|
+
waivers: [],
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
dryRun: false,
|
|
168
|
+
});
|
|
169
|
+
const upsert = parseToolText(upsertRaw);
|
|
170
|
+
const firstResult = upsert.results?.[0];
|
|
171
|
+
const failed = Number(upsert.summary?.failed ?? 0) > 0;
|
|
172
|
+
registration = {
|
|
173
|
+
ok: !failed,
|
|
174
|
+
detail: failed
|
|
175
|
+
? `failed to register agent entry (${upsert.errors?.[0]?.error ?? "unknown"})`
|
|
176
|
+
: `agent entry ${firstResult?.action ?? "upserted"}`,
|
|
177
|
+
graphEntityId: firstResult?.graphEntityId ?? null,
|
|
178
|
+
action: firstResult?.action ?? null,
|
|
179
|
+
errors: upsert.errors,
|
|
180
|
+
};
|
|
181
|
+
if (!registration.ok) {
|
|
182
|
+
await emitInitObservation({
|
|
183
|
+
agentId,
|
|
184
|
+
policyBundleHash: policies.policyBundleHash,
|
|
185
|
+
message: `init-agent registration failed: ${registration.detail}`,
|
|
186
|
+
confidence: 1,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (registration.ok && bindToExternalKey) {
|
|
190
|
+
binding.attempted = true;
|
|
191
|
+
const relRaw = await callMcpTool("nexarch_upsert_relationships", {
|
|
192
|
+
relationships: [
|
|
193
|
+
{
|
|
194
|
+
relationshipTypeCode: bindRelationshipType,
|
|
195
|
+
fromEntityExternalKey: `agent:${agentId}`,
|
|
196
|
+
toEntityExternalKey: bindToExternalKey,
|
|
197
|
+
confidence: 1,
|
|
198
|
+
attributes: {
|
|
199
|
+
source: "nexarch-cli-init-agent",
|
|
200
|
+
createdAt: nowIso,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
agentContext: {
|
|
205
|
+
agentId,
|
|
206
|
+
agentRunId: `init-agent-bind-${Date.now()}`,
|
|
207
|
+
repoRef: "nexarch-cli/init-agent",
|
|
208
|
+
observedAt: nowIso,
|
|
209
|
+
source: "nexarch-cli",
|
|
210
|
+
model: "n/a",
|
|
211
|
+
provider: "n/a",
|
|
212
|
+
},
|
|
213
|
+
policyContext: {
|
|
214
|
+
policyBundleHash: policies.policyBundleHash,
|
|
215
|
+
alignmentSummary: {
|
|
216
|
+
score: 1,
|
|
217
|
+
violations: [],
|
|
218
|
+
waivers: [],
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
dryRun: false,
|
|
222
|
+
});
|
|
223
|
+
const rel = parseToolText(relRaw);
|
|
224
|
+
const relResult = rel.results?.[0];
|
|
225
|
+
const relFailed = Number(rel.summary?.failed ?? 0) > 0;
|
|
226
|
+
binding = {
|
|
227
|
+
attempted: true,
|
|
228
|
+
ok: !relFailed,
|
|
229
|
+
detail: relFailed
|
|
230
|
+
? `failed to bind agent (${rel.errors?.[0]?.error ?? "unknown"})`
|
|
231
|
+
: `agent bound via ${bindRelationshipType}`,
|
|
232
|
+
graphRelationshipId: relResult?.graphRelationshipId ?? null,
|
|
233
|
+
action: relResult?.action ?? null,
|
|
234
|
+
errors: rel.errors,
|
|
235
|
+
};
|
|
236
|
+
if (!binding.ok) {
|
|
237
|
+
await emitInitObservation({
|
|
238
|
+
agentId,
|
|
239
|
+
policyBundleHash: policies.policyBundleHash,
|
|
240
|
+
message: `init-agent binding failed: ${binding.detail}`,
|
|
241
|
+
confidence: 1,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
checks.push({
|
|
247
|
+
name: "agent.registration",
|
|
248
|
+
ok: registration.ok,
|
|
249
|
+
detail: registration.detail,
|
|
250
|
+
});
|
|
251
|
+
if (binding.attempted) {
|
|
252
|
+
checks.push({
|
|
253
|
+
name: "agent.binding",
|
|
254
|
+
ok: binding.ok,
|
|
255
|
+
detail: binding.detail,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
const allPassed = checks.every((c) => c.ok);
|
|
259
|
+
if (asJson) {
|
|
260
|
+
const output = {
|
|
261
|
+
ok: strict ? allPassed : true,
|
|
262
|
+
passed: allPassed,
|
|
263
|
+
strict,
|
|
264
|
+
checks,
|
|
265
|
+
summary: {
|
|
266
|
+
total: checks.length,
|
|
267
|
+
ok: checks.filter((c) => c.ok).length,
|
|
268
|
+
failed: checks.filter((c) => !c.ok).length,
|
|
269
|
+
},
|
|
270
|
+
policyBundleHash: policies.policyBundleHash ?? null,
|
|
271
|
+
contractVersion: contract.contractVersion ?? null,
|
|
272
|
+
agentId,
|
|
273
|
+
registration,
|
|
274
|
+
binding,
|
|
275
|
+
companyId: creds.companyId,
|
|
276
|
+
};
|
|
277
|
+
process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
|
|
278
|
+
if (strict && !allPassed)
|
|
279
|
+
process.exitCode = 1;
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
console.log("Running agent onboarding handshake…\n");
|
|
283
|
+
for (const check of checks) {
|
|
284
|
+
console.log(`${check.ok ? "✓" : "✗"} ${check.name}`);
|
|
285
|
+
if (check.detail)
|
|
286
|
+
console.log(` ${check.detail}`);
|
|
287
|
+
}
|
|
288
|
+
if (registration.graphEntityId) {
|
|
289
|
+
console.log(`\nRegistered graph entity: ${registration.graphEntityId}`);
|
|
290
|
+
}
|
|
291
|
+
if (binding.attempted && binding.graphRelationshipId) {
|
|
292
|
+
console.log(`Bound relationship id: ${binding.graphRelationshipId}`);
|
|
293
|
+
}
|
|
294
|
+
console.log("");
|
|
295
|
+
if (allPassed) {
|
|
296
|
+
console.log("✅ Agent handshake + registration completed.");
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
console.log("⚠ Handshake/registration completed with issues.");
|
|
300
|
+
if (strict) {
|
|
301
|
+
console.log("Strict mode enabled, exiting non-zero.");
|
|
302
|
+
process.exitCode = 1;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { status } from "./commands/status.js";
|
|
|
5
5
|
import { setup } from "./commands/setup.js";
|
|
6
6
|
import { mcpConfig } from "./commands/mcp-config.js";
|
|
7
7
|
import { mcpProxy } from "./commands/mcp-proxy.js";
|
|
8
|
+
import { initAgent } from "./commands/init-agent.js";
|
|
8
9
|
const [, , command, ...args] = process.argv;
|
|
9
10
|
const commands = {
|
|
10
11
|
login,
|
|
@@ -13,6 +14,7 @@ const commands = {
|
|
|
13
14
|
setup,
|
|
14
15
|
"mcp-config": mcpConfig,
|
|
15
16
|
"mcp-proxy": mcpProxy,
|
|
17
|
+
"init-agent": initAgent,
|
|
16
18
|
};
|
|
17
19
|
async function main() {
|
|
18
20
|
const handler = commands[command ?? ""];
|
|
@@ -27,6 +29,9 @@ Usage:
|
|
|
27
29
|
nexarch setup Auto-configure your MCP client (Claude Desktop, Cursor, etc.)
|
|
28
30
|
nexarch mcp-config Print MCP server config block for manual setup
|
|
29
31
|
nexarch mcp-proxy Run as stdio MCP proxy (used by MCP clients)
|
|
32
|
+
nexarch init-agent Run handshake + mandatory agent registration in graph
|
|
33
|
+
Options: --agent-id <id> --bind-to-external-key <key>
|
|
34
|
+
--bind-relationship-type <code> --json --strict
|
|
30
35
|
`);
|
|
31
36
|
process.exit(command ? 1 : 0);
|
|
32
37
|
}
|
package/dist/lib/mcp.js
CHANGED
|
@@ -2,13 +2,13 @@ import https from "https";
|
|
|
2
2
|
import { requireCredentials } from "./credentials.js";
|
|
3
3
|
const MCP_GATEWAY_URL = "https://mcp.nexarch.ai";
|
|
4
4
|
const REQUEST_TIMEOUT_MS = 30 * 1000;
|
|
5
|
-
|
|
5
|
+
async function callMcpRpc(method, params = {}) {
|
|
6
6
|
const creds = requireCredentials();
|
|
7
7
|
const body = JSON.stringify({
|
|
8
8
|
jsonrpc: "2.0",
|
|
9
9
|
id: 1,
|
|
10
|
-
method
|
|
11
|
-
params
|
|
10
|
+
method,
|
|
11
|
+
params,
|
|
12
12
|
});
|
|
13
13
|
return new Promise((resolve, reject) => {
|
|
14
14
|
const url = new URL("/mcp", MCP_GATEWAY_URL);
|
|
@@ -19,7 +19,7 @@ export async function callMcpTool(toolName, toolArgs = {}) {
|
|
|
19
19
|
method: "POST",
|
|
20
20
|
headers: {
|
|
21
21
|
"Content-Type": "application/json",
|
|
22
|
-
|
|
22
|
+
Authorization: `Bearer ${creds.token}`,
|
|
23
23
|
"x-company-id": creds.companyId,
|
|
24
24
|
"Content-Length": Buffer.byteLength(body),
|
|
25
25
|
},
|
|
@@ -34,7 +34,7 @@ export async function callMcpTool(toolName, toolArgs = {}) {
|
|
|
34
34
|
if (json.error) {
|
|
35
35
|
reject(new Error(json.error.message));
|
|
36
36
|
}
|
|
37
|
-
else if (json.result) {
|
|
37
|
+
else if (json.result !== undefined) {
|
|
38
38
|
resolve(json.result);
|
|
39
39
|
}
|
|
40
40
|
else {
|
|
@@ -54,6 +54,20 @@ export async function callMcpTool(toolName, toolArgs = {}) {
|
|
|
54
54
|
req.end();
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
|
+
export async function callMcpTool(toolName, toolArgs = {}) {
|
|
58
|
+
return callMcpRpc("tools/call", { name: toolName, arguments: toolArgs });
|
|
59
|
+
}
|
|
60
|
+
export async function mcpInitialize() {
|
|
61
|
+
return callMcpRpc("initialize", {
|
|
62
|
+
protocolVersion: "2024-11-05",
|
|
63
|
+
capabilities: {},
|
|
64
|
+
clientInfo: { name: "nexarch-cli", version: "0.1.6" },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export async function mcpListTools() {
|
|
68
|
+
const result = await callMcpRpc("tools/list", {});
|
|
69
|
+
return result.tools ?? [];
|
|
70
|
+
}
|
|
57
71
|
export async function forwardToGateway(token, companyId, body) {
|
|
58
72
|
return new Promise((resolve, reject) => {
|
|
59
73
|
const url = new URL("/mcp", MCP_GATEWAY_URL);
|
|
@@ -64,7 +78,7 @@ export async function forwardToGateway(token, companyId, body) {
|
|
|
64
78
|
method: "POST",
|
|
65
79
|
headers: {
|
|
66
80
|
"Content-Type": "application/json",
|
|
67
|
-
|
|
81
|
+
Authorization: `Bearer ${token}`,
|
|
68
82
|
"x-company-id": companyId,
|
|
69
83
|
"Content-Length": Buffer.byteLength(body),
|
|
70
84
|
},
|