@agent-wire/terminal 1.7.9 → 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 +488 -451
- package/dist/api-token.d.ts +2 -0
- package/dist/api-token.js +4 -0
- package/dist/api-token.js.map +1 -1
- package/dist/api.d.ts +35 -8
- package/dist/api.js +25 -12
- 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.js +14 -12
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.js +36 -2
- package/dist/config.js.map +1 -1
- package/dist/mcp-server.d.ts +1 -6
- package/dist/mcp-server.js +119 -142
- 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 +22 -1
- package/dist/response-shaper.js.map +1 -1
- 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 +541 -8
- package/dist/tool-projection.js.map +1 -1
- package/package.json +1 -1
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';
|
|
@@ -11,12 +13,13 @@ import { readFile, writeFile, rename as fsRename, mkdir as fsMkdir, stat as fsSt
|
|
|
11
13
|
import { join, isAbsolute } from 'path';
|
|
12
14
|
import { loadConfig, saveConfig, CONFIG_DIR } from './config.js';
|
|
13
15
|
import { resolveApiToken } from './api-token.js';
|
|
14
|
-
import {
|
|
16
|
+
import { CatalogIdentityMismatch, startCatalogFollow, waitForCatalogReady } from './catalog-follow.js';
|
|
17
|
+
import { loadSlotConfig, saveSlotConfig, slotPath, migrateLegacyAgentsToSlots } from './slots.js';
|
|
15
18
|
import { WireAPI, WireApiRefusal, RegistrationError } from './api.js';
|
|
16
19
|
import { resumeToken } from './resume.js';
|
|
17
20
|
import { shapeStatus, shapeRead, shapeInspect, shapeGraph, shapeCorpora, shapeMyContributions, shapeEarnings, shapeOpportunities, shapeRate, shapePatrolScan, shapeNotifications, shapeTasks, shapeMessages, } from './response-shaper.js';
|
|
18
21
|
import { startBackgroundWorkLoop } from './background-worker.js';
|
|
19
|
-
import { parseToolDefinition, fetchCatalogDefinitionItems, invokeToolDefinition, dedupeToolDefinitions, withToolDefinitionAliases, fingerprintToolDefinitions,
|
|
22
|
+
import { parseToolDefinition, fetchCatalogDefinitionItems, invokeToolDefinition, dedupeToolDefinitions, withToolDefinitionAliases, fingerprintToolDefinitions, toolDefinitionInputShape, } from './tool-projection.js';
|
|
20
23
|
import {
|
|
21
24
|
// Mission #1.5-rev2: agents/ cache fully retired. loadAgentIdentity /
|
|
22
25
|
// saveAgentIdentity / deleteAgentIdentity are no longer imported here;
|
|
@@ -29,7 +32,6 @@ import { walkDirectory, computeDiff, pushFiles, pullFiles, parseManifest, serial
|
|
|
29
32
|
import { WIRE_HELP_POINTER } from './help-pointer.js';
|
|
30
33
|
import { pullLocalSubscriptions } from './local-pull.js';
|
|
31
34
|
const SURFACE_REFRESH_TTL_MS = 60_000;
|
|
32
|
-
const DEFAULT_TOOL_REFRESH_POLL_INTERVAL_MS = 90_000;
|
|
33
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"}).';
|
|
34
36
|
function normalizeSurfaceName(name) {
|
|
35
37
|
return name.trim().toLowerCase().replace(/[^a-z0-9_]+/g, '_').replace(/^_+|_+$/g, '');
|
|
@@ -74,10 +76,6 @@ function dedupeSurfaces(surfaces) {
|
|
|
74
76
|
}
|
|
75
77
|
return Array.from(byName.values()).sort((a, b) => a.surface.localeCompare(b.surface));
|
|
76
78
|
}
|
|
77
|
-
function extractQueryItems(response) {
|
|
78
|
-
const items = response.items ?? response.results ?? response.contributions;
|
|
79
|
-
return Array.isArray(items) ? items : [];
|
|
80
|
-
}
|
|
81
79
|
function normalizeApiRootPath(path) {
|
|
82
80
|
const trimmed = path.trim();
|
|
83
81
|
if (!trimmed)
|
|
@@ -85,11 +83,11 @@ function normalizeApiRootPath(path) {
|
|
|
85
83
|
const withSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
86
84
|
return withSlash.replace(/^\/api\/v1(?=\/|$)/, '') || '/';
|
|
87
85
|
}
|
|
88
|
-
async function fetchSurfaceConfigItems(api) {
|
|
89
|
-
return fetchCatalogDefinitionItems(api, 'surface');
|
|
86
|
+
async function fetchSurfaceConfigItems(api, forceRefresh = false) {
|
|
87
|
+
return fetchCatalogDefinitionItems(api, 'surface', forceRefresh);
|
|
90
88
|
}
|
|
91
|
-
async function refreshSubstrateSurfaces(api) {
|
|
92
|
-
const items = await fetchSurfaceConfigItems(api);
|
|
89
|
+
async function refreshSubstrateSurfaces(api, forceRefresh = false) {
|
|
90
|
+
const items = await fetchSurfaceConfigItems(api, forceRefresh);
|
|
93
91
|
return dedupeSurfaces(items
|
|
94
92
|
.filter((item) => item.type === 'surface_config')
|
|
95
93
|
.map(parseSurfaceConfig)
|
|
@@ -108,49 +106,6 @@ function fingerprintSubstrateSurfaces(surfaces) {
|
|
|
108
106
|
.sort((a, b) => a.toolName.localeCompare(b.toolName));
|
|
109
107
|
return createHash('sha256').update(JSON.stringify(projected)).digest('hex');
|
|
110
108
|
}
|
|
111
|
-
function numericStructuredValue(data) {
|
|
112
|
-
for (const key of ['poll_interval_ms', 'interval_ms', 'value_ms', 'value']) {
|
|
113
|
-
const value = data[key];
|
|
114
|
-
if (typeof value === 'number' && Number.isFinite(value))
|
|
115
|
-
return value;
|
|
116
|
-
if (typeof value === 'string' && value.trim()) {
|
|
117
|
-
const parsed = Number(value);
|
|
118
|
-
if (Number.isFinite(parsed))
|
|
119
|
-
return parsed;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
for (const key of ['poll_interval_seconds', 'interval_seconds', 'value_seconds']) {
|
|
123
|
-
const value = data[key];
|
|
124
|
-
if (typeof value === 'number' && Number.isFinite(value))
|
|
125
|
-
return value * 1000;
|
|
126
|
-
if (typeof value === 'string' && value.trim()) {
|
|
127
|
-
const parsed = Number(value);
|
|
128
|
-
if (Number.isFinite(parsed))
|
|
129
|
-
return parsed * 1000;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
function parseToolRefreshPollIntervalMs(items) {
|
|
135
|
-
const latest = items
|
|
136
|
-
.filter((item) => item.type === 'economic_parameter')
|
|
137
|
-
.filter((item) => {
|
|
138
|
-
const data = item.structured_data ?? {};
|
|
139
|
-
const name = data.parameter_name ?? data.name ?? data.key;
|
|
140
|
-
return (name === 'refresh_mcp_tools_poll_interval_ms' ||
|
|
141
|
-
name === 'refresh_mcp_tools_poll_interval_seconds' ||
|
|
142
|
-
name === 'mcp_tool_refresh_poll_interval_ms' ||
|
|
143
|
-
name === 'mcp_tool_refresh_poll_interval_seconds');
|
|
144
|
-
})
|
|
145
|
-
.at(-1);
|
|
146
|
-
if (!latest)
|
|
147
|
-
return { pollIntervalMs: DEFAULT_TOOL_REFRESH_POLL_INTERVAL_MS, source: 'default' };
|
|
148
|
-
const value = numericStructuredValue(latest.structured_data ?? {});
|
|
149
|
-
if (value === null || value <= 0) {
|
|
150
|
-
throw new Error(`Invalid refresh-MCP-tools economic_parameter ${latest.handle_path ?? '(unhandled)'}: expected a positive interval value`);
|
|
151
|
-
}
|
|
152
|
-
return { pollIntervalMs: Math.round(value), source: 'economic_parameter', handle: latest.handle_path };
|
|
153
|
-
}
|
|
154
109
|
function toolsAvailableMsg(verificationStatus) {
|
|
155
110
|
return verificationStatus === 'confirmed'
|
|
156
111
|
? 'All tools available.'
|
|
@@ -183,6 +138,7 @@ function buildResumeResponse(params) {
|
|
|
183
138
|
verification_status: resumedStatus,
|
|
184
139
|
help: WIRE_HELP_POINTER,
|
|
185
140
|
tools_available: toolsAvailableMsg(resumedStatus),
|
|
141
|
+
...(params.welcomeGrant ? { welcome_grant: params.welcomeGrant } : {}),
|
|
186
142
|
};
|
|
187
143
|
if (params.sinceLastSession && typeof params.sinceLastSession === 'object') {
|
|
188
144
|
const sls = params.sinceLastSession;
|
|
@@ -210,12 +166,11 @@ export const __mcpSurfaceTestInternals = {
|
|
|
210
166
|
normalizeApiRootPath,
|
|
211
167
|
SURFACE_REFRESH_TTL_MS,
|
|
212
168
|
fingerprintSubstrateSurfaces,
|
|
213
|
-
parseToolRefreshPollIntervalMs,
|
|
214
169
|
buildResumeResponse,
|
|
215
170
|
looksLikeDeviceAuthFailure,
|
|
216
171
|
};
|
|
217
172
|
export async function startMcpServer(options) {
|
|
218
|
-
const loadedConfig = loadConfig({ allowInvalidTokenFile: true });
|
|
173
|
+
const loadedConfig = loadConfig({ allowInvalidTokenFile: true, ownIdentityOnly: true });
|
|
219
174
|
loadedConfig.apiUrl = process.env.WIRE_API_URL ?? loadedConfig.apiUrl;
|
|
220
175
|
// MCP identity is slot-owned now. Keep node/base config, but deliberately
|
|
221
176
|
// strip any legacy config/env agent token before constructing pre-pin APIs.
|
|
@@ -229,8 +184,7 @@ export async function startMcpServer(options) {
|
|
|
229
184
|
// tool handlers go through the `api` closure variable below — bootApi
|
|
230
185
|
// is for identity-resolution operations only.
|
|
231
186
|
const bootApi = new WireAPI(config, threadId);
|
|
232
|
-
//
|
|
233
|
-
// identity through GET /me; failed validation leaves wire_identify to pin later.
|
|
187
|
+
// This contains only an explicit process credential, never the shared saved token.
|
|
234
188
|
const discoveryApi = new WireAPI(loadedConfig, threadId);
|
|
235
189
|
// `api` starts as bootApi (no agent token). Tool handlers MUST gate
|
|
236
190
|
// through requireIdentity() before using it; that gate ensures the
|
|
@@ -246,7 +200,8 @@ export async function startMcpServer(options) {
|
|
|
246
200
|
let identifyingName = null;
|
|
247
201
|
let worker = null;
|
|
248
202
|
let workerStartAttempted = false;
|
|
249
|
-
let
|
|
203
|
+
let catalogFollower;
|
|
204
|
+
let missingCatalogIdentityLogged = false;
|
|
250
205
|
// ── Migration on first boot ──────────────────────────────────────
|
|
251
206
|
// One-time copy from ~/.wire-node/agents/<name>.json to
|
|
252
207
|
// ~/.wire-node/slots/<slot>.json. Idempotent.
|
|
@@ -362,6 +317,12 @@ export async function startMcpServer(options) {
|
|
|
362
317
|
// Defensive — the handler-entry guard should have prevented this.
|
|
363
318
|
throw new Error(`Pin contract violation: tried to establish slot "${name}" while pinned to "${processSlot.name}"`);
|
|
364
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
|
+
}
|
|
365
326
|
// Build the API client bound to this slot's token. We build a fresh
|
|
366
327
|
// config object rather than mutating the closure-shared one — the
|
|
367
328
|
// disk-side config.apiToken stays untouched.
|
|
@@ -381,8 +342,10 @@ export async function startMcpServer(options) {
|
|
|
381
342
|
if (!config.nodeId) {
|
|
382
343
|
console.error('[wire-mcp] No node ID found, auto-registering after slot pin...');
|
|
383
344
|
try {
|
|
384
|
-
const node = await api.registerNode(config.nodeName, config.capabilities);
|
|
345
|
+
const node = await api.registerNode(config.nodeName, config.capabilities, { node_handle: config.nodeHandle });
|
|
385
346
|
config.nodeId = node.node_id;
|
|
347
|
+
if (node.registration_notice)
|
|
348
|
+
console.error(node.registration_notice);
|
|
386
349
|
saveConfig(config);
|
|
387
350
|
console.error(`[wire-mcp] Auto-registered node: ${node.node_id} (${config.nodeName})`);
|
|
388
351
|
}
|
|
@@ -509,8 +472,8 @@ export async function startMcpServer(options) {
|
|
|
509
472
|
});
|
|
510
473
|
dynamicSurfaceTools.set(surface.toolName, { ...registered, surface });
|
|
511
474
|
}
|
|
512
|
-
async function fetchToolDefinitions() {
|
|
513
|
-
const hydrated = await fetchCatalogDefinitionItems(
|
|
475
|
+
async function fetchToolDefinitions(forceRefresh = false) {
|
|
476
|
+
const hydrated = await fetchCatalogDefinitionItems(api, 'tool_definition', forceRefresh);
|
|
514
477
|
const definitions = withToolDefinitionAliases(dedupeToolDefinitions(hydrated
|
|
515
478
|
.filter((item) => item !== null)
|
|
516
479
|
.map((item) => parseToolDefinition(item))
|
|
@@ -519,7 +482,7 @@ export async function startMcpServer(options) {
|
|
|
519
482
|
}
|
|
520
483
|
function projectedToolDescription(def) {
|
|
521
484
|
const handle = def.configHandle ? ` Definition: ${def.configHandle}.` : '';
|
|
522
|
-
return `${def.description}${handle}`;
|
|
485
|
+
return `${def.description}${['wire_read', 'wire_access_contribution'].includes(def.toolName) ? ` ${b107Copy.read.tool}` : ''}${handle}`;
|
|
523
486
|
}
|
|
524
487
|
function registerProjectedCoreTool(def) {
|
|
525
488
|
const fingerprint = fingerprintToolDefinitions([def]);
|
|
@@ -547,13 +510,22 @@ export async function startMcpServer(options) {
|
|
|
547
510
|
const registered = server.registerTool(def.toolName, {
|
|
548
511
|
title: def.toolName,
|
|
549
512
|
description: projectedToolDescription(def),
|
|
550
|
-
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(),
|
|
551
516
|
_meta: {
|
|
552
517
|
tool_definition_handle: def.configHandle,
|
|
553
518
|
binding: def.binding,
|
|
554
519
|
discovery: 'tool_definition',
|
|
555
520
|
},
|
|
556
521
|
}, async (params) => {
|
|
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
|
+
}
|
|
557
529
|
const gate = def.toolName === 'wire_help' ? null : def.gate === 'identity' ? requireIdentity() : await requireConfirmed();
|
|
558
530
|
if (gate)
|
|
559
531
|
return { content: [gate], isError: true };
|
|
@@ -575,7 +547,9 @@ export async function startMcpServer(options) {
|
|
|
575
547
|
});
|
|
576
548
|
projectedCoreTools.set(def.toolName, { remove: registered.remove.bind(registered), fingerprint, definition: def });
|
|
577
549
|
}
|
|
578
|
-
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.
|
|
579
553
|
if (!forceRefresh &&
|
|
580
554
|
surfaceCache &&
|
|
581
555
|
toolDefinitionCache &&
|
|
@@ -596,7 +570,7 @@ export async function startMcpServer(options) {
|
|
|
596
570
|
// Reconcile atomically. A failed discovery read preserves session tools
|
|
597
571
|
// and the last good disk cache, including on a cold surface read.
|
|
598
572
|
const [toolDefinitions, surfaces] = await Promise.all([
|
|
599
|
-
fetchToolDefinitions(), refreshSubstrateSurfaces(
|
|
573
|
+
fetchToolDefinitions(forceCatalogRefresh), refreshSubstrateSurfaces(api, forceCatalogRefresh),
|
|
600
574
|
]);
|
|
601
575
|
void saveToolDefinitionsCache(toolDefinitions);
|
|
602
576
|
surfaceCache = { fetchedAt: Date.now(), surfaces };
|
|
@@ -665,6 +639,42 @@ export async function startMcpServer(options) {
|
|
|
665
639
|
}
|
|
666
640
|
console.error(`[wire-mcp] tool-definitions-cache: warm-booted ${cachedDefs.length} projected tool(s) from disk`);
|
|
667
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
|
+
}
|
|
668
678
|
try {
|
|
669
679
|
const result = await refreshDynamicSurfaceTools(true);
|
|
670
680
|
console.error(`[wire-mcp] Surface discovery (${source}): ${result.surfaces.length} surface_config tools registered`);
|
|
@@ -672,10 +682,6 @@ export async function startMcpServer(options) {
|
|
|
672
682
|
catch (err) {
|
|
673
683
|
console.error(`[wire-mcp] Surface discovery failed (${source}): ${err.message}`);
|
|
674
684
|
}
|
|
675
|
-
finally {
|
|
676
|
-
if (processSlot)
|
|
677
|
-
startToolRefreshPoller();
|
|
678
|
-
}
|
|
679
685
|
}
|
|
680
686
|
function notifyToolListChanged(reason) {
|
|
681
687
|
if (!server.isConnected())
|
|
@@ -683,52 +689,8 @@ export async function startMcpServer(options) {
|
|
|
683
689
|
server.sendToolListChanged();
|
|
684
690
|
console.error(`[wire-mcp] tools/list_changed emitted (${reason})`);
|
|
685
691
|
}
|
|
686
|
-
async function loadToolRefreshPollIntervalMs() {
|
|
687
|
-
try {
|
|
688
|
-
const result = await api.query({
|
|
689
|
-
text: 'refresh_mcp_tools_poll_interval economic_parameter',
|
|
690
|
-
domain: 'all',
|
|
691
|
-
limit: 25,
|
|
692
|
-
sort: 'recency',
|
|
693
|
-
});
|
|
694
|
-
const policy = parseToolRefreshPollIntervalMs(extractQueryItems(result));
|
|
695
|
-
if (policy.source === 'default') {
|
|
696
|
-
console.error(`[wire-mcp] WARNING: refresh-MCP-tools economic_parameter not found; using default poll interval ${policy.pollIntervalMs}ms`);
|
|
697
|
-
}
|
|
698
|
-
else {
|
|
699
|
-
console.error(`[wire-mcp] refresh-MCP-tools poll interval ${policy.pollIntervalMs}ms from ${policy.handle}`);
|
|
700
|
-
}
|
|
701
|
-
return policy.pollIntervalMs;
|
|
702
|
-
}
|
|
703
|
-
catch (err) {
|
|
704
|
-
console.error(`[wire-mcp] WARNING: refresh-MCP-tools policy load failed: ${err.message}; using default poll interval`);
|
|
705
|
-
return DEFAULT_TOOL_REFRESH_POLL_INTERVAL_MS;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
function startToolRefreshPoller() {
|
|
709
|
-
if (toolRefreshPollerStarted)
|
|
710
|
-
return;
|
|
711
|
-
toolRefreshPollerStarted = true;
|
|
712
|
-
void (async () => {
|
|
713
|
-
const intervalMs = await loadToolRefreshPollIntervalMs();
|
|
714
|
-
const timer = setInterval(() => {
|
|
715
|
-
void (async () => {
|
|
716
|
-
try {
|
|
717
|
-
const result = await refreshDynamicSurfaceTools(true);
|
|
718
|
-
if (result.changed || result.added.length > 0 || result.removed.length > 0) {
|
|
719
|
-
notifyToolListChanged('auto-refresh');
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
catch (err) {
|
|
723
|
-
console.error(`[wire-mcp] WARNING: refresh-MCP-tools auto refresh failed: ${err.message}`);
|
|
724
|
-
}
|
|
725
|
-
})();
|
|
726
|
-
}, intervalMs);
|
|
727
|
-
timer.unref?.();
|
|
728
|
-
})();
|
|
729
|
-
}
|
|
730
692
|
async function refreshToolsResultJson(reason) {
|
|
731
|
-
const result = await refreshDynamicSurfaceTools(true);
|
|
693
|
+
const result = await refreshDynamicSurfaceTools(true, true);
|
|
732
694
|
notifyToolListChanged(reason);
|
|
733
695
|
return JSON.stringify({
|
|
734
696
|
refreshed: true,
|
|
@@ -846,6 +808,7 @@ export async function startMcpServer(options) {
|
|
|
846
808
|
operator: processSlot.identity.operatorEmail,
|
|
847
809
|
verification_status: vs,
|
|
848
810
|
pool_credits: me?.pool_credits ?? 'unknown',
|
|
811
|
+
...(me?.welcome_grant ? { welcome_grant: me.welcome_grant } : {}),
|
|
849
812
|
note: 'Process is already pinned to this slot; no re-resolution needed.',
|
|
850
813
|
help: WIRE_HELP_POINTER,
|
|
851
814
|
tools_available: toolsAvailableMsg(vs),
|
|
@@ -877,6 +840,7 @@ export async function startMcpServer(options) {
|
|
|
877
840
|
identity: `${me?.name || processSlot.name} (concurrent same-name call resolved)`,
|
|
878
841
|
verification_status: vs,
|
|
879
842
|
pool_credits: me?.pool_credits ?? 'unknown',
|
|
843
|
+
...(me?.welcome_grant ? { welcome_grant: me.welcome_grant } : {}),
|
|
880
844
|
help: WIRE_HELP_POINTER,
|
|
881
845
|
tools_available: toolsAvailableMsg(vs),
|
|
882
846
|
}, null, 2) }],
|
|
@@ -955,6 +919,7 @@ export async function startMcpServer(options) {
|
|
|
955
919
|
operator: existing.operatorEmail,
|
|
956
920
|
verification_status: freshStatus,
|
|
957
921
|
pool_credits: me.pool_credits ?? 'unknown',
|
|
922
|
+
...(me.welcome_grant ? { welcome_grant: me.welcome_grant } : {}),
|
|
958
923
|
help: WIRE_HELP_POINTER,
|
|
959
924
|
tools_available: toolsAvailableMsg(freshStatus),
|
|
960
925
|
}, null, 2) }],
|
|
@@ -1098,6 +1063,7 @@ export async function startMcpServer(options) {
|
|
|
1098
1063
|
status: 'registered',
|
|
1099
1064
|
identity: `${params.name} (pseudo-ID: ${result.pseudo_id})`,
|
|
1100
1065
|
verification_status: result.verification_status,
|
|
1066
|
+
...(result.welcome_grant ? { welcome_grant: result.welcome_grant, pool_credits: result.pool_credits } : {}),
|
|
1101
1067
|
verification: isConfirmed
|
|
1102
1068
|
? 'Operator already verified. Full access granted.'
|
|
1103
1069
|
: `Approval required: check ${params.operator_email} for verification and approval emails.`,
|
|
@@ -1149,6 +1115,7 @@ export async function startMcpServer(options) {
|
|
|
1149
1115
|
operatorEmail: params.operator_email,
|
|
1150
1116
|
verificationStatus: resumed.verification_status,
|
|
1151
1117
|
sinceLastSession: resumed.since_last_session,
|
|
1118
|
+
welcomeGrant: await api.getMe().then(me => me.welcome_grant).catch(() => undefined),
|
|
1152
1119
|
}), null, 2) }],
|
|
1153
1120
|
};
|
|
1154
1121
|
}
|
|
@@ -1217,6 +1184,7 @@ export async function startMcpServer(options) {
|
|
|
1217
1184
|
status: 'registered',
|
|
1218
1185
|
identity: `${params.name} (pseudo-ID: ${result.pseudo_id})`,
|
|
1219
1186
|
verification_status: result.verification_status,
|
|
1187
|
+
...(result.welcome_grant ? { welcome_grant: result.welcome_grant, pool_credits: result.pool_credits } : {}),
|
|
1220
1188
|
help: WIRE_HELP_POINTER,
|
|
1221
1189
|
tools_available: toolsAvailableMsg(result.verification_status),
|
|
1222
1190
|
};
|
|
@@ -1295,6 +1263,7 @@ export async function startMcpServer(options) {
|
|
|
1295
1263
|
operatorEmail: params.operator_email,
|
|
1296
1264
|
verificationStatus: resumedStatus,
|
|
1297
1265
|
sinceLastSession: resumed.since_last_session,
|
|
1266
|
+
welcomeGrant: await api.getMe().then(me => me.welcome_grant).catch(() => undefined),
|
|
1298
1267
|
}), null, 2) }],
|
|
1299
1268
|
};
|
|
1300
1269
|
}
|
|
@@ -1353,6 +1322,7 @@ export async function startMcpServer(options) {
|
|
|
1353
1322
|
status: 'registered',
|
|
1354
1323
|
identity: `${params.name} (pseudo-ID: ${result.pseudo_id})`,
|
|
1355
1324
|
verification_status: result.verification_status,
|
|
1325
|
+
...(result.welcome_grant ? { welcome_grant: result.welcome_grant, pool_credits: result.pool_credits } : {}),
|
|
1356
1326
|
help: WIRE_HELP_POINTER,
|
|
1357
1327
|
tools_available: toolsAvailableMsg(result.verification_status),
|
|
1358
1328
|
};
|
|
@@ -1445,7 +1415,7 @@ export async function startMcpServer(options) {
|
|
|
1445
1415
|
'Reputation is algorithmic and never manually adjusted.',
|
|
1446
1416
|
'Pseudo-IDs protect agent privacy. Master identity mappings are never publicly disclosed.',
|
|
1447
1417
|
'Self-dealing between your own agents is prohibited.',
|
|
1448
|
-
'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.',
|
|
1449
1419
|
'AI-generated content may contain errors — independently verify important information.',
|
|
1450
1420
|
'Dispute resolution via binding arbitration (Delaware). 30-day opt-out window available.',
|
|
1451
1421
|
],
|
|
@@ -1740,7 +1710,7 @@ export async function startMcpServer(options) {
|
|
|
1740
1710
|
}
|
|
1741
1711
|
});
|
|
1742
1712
|
// ── Tool: wire_retract ────────────────────────────────────────────────
|
|
1743
|
-
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.', {
|
|
1744
1714
|
contribution_id: z.string().describe('UUID of your contribution to retract'),
|
|
1745
1715
|
}, async (params) => {
|
|
1746
1716
|
const gate = await requireConfirmed();
|
|
@@ -1755,23 +1725,30 @@ export async function startMcpServer(options) {
|
|
|
1755
1725
|
}
|
|
1756
1726
|
});
|
|
1757
1727
|
// ── Tool: wire_read (was wire_access_contribution) ──────────────────
|
|
1758
|
-
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.', {
|
|
1759
|
-
|
|
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,
|
|
1760
1731
|
include_ids: z.boolean().optional().describe('Diagnostic only: include UUID ids alongside handle-path primary identifiers. Defaults false.'),
|
|
1761
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 };
|
|
1762
1738
|
const gate = requireIdentity();
|
|
1763
1739
|
if (gate)
|
|
1764
1740
|
return { content: [gate], isError: true };
|
|
1765
1741
|
// P0.11 BLOCKER 3 fix: stop pre-network UUID gating. The route accepts
|
|
1766
1742
|
// UUIDs and handle paths, the package client encodes both safely. We
|
|
1767
1743
|
// still classify for clean MCP-side error messages on malformed input.
|
|
1768
|
-
const isUuid = UUID_RE.test(
|
|
1769
|
-
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);
|
|
1770
1746
|
if (!isUuid && !isHandle) {
|
|
1747
|
+
const field = params.handle !== undefined ? 'handle' : 'contribution_id';
|
|
1771
1748
|
return {
|
|
1772
1749
|
content: [{
|
|
1773
1750
|
type: 'text',
|
|
1774
|
-
text:
|
|
1751
|
+
text: `Invalid ${field}: give a handle path like nightingale/77/3 or a contribution id.`,
|
|
1775
1752
|
}],
|
|
1776
1753
|
isError: true,
|
|
1777
1754
|
};
|
|
@@ -1779,9 +1756,9 @@ export async function startMcpServer(options) {
|
|
|
1779
1756
|
try {
|
|
1780
1757
|
// accessContribution resolves slash-bearing handle paths before the
|
|
1781
1758
|
// paid/idempotent read so deployment path decoding cannot strand MCP.
|
|
1782
|
-
const result = await api.accessContribution(
|
|
1759
|
+
const result = await api.accessContribution(readParams);
|
|
1783
1760
|
if (shaperContext) {
|
|
1784
|
-
shaperContext.lastAccessedContributionId =
|
|
1761
|
+
shaperContext.lastAccessedContributionId = readParams.contribution_id;
|
|
1785
1762
|
}
|
|
1786
1763
|
const shaped = shapeRead(result, { includeIds: params.include_ids === true });
|
|
1787
1764
|
return { content: [{ type: 'text', text: JSON.stringify(shaped, null, 2) }] };
|
|
@@ -2029,8 +2006,8 @@ export async function startMcpServer(options) {
|
|
|
2029
2006
|
return {
|
|
2030
2007
|
relativePath: r.relativePath,
|
|
2031
2008
|
documentId: remoteDoc.id,
|
|
2032
|
-
localHash:
|
|
2033
|
-
remoteHash: remoteDoc.body_hash,
|
|
2009
|
+
localHash: r.hash,
|
|
2010
|
+
remoteHash: remoteDoc.body_hash ?? r.hash,
|
|
2034
2011
|
remoteStatus: remoteDoc.status || 'draft',
|
|
2035
2012
|
remoteAnchored: remoteDoc.anchored,
|
|
2036
2013
|
remoteVersionNumber: remoteDoc.version_number,
|
|
@@ -2917,7 +2894,7 @@ export async function startMcpServer(options) {
|
|
|
2917
2894
|
}
|
|
2918
2895
|
});
|
|
2919
2896
|
// ── Tool: wire_notifications ───────────────────────────────────────
|
|
2920
|
-
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.', {
|
|
2921
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'),
|
|
2922
2899
|
read: z.enum(['true', 'false', 'all']).optional().describe('Filter: "true" = read only, "false" = unread only, "all" = everything'),
|
|
2923
2900
|
notification_ids: z.array(z.string()).optional().describe('Notification UUIDs to mark read (required for mark_read unless all=true)'),
|
|
@@ -3007,16 +2984,22 @@ export async function startMcpServer(options) {
|
|
|
3007
2984
|
}
|
|
3008
2985
|
});
|
|
3009
2986
|
// wire_access_contribution → wire_read
|
|
3010
|
-
server.tool('wire_access_contribution', '[DEPRECATED → wire_read]
|
|
3011
|
-
|
|
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),
|
|
3012
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 };
|
|
3013
2996
|
const gate = requireIdentity();
|
|
3014
2997
|
if (gate)
|
|
3015
2998
|
return { content: [gate], isError: true };
|
|
3016
2999
|
try {
|
|
3017
|
-
const result = await api.accessContribution(
|
|
3000
|
+
const result = await api.accessContribution(readParams);
|
|
3018
3001
|
if (shaperContext) {
|
|
3019
|
-
shaperContext.lastAccessedContributionId =
|
|
3002
|
+
shaperContext.lastAccessedContributionId = readParams.contribution_id;
|
|
3020
3003
|
}
|
|
3021
3004
|
const shaped = shapeRead(result);
|
|
3022
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) }] };
|
|
@@ -3362,17 +3345,10 @@ export async function startMcpServer(options) {
|
|
|
3362
3345
|
}
|
|
3363
3346
|
// ── Connect transport ────────────────────────────────────────────────
|
|
3364
3347
|
// Discover before the first tools/list. Without credentials, retry at pin.
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
resumed = await resumeToken(discoveryApi, configuredToken);
|
|
3370
|
-
}
|
|
3371
|
-
catch (error) {
|
|
3372
|
-
console.error('[wire-mcp] Configured-token resume failed; continuing unpinned:', error instanceof Error ? error.message : String(error));
|
|
3373
|
-
}
|
|
3374
|
-
if (resumed)
|
|
3375
|
-
establishPin(resumed.name, resumed.identity, 'configured-token');
|
|
3348
|
+
if (!loadedConfig.apiToken && loadedConfig.agentName) {
|
|
3349
|
+
const ownSlot = loadSlotConfig(loadedConfig.agentName);
|
|
3350
|
+
if (ownSlot)
|
|
3351
|
+
establishPin(loadedConfig.agentName, ownSlot, 'configured-slot');
|
|
3376
3352
|
}
|
|
3377
3353
|
await discoverSurfacesAtPin('startup');
|
|
3378
3354
|
if (!server._registeredTools.wire_help) {
|
|
@@ -3414,6 +3390,7 @@ export async function startMcpServer(options) {
|
|
|
3414
3390
|
}
|
|
3415
3391
|
// Graceful shutdown
|
|
3416
3392
|
const cleanup = () => {
|
|
3393
|
+
catalogFollower?.stop();
|
|
3417
3394
|
if (worker)
|
|
3418
3395
|
worker.stop();
|
|
3419
3396
|
releaseLock();
|