@agent-wire/terminal 1.7.7 → 1.7.10
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 +519 -435
- package/dist/api-token.d.ts +9 -0
- package/dist/api-token.js +46 -0
- package/dist/api-token.js.map +1 -0
- package/dist/api.d.ts +50 -10
- package/dist/api.js +78 -31
- package/dist/api.js.map +1 -1
- package/dist/b107-copy.d.ts +68 -0
- package/dist/b107-copy.js +69 -0
- package/dist/b107-copy.js.map +1 -0
- package/dist/catalog-follow.d.ts +32 -0
- package/dist/catalog-follow.js +184 -0
- package/dist/catalog-follow.js.map +1 -0
- package/dist/cli-projection.js +20 -5
- package/dist/cli-projection.js.map +1 -1
- package/dist/cli.js +32 -19
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +14 -1
- package/dist/config.js +54 -9
- package/dist/config.js.map +1 -1
- package/dist/mcp-server.d.ts +1 -6
- package/dist/mcp-server.js +161 -144
- package/dist/mcp-server.js.map +1 -1
- package/dist/read-tool-input.d.ts +15 -0
- package/dist/read-tool-input.js +15 -0
- package/dist/read-tool-input.js.map +1 -0
- package/dist/response-shaper.js +76 -15
- package/dist/response-shaper.js.map +1 -1
- package/dist/resume.d.ts +7 -0
- package/dist/resume.js +15 -0
- package/dist/resume.js.map +1 -0
- package/dist/slots.d.ts +1 -0
- package/dist/slots.js +1 -1
- package/dist/slots.js.map +1 -1
- package/dist/sync/diff.js +7 -1
- package/dist/sync/diff.js.map +1 -1
- package/dist/sync/pull.d.ts +1 -0
- package/dist/sync/pull.js +17 -9
- package/dist/sync/pull.js.map +1 -1
- package/dist/sync/types.d.ts +2 -1
- package/dist/tool-projection.d.ts +19 -2
- package/dist/tool-projection.js +544 -8
- package/dist/tool-projection.js.map +1 -1
- package/package.json +58 -58
package/dist/mcp-server.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { b107Copy } from './b107-copy.js';
|
|
2
|
+
import { readIdentifierShape, normalizeReadIdentifier } from './read-tool-input.js';
|
|
1
3
|
import { isCreditValue } from './contracts/credit-amount.js';
|
|
2
4
|
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
@@ -10,11 +12,14 @@ import { createHash, randomUUID } from 'crypto';
|
|
|
10
12
|
import { readFile, writeFile, rename as fsRename, mkdir as fsMkdir, stat as fsStat } from 'fs/promises';
|
|
11
13
|
import { join, isAbsolute } from 'path';
|
|
12
14
|
import { loadConfig, saveConfig, CONFIG_DIR } from './config.js';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
+
import { resolveApiToken } from './api-token.js';
|
|
16
|
+
import { CatalogIdentityMismatch, startCatalogFollow, waitForCatalogReady } from './catalog-follow.js';
|
|
17
|
+
import { loadSlotConfig, saveSlotConfig, slotPath, migrateLegacyAgentsToSlots } from './slots.js';
|
|
18
|
+
import { WireAPI, WireApiRefusal, RegistrationError } from './api.js';
|
|
19
|
+
import { resumeToken } from './resume.js';
|
|
15
20
|
import { shapeStatus, shapeRead, shapeInspect, shapeGraph, shapeCorpora, shapeMyContributions, shapeEarnings, shapeOpportunities, shapeRate, shapePatrolScan, shapeNotifications, shapeTasks, shapeMessages, } from './response-shaper.js';
|
|
16
21
|
import { startBackgroundWorkLoop } from './background-worker.js';
|
|
17
|
-
import { parseToolDefinition, fetchCatalogDefinitionItems, invokeToolDefinition, dedupeToolDefinitions, withToolDefinitionAliases, fingerprintToolDefinitions,
|
|
22
|
+
import { parseToolDefinition, fetchCatalogDefinitionItems, invokeToolDefinition, dedupeToolDefinitions, withToolDefinitionAliases, fingerprintToolDefinitions, toolDefinitionInputShape, } from './tool-projection.js';
|
|
18
23
|
import {
|
|
19
24
|
// Mission #1.5-rev2: agents/ cache fully retired. loadAgentIdentity /
|
|
20
25
|
// saveAgentIdentity / deleteAgentIdentity are no longer imported here;
|
|
@@ -27,7 +32,6 @@ import { walkDirectory, computeDiff, pushFiles, pullFiles, parseManifest, serial
|
|
|
27
32
|
import { WIRE_HELP_POINTER } from './help-pointer.js';
|
|
28
33
|
import { pullLocalSubscriptions } from './local-pull.js';
|
|
29
34
|
const SURFACE_REFRESH_TTL_MS = 60_000;
|
|
30
|
-
const DEFAULT_TOOL_REFRESH_POLL_INTERVAL_MS = 90_000;
|
|
31
35
|
const WIRE_DOORWAY_AFFORDANCE = 'Not identified. Call wire_identify({name, operator_email}) — ask your human for their email. Then wire_help({preset:"brief-my-human"}).';
|
|
32
36
|
function normalizeSurfaceName(name) {
|
|
33
37
|
return name.trim().toLowerCase().replace(/[^a-z0-9_]+/g, '_').replace(/^_+|_+$/g, '');
|
|
@@ -72,10 +76,6 @@ function dedupeSurfaces(surfaces) {
|
|
|
72
76
|
}
|
|
73
77
|
return Array.from(byName.values()).sort((a, b) => a.surface.localeCompare(b.surface));
|
|
74
78
|
}
|
|
75
|
-
function extractQueryItems(response) {
|
|
76
|
-
const items = response.items ?? response.results ?? response.contributions;
|
|
77
|
-
return Array.isArray(items) ? items : [];
|
|
78
|
-
}
|
|
79
79
|
function normalizeApiRootPath(path) {
|
|
80
80
|
const trimmed = path.trim();
|
|
81
81
|
if (!trimmed)
|
|
@@ -83,11 +83,11 @@ function normalizeApiRootPath(path) {
|
|
|
83
83
|
const withSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
84
84
|
return withSlash.replace(/^\/api\/v1(?=\/|$)/, '') || '/';
|
|
85
85
|
}
|
|
86
|
-
async function fetchSurfaceConfigItems(api) {
|
|
87
|
-
return fetchCatalogDefinitionItems(api, 'surface');
|
|
86
|
+
async function fetchSurfaceConfigItems(api, forceRefresh = false) {
|
|
87
|
+
return fetchCatalogDefinitionItems(api, 'surface', forceRefresh);
|
|
88
88
|
}
|
|
89
|
-
async function refreshSubstrateSurfaces(api) {
|
|
90
|
-
const items = await fetchSurfaceConfigItems(api);
|
|
89
|
+
async function refreshSubstrateSurfaces(api, forceRefresh = false) {
|
|
90
|
+
const items = await fetchSurfaceConfigItems(api, forceRefresh);
|
|
91
91
|
return dedupeSurfaces(items
|
|
92
92
|
.filter((item) => item.type === 'surface_config')
|
|
93
93
|
.map(parseSurfaceConfig)
|
|
@@ -106,53 +106,10 @@ function fingerprintSubstrateSurfaces(surfaces) {
|
|
|
106
106
|
.sort((a, b) => a.toolName.localeCompare(b.toolName));
|
|
107
107
|
return createHash('sha256').update(JSON.stringify(projected)).digest('hex');
|
|
108
108
|
}
|
|
109
|
-
function numericStructuredValue(data) {
|
|
110
|
-
for (const key of ['poll_interval_ms', 'interval_ms', 'value_ms', 'value']) {
|
|
111
|
-
const value = data[key];
|
|
112
|
-
if (typeof value === 'number' && Number.isFinite(value))
|
|
113
|
-
return value;
|
|
114
|
-
if (typeof value === 'string' && value.trim()) {
|
|
115
|
-
const parsed = Number(value);
|
|
116
|
-
if (Number.isFinite(parsed))
|
|
117
|
-
return parsed;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
for (const key of ['poll_interval_seconds', 'interval_seconds', 'value_seconds']) {
|
|
121
|
-
const value = data[key];
|
|
122
|
-
if (typeof value === 'number' && Number.isFinite(value))
|
|
123
|
-
return value * 1000;
|
|
124
|
-
if (typeof value === 'string' && value.trim()) {
|
|
125
|
-
const parsed = Number(value);
|
|
126
|
-
if (Number.isFinite(parsed))
|
|
127
|
-
return parsed * 1000;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
function parseToolRefreshPollIntervalMs(items) {
|
|
133
|
-
const latest = items
|
|
134
|
-
.filter((item) => item.type === 'economic_parameter')
|
|
135
|
-
.filter((item) => {
|
|
136
|
-
const data = item.structured_data ?? {};
|
|
137
|
-
const name = data.parameter_name ?? data.name ?? data.key;
|
|
138
|
-
return (name === 'refresh_mcp_tools_poll_interval_ms' ||
|
|
139
|
-
name === 'refresh_mcp_tools_poll_interval_seconds' ||
|
|
140
|
-
name === 'mcp_tool_refresh_poll_interval_ms' ||
|
|
141
|
-
name === 'mcp_tool_refresh_poll_interval_seconds');
|
|
142
|
-
})
|
|
143
|
-
.at(-1);
|
|
144
|
-
if (!latest)
|
|
145
|
-
return { pollIntervalMs: DEFAULT_TOOL_REFRESH_POLL_INTERVAL_MS, source: 'default' };
|
|
146
|
-
const value = numericStructuredValue(latest.structured_data ?? {});
|
|
147
|
-
if (value === null || value <= 0) {
|
|
148
|
-
throw new Error(`Invalid refresh-MCP-tools economic_parameter ${latest.handle_path ?? '(unhandled)'}: expected a positive interval value`);
|
|
149
|
-
}
|
|
150
|
-
return { pollIntervalMs: Math.round(value), source: 'economic_parameter', handle: latest.handle_path };
|
|
151
|
-
}
|
|
152
109
|
function toolsAvailableMsg(verificationStatus) {
|
|
153
110
|
return verificationStatus === 'confirmed'
|
|
154
111
|
? 'All tools available.'
|
|
155
|
-
: 'Pending verification: wire_identify, wire_query, wire_graph, wire_inspect, wire_status, wire_corpora, wire_legal, wire_pulse, wire_opportunities
|
|
112
|
+
: 'Pending verification: wire_identify, wire_query, wire_graph, wire_inspect, wire_status, wire_corpora, wire_legal, wire_pulse, wire_opportunities available now. Credit-affecting tools (wire_contribute, wire_rate, wire_flag, wire_correct, wire_messages, wire_market, wire_games, wire_supersede) require operator approval — check your operator\'s email.';
|
|
156
113
|
}
|
|
157
114
|
function looksLikeDeviceAuthFailure(error) {
|
|
158
115
|
if (!error)
|
|
@@ -181,6 +138,7 @@ function buildResumeResponse(params) {
|
|
|
181
138
|
verification_status: resumedStatus,
|
|
182
139
|
help: WIRE_HELP_POINTER,
|
|
183
140
|
tools_available: toolsAvailableMsg(resumedStatus),
|
|
141
|
+
...(params.welcomeGrant ? { welcome_grant: params.welcomeGrant } : {}),
|
|
184
142
|
};
|
|
185
143
|
if (params.sinceLastSession && typeof params.sinceLastSession === 'object') {
|
|
186
144
|
const sls = params.sinceLastSession;
|
|
@@ -208,12 +166,11 @@ export const __mcpSurfaceTestInternals = {
|
|
|
208
166
|
normalizeApiRootPath,
|
|
209
167
|
SURFACE_REFRESH_TTL_MS,
|
|
210
168
|
fingerprintSubstrateSurfaces,
|
|
211
|
-
parseToolRefreshPollIntervalMs,
|
|
212
169
|
buildResumeResponse,
|
|
213
170
|
looksLikeDeviceAuthFailure,
|
|
214
171
|
};
|
|
215
172
|
export async function startMcpServer(options) {
|
|
216
|
-
const loadedConfig = loadConfig();
|
|
173
|
+
const loadedConfig = loadConfig({ allowInvalidTokenFile: true, ownIdentityOnly: true });
|
|
217
174
|
loadedConfig.apiUrl = process.env.WIRE_API_URL ?? loadedConfig.apiUrl;
|
|
218
175
|
// MCP identity is slot-owned now. Keep node/base config, but deliberately
|
|
219
176
|
// strip any legacy config/env agent token before constructing pre-pin APIs.
|
|
@@ -227,10 +184,8 @@ export async function startMcpServer(options) {
|
|
|
227
184
|
// tool handlers go through the `api` closure variable below — bootApi
|
|
228
185
|
// is for identity-resolution operations only.
|
|
229
186
|
const bootApi = new WireAPI(config, threadId);
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
const discoveryApi = new WireAPI({ ...config,
|
|
233
|
-
apiToken: process.env.WIRE_API_TOKEN ?? loadedConfig.apiToken }, threadId);
|
|
187
|
+
// This contains only an explicit process credential, never the shared saved token.
|
|
188
|
+
const discoveryApi = new WireAPI(loadedConfig, threadId);
|
|
234
189
|
// `api` starts as bootApi (no agent token). Tool handlers MUST gate
|
|
235
190
|
// through requireIdentity() before using it; that gate ensures the
|
|
236
191
|
// closure has been promoted to the slot's authenticated WireAPI by
|
|
@@ -245,7 +200,8 @@ export async function startMcpServer(options) {
|
|
|
245
200
|
let identifyingName = null;
|
|
246
201
|
let worker = null;
|
|
247
202
|
let workerStartAttempted = false;
|
|
248
|
-
let
|
|
203
|
+
let catalogFollower;
|
|
204
|
+
let missingCatalogIdentityLogged = false;
|
|
249
205
|
// ── Migration on first boot ──────────────────────────────────────
|
|
250
206
|
// One-time copy from ~/.wire-node/agents/<name>.json to
|
|
251
207
|
// ~/.wire-node/slots/<slot>.json. Idempotent.
|
|
@@ -267,6 +223,12 @@ export async function startMcpServer(options) {
|
|
|
267
223
|
function requireIdentity() {
|
|
268
224
|
if (processSlot)
|
|
269
225
|
return null;
|
|
226
|
+
try {
|
|
227
|
+
resolveApiToken(loadedConfig);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
return { type: 'text', text: error.message };
|
|
231
|
+
}
|
|
270
232
|
return { type: 'text', text: WIRE_DOORWAY_AFFORDANCE };
|
|
271
233
|
}
|
|
272
234
|
// ── Session state for response shaping ──────────────────────────────
|
|
@@ -304,7 +266,8 @@ export async function startMcpServer(options) {
|
|
|
304
266
|
verificationStatus: me.verification_status,
|
|
305
267
|
};
|
|
306
268
|
processSlot = { ...processSlot, identity: refreshedIdentity };
|
|
307
|
-
|
|
269
|
+
if (loadedConfig.apiTokenFile === undefined)
|
|
270
|
+
saveSlotConfig(processSlot.name, refreshedIdentity);
|
|
308
271
|
}
|
|
309
272
|
}
|
|
310
273
|
async function ensureShaperContext(options = {}) {
|
|
@@ -354,13 +317,21 @@ export async function startMcpServer(options) {
|
|
|
354
317
|
// Defensive — the handler-entry guard should have prevented this.
|
|
355
318
|
throw new Error(`Pin contract violation: tried to establish slot "${name}" while pinned to "${processSlot.name}"`);
|
|
356
319
|
}
|
|
320
|
+
// A successful interactive identify replaces an earlier failed configured
|
|
321
|
+
// credential attempt immediately, without waiting out its backoff.
|
|
322
|
+
if (catalogFollower && source !== 'configured-token') {
|
|
323
|
+
catalogFollower.stop();
|
|
324
|
+
catalogFollower = undefined;
|
|
325
|
+
}
|
|
357
326
|
// Build the API client bound to this slot's token. We build a fresh
|
|
358
327
|
// config object rather than mutating the closure-shared one — the
|
|
359
328
|
// disk-side config.apiToken stays untouched.
|
|
360
329
|
const slotApi = new WireAPI({ ...config, apiToken: identity.apiToken }, threadId);
|
|
361
330
|
processSlot = { name, identity };
|
|
362
331
|
api = slotApi;
|
|
363
|
-
|
|
332
|
+
// A configured file remains the credential source; don't copy it into a slot.
|
|
333
|
+
if (identity.operatorEmail && loadedConfig.apiTokenFile === undefined)
|
|
334
|
+
saveSlotConfig(name, identity);
|
|
364
335
|
console.error(`[wire-mcp] Pin established: ${name} (${identity.pseudoId}) [source=${source}]`);
|
|
365
336
|
return identity;
|
|
366
337
|
}
|
|
@@ -371,8 +342,10 @@ export async function startMcpServer(options) {
|
|
|
371
342
|
if (!config.nodeId) {
|
|
372
343
|
console.error('[wire-mcp] No node ID found, auto-registering after slot pin...');
|
|
373
344
|
try {
|
|
374
|
-
const node = await api.registerNode(config.nodeName, config.capabilities);
|
|
345
|
+
const node = await api.registerNode(config.nodeName, config.capabilities, { node_handle: config.nodeHandle });
|
|
375
346
|
config.nodeId = node.node_id;
|
|
347
|
+
if (node.registration_notice)
|
|
348
|
+
console.error(node.registration_notice);
|
|
376
349
|
saveConfig(config);
|
|
377
350
|
console.error(`[wire-mcp] Auto-registered node: ${node.node_id} (${config.nodeName})`);
|
|
378
351
|
}
|
|
@@ -499,19 +472,17 @@ export async function startMcpServer(options) {
|
|
|
499
472
|
});
|
|
500
473
|
dynamicSurfaceTools.set(surface.toolName, { ...registered, surface });
|
|
501
474
|
}
|
|
502
|
-
async function fetchToolDefinitions() {
|
|
503
|
-
const hydrated = await fetchCatalogDefinitionItems(
|
|
475
|
+
async function fetchToolDefinitions(forceRefresh = false) {
|
|
476
|
+
const hydrated = await fetchCatalogDefinitionItems(api, 'tool_definition', forceRefresh);
|
|
504
477
|
const definitions = withToolDefinitionAliases(dedupeToolDefinitions(hydrated
|
|
505
478
|
.filter((item) => item !== null)
|
|
506
479
|
.map((item) => parseToolDefinition(item))
|
|
507
480
|
.filter((def) => def !== null)));
|
|
508
|
-
// Persist to disk cache (atomic write). Errors are non-fatal.
|
|
509
|
-
void saveToolDefinitionsCache(definitions);
|
|
510
481
|
return definitions;
|
|
511
482
|
}
|
|
512
483
|
function projectedToolDescription(def) {
|
|
513
484
|
const handle = def.configHandle ? ` Definition: ${def.configHandle}.` : '';
|
|
514
|
-
return `${def.description}${handle}`;
|
|
485
|
+
return `${def.description}${['wire_read', 'wire_access_contribution'].includes(def.toolName) ? ` ${b107Copy.read.tool}` : ''}${handle}`;
|
|
515
486
|
}
|
|
516
487
|
function registerProjectedCoreTool(def) {
|
|
517
488
|
const fingerprint = fingerprintToolDefinitions([def]);
|
|
@@ -539,14 +510,23 @@ export async function startMcpServer(options) {
|
|
|
539
510
|
const registered = server.registerTool(def.toolName, {
|
|
540
511
|
title: def.toolName,
|
|
541
512
|
description: projectedToolDescription(def),
|
|
542
|
-
inputSchema: z.object(
|
|
513
|
+
inputSchema: z.object({ ...toolDefinitionInputShape(def),
|
|
514
|
+
...(['wire_read', 'wire_access_contribution'].includes(def.toolName) ? { ...readIdentifierShape, accept_price: creditAmountSchema(0).optional().describe(b107Copy.read.parameter) } : {}),
|
|
515
|
+
}).strict(),
|
|
543
516
|
_meta: {
|
|
544
517
|
tool_definition_handle: def.configHandle,
|
|
545
518
|
binding: def.binding,
|
|
546
519
|
discovery: 'tool_definition',
|
|
547
520
|
},
|
|
548
521
|
}, async (params) => {
|
|
549
|
-
|
|
522
|
+
if (['wire_read', 'wire_access_contribution'].includes(def.toolName)) {
|
|
523
|
+
const normalized = normalizeReadIdentifier(params);
|
|
524
|
+
if ('error' in normalized)
|
|
525
|
+
return { content: [{ type: 'text', text: JSON.stringify(normalized, null, 2) }], isError: true };
|
|
526
|
+
const { handle, ...rest } = params;
|
|
527
|
+
params = { ...rest, contribution_id: normalized.id };
|
|
528
|
+
}
|
|
529
|
+
const gate = def.toolName === 'wire_help' ? null : def.gate === 'identity' ? requireIdentity() : await requireConfirmed();
|
|
550
530
|
if (gate)
|
|
551
531
|
return { content: [gate], isError: true };
|
|
552
532
|
try {
|
|
@@ -559,12 +539,17 @@ export async function startMcpServer(options) {
|
|
|
559
539
|
};
|
|
560
540
|
}
|
|
561
541
|
catch (err) {
|
|
542
|
+
if (err instanceof WireApiRefusal) {
|
|
543
|
+
return { content: [{ type: 'text', text: JSON.stringify(err.body, null, 2) }], isError: true };
|
|
544
|
+
}
|
|
562
545
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
563
546
|
}
|
|
564
547
|
});
|
|
565
548
|
projectedCoreTools.set(def.toolName, { remove: registered.remove.bind(registered), fingerprint, definition: def });
|
|
566
549
|
}
|
|
567
|
-
async function refreshDynamicSurfaceTools(forceRefresh) {
|
|
550
|
+
async function refreshDynamicSurfaceTools(forceRefresh, forceCatalogRefresh = false) {
|
|
551
|
+
// Session-cache bypass is also used by startup/polling. Only a manual
|
|
552
|
+
// refresh requests a server catalog refresh on the first discovery page.
|
|
568
553
|
if (!forceRefresh &&
|
|
569
554
|
surfaceCache &&
|
|
570
555
|
toolDefinitionCache &&
|
|
@@ -582,12 +567,12 @@ export async function startMcpServer(options) {
|
|
|
582
567
|
removed: [],
|
|
583
568
|
};
|
|
584
569
|
}
|
|
585
|
-
//
|
|
586
|
-
|
|
587
|
-
const surfaces = await
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
570
|
+
// Reconcile atomically. A failed discovery read preserves session tools
|
|
571
|
+
// and the last good disk cache, including on a cold surface read.
|
|
572
|
+
const [toolDefinitions, surfaces] = await Promise.all([
|
|
573
|
+
fetchToolDefinitions(forceCatalogRefresh), refreshSubstrateSurfaces(api, forceCatalogRefresh),
|
|
574
|
+
]);
|
|
575
|
+
void saveToolDefinitionsCache(toolDefinitions);
|
|
591
576
|
surfaceCache = { fetchedAt: Date.now(), surfaces };
|
|
592
577
|
toolDefinitionCache = { fetchedAt: Date.now(), definitions: toolDefinitions };
|
|
593
578
|
const removed = [];
|
|
@@ -654,11 +639,45 @@ export async function startMcpServer(options) {
|
|
|
654
639
|
}
|
|
655
640
|
console.error(`[wire-mcp] tool-definitions-cache: warm-booted ${cachedDefs.length} projected tool(s) from disk`);
|
|
656
641
|
}
|
|
642
|
+
if (processSlot || loadedConfig.apiToken) {
|
|
643
|
+
if (!catalogFollower) {
|
|
644
|
+
catalogFollower = startCatalogFollow({
|
|
645
|
+
identity: processSlot?.name ?? loadedConfig.agentName ?? 'configured agent',
|
|
646
|
+
openStream: signal => (processSlot ? api : discoveryApi).streamEvents({ triggers: ['catalog.changed'] }, { signal }),
|
|
647
|
+
refresh: async () => {
|
|
648
|
+
if (!processSlot) {
|
|
649
|
+
const resumed = await resumeToken(discoveryApi, loadedConfig.apiToken);
|
|
650
|
+
if (loadedConfig.agentName && resumed.name.toLowerCase() !== loadedConfig.agentName.toLowerCase()) {
|
|
651
|
+
throw new CatalogIdentityMismatch(`Configured token belongs to ${resumed.name}, but WIRE_AGENT_NAME is ${loadedConfig.agentName}; set matching per-server credentials.`);
|
|
652
|
+
}
|
|
653
|
+
establishPin(resumed.name, resumed.identity, 'configured-token');
|
|
654
|
+
}
|
|
655
|
+
const result = await refreshDynamicSurfaceTools(true);
|
|
656
|
+
if (result.changed || result.added.length || result.removed.length)
|
|
657
|
+
notifyToolListChanged('catalog changed');
|
|
658
|
+
return result;
|
|
659
|
+
},
|
|
660
|
+
log: line => console.error(line),
|
|
661
|
+
}, {
|
|
662
|
+
feed: loadedConfig.catalogFeed,
|
|
663
|
+
intervalMs: loadedConfig.catalogRefreshIntervalMs,
|
|
664
|
+
maxBackoffMs: loadedConfig.catalogBackoffMaxMs,
|
|
665
|
+
feedTimeoutMs: loadedConfig.catalogFeedTimeoutMs,
|
|
666
|
+
eventJitterMaxMs: loadedConfig.catalogEventJitterMaxMs,
|
|
667
|
+
reconnectJitterMaxMs: loadedConfig.catalogReconnectJitterMaxMs,
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
await waitForCatalogReady(catalogFollower, loadedConfig.catalogStartupWaitMs);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (!missingCatalogIdentityLogged) {
|
|
674
|
+
missingCatalogIdentityLogged = true;
|
|
675
|
+
const missingSlot = loadedConfig.agentName ? ` No valid slot file at ${slotPath(loadedConfig.agentName)}.` : '';
|
|
676
|
+
console.error(`[wire-mcp] Authenticated catalog refresh skipped.${missingSlot} Set WIRE_AGENT_NAME per server to your own saved slot, or call wire_identify.`);
|
|
677
|
+
}
|
|
657
678
|
try {
|
|
658
679
|
const result = await refreshDynamicSurfaceTools(true);
|
|
659
680
|
console.error(`[wire-mcp] Surface discovery (${source}): ${result.surfaces.length} surface_config tools registered`);
|
|
660
|
-
if (processSlot)
|
|
661
|
-
startToolRefreshPoller();
|
|
662
681
|
}
|
|
663
682
|
catch (err) {
|
|
664
683
|
console.error(`[wire-mcp] Surface discovery failed (${source}): ${err.message}`);
|
|
@@ -670,52 +689,8 @@ export async function startMcpServer(options) {
|
|
|
670
689
|
server.sendToolListChanged();
|
|
671
690
|
console.error(`[wire-mcp] tools/list_changed emitted (${reason})`);
|
|
672
691
|
}
|
|
673
|
-
async function loadToolRefreshPollIntervalMs() {
|
|
674
|
-
try {
|
|
675
|
-
const result = await api.query({
|
|
676
|
-
text: 'refresh_mcp_tools_poll_interval economic_parameter',
|
|
677
|
-
domain: 'all',
|
|
678
|
-
limit: 25,
|
|
679
|
-
sort: 'recency',
|
|
680
|
-
});
|
|
681
|
-
const policy = parseToolRefreshPollIntervalMs(extractQueryItems(result));
|
|
682
|
-
if (policy.source === 'default') {
|
|
683
|
-
console.error(`[wire-mcp] WARNING: refresh-MCP-tools economic_parameter not found; using default poll interval ${policy.pollIntervalMs}ms`);
|
|
684
|
-
}
|
|
685
|
-
else {
|
|
686
|
-
console.error(`[wire-mcp] refresh-MCP-tools poll interval ${policy.pollIntervalMs}ms from ${policy.handle}`);
|
|
687
|
-
}
|
|
688
|
-
return policy.pollIntervalMs;
|
|
689
|
-
}
|
|
690
|
-
catch (err) {
|
|
691
|
-
console.error(`[wire-mcp] WARNING: refresh-MCP-tools policy load failed: ${err.message}; using default poll interval`);
|
|
692
|
-
return DEFAULT_TOOL_REFRESH_POLL_INTERVAL_MS;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
function startToolRefreshPoller() {
|
|
696
|
-
if (toolRefreshPollerStarted)
|
|
697
|
-
return;
|
|
698
|
-
toolRefreshPollerStarted = true;
|
|
699
|
-
void (async () => {
|
|
700
|
-
const intervalMs = await loadToolRefreshPollIntervalMs();
|
|
701
|
-
const timer = setInterval(() => {
|
|
702
|
-
void (async () => {
|
|
703
|
-
try {
|
|
704
|
-
const result = await refreshDynamicSurfaceTools(true);
|
|
705
|
-
if (result.changed || result.added.length > 0 || result.removed.length > 0) {
|
|
706
|
-
notifyToolListChanged('auto-refresh');
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
catch (err) {
|
|
710
|
-
console.error(`[wire-mcp] WARNING: refresh-MCP-tools auto refresh failed: ${err.message}`);
|
|
711
|
-
}
|
|
712
|
-
})();
|
|
713
|
-
}, intervalMs);
|
|
714
|
-
timer.unref?.();
|
|
715
|
-
})();
|
|
716
|
-
}
|
|
717
692
|
async function refreshToolsResultJson(reason) {
|
|
718
|
-
const result = await refreshDynamicSurfaceTools(true);
|
|
693
|
+
const result = await refreshDynamicSurfaceTools(true, true);
|
|
719
694
|
notifyToolListChanged(reason);
|
|
720
695
|
return JSON.stringify({
|
|
721
696
|
refreshed: true,
|
|
@@ -833,6 +808,7 @@ export async function startMcpServer(options) {
|
|
|
833
808
|
operator: processSlot.identity.operatorEmail,
|
|
834
809
|
verification_status: vs,
|
|
835
810
|
pool_credits: me?.pool_credits ?? 'unknown',
|
|
811
|
+
...(me?.welcome_grant ? { welcome_grant: me.welcome_grant } : {}),
|
|
836
812
|
note: 'Process is already pinned to this slot; no re-resolution needed.',
|
|
837
813
|
help: WIRE_HELP_POINTER,
|
|
838
814
|
tools_available: toolsAvailableMsg(vs),
|
|
@@ -864,6 +840,7 @@ export async function startMcpServer(options) {
|
|
|
864
840
|
identity: `${me?.name || processSlot.name} (concurrent same-name call resolved)`,
|
|
865
841
|
verification_status: vs,
|
|
866
842
|
pool_credits: me?.pool_credits ?? 'unknown',
|
|
843
|
+
...(me?.welcome_grant ? { welcome_grant: me.welcome_grant } : {}),
|
|
867
844
|
help: WIRE_HELP_POINTER,
|
|
868
845
|
tools_available: toolsAvailableMsg(vs),
|
|
869
846
|
}, null, 2) }],
|
|
@@ -942,6 +919,7 @@ export async function startMcpServer(options) {
|
|
|
942
919
|
operator: existing.operatorEmail,
|
|
943
920
|
verification_status: freshStatus,
|
|
944
921
|
pool_credits: me.pool_credits ?? 'unknown',
|
|
922
|
+
...(me.welcome_grant ? { welcome_grant: me.welcome_grant } : {}),
|
|
945
923
|
help: WIRE_HELP_POINTER,
|
|
946
924
|
tools_available: toolsAvailableMsg(freshStatus),
|
|
947
925
|
}, null, 2) }],
|
|
@@ -1085,6 +1063,7 @@ export async function startMcpServer(options) {
|
|
|
1085
1063
|
status: 'registered',
|
|
1086
1064
|
identity: `${params.name} (pseudo-ID: ${result.pseudo_id})`,
|
|
1087
1065
|
verification_status: result.verification_status,
|
|
1066
|
+
...(result.welcome_grant ? { welcome_grant: result.welcome_grant, pool_credits: result.pool_credits } : {}),
|
|
1088
1067
|
verification: isConfirmed
|
|
1089
1068
|
? 'Operator already verified. Full access granted.'
|
|
1090
1069
|
: `Approval required: check ${params.operator_email} for verification and approval emails.`,
|
|
@@ -1136,6 +1115,7 @@ export async function startMcpServer(options) {
|
|
|
1136
1115
|
operatorEmail: params.operator_email,
|
|
1137
1116
|
verificationStatus: resumed.verification_status,
|
|
1138
1117
|
sinceLastSession: resumed.since_last_session,
|
|
1118
|
+
welcomeGrant: await api.getMe().then(me => me.welcome_grant).catch(() => undefined),
|
|
1139
1119
|
}), null, 2) }],
|
|
1140
1120
|
};
|
|
1141
1121
|
}
|
|
@@ -1204,6 +1184,7 @@ export async function startMcpServer(options) {
|
|
|
1204
1184
|
status: 'registered',
|
|
1205
1185
|
identity: `${params.name} (pseudo-ID: ${result.pseudo_id})`,
|
|
1206
1186
|
verification_status: result.verification_status,
|
|
1187
|
+
...(result.welcome_grant ? { welcome_grant: result.welcome_grant, pool_credits: result.pool_credits } : {}),
|
|
1207
1188
|
help: WIRE_HELP_POINTER,
|
|
1208
1189
|
tools_available: toolsAvailableMsg(result.verification_status),
|
|
1209
1190
|
};
|
|
@@ -1282,6 +1263,7 @@ export async function startMcpServer(options) {
|
|
|
1282
1263
|
operatorEmail: params.operator_email,
|
|
1283
1264
|
verificationStatus: resumedStatus,
|
|
1284
1265
|
sinceLastSession: resumed.since_last_session,
|
|
1266
|
+
welcomeGrant: await api.getMe().then(me => me.welcome_grant).catch(() => undefined),
|
|
1285
1267
|
}), null, 2) }],
|
|
1286
1268
|
};
|
|
1287
1269
|
}
|
|
@@ -1340,6 +1322,7 @@ export async function startMcpServer(options) {
|
|
|
1340
1322
|
status: 'registered',
|
|
1341
1323
|
identity: `${params.name} (pseudo-ID: ${result.pseudo_id})`,
|
|
1342
1324
|
verification_status: result.verification_status,
|
|
1325
|
+
...(result.welcome_grant ? { welcome_grant: result.welcome_grant, pool_credits: result.pool_credits } : {}),
|
|
1343
1326
|
help: WIRE_HELP_POINTER,
|
|
1344
1327
|
tools_available: toolsAvailableMsg(result.verification_status),
|
|
1345
1328
|
};
|
|
@@ -1427,12 +1410,12 @@ export async function startMcpServer(options) {
|
|
|
1427
1410
|
'Credits are internal platform tokens, not currency. No guaranteed dollar value. Credits do not expire while your account is active.',
|
|
1428
1411
|
'All credits pool at the operator level (shared by all your agents).',
|
|
1429
1412
|
'Contributions to the graph are permanent. Retraction makes them invisible but preserved for audit.',
|
|
1430
|
-
'Citation royalties
|
|
1413
|
+
'Citation royalties follow policy v2: 60% creator, 37.5% recursive sources, 2.5% Float plus integer dust; cited leaves keep their whole incoming allocation.',
|
|
1431
1414
|
'Contributors are independent parties — the Wire does not direct, control, or edit contributions. All automated metadata is algorithmic, not editorial.',
|
|
1432
1415
|
'Reputation is algorithmic and never manually adjusted.',
|
|
1433
1416
|
'Pseudo-IDs protect agent privacy. Master identity mappings are never publicly disclosed.',
|
|
1434
1417
|
'Self-dealing between your own agents is prohibited.',
|
|
1435
|
-
'Operators buy 1,000,000 credits per US dollar, in whole-dollar quantities from $4 to $1,000. New operators receive
|
|
1418
|
+
'Operators buy 1,000,000 credits per US dollar, in whole-dollar quantities from $4 to $1,000. New operators receive welcome credits on email verification, once per verified address (at most 10 addresses that share one inbox). Until verified, the account has 0 welcome credits. Open the verification link sent to your operator email and press Approve; resend with POST /api/v1/wire/agent/resend-approval. Refunds and disputes reverse the recorded purchase mint. Buyback is unimplemented and unavailable; its future ceiling is $0.50 per 1,000,000 credits, eligible only after the platform holds $200,000 cash.',
|
|
1436
1419
|
'AI-generated content may contain errors — independently verify important information.',
|
|
1437
1420
|
'Dispute resolution via binding arbitration (Delaware). 30-day opt-out window available.',
|
|
1438
1421
|
],
|
|
@@ -1578,6 +1561,11 @@ export async function startMcpServer(options) {
|
|
|
1578
1561
|
action: z.enum(['list', 'documents', 'create', 'health', 'revenue', 'bulk_ingest']).optional().describe('Action to perform. Defaults to documents when slug is present, otherwise list.'),
|
|
1579
1562
|
slug: z.string().optional().describe('Corpus slug to browse. Omit to list all corpora.'),
|
|
1580
1563
|
title: z.string().optional().describe('Corpus title for create.'),
|
|
1564
|
+
attestation_tier: z.enum(['ownership', 'authorization', 'good_faith']).optional().describe('Required for create. Choose explicitly: you own the rights, have authorization, or have a good-faith rights basis. Immutable after creation.'),
|
|
1565
|
+
authorizing_party: z.string().optional().describe('Required for authorization: who authorized you.'),
|
|
1566
|
+
authorization_basis: z.string().optional().describe('Required for authorization: permission you received.'),
|
|
1567
|
+
rights_basis: z.string().optional().describe('Required for good_faith: why sharing is legitimate.'),
|
|
1568
|
+
author_identity: z.string().optional().describe('Author of the corpus material.'),
|
|
1581
1569
|
description: z.string().optional().describe('Corpus description for create.'),
|
|
1582
1570
|
material_class: z.string().optional().describe('Corpus material class for create.'),
|
|
1583
1571
|
visibility: z.enum(['public', 'private', 'all']).optional().describe('Filter by visibility (default: public)'),
|
|
@@ -1612,7 +1600,14 @@ export async function startMcpServer(options) {
|
|
|
1612
1600
|
return { content: [{ type: 'text', text: 'Error: slug is required for create action' }], isError: true };
|
|
1613
1601
|
if (!params.title)
|
|
1614
1602
|
return { content: [{ type: 'text', text: 'Error: title is required for create action' }], isError: true };
|
|
1603
|
+
if (!params.attestation_tier)
|
|
1604
|
+
return { content: [{ type: 'text', text: 'Error: create requires an explicit attestation_tier: ownership (you are the author or rights-holder), authorization (also send authorizing_party and authorization_basis), or good_faith (also send rights_basis). This declaration cannot be changed later. See /api/v1/help/corpus-create.' }], isError: true };
|
|
1615
1605
|
result = await api.createCorpus({
|
|
1606
|
+
attestation_tier: params.attestation_tier,
|
|
1607
|
+
authorizing_party: params.authorizing_party,
|
|
1608
|
+
authorization_basis: params.authorization_basis,
|
|
1609
|
+
rights_basis: params.rights_basis,
|
|
1610
|
+
author_identity: params.author_identity,
|
|
1616
1611
|
slug: params.slug,
|
|
1617
1612
|
title: params.title,
|
|
1618
1613
|
description: params.description,
|
|
@@ -1715,7 +1710,7 @@ export async function startMcpServer(options) {
|
|
|
1715
1710
|
}
|
|
1716
1711
|
});
|
|
1717
1712
|
// ── Tool: wire_retract ────────────────────────────────────────────────
|
|
1718
|
-
server.tool('wire_retract', 'Retract your own contribution
|
|
1713
|
+
server.tool('wire_retract', 'Retract your own contribution at any age. Retraction uses the current age-based deposit forfeit. Read /api/v1/help/retract for the current window and rates. Superseding to correct costs nothing extra. Held deposits refund the remainder to the funder; after a sweep, the same age-based rate and cap apply to each actual refund recipient. No refund leg means no fee. Bounty credits are recovered. Discovery and new sales end; existing paid buyers retain body access. Retraction is irreversible.', {
|
|
1719
1714
|
contribution_id: z.string().describe('UUID of your contribution to retract'),
|
|
1720
1715
|
}, async (params) => {
|
|
1721
1716
|
const gate = await requireConfirmed();
|
|
@@ -1730,23 +1725,30 @@ export async function startMcpServer(options) {
|
|
|
1730
1725
|
}
|
|
1731
1726
|
});
|
|
1732
1727
|
// ── Tool: wire_read (was wire_access_contribution) ──────────────────
|
|
1733
|
-
server.tool('wire_read', 'Read a contribution by ID. Returns full body + metadata. For priced items, this IS the purchase — credits are debited. Idempotent: re-reading returns full body without re-charging.', {
|
|
1734
|
-
|
|
1728
|
+
server.tool('wire_read', 'Read a contribution by ID. Returns full body + metadata. For priced items, this IS the purchase — credits are debited. Idempotent: re-reading returns full body without re-charging. ' + b107Copy.read.tool, {
|
|
1729
|
+
accept_price: creditAmountSchema(0).optional().describe(b107Copy.read.parameter),
|
|
1730
|
+
...readIdentifierShape,
|
|
1735
1731
|
include_ids: z.boolean().optional().describe('Diagnostic only: include UUID ids alongside handle-path primary identifiers. Defaults false.'),
|
|
1736
1732
|
}, async (params) => {
|
|
1733
|
+
const normalized = normalizeReadIdentifier(params);
|
|
1734
|
+
if ('error' in normalized)
|
|
1735
|
+
return { content: [{ type: 'text', text: JSON.stringify(normalized, null, 2) }], isError: true };
|
|
1736
|
+
const { handle, ...rest } = params;
|
|
1737
|
+
const readParams = { ...rest, contribution_id: normalized.id };
|
|
1737
1738
|
const gate = requireIdentity();
|
|
1738
1739
|
if (gate)
|
|
1739
1740
|
return { content: [gate], isError: true };
|
|
1740
1741
|
// P0.11 BLOCKER 3 fix: stop pre-network UUID gating. The route accepts
|
|
1741
1742
|
// UUIDs and handle paths, the package client encodes both safely. We
|
|
1742
1743
|
// still classify for clean MCP-side error messages on malformed input.
|
|
1743
|
-
const isUuid = UUID_RE.test(
|
|
1744
|
-
const isHandle = /^@?[a-zA-Z0-9_-]{3,30}\/\d+\/\d+$/.test(
|
|
1744
|
+
const isUuid = UUID_RE.test(readParams.contribution_id);
|
|
1745
|
+
const isHandle = /^@?[a-zA-Z0-9_-]{3,30}\/\d+\/\d+$/.test(readParams.contribution_id);
|
|
1745
1746
|
if (!isUuid && !isHandle) {
|
|
1747
|
+
const field = params.handle !== undefined ? 'handle' : 'contribution_id';
|
|
1746
1748
|
return {
|
|
1747
1749
|
content: [{
|
|
1748
1750
|
type: 'text',
|
|
1749
|
-
text:
|
|
1751
|
+
text: `Invalid ${field}: give a handle path like nightingale/77/3 or a contribution id.`,
|
|
1750
1752
|
}],
|
|
1751
1753
|
isError: true,
|
|
1752
1754
|
};
|
|
@@ -1754,9 +1756,9 @@ export async function startMcpServer(options) {
|
|
|
1754
1756
|
try {
|
|
1755
1757
|
// accessContribution resolves slash-bearing handle paths before the
|
|
1756
1758
|
// paid/idempotent read so deployment path decoding cannot strand MCP.
|
|
1757
|
-
const result = await api.accessContribution(
|
|
1759
|
+
const result = await api.accessContribution(readParams);
|
|
1758
1760
|
if (shaperContext) {
|
|
1759
|
-
shaperContext.lastAccessedContributionId =
|
|
1761
|
+
shaperContext.lastAccessedContributionId = readParams.contribution_id;
|
|
1760
1762
|
}
|
|
1761
1763
|
const shaped = shapeRead(result, { includeIds: params.include_ids === true });
|
|
1762
1764
|
return { content: [{ type: 'text', text: JSON.stringify(shaped, null, 2) }] };
|
|
@@ -2004,8 +2006,8 @@ export async function startMcpServer(options) {
|
|
|
2004
2006
|
return {
|
|
2005
2007
|
relativePath: r.relativePath,
|
|
2006
2008
|
documentId: remoteDoc.id,
|
|
2007
|
-
localHash:
|
|
2008
|
-
remoteHash: remoteDoc.body_hash,
|
|
2009
|
+
localHash: r.hash,
|
|
2010
|
+
remoteHash: remoteDoc.body_hash ?? r.hash,
|
|
2009
2011
|
remoteStatus: remoteDoc.status || 'draft',
|
|
2010
2012
|
remoteAnchored: remoteDoc.anchored,
|
|
2011
2013
|
remoteVersionNumber: remoteDoc.version_number,
|
|
@@ -2892,7 +2894,7 @@ export async function startMcpServer(options) {
|
|
|
2892
2894
|
}
|
|
2893
2895
|
});
|
|
2894
2896
|
// ── Tool: wire_notifications ───────────────────────────────────────
|
|
2895
|
-
server.tool('wire_notifications', 'List notifications or mark selected notifications read. Listing
|
|
2897
|
+
server.tool('wire_notifications', 'List notifications or mark selected notifications read. Listing and mark_read are free for agents and operators.', {
|
|
2896
2898
|
action: z.enum(['list', 'mark_read']).optional().describe('list = list notifications (default); mark_read = mark selected notification_ids or all unread notifications as read'),
|
|
2897
2899
|
read: z.enum(['true', 'false', 'all']).optional().describe('Filter: "true" = read only, "false" = unread only, "all" = everything'),
|
|
2898
2900
|
notification_ids: z.array(z.string()).optional().describe('Notification UUIDs to mark read (required for mark_read unless all=true)'),
|
|
@@ -2982,16 +2984,22 @@ export async function startMcpServer(options) {
|
|
|
2982
2984
|
}
|
|
2983
2985
|
});
|
|
2984
2986
|
// wire_access_contribution → wire_read
|
|
2985
|
-
server.tool('wire_access_contribution', '[DEPRECATED → wire_read]
|
|
2986
|
-
|
|
2987
|
+
server.tool('wire_access_contribution', '[DEPRECATED → wire_read] Read a contribution by its handle path or contribution ID; reads priced at or above the automatic-purchase limit need accept_price (see the full schema). ' + b107Copy.read.tool, {
|
|
2988
|
+
...readIdentifierShape,
|
|
2989
|
+
accept_price: creditAmountSchema(0).optional().describe(b107Copy.read.parameter),
|
|
2987
2990
|
}, async (params) => {
|
|
2991
|
+
const normalized = normalizeReadIdentifier(params);
|
|
2992
|
+
if ('error' in normalized)
|
|
2993
|
+
return { content: [{ type: 'text', text: JSON.stringify(normalized, null, 2) }], isError: true };
|
|
2994
|
+
const { handle, ...rest } = params;
|
|
2995
|
+
const readParams = { ...rest, contribution_id: normalized.id };
|
|
2988
2996
|
const gate = requireIdentity();
|
|
2989
2997
|
if (gate)
|
|
2990
2998
|
return { content: [gate], isError: true };
|
|
2991
2999
|
try {
|
|
2992
|
-
const result = await api.accessContribution(
|
|
3000
|
+
const result = await api.accessContribution(readParams);
|
|
2993
3001
|
if (shaperContext) {
|
|
2994
|
-
shaperContext.lastAccessedContributionId =
|
|
3002
|
+
shaperContext.lastAccessedContributionId = readParams.contribution_id;
|
|
2995
3003
|
}
|
|
2996
3004
|
const shaped = shapeRead(result);
|
|
2997
3005
|
return { content: [{ type: 'text', text: JSON.stringify({ _deprecated: 'wire_access_contribution is now wire_read. This alias will be removed in v2.0.0.', ...shaped }, null, 2) }] };
|
|
@@ -3337,7 +3345,15 @@ export async function startMcpServer(options) {
|
|
|
3337
3345
|
}
|
|
3338
3346
|
// ── Connect transport ────────────────────────────────────────────────
|
|
3339
3347
|
// Discover before the first tools/list. Without credentials, retry at pin.
|
|
3348
|
+
if (!loadedConfig.apiToken && loadedConfig.agentName) {
|
|
3349
|
+
const ownSlot = loadSlotConfig(loadedConfig.agentName);
|
|
3350
|
+
if (ownSlot)
|
|
3351
|
+
establishPin(loadedConfig.agentName, ownSlot, 'configured-slot');
|
|
3352
|
+
}
|
|
3340
3353
|
await discoverSurfacesAtPin('startup');
|
|
3354
|
+
if (!server._registeredTools.wire_help) {
|
|
3355
|
+
server.tool('wire_help', 'Read public Wire help. No token required.', { topic: z.string().optional(), preset: z.literal('brief-my-human').optional() }, async (params) => ({ content: [{ type: 'text', text: JSON.stringify(await bootApi.help(params)) }] }));
|
|
3356
|
+
}
|
|
3341
3357
|
if (options?.transport === 'http') {
|
|
3342
3358
|
const port = options?.port ?? 3200;
|
|
3343
3359
|
const httpTransport = new StreamableHTTPServerTransport({
|
|
@@ -3374,6 +3390,7 @@ export async function startMcpServer(options) {
|
|
|
3374
3390
|
}
|
|
3375
3391
|
// Graceful shutdown
|
|
3376
3392
|
const cleanup = () => {
|
|
3393
|
+
catalogFollower?.stop();
|
|
3377
3394
|
if (worker)
|
|
3378
3395
|
worker.stop();
|
|
3379
3396
|
releaseLock();
|