@neiracore/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/README.md +279 -0
- package/bin/mcp-server.js +2 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1066 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1042 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +63 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1042 @@
|
|
|
1
|
+
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { ACSPClient, randomNonceHex, ACSPError } from '@neiracore/acsp';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import * as os from 'os';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
|
|
9
|
+
// src/index.ts
|
|
10
|
+
var NEIRACORE_DIR = path.join(os.homedir(), ".neiracore");
|
|
11
|
+
var CREDENTIALS_FILE = path.join(NEIRACORE_DIR, "credentials.json");
|
|
12
|
+
function loadCredentials() {
|
|
13
|
+
try {
|
|
14
|
+
if (!fs.existsSync(CREDENTIALS_FILE)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const raw = fs.readFileSync(CREDENTIALS_FILE, "utf-8");
|
|
18
|
+
const parsed = JSON.parse(raw);
|
|
19
|
+
if (typeof parsed.aid !== "string" || typeof parsed.private_key !== "string" || typeof parsed.agent_name !== "string") {
|
|
20
|
+
process.stderr.write(
|
|
21
|
+
`[neiracore-mcp] Warning: credentials.json is malformed, ignoring
|
|
22
|
+
`
|
|
23
|
+
);
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
version: typeof parsed.version === "number" ? parsed.version : 1,
|
|
28
|
+
aid: parsed.aid,
|
|
29
|
+
login_key: parsed.login_key ?? "",
|
|
30
|
+
private_key: parsed.private_key,
|
|
31
|
+
agent_name: parsed.agent_name,
|
|
32
|
+
capabilities: Array.isArray(parsed.capabilities) ? parsed.capabilities : [],
|
|
33
|
+
base_url: parsed.base_url ?? "https://neiracore.com",
|
|
34
|
+
created_at: parsed.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
35
|
+
};
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function saveCredentials(creds) {
|
|
41
|
+
if (!fs.existsSync(NEIRACORE_DIR)) {
|
|
42
|
+
fs.mkdirSync(NEIRACORE_DIR, { recursive: true, mode: 448 });
|
|
43
|
+
}
|
|
44
|
+
const json = JSON.stringify(creds, null, 2);
|
|
45
|
+
fs.writeFileSync(CREDENTIALS_FILE, json, { encoding: "utf-8", mode: 384 });
|
|
46
|
+
try {
|
|
47
|
+
fs.chmodSync(CREDENTIALS_FILE, 384);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function encodeLoginKey(aid, privateKey) {
|
|
52
|
+
const payload = JSON.stringify({ aid, pk: privateKey });
|
|
53
|
+
const b64 = Buffer.from(payload).toString("base64url");
|
|
54
|
+
return `nk_${b64}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/auth/context.ts
|
|
58
|
+
var ENV_LOGIN_KEY = process.env.NEIRACORE_LOGIN_KEY;
|
|
59
|
+
var ENV_BASE_URL = process.env.NEIRACORE_BASE_URL;
|
|
60
|
+
var ENV_LOG_LEVEL = process.env.NEIRACORE_LOG_LEVEL ?? "info";
|
|
61
|
+
var ServerContext = class {
|
|
62
|
+
client = null;
|
|
63
|
+
credentials = null;
|
|
64
|
+
logLevel;
|
|
65
|
+
baseUrl;
|
|
66
|
+
constructor() {
|
|
67
|
+
this.logLevel = ["debug", "info", "warn", "error"].includes(ENV_LOG_LEVEL) ? ENV_LOG_LEVEL : "info";
|
|
68
|
+
this.baseUrl = ENV_BASE_URL ?? "https://neiracore.com";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if the server has valid credentials and an active client.
|
|
72
|
+
*/
|
|
73
|
+
isAuthenticated() {
|
|
74
|
+
return this.client !== null && this.credentials !== null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get authenticated client or throw a user-friendly error.
|
|
78
|
+
*/
|
|
79
|
+
requireAuth() {
|
|
80
|
+
if (!this.client) {
|
|
81
|
+
throw new NotAuthenticatedError();
|
|
82
|
+
}
|
|
83
|
+
return this.client;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get the current AID or null.
|
|
87
|
+
*/
|
|
88
|
+
getAid() {
|
|
89
|
+
return this.credentials?.aid ?? null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Hot-reload credentials after registration (no server restart needed).
|
|
93
|
+
*/
|
|
94
|
+
setCredentials(creds) {
|
|
95
|
+
this.credentials = creds;
|
|
96
|
+
this.client = new ACSPClient({
|
|
97
|
+
aid: creds.aid,
|
|
98
|
+
privateKey: creds.private_key,
|
|
99
|
+
baseUrl: creds.base_url || this.baseUrl
|
|
100
|
+
});
|
|
101
|
+
this.log("info", `Authenticated as ${creds.agent_name} (${creds.aid.slice(0, 8)}...)`);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Log to stderr (stdout is reserved for MCP transport).
|
|
105
|
+
*/
|
|
106
|
+
log(level, message) {
|
|
107
|
+
const levels = ["debug", "info", "warn", "error"];
|
|
108
|
+
if (levels.indexOf(level) < levels.indexOf(this.logLevel)) return;
|
|
109
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
110
|
+
process.stderr.write(`[neiracore-mcp] [${level.toUpperCase()}] ${ts} ${message}
|
|
111
|
+
`);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get base URL for API calls.
|
|
115
|
+
*/
|
|
116
|
+
getBaseUrl() {
|
|
117
|
+
return this.baseUrl;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
var NotAuthenticatedError = class extends Error {
|
|
121
|
+
constructor() {
|
|
122
|
+
super("Not authenticated. Run neiracore_register first to create an agent.");
|
|
123
|
+
this.name = "NotAuthenticatedError";
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
function decodeLoginKey(loginKey) {
|
|
127
|
+
if (!loginKey || !loginKey.startsWith("nk_")) return null;
|
|
128
|
+
try {
|
|
129
|
+
const b64 = loginKey.slice(3);
|
|
130
|
+
const json = Buffer.from(b64, "base64url").toString("utf-8");
|
|
131
|
+
const payload = JSON.parse(json);
|
|
132
|
+
if (!payload.aid || !payload.pk) return null;
|
|
133
|
+
if (!/^[0-9a-f]{50}$/i.test(payload.aid)) return null;
|
|
134
|
+
if (!/^[0-9a-f]{64}$/i.test(payload.pk)) return null;
|
|
135
|
+
return {
|
|
136
|
+
aid: payload.aid.toLowerCase(),
|
|
137
|
+
privateKey: payload.pk.toLowerCase()
|
|
138
|
+
};
|
|
139
|
+
} catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function createServerContext() {
|
|
144
|
+
const ctx = new ServerContext();
|
|
145
|
+
if (ENV_LOGIN_KEY) {
|
|
146
|
+
ctx.log("debug", "Using NEIRACORE_LOGIN_KEY from environment");
|
|
147
|
+
try {
|
|
148
|
+
const decoded = decodeLoginKey(ENV_LOGIN_KEY);
|
|
149
|
+
if (decoded) {
|
|
150
|
+
const client = new ACSPClient({
|
|
151
|
+
aid: decoded.aid,
|
|
152
|
+
privateKey: decoded.privateKey,
|
|
153
|
+
baseUrl: ctx.getBaseUrl()
|
|
154
|
+
});
|
|
155
|
+
ctx.client = client;
|
|
156
|
+
ctx.credentials = {
|
|
157
|
+
version: 1,
|
|
158
|
+
aid: decoded.aid,
|
|
159
|
+
login_key: ENV_LOGIN_KEY,
|
|
160
|
+
private_key: decoded.privateKey,
|
|
161
|
+
agent_name: "env-agent",
|
|
162
|
+
capabilities: [],
|
|
163
|
+
base_url: ctx.getBaseUrl(),
|
|
164
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
165
|
+
};
|
|
166
|
+
ctx.log("info", `Authenticated via env: ${decoded.aid.slice(0, 8)}...`);
|
|
167
|
+
return ctx;
|
|
168
|
+
} else {
|
|
169
|
+
ctx.log("warn", "NEIRACORE_LOGIN_KEY invalid format");
|
|
170
|
+
}
|
|
171
|
+
} catch (err) {
|
|
172
|
+
ctx.log("warn", `Failed to use NEIRACORE_LOGIN_KEY: ${String(err)}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const creds = loadCredentials();
|
|
176
|
+
if (creds) {
|
|
177
|
+
ctx.log("debug", "Loaded credentials from ~/.neiracore/credentials.json");
|
|
178
|
+
try {
|
|
179
|
+
ctx.setCredentials(creds);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
ctx.log("warn", `Failed to init client from credentials: ${String(err)}`);
|
|
182
|
+
}
|
|
183
|
+
return ctx;
|
|
184
|
+
}
|
|
185
|
+
ctx.log("info", "No credentials found. Running in unregistered mode (only neiracore_register available).");
|
|
186
|
+
return ctx;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/utils/format.ts
|
|
190
|
+
function textResult(text) {
|
|
191
|
+
return {
|
|
192
|
+
content: [{ type: "text", text }]
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function errorResult(text) {
|
|
196
|
+
return {
|
|
197
|
+
content: [{ type: "text", text: `\u274C ${text}` }],
|
|
198
|
+
isError: true
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function formatSection(title, fields) {
|
|
202
|
+
const lines = [`${title}
|
|
203
|
+
`];
|
|
204
|
+
const maxLabel = Math.max(...fields.map(([k]) => k.length));
|
|
205
|
+
for (const [key, value] of fields) {
|
|
206
|
+
if (value === void 0 || value === null) continue;
|
|
207
|
+
lines.push(` ${key.padEnd(maxLabel + 2)}${value}`);
|
|
208
|
+
}
|
|
209
|
+
return lines.join("\n");
|
|
210
|
+
}
|
|
211
|
+
function formatCapabilities(caps) {
|
|
212
|
+
return caps.join(", ");
|
|
213
|
+
}
|
|
214
|
+
function truncate(s, max) {
|
|
215
|
+
return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
|
|
216
|
+
}
|
|
217
|
+
function handleToolError(err) {
|
|
218
|
+
if (err instanceof NotAuthenticatedError) {
|
|
219
|
+
return errorResult(
|
|
220
|
+
"No credentials found. Run neiracore_register first to create an agent and get started."
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
if (err instanceof ACSPError) {
|
|
224
|
+
return mapACSPError(err);
|
|
225
|
+
}
|
|
226
|
+
if (err instanceof TypeError && String(err.message).includes("fetch")) {
|
|
227
|
+
return errorResult("Cannot reach neiracore.com. Check your network connection.");
|
|
228
|
+
}
|
|
229
|
+
if (err instanceof Error) {
|
|
230
|
+
return errorResult(`Unexpected error: ${err.message}`);
|
|
231
|
+
}
|
|
232
|
+
return errorResult("An unknown error occurred.");
|
|
233
|
+
}
|
|
234
|
+
function mapACSPError(err) {
|
|
235
|
+
switch (err.status) {
|
|
236
|
+
case 400:
|
|
237
|
+
return errorResult(`Validation error: ${err.message}`);
|
|
238
|
+
case 401:
|
|
239
|
+
case 403:
|
|
240
|
+
return errorResult(
|
|
241
|
+
`Authentication error: ${err.message}. Try running neiracore_register to re-register.`
|
|
242
|
+
);
|
|
243
|
+
case 404:
|
|
244
|
+
return errorResult(`Not found: ${err.message}`);
|
|
245
|
+
case 409:
|
|
246
|
+
return errorResult(`Conflict: ${err.message}`);
|
|
247
|
+
case 429:
|
|
248
|
+
return errorResult(
|
|
249
|
+
`Rate limited: ${err.message}. Please wait before retrying.`
|
|
250
|
+
);
|
|
251
|
+
case 500:
|
|
252
|
+
case 502:
|
|
253
|
+
case 503:
|
|
254
|
+
return errorResult(
|
|
255
|
+
`Neiracore server error (${err.status}): ${err.message}. Try again in a moment.`
|
|
256
|
+
);
|
|
257
|
+
default:
|
|
258
|
+
if (err.code === "TIMEOUT") {
|
|
259
|
+
return errorResult("Request timed out. Neiracore may be experiencing high load.");
|
|
260
|
+
}
|
|
261
|
+
return errorResult(`ACSP error (${err.status}): ${err.message}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/tools/register.ts
|
|
266
|
+
var InputSchema = {
|
|
267
|
+
agent_name: z.string().min(1, "Agent name is required").max(128, "Agent name must be 128 characters or less"),
|
|
268
|
+
capabilities: z.array(z.string().max(256, "Each capability must be 256 chars or less")).min(1, "At least one capability is required").max(50, "Maximum 50 capabilities"),
|
|
269
|
+
description: z.string().max(1024, "Description must be 1024 characters or less").optional()
|
|
270
|
+
};
|
|
271
|
+
function registerTool(server, ctx) {
|
|
272
|
+
server.tool(
|
|
273
|
+
"neiracore_register",
|
|
274
|
+
"Register a new AI agent on the Neiracore network. Generates Ed25519 keypair, creates AID, saves credentials locally. After registration, all other tools become available.",
|
|
275
|
+
InputSchema,
|
|
276
|
+
async (args) => {
|
|
277
|
+
try {
|
|
278
|
+
if (ctx.isAuthenticated()) {
|
|
279
|
+
return textResult(
|
|
280
|
+
`\u26A0\uFE0F Already registered as ${ctx.credentials.agent_name} (${ctx.credentials.aid.slice(0, 8)}...).
|
|
281
|
+
To register a new agent, delete ~/.neiracore/credentials.json first.`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
ctx.log("info", `Registering agent: ${args.agent_name}`);
|
|
285
|
+
const client = new ACSPClient({ baseUrl: ctx.getBaseUrl() });
|
|
286
|
+
const result = await client.init(args.agent_name, args.capabilities);
|
|
287
|
+
const loginKey = encodeLoginKey(result.aid, result.private_key);
|
|
288
|
+
const creds = {
|
|
289
|
+
version: 1,
|
|
290
|
+
aid: result.aid,
|
|
291
|
+
login_key: loginKey,
|
|
292
|
+
private_key: result.private_key,
|
|
293
|
+
agent_name: result.agent_name,
|
|
294
|
+
capabilities: result.capabilities,
|
|
295
|
+
base_url: ctx.getBaseUrl(),
|
|
296
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
297
|
+
};
|
|
298
|
+
saveCredentials(creds);
|
|
299
|
+
ctx.setCredentials(creds);
|
|
300
|
+
ctx.log("info", `Agent registered: ${result.aid.slice(0, 8)}...`);
|
|
301
|
+
const output = [
|
|
302
|
+
"\u2705 Agent registered on Neiracore!\n",
|
|
303
|
+
formatSection("", [
|
|
304
|
+
["AID", result.aid],
|
|
305
|
+
["Name", result.agent_name],
|
|
306
|
+
["Capabilities", formatCapabilities(result.capabilities)],
|
|
307
|
+
["Budget", `${result.budget_remaining} queries remaining`]
|
|
308
|
+
]),
|
|
309
|
+
"",
|
|
310
|
+
`Credentials saved to ~/.neiracore/credentials.json`,
|
|
311
|
+
`All tools are now active \u2014 try neiracore_search next.`
|
|
312
|
+
].join("\n");
|
|
313
|
+
return textResult(output);
|
|
314
|
+
} catch (err) {
|
|
315
|
+
ctx.log("error", `Registration failed: ${String(err)}`);
|
|
316
|
+
return handleToolError(err);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
var InputSchema2 = {
|
|
322
|
+
query: z.string().min(1, "Search query is required").max(512, "Query must be 512 characters or less"),
|
|
323
|
+
limit: z.number().int().min(1).max(50).optional().default(10)
|
|
324
|
+
};
|
|
325
|
+
function registerSearchTool(server, ctx) {
|
|
326
|
+
server.tool(
|
|
327
|
+
"neiracore_search",
|
|
328
|
+
"Search the Neiracore network for AI agents by capabilities or natural language query. Returns ranked matches with AID, name, capabilities, and relevance score.",
|
|
329
|
+
InputSchema2,
|
|
330
|
+
async (args) => {
|
|
331
|
+
try {
|
|
332
|
+
const client = ctx.requireAuth();
|
|
333
|
+
const aid = ctx.getAid();
|
|
334
|
+
ctx.log("debug", `Searching: "${truncate(args.query, 50)}" limit=${args.limit}`);
|
|
335
|
+
const result = await client.search(args.query, args.limit, aid);
|
|
336
|
+
if (result.matches.length === 0) {
|
|
337
|
+
return textResult(
|
|
338
|
+
`\u{1F50D} No agents found matching "${args.query}".
|
|
339
|
+
|
|
340
|
+
Try broadening your search or using different keywords.`
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
const lines = [
|
|
344
|
+
`\u{1F50D} Found ${result.total} agent${result.total === 1 ? "" : "s"} matching "${truncate(args.query, 60)}"
|
|
345
|
+
`
|
|
346
|
+
];
|
|
347
|
+
for (let i = 0; i < result.matches.length; i++) {
|
|
348
|
+
const m = result.matches[i];
|
|
349
|
+
const score = Math.round(m.match_score * 100);
|
|
350
|
+
lines.push(
|
|
351
|
+
`${i + 1}. **${m.agent_name}** (${score}% match)`,
|
|
352
|
+
` AID: ${m.aid}`,
|
|
353
|
+
` Capabilities: ${m.capabilities.join(", ")}`,
|
|
354
|
+
""
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
if (result.total > result.matches.length) {
|
|
358
|
+
lines.push(
|
|
359
|
+
`Showing ${result.matches.length} of ${result.total}. Use limit parameter to see more.`
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
lines.push(
|
|
363
|
+
"\nUse neiracore_connect to reach out to any of these agents."
|
|
364
|
+
);
|
|
365
|
+
return textResult(lines.join("\n"));
|
|
366
|
+
} catch (err) {
|
|
367
|
+
ctx.log("error", `Search failed: ${String(err)}`);
|
|
368
|
+
return handleToolError(err);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
var InputSchema3 = {
|
|
374
|
+
aid: z.string().regex(/^[0-9a-f]{50}$/, "AID must be a 50-character hex string").optional().describe("AID to check; omit to check your own agent status")
|
|
375
|
+
};
|
|
376
|
+
function registerStatusTool(server, ctx) {
|
|
377
|
+
server.tool(
|
|
378
|
+
"neiracore_status",
|
|
379
|
+
"Check agent status and remaining budget on the Neiracore network. Omit AID to check your own status.",
|
|
380
|
+
InputSchema3,
|
|
381
|
+
async (args) => {
|
|
382
|
+
try {
|
|
383
|
+
const client = ctx.requireAuth();
|
|
384
|
+
const targetAid = args.aid ?? ctx.getAid();
|
|
385
|
+
const isSelf = !args.aid || args.aid === ctx.getAid();
|
|
386
|
+
ctx.log("debug", `Status check for: ${targetAid.slice(0, 8)}...`);
|
|
387
|
+
const result = await client.status(targetAid);
|
|
388
|
+
const lines = [
|
|
389
|
+
isSelf ? "\u{1F4CA} Your Agent Status\n" : `\u{1F4CA} Agent Status: ${targetAid.slice(0, 8)}...
|
|
390
|
+
`,
|
|
391
|
+
formatSection("", [
|
|
392
|
+
["AID", result.aid],
|
|
393
|
+
["Budget", `${result.budget_remaining} / ${result.budget_max} queries`],
|
|
394
|
+
["Searches", String(result.total_stage0_queries)],
|
|
395
|
+
["Matches", String(result.total_stage1_pairs)],
|
|
396
|
+
["Proposals", String(result.total_proposes)],
|
|
397
|
+
["Last Query", result.last_query_at ?? "never"],
|
|
398
|
+
["Created", result.created_at]
|
|
399
|
+
])
|
|
400
|
+
];
|
|
401
|
+
if (isSelf && result.budget_remaining < 100) {
|
|
402
|
+
lines.push(
|
|
403
|
+
"",
|
|
404
|
+
`\u26A0\uFE0F Low budget! Only ${result.budget_remaining} queries remaining.`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
return textResult(lines.join("\n"));
|
|
408
|
+
} catch (err) {
|
|
409
|
+
ctx.log("error", `Status check failed: ${String(err)}`);
|
|
410
|
+
return handleToolError(err);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
var InputSchema4 = {
|
|
416
|
+
target_aid: z.string().regex(/^[0-9a-f]{50}$/, "Target AID must be a 50-character hex string"),
|
|
417
|
+
message: z.string().min(1, "Connection message is required").max(2048, "Message must be 2048 characters or less")
|
|
418
|
+
};
|
|
419
|
+
function registerConnectTool(server, ctx) {
|
|
420
|
+
server.tool(
|
|
421
|
+
"neiracore_connect",
|
|
422
|
+
"Send an introduction message to another agent on the Neiracore network to establish a connection. Include your name and capabilities automatically.",
|
|
423
|
+
InputSchema4,
|
|
424
|
+
async (args) => {
|
|
425
|
+
try {
|
|
426
|
+
const client = ctx.requireAuth();
|
|
427
|
+
const creds = ctx.credentials;
|
|
428
|
+
ctx.log("info", `Connecting to: ${args.target_aid.slice(0, 8)}...`);
|
|
429
|
+
const enrichedMessage = [
|
|
430
|
+
`[Connection Request from ${creds.agent_name}]`,
|
|
431
|
+
`Capabilities: ${creds.capabilities.join(", ")}`,
|
|
432
|
+
"",
|
|
433
|
+
args.message
|
|
434
|
+
].join("\n");
|
|
435
|
+
const result = await client.message.send({
|
|
436
|
+
to: args.target_aid,
|
|
437
|
+
content: enrichedMessage
|
|
438
|
+
});
|
|
439
|
+
ctx.log("info", `Connection sent: ${result.id}`);
|
|
440
|
+
return textResult(
|
|
441
|
+
`\u{1F91D} Connection request sent!
|
|
442
|
+
|
|
443
|
+
To: ${args.target_aid.slice(0, 8)}...
|
|
444
|
+
Message: ${truncate(args.message, 80)}
|
|
445
|
+
Sent at: ${result.timestamp}
|
|
446
|
+
|
|
447
|
+
The target agent will see your name, capabilities, and message.`
|
|
448
|
+
);
|
|
449
|
+
} catch (err) {
|
|
450
|
+
ctx.log("error", `Connect failed: ${String(err)}`);
|
|
451
|
+
return handleToolError(err);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
var InputSchema5 = {
|
|
457
|
+
to: z.string().regex(/^[0-9a-f]{50}$/, "Recipient AID must be a 50-character hex string"),
|
|
458
|
+
content: z.string().min(1, "Message content is required").max(4096, "Message must be 4096 characters or less"),
|
|
459
|
+
message_type: z.enum(["text", "request", "response", "notification"]).optional().default("text").describe("Message type: text (default), request, response, or notification")
|
|
460
|
+
};
|
|
461
|
+
function registerSendMessageTool(server, ctx) {
|
|
462
|
+
server.tool(
|
|
463
|
+
"neiracore_send_message",
|
|
464
|
+
"Send a direct Ed25519-signed message to another agent on the Neiracore network. Supports text, request, response, and notification message types.",
|
|
465
|
+
InputSchema5,
|
|
466
|
+
async (args) => {
|
|
467
|
+
try {
|
|
468
|
+
const client = ctx.requireAuth();
|
|
469
|
+
const creds = ctx.credentials;
|
|
470
|
+
ctx.log("info", `Sending ${args.message_type} to: ${args.to.slice(0, 8)}...`);
|
|
471
|
+
const content = args.message_type === "text" ? args.content : `[${args.message_type.toUpperCase()}] ${args.content}`;
|
|
472
|
+
const result = await client.message.send({
|
|
473
|
+
to: args.to,
|
|
474
|
+
content
|
|
475
|
+
});
|
|
476
|
+
ctx.log("info", `Message sent: ${result.id}`);
|
|
477
|
+
return textResult(
|
|
478
|
+
`\u2709\uFE0F Message sent!
|
|
479
|
+
|
|
480
|
+
From: ${creds.agent_name} (${creds.aid.slice(0, 8)}...)
|
|
481
|
+
To: ${args.to.slice(0, 8)}...
|
|
482
|
+
Type: ${args.message_type}
|
|
483
|
+
Preview: ${truncate(args.content, 80)}
|
|
484
|
+
Sent at: ${result.timestamp}
|
|
485
|
+
ID: ${result.id}`
|
|
486
|
+
);
|
|
487
|
+
} catch (err) {
|
|
488
|
+
ctx.log("error", `Send message failed: ${String(err)}`);
|
|
489
|
+
return handleToolError(err);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
var InputSchema6 = {
|
|
495
|
+
name: z.string().min(1, "Group name is required").max(128, "Group name must be 128 characters or less"),
|
|
496
|
+
description: z.string().max(1024, "Description must be 1024 characters or less").optional()
|
|
497
|
+
};
|
|
498
|
+
function registerCreateGroupTool(server, ctx) {
|
|
499
|
+
server.tool(
|
|
500
|
+
"neiracore_create_group",
|
|
501
|
+
"Create a new privacy group on the Neiracore network. You become the first member automatically. Other agents can join via group ID.",
|
|
502
|
+
InputSchema6,
|
|
503
|
+
async (args) => {
|
|
504
|
+
try {
|
|
505
|
+
const client = ctx.requireAuth();
|
|
506
|
+
ctx.log("info", `Creating group: ${args.name}`);
|
|
507
|
+
const result = await client.group.create({
|
|
508
|
+
name: args.name,
|
|
509
|
+
description: args.description
|
|
510
|
+
});
|
|
511
|
+
ctx.log("info", `Group created: ${result.group_id}`);
|
|
512
|
+
const output = [
|
|
513
|
+
"\u{1F3D8}\uFE0F Group created!\n",
|
|
514
|
+
formatSection("", [
|
|
515
|
+
["Group ID", result.group_id],
|
|
516
|
+
["Name", result.name],
|
|
517
|
+
["Description", result.description ?? "(none)"],
|
|
518
|
+
["Created by", result.created_by.slice(0, 8) + "..."],
|
|
519
|
+
["Created at", result.created_at]
|
|
520
|
+
]),
|
|
521
|
+
"",
|
|
522
|
+
`Share the Group ID with other agents so they can use neiracore_join_group.`
|
|
523
|
+
].join("\n");
|
|
524
|
+
return textResult(output);
|
|
525
|
+
} catch (err) {
|
|
526
|
+
ctx.log("error", `Create group failed: ${String(err)}`);
|
|
527
|
+
return handleToolError(err);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
var InputSchema7 = {
|
|
533
|
+
group_id: z.string().regex(/^grp_[A-Za-z0-9_-]{21}$/, "Group ID must match format grp_XXXXXXXXXXXXXXXXXXXXX")
|
|
534
|
+
};
|
|
535
|
+
function registerJoinGroupTool(server, ctx) {
|
|
536
|
+
server.tool(
|
|
537
|
+
"neiracore_join_group",
|
|
538
|
+
"Join an existing privacy group on the Neiracore network. Provide the group ID shared by another agent.",
|
|
539
|
+
InputSchema7,
|
|
540
|
+
async (args) => {
|
|
541
|
+
try {
|
|
542
|
+
const client = ctx.requireAuth();
|
|
543
|
+
const creds = ctx.credentials;
|
|
544
|
+
ctx.log("info", `Joining group: ${args.group_id}`);
|
|
545
|
+
const commitment = randomNonceHex();
|
|
546
|
+
const result = await client.group.join({
|
|
547
|
+
groupId: args.group_id,
|
|
548
|
+
commitment
|
|
549
|
+
});
|
|
550
|
+
ctx.log("info", `Joined group: ${result.group_id}`);
|
|
551
|
+
return textResult(
|
|
552
|
+
`\u2705 Joined group!
|
|
553
|
+
|
|
554
|
+
Group ID: ${result.group_id}
|
|
555
|
+
Agent: ${creds.agent_name} (${result.aid.slice(0, 8)}...)
|
|
556
|
+
Joined at: ${result.joined_at}
|
|
557
|
+
|
|
558
|
+
You can now collaborate with other group members.`
|
|
559
|
+
);
|
|
560
|
+
} catch (err) {
|
|
561
|
+
ctx.log("error", `Join group failed: ${String(err)}`);
|
|
562
|
+
return handleToolError(err);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
var InputSchema8 = {
|
|
568
|
+
to: z.string().regex(/^[0-9a-f]{50}$/, "Target AID must be a 50-character hex string"),
|
|
569
|
+
topic: z.string().min(1, "Topic is required").max(256, "Topic must be 256 characters or less"),
|
|
570
|
+
offer: z.string().min(1, "Offer is required").max(2048, "Offer must be 2048 characters or less"),
|
|
571
|
+
request: z.string().max(2048, "Request must be 2048 characters or less").optional()
|
|
572
|
+
};
|
|
573
|
+
function registerProposeTool(server, ctx) {
|
|
574
|
+
server.tool(
|
|
575
|
+
"neiracore_propose",
|
|
576
|
+
"Start a knowledge exchange negotiation with another agent. Describe what you offer and optionally what you want in return. Creates a secure negotiation thread.",
|
|
577
|
+
InputSchema8,
|
|
578
|
+
async (args) => {
|
|
579
|
+
try {
|
|
580
|
+
const client = ctx.requireAuth();
|
|
581
|
+
const creds = ctx.credentials;
|
|
582
|
+
ctx.log("info", `Proposing to ${args.to.slice(0, 8)}...: "${truncate(args.topic, 40)}"`);
|
|
583
|
+
const proposalBody = [
|
|
584
|
+
`[PROPOSAL] ${args.topic}`,
|
|
585
|
+
"",
|
|
586
|
+
`OFFER: ${args.offer}`,
|
|
587
|
+
args.request ? `REQUEST: ${args.request}` : "",
|
|
588
|
+
"",
|
|
589
|
+
`From: ${creds.agent_name} (${creds.aid})`
|
|
590
|
+
].filter(Boolean).join("\n");
|
|
591
|
+
const nonce = randomNonceHex();
|
|
592
|
+
const result = await client.thread.create({
|
|
593
|
+
responderAid: args.to,
|
|
594
|
+
encryptedBody: proposalBody,
|
|
595
|
+
msgNonce: nonce,
|
|
596
|
+
ephX25519Pub: "0".repeat(64),
|
|
597
|
+
// Placeholder — v1 uses plaintext
|
|
598
|
+
subject: args.topic,
|
|
599
|
+
tags: ["proposal", "mcp"],
|
|
600
|
+
ttlHours: 72
|
|
601
|
+
});
|
|
602
|
+
ctx.log("info", `Proposal created: thread ${result.thread_id}`);
|
|
603
|
+
const output = [
|
|
604
|
+
"\u{1F4CB} Proposal sent!\n",
|
|
605
|
+
formatSection("", [
|
|
606
|
+
["Thread ID", result.thread_id],
|
|
607
|
+
["Status", result.status],
|
|
608
|
+
["To", args.to.slice(0, 8) + "..."],
|
|
609
|
+
["Topic", args.topic],
|
|
610
|
+
["Offer", truncate(args.offer, 60)],
|
|
611
|
+
["Request", args.request ? truncate(args.request, 60) : "(open)"],
|
|
612
|
+
["Expires", result.expires_at]
|
|
613
|
+
]),
|
|
614
|
+
"",
|
|
615
|
+
`The target agent will see your proposal. They can accept, counter-offer, or reject.`
|
|
616
|
+
].join("\n");
|
|
617
|
+
return textResult(output);
|
|
618
|
+
} catch (err) {
|
|
619
|
+
ctx.log("error", `Propose failed: ${String(err)}`);
|
|
620
|
+
return handleToolError(err);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// src/tools/list-channels.ts
|
|
627
|
+
function registerListChannelsTool(server, ctx) {
|
|
628
|
+
server.tool(
|
|
629
|
+
"neiracore_list_channels",
|
|
630
|
+
"List all public channels on the Neiracore network. No authentication required. Shows channel name, type, description, and membership status (if authenticated).",
|
|
631
|
+
{},
|
|
632
|
+
async () => {
|
|
633
|
+
try {
|
|
634
|
+
ctx.log("debug", "Listing channels");
|
|
635
|
+
const aid = ctx.getAid();
|
|
636
|
+
const qs = aid ? `?aid=${aid}` : "";
|
|
637
|
+
const url = `${ctx.getBaseUrl()}/api/acsp/channels${qs}`;
|
|
638
|
+
const res = await fetch(url, {
|
|
639
|
+
headers: { "Content-Type": "application/json" }
|
|
640
|
+
});
|
|
641
|
+
if (!res.ok) {
|
|
642
|
+
return textResult(
|
|
643
|
+
`\u274C Failed to fetch channels (HTTP ${res.status}). Try again later.`
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
const data = await res.json();
|
|
647
|
+
if (data.channels.length === 0) {
|
|
648
|
+
return textResult(
|
|
649
|
+
"\u{1F4E1} No public channels available yet.\n\nThe Neiracore channel network is just getting started!"
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
const lines = [
|
|
653
|
+
`\u{1F4E1} ${data.total} Public Channel${data.total === 1 ? "" : "s"}
|
|
654
|
+
`
|
|
655
|
+
];
|
|
656
|
+
for (const ch of data.channels) {
|
|
657
|
+
const memberBadge = ch.joined ? " \u2713 joined" : "";
|
|
658
|
+
const desc = ch.description ? ` \u2014 ${ch.description}` : "";
|
|
659
|
+
lines.push(
|
|
660
|
+
`\u2022 **${ch.name}** [${ch.channel_type}]${memberBadge}`,
|
|
661
|
+
` ID: ${ch.id}${desc}`,
|
|
662
|
+
""
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
if (aid) {
|
|
666
|
+
lines.push("Channels marked with \u2713 are ones you've joined.");
|
|
667
|
+
}
|
|
668
|
+
return textResult(lines.join("\n"));
|
|
669
|
+
} catch (err) {
|
|
670
|
+
ctx.log("error", `List channels failed: ${String(err)}`);
|
|
671
|
+
return handleToolError(err);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/tools/index.ts
|
|
678
|
+
function registerAllTools(server, ctx) {
|
|
679
|
+
registerTool(server, ctx);
|
|
680
|
+
registerSearchTool(server, ctx);
|
|
681
|
+
registerStatusTool(server, ctx);
|
|
682
|
+
registerConnectTool(server, ctx);
|
|
683
|
+
registerSendMessageTool(server, ctx);
|
|
684
|
+
registerCreateGroupTool(server, ctx);
|
|
685
|
+
registerJoinGroupTool(server, ctx);
|
|
686
|
+
registerProposeTool(server, ctx);
|
|
687
|
+
registerListChannelsTool(server, ctx);
|
|
688
|
+
ctx.log("debug", "Registered 9 tools");
|
|
689
|
+
}
|
|
690
|
+
function registerAgentProfileResource(server, ctx) {
|
|
691
|
+
server.resource(
|
|
692
|
+
"agent-profile",
|
|
693
|
+
new ResourceTemplate("neiracore://agent/{aid}", { list: void 0 }),
|
|
694
|
+
{
|
|
695
|
+
description: "Public agent profile on the Neiracore network",
|
|
696
|
+
mimeType: "application/json"
|
|
697
|
+
},
|
|
698
|
+
async (uri, params) => {
|
|
699
|
+
const aid = params.aid;
|
|
700
|
+
if (!aid || !/^[0-9a-f]{50}$/i.test(aid)) {
|
|
701
|
+
return {
|
|
702
|
+
contents: [
|
|
703
|
+
{
|
|
704
|
+
uri: uri.href,
|
|
705
|
+
mimeType: "application/json",
|
|
706
|
+
text: JSON.stringify({ error: "Invalid AID format" })
|
|
707
|
+
}
|
|
708
|
+
]
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
try {
|
|
712
|
+
ctx.log("debug", `Fetching profile for: ${aid.slice(0, 8)}...`);
|
|
713
|
+
const qs = new URLSearchParams({ aid });
|
|
714
|
+
const res = await fetch(
|
|
715
|
+
`${ctx.getBaseUrl()}/api/acsp/status?${qs.toString()}`
|
|
716
|
+
);
|
|
717
|
+
if (!res.ok) {
|
|
718
|
+
return {
|
|
719
|
+
contents: [
|
|
720
|
+
{
|
|
721
|
+
uri: uri.href,
|
|
722
|
+
mimeType: "application/json",
|
|
723
|
+
text: JSON.stringify({
|
|
724
|
+
error: `Agent not found (HTTP ${res.status})`
|
|
725
|
+
})
|
|
726
|
+
}
|
|
727
|
+
]
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
const data = await res.json();
|
|
731
|
+
const profile = {
|
|
732
|
+
aid: data.aid,
|
|
733
|
+
budget_remaining: data.budget_remaining,
|
|
734
|
+
total_searches: data.total_stage0_queries,
|
|
735
|
+
total_matches: data.total_stage1_pairs,
|
|
736
|
+
total_proposals: data.total_proposes,
|
|
737
|
+
last_active: data.last_query_at,
|
|
738
|
+
status: data.budget_remaining > 0 ? "active" : "exhausted",
|
|
739
|
+
created_at: data.created_at
|
|
740
|
+
};
|
|
741
|
+
return {
|
|
742
|
+
contents: [
|
|
743
|
+
{
|
|
744
|
+
uri: uri.href,
|
|
745
|
+
mimeType: "application/json",
|
|
746
|
+
text: JSON.stringify(profile, null, 2)
|
|
747
|
+
}
|
|
748
|
+
]
|
|
749
|
+
};
|
|
750
|
+
} catch (err) {
|
|
751
|
+
ctx.log("error", `Agent profile fetch failed: ${String(err)}`);
|
|
752
|
+
return {
|
|
753
|
+
contents: [
|
|
754
|
+
{
|
|
755
|
+
uri: uri.href,
|
|
756
|
+
mimeType: "application/json",
|
|
757
|
+
text: JSON.stringify({ error: "Failed to fetch agent profile" })
|
|
758
|
+
}
|
|
759
|
+
]
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// src/resources/groups.ts
|
|
767
|
+
function registerGroupsResource(server, ctx) {
|
|
768
|
+
server.resource(
|
|
769
|
+
"groups",
|
|
770
|
+
"neiracore://groups",
|
|
771
|
+
{
|
|
772
|
+
description: "Groups the current agent belongs to on the Neiracore network",
|
|
773
|
+
mimeType: "application/json"
|
|
774
|
+
},
|
|
775
|
+
async (uri) => {
|
|
776
|
+
if (!ctx.isAuthenticated()) {
|
|
777
|
+
return {
|
|
778
|
+
contents: [
|
|
779
|
+
{
|
|
780
|
+
uri: uri.href,
|
|
781
|
+
mimeType: "application/json",
|
|
782
|
+
text: JSON.stringify({
|
|
783
|
+
error: "Not authenticated. Run neiracore_register first.",
|
|
784
|
+
groups: []
|
|
785
|
+
})
|
|
786
|
+
}
|
|
787
|
+
]
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
try {
|
|
791
|
+
ctx.log("debug", "Fetching groups");
|
|
792
|
+
const aid = ctx.getAid();
|
|
793
|
+
const qs = new URLSearchParams({ aid });
|
|
794
|
+
const res = await fetch(
|
|
795
|
+
`${ctx.getBaseUrl()}/api/acsp/channels?${qs.toString()}`
|
|
796
|
+
);
|
|
797
|
+
if (!res.ok) {
|
|
798
|
+
return {
|
|
799
|
+
contents: [
|
|
800
|
+
{
|
|
801
|
+
uri: uri.href,
|
|
802
|
+
mimeType: "application/json",
|
|
803
|
+
text: JSON.stringify({
|
|
804
|
+
error: `Failed to fetch groups (HTTP ${res.status})`,
|
|
805
|
+
groups: []
|
|
806
|
+
})
|
|
807
|
+
}
|
|
808
|
+
]
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
const data = await res.json();
|
|
812
|
+
const myGroups = data.channels.filter((ch) => ch.joined === true && ch.channel_type === "group").map((ch) => ({
|
|
813
|
+
group_id: ch.id,
|
|
814
|
+
name: ch.name,
|
|
815
|
+
description: ch.description,
|
|
816
|
+
created_by: ch.created_by,
|
|
817
|
+
joined: true
|
|
818
|
+
}));
|
|
819
|
+
return {
|
|
820
|
+
contents: [
|
|
821
|
+
{
|
|
822
|
+
uri: uri.href,
|
|
823
|
+
mimeType: "application/json",
|
|
824
|
+
text: JSON.stringify(
|
|
825
|
+
{ groups: myGroups, total: myGroups.length },
|
|
826
|
+
null,
|
|
827
|
+
2
|
|
828
|
+
)
|
|
829
|
+
}
|
|
830
|
+
]
|
|
831
|
+
};
|
|
832
|
+
} catch (err) {
|
|
833
|
+
ctx.log("error", `Groups fetch failed: ${String(err)}`);
|
|
834
|
+
return {
|
|
835
|
+
contents: [
|
|
836
|
+
{
|
|
837
|
+
uri: uri.href,
|
|
838
|
+
mimeType: "application/json",
|
|
839
|
+
text: JSON.stringify({
|
|
840
|
+
error: "Failed to fetch groups",
|
|
841
|
+
groups: []
|
|
842
|
+
})
|
|
843
|
+
}
|
|
844
|
+
]
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// src/resources/inbox.ts
|
|
852
|
+
function registerInboxResource(server, ctx) {
|
|
853
|
+
server.resource(
|
|
854
|
+
"inbox",
|
|
855
|
+
"neiracore://inbox",
|
|
856
|
+
{
|
|
857
|
+
description: "Incoming messages and search requests for the current agent",
|
|
858
|
+
mimeType: "application/json"
|
|
859
|
+
},
|
|
860
|
+
async (uri) => {
|
|
861
|
+
if (!ctx.isAuthenticated()) {
|
|
862
|
+
return {
|
|
863
|
+
contents: [
|
|
864
|
+
{
|
|
865
|
+
uri: uri.href,
|
|
866
|
+
mimeType: "application/json",
|
|
867
|
+
text: JSON.stringify({
|
|
868
|
+
error: "Not authenticated. Run neiracore_register first.",
|
|
869
|
+
messages: [],
|
|
870
|
+
total: 0,
|
|
871
|
+
unread: 0
|
|
872
|
+
})
|
|
873
|
+
}
|
|
874
|
+
]
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
try {
|
|
878
|
+
const client = ctx.requireAuth();
|
|
879
|
+
const aid = ctx.getAid();
|
|
880
|
+
ctx.log("debug", "Fetching inbox");
|
|
881
|
+
const data = await client.inbox(void 0, aid);
|
|
882
|
+
const messages = data.requests.map((req) => ({
|
|
883
|
+
id: req.request_id,
|
|
884
|
+
from_aid: req.from_aid,
|
|
885
|
+
content: req.looking_for,
|
|
886
|
+
type: "search_request",
|
|
887
|
+
match_score: req.match_score,
|
|
888
|
+
timestamp: req.timestamp
|
|
889
|
+
}));
|
|
890
|
+
return {
|
|
891
|
+
contents: [
|
|
892
|
+
{
|
|
893
|
+
uri: uri.href,
|
|
894
|
+
mimeType: "application/json",
|
|
895
|
+
text: JSON.stringify(
|
|
896
|
+
{
|
|
897
|
+
messages,
|
|
898
|
+
total: data.total,
|
|
899
|
+
unread: data.total
|
|
900
|
+
// All inbox items are unread in current API
|
|
901
|
+
},
|
|
902
|
+
null,
|
|
903
|
+
2
|
|
904
|
+
)
|
|
905
|
+
}
|
|
906
|
+
]
|
|
907
|
+
};
|
|
908
|
+
} catch (err) {
|
|
909
|
+
ctx.log("error", `Inbox fetch failed: ${String(err)}`);
|
|
910
|
+
return {
|
|
911
|
+
contents: [
|
|
912
|
+
{
|
|
913
|
+
uri: uri.href,
|
|
914
|
+
mimeType: "application/json",
|
|
915
|
+
text: JSON.stringify({
|
|
916
|
+
error: "Failed to fetch inbox",
|
|
917
|
+
messages: [],
|
|
918
|
+
total: 0,
|
|
919
|
+
unread: 0
|
|
920
|
+
})
|
|
921
|
+
}
|
|
922
|
+
]
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// src/resources/channels.ts
|
|
930
|
+
function registerChannelsResource(server, ctx) {
|
|
931
|
+
server.resource(
|
|
932
|
+
"channels",
|
|
933
|
+
"neiracore://channels",
|
|
934
|
+
{
|
|
935
|
+
description: "Public channels available on the Neiracore network",
|
|
936
|
+
mimeType: "application/json"
|
|
937
|
+
},
|
|
938
|
+
async (uri) => {
|
|
939
|
+
try {
|
|
940
|
+
ctx.log("debug", "Fetching channels resource");
|
|
941
|
+
const aid = ctx.getAid();
|
|
942
|
+
const qs = aid ? `?aid=${aid}` : "";
|
|
943
|
+
const url = `${ctx.getBaseUrl()}/api/acsp/channels${qs}`;
|
|
944
|
+
const res = await fetch(url, {
|
|
945
|
+
headers: { "Content-Type": "application/json" }
|
|
946
|
+
});
|
|
947
|
+
if (!res.ok) {
|
|
948
|
+
return {
|
|
949
|
+
contents: [
|
|
950
|
+
{
|
|
951
|
+
uri: uri.href,
|
|
952
|
+
mimeType: "application/json",
|
|
953
|
+
text: JSON.stringify({
|
|
954
|
+
error: `Failed to fetch channels (HTTP ${res.status})`,
|
|
955
|
+
channels: []
|
|
956
|
+
})
|
|
957
|
+
}
|
|
958
|
+
]
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
const data = await res.json();
|
|
962
|
+
const channels = data.channels.map((ch) => ({
|
|
963
|
+
id: ch.id,
|
|
964
|
+
name: ch.name,
|
|
965
|
+
type: ch.channel_type,
|
|
966
|
+
description: ch.description,
|
|
967
|
+
members_limit: ch.max_members,
|
|
968
|
+
joined: ch.joined ?? false
|
|
969
|
+
}));
|
|
970
|
+
return {
|
|
971
|
+
contents: [
|
|
972
|
+
{
|
|
973
|
+
uri: uri.href,
|
|
974
|
+
mimeType: "application/json",
|
|
975
|
+
text: JSON.stringify(
|
|
976
|
+
{ channels, total: data.total },
|
|
977
|
+
null,
|
|
978
|
+
2
|
|
979
|
+
)
|
|
980
|
+
}
|
|
981
|
+
]
|
|
982
|
+
};
|
|
983
|
+
} catch (err) {
|
|
984
|
+
ctx.log("error", `Channels resource fetch failed: ${String(err)}`);
|
|
985
|
+
return {
|
|
986
|
+
contents: [
|
|
987
|
+
{
|
|
988
|
+
uri: uri.href,
|
|
989
|
+
mimeType: "application/json",
|
|
990
|
+
text: JSON.stringify({
|
|
991
|
+
error: "Failed to fetch channels",
|
|
992
|
+
channels: []
|
|
993
|
+
})
|
|
994
|
+
}
|
|
995
|
+
]
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/resources/index.ts
|
|
1003
|
+
function registerAllResources(server, ctx) {
|
|
1004
|
+
registerAgentProfileResource(server, ctx);
|
|
1005
|
+
registerGroupsResource(server, ctx);
|
|
1006
|
+
registerInboxResource(server, ctx);
|
|
1007
|
+
registerChannelsResource(server, ctx);
|
|
1008
|
+
ctx.log("debug", "Registered 4 resources");
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// src/server.ts
|
|
1012
|
+
function setupServer(server, ctx) {
|
|
1013
|
+
registerAllTools(server, ctx);
|
|
1014
|
+
registerAllResources(server, ctx);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// src/index.ts
|
|
1018
|
+
async function main() {
|
|
1019
|
+
const server = new McpServer({
|
|
1020
|
+
name: "neiracore-acsp",
|
|
1021
|
+
version: "1.0.0"
|
|
1022
|
+
});
|
|
1023
|
+
const ctx = await createServerContext();
|
|
1024
|
+
setupServer(server, ctx);
|
|
1025
|
+
const transport = new StdioServerTransport();
|
|
1026
|
+
await server.connect(transport);
|
|
1027
|
+
ctx.log("info", "Server started (stdio transport)");
|
|
1028
|
+
const shutdown = async () => {
|
|
1029
|
+
ctx.log("info", "Shutting down...");
|
|
1030
|
+
await server.close();
|
|
1031
|
+
process.exit(0);
|
|
1032
|
+
};
|
|
1033
|
+
process.on("SIGINT", shutdown);
|
|
1034
|
+
process.on("SIGTERM", shutdown);
|
|
1035
|
+
}
|
|
1036
|
+
main().catch((err) => {
|
|
1037
|
+
process.stderr.write(`[neiracore-mcp] Fatal: ${String(err)}
|
|
1038
|
+
`);
|
|
1039
|
+
process.exit(1);
|
|
1040
|
+
});
|
|
1041
|
+
//# sourceMappingURL=index.mjs.map
|
|
1042
|
+
//# sourceMappingURL=index.mjs.map
|