@agent-wire/terminal 1.7.1 → 1.7.4
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 +11 -0
- package/dist/api.d.ts +2 -1
- package/dist/api.js +2 -2
- package/dist/api.js.map +1 -1
- package/dist/cli.js +49 -8
- package/dist/cli.js.map +1 -1
- package/dist/identity.d.ts +1 -0
- package/dist/identity.js +6 -0
- package/dist/identity.js.map +1 -1
- package/dist/mcp-server.d.ts +10 -0
- package/dist/mcp-server.js +377 -342
- package/dist/mcp-server.js.map +1 -1
- package/dist/tool-projection.d.ts +58 -0
- package/dist/tool-projection.js +144 -0
- package/dist/tool-projection.js.map +1 -0
- package/package.json +1 -1
package/dist/mcp-server.js
CHANGED
|
@@ -5,20 +5,21 @@ import { createServer } from 'node:http';
|
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { hostname } from 'os';
|
|
7
7
|
import { createHash, randomUUID } from 'crypto';
|
|
8
|
-
import { readFile, writeFile, stat as fsStat } from 'fs/promises';
|
|
8
|
+
import { readFile, writeFile, rename as fsRename, mkdir as fsMkdir, stat as fsStat } from 'fs/promises';
|
|
9
9
|
import { join, isAbsolute } from 'path';
|
|
10
|
-
import { loadConfig, saveConfig } from './config.js';
|
|
10
|
+
import { loadConfig, saveConfig, CONFIG_DIR } from './config.js';
|
|
11
11
|
import { loadSlotConfig, saveSlotConfig, migrateLegacyAgentsToSlots } from './slots.js';
|
|
12
12
|
import { WireAPI } from './api.js';
|
|
13
|
-
import { shapeStatus,
|
|
13
|
+
import { shapeStatus, shapeRead, shapeInspect, shapeGraph, shapeCorpora, shapeMyContributions, shapeEarnings, shapePulse, shapeOpportunities, shapeRate, shapePatrolScan, shapeNotifications, shapeTasks, shapeMessages, } from './response-shaper.js';
|
|
14
14
|
import { startBackgroundWorkLoop } from './background-worker.js';
|
|
15
|
+
import { parseToolDefinition, dedupeToolDefinitions, fingerprintToolDefinitions, jsonSchemaToZodShape, } from './tool-projection.js';
|
|
15
16
|
import {
|
|
16
17
|
// Mission #1.5-rev2: agents/ cache fully retired. loadAgentIdentity /
|
|
17
18
|
// saveAgentIdentity / deleteAgentIdentity are no longer imported here;
|
|
18
19
|
// their replacements live in slots.ts. The legacy functions in
|
|
19
20
|
// identity.ts remain for backward compat with wait-cli.ts during the
|
|
20
21
|
// migration window.
|
|
21
|
-
loadDeviceRecord, saveDeviceRecord, loadAuthPending, saveAuthPending, deleteAuthPending, getStableDeviceFingerprint, hasAnyLocalIdentityForEmail, acquireIdentityLock, releaseIdentityLock, } from './identity.js';
|
|
22
|
+
loadDeviceRecord, saveDeviceRecord, deleteDeviceRecord, loadAuthPending, saveAuthPending, deleteAuthPending, getStableDeviceFingerprint, hasAnyLocalIdentityForEmail, acquireIdentityLock, releaseIdentityLock, } from './identity.js';
|
|
22
23
|
import { acquireLock, releaseLock, isWorkerRunning } from './lockfile.js';
|
|
23
24
|
import { walkDirectory, computeDiff, pushFiles, pullFiles, parseManifest, serializeManifest, updateManifest, paginateAll, } from './sync/index.js';
|
|
24
25
|
import { WIRE_HELP_POINTER } from './help-pointer.js';
|
|
@@ -147,6 +148,46 @@ function parseToolRefreshPollIntervalMs(items) {
|
|
|
147
148
|
}
|
|
148
149
|
return { pollIntervalMs: Math.round(value), source: 'economic_parameter', handle: latest.handle_path };
|
|
149
150
|
}
|
|
151
|
+
function toolsAvailableMsg(verificationStatus) {
|
|
152
|
+
return verificationStatus === 'confirmed'
|
|
153
|
+
? 'All tools available.'
|
|
154
|
+
: 'Pending verification: wire_identify, wire_query, wire_graph, wire_inspect, wire_status, wire_corpora, wire_legal, wire_pulse, wire_opportunities, wire_roster 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.';
|
|
155
|
+
}
|
|
156
|
+
function looksLikeDeviceAuthFailure(error) {
|
|
157
|
+
if (!error)
|
|
158
|
+
return false;
|
|
159
|
+
return /\b(401|410)\b|revoked|device authorization|device secret|unauthorized/i.test(error.message);
|
|
160
|
+
}
|
|
161
|
+
function buildResumeResponse(params) {
|
|
162
|
+
const resumedStatus = typeof params.verificationStatus === 'string'
|
|
163
|
+
? params.verificationStatus
|
|
164
|
+
: 'unknown';
|
|
165
|
+
const resumeResponse = {
|
|
166
|
+
status: 'resumed',
|
|
167
|
+
identity: `${params.requestedName} (pseudo-ID: ${params.pseudoId})`,
|
|
168
|
+
operator: params.operatorEmail,
|
|
169
|
+
verification_status: resumedStatus,
|
|
170
|
+
help: WIRE_HELP_POINTER,
|
|
171
|
+
tools_available: toolsAvailableMsg(resumedStatus),
|
|
172
|
+
};
|
|
173
|
+
if (params.sinceLastSession && typeof params.sinceLastSession === 'object') {
|
|
174
|
+
const sls = params.sinceLastSession;
|
|
175
|
+
const parts = [];
|
|
176
|
+
if (sls.new_messages > 0)
|
|
177
|
+
parts.push(`${sls.new_messages} new message${sls.new_messages !== 1 ? 's' : ''}`);
|
|
178
|
+
if (sls.new_tasks > 0)
|
|
179
|
+
parts.push(`${sls.new_tasks} new task${sls.new_tasks !== 1 ? 's' : ''}`);
|
|
180
|
+
if (sls.new_contributions > 0)
|
|
181
|
+
parts.push(`${sls.new_contributions} new contribution${sls.new_contributions !== 1 ? 's' : ''}`);
|
|
182
|
+
if (sls.credits_earned > 0)
|
|
183
|
+
parts.push(`${sls.credits_earned} credits earned`);
|
|
184
|
+
if (parts.length > 0) {
|
|
185
|
+
resumeResponse.while_you_were_away = `While you were away: ${parts.join(', ')}.`;
|
|
186
|
+
}
|
|
187
|
+
resumeResponse.since_last_session = sls;
|
|
188
|
+
}
|
|
189
|
+
return resumeResponse;
|
|
190
|
+
}
|
|
150
191
|
export const __mcpSurfaceTestInternals = {
|
|
151
192
|
normalizeSurfaceName,
|
|
152
193
|
surfaceToolName,
|
|
@@ -156,6 +197,8 @@ export const __mcpSurfaceTestInternals = {
|
|
|
156
197
|
SURFACE_REFRESH_TTL_MS,
|
|
157
198
|
fingerprintSubstrateSurfaces,
|
|
158
199
|
parseToolRefreshPollIntervalMs,
|
|
200
|
+
buildResumeResponse,
|
|
201
|
+
looksLikeDeviceAuthFailure,
|
|
159
202
|
};
|
|
160
203
|
export async function startMcpServer(options) {
|
|
161
204
|
const loadedConfig = loadConfig();
|
|
@@ -341,7 +384,46 @@ export async function startMcpServer(options) {
|
|
|
341
384
|
version: '1.0.0',
|
|
342
385
|
});
|
|
343
386
|
const dynamicSurfaceTools = new Map();
|
|
387
|
+
const projectedCoreTools = new Map();
|
|
344
388
|
let surfaceCache = null;
|
|
389
|
+
let toolDefinitionCache = null;
|
|
390
|
+
// ── Disk warm-boot cache for projected tool definitions ──────────────
|
|
391
|
+
// After each successful fetchToolDefinitions(), the parsed ToolDefinition[]
|
|
392
|
+
// is persisted as JSON to tool-definitions-cache.json in CONFIG_DIR via an
|
|
393
|
+
// atomic tmp-file + rename. At startup (discoverSurfacesAtPin), the cache is
|
|
394
|
+
// loaded first so projected tools exist before the first network refresh.
|
|
395
|
+
// A corrupt or unreadable cache is silently ignored and overwritten on next
|
|
396
|
+
// refresh. The cache is never a source of truth — the next successful refresh
|
|
397
|
+
// always overwrites it and removes tools whose definitions disappeared.
|
|
398
|
+
const TOOL_DEF_CACHE_PATH = join(CONFIG_DIR, 'tool-definitions-cache.json');
|
|
399
|
+
const TOOL_DEF_CACHE_TMP_PATH = join(CONFIG_DIR, 'tool-definitions-cache.json.tmp');
|
|
400
|
+
async function saveToolDefinitionsCache(definitions) {
|
|
401
|
+
try {
|
|
402
|
+
await fsMkdir(CONFIG_DIR, { recursive: true });
|
|
403
|
+
await writeFile(TOOL_DEF_CACHE_TMP_PATH, JSON.stringify(definitions, null, 2), { mode: 0o600 });
|
|
404
|
+
await fsRename(TOOL_DEF_CACHE_TMP_PATH, TOOL_DEF_CACHE_PATH);
|
|
405
|
+
}
|
|
406
|
+
catch (err) {
|
|
407
|
+
console.error(`[wire-mcp] tool-definitions-cache: save failed (non-fatal): ${err.message}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
async function loadToolDefinitionsCache() {
|
|
411
|
+
try {
|
|
412
|
+
const raw = await readFile(TOOL_DEF_CACHE_PATH, 'utf-8');
|
|
413
|
+
const parsed = JSON.parse(raw);
|
|
414
|
+
if (!Array.isArray(parsed))
|
|
415
|
+
return [];
|
|
416
|
+
return parsed.filter((item) => item !== null && typeof item === 'object' &&
|
|
417
|
+
typeof item.toolName === 'string' &&
|
|
418
|
+
typeof item.description === 'string' &&
|
|
419
|
+
item.parameterSchema !== null && typeof item.parameterSchema === 'object' &&
|
|
420
|
+
item.binding !== null && typeof item.binding === 'object');
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
// Missing or corrupt cache is not an error — will be rebuilt on next refresh.
|
|
424
|
+
return [];
|
|
425
|
+
}
|
|
426
|
+
}
|
|
345
427
|
let activeToolSetFingerprint = null;
|
|
346
428
|
function surfaceToolDescription(surface) {
|
|
347
429
|
const handle = surface.configHandle ? ` Config: ${surface.configHandle}.` : '';
|
|
@@ -400,39 +482,157 @@ export async function startMcpServer(options) {
|
|
|
400
482
|
});
|
|
401
483
|
dynamicSurfaceTools.set(surface.toolName, { ...registered, surface });
|
|
402
484
|
}
|
|
485
|
+
async function fetchToolDefinitions() {
|
|
486
|
+
const response = await api.query({ text: 'tool_definition', domain: 'all', limit: 100, sort: 'recency' });
|
|
487
|
+
const items = extractQueryItems(response);
|
|
488
|
+
const toolItems = items.filter((item) => item.type === 'tool_definition');
|
|
489
|
+
// Query items are teasers — structured_data is absent. Hydrate each
|
|
490
|
+
// tool_definition item with a full contribution read so parseToolDefinition
|
|
491
|
+
// receives the complete structured_data payload.
|
|
492
|
+
const hydrated = await Promise.all(toolItems.map(async (item) => {
|
|
493
|
+
const identifier = item.handle_path ?? item.id;
|
|
494
|
+
if (!identifier)
|
|
495
|
+
return null;
|
|
496
|
+
try {
|
|
497
|
+
const full = await api.accessContribution(identifier);
|
|
498
|
+
return {
|
|
499
|
+
type: full.type,
|
|
500
|
+
title: full.title,
|
|
501
|
+
handle_path: (full.handle_path ?? identifier),
|
|
502
|
+
structured_data: full.structured_data,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
catch (err) {
|
|
506
|
+
console.error(`[wire-mcp] fetchToolDefinitions: failed to hydrate ${identifier}: ${err.message}`);
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
}));
|
|
510
|
+
const definitions = dedupeToolDefinitions(hydrated
|
|
511
|
+
.filter((item) => item !== null)
|
|
512
|
+
.map((item) => parseToolDefinition(item))
|
|
513
|
+
.filter((def) => def !== null));
|
|
514
|
+
// Persist to disk cache (atomic write). Errors are non-fatal.
|
|
515
|
+
void saveToolDefinitionsCache(definitions);
|
|
516
|
+
return definitions;
|
|
517
|
+
}
|
|
518
|
+
function projectedToolDescription(def) {
|
|
519
|
+
const handle = def.configHandle ? ` Definition: ${def.configHandle}.` : '';
|
|
520
|
+
return `${def.description}${handle}`;
|
|
521
|
+
}
|
|
522
|
+
function registerProjectedCoreTool(def) {
|
|
523
|
+
const fingerprint = fingerprintToolDefinitions([def]);
|
|
524
|
+
const existing = projectedCoreTools.get(def.toolName);
|
|
525
|
+
if (existing) {
|
|
526
|
+
if (existing.fingerprint === fingerprint)
|
|
527
|
+
return;
|
|
528
|
+
existing.remove();
|
|
529
|
+
projectedCoreTools.delete(def.toolName);
|
|
530
|
+
}
|
|
531
|
+
const registered = server.registerTool(def.toolName, {
|
|
532
|
+
title: def.toolName,
|
|
533
|
+
description: projectedToolDescription(def),
|
|
534
|
+
inputSchema: jsonSchemaToZodShape(def.parameterSchema),
|
|
535
|
+
_meta: {
|
|
536
|
+
tool_definition_handle: def.configHandle,
|
|
537
|
+
binding: def.binding,
|
|
538
|
+
discovery: 'tool_definition',
|
|
539
|
+
},
|
|
540
|
+
}, async (params) => {
|
|
541
|
+
const gate = def.gate === 'identity' ? requireIdentity() : await requireConfirmed();
|
|
542
|
+
if (gate)
|
|
543
|
+
return { content: [gate], isError: true };
|
|
544
|
+
try {
|
|
545
|
+
let result;
|
|
546
|
+
if (def.binding.kind === 'api_method') {
|
|
547
|
+
const method = api[def.binding.method ?? ''];
|
|
548
|
+
if (typeof method !== 'function') {
|
|
549
|
+
throw new Error(`tool_definition ${def.configHandle ?? def.toolName} binds to unknown adapter method '${def.binding.method}'`);
|
|
550
|
+
}
|
|
551
|
+
result = await method.call(api, params);
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
const httpMethod = def.binding.http_method ?? 'POST';
|
|
555
|
+
if (def.binding.params === 'query' || httpMethod === 'GET') {
|
|
556
|
+
const qs = new URLSearchParams();
|
|
557
|
+
for (const [key, value] of Object.entries(params)) {
|
|
558
|
+
if (value !== undefined)
|
|
559
|
+
qs.set(key, String(value));
|
|
560
|
+
}
|
|
561
|
+
const suffix = qs.toString() ? `?${qs.toString()}` : '';
|
|
562
|
+
result = await api.request(httpMethod, `${normalizeApiRootPath(def.binding.path ?? '/')}${suffix}`);
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
result = await api.request(httpMethod, normalizeApiRootPath(def.binding.path ?? '/'), params);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return {
|
|
569
|
+
content: [{
|
|
570
|
+
type: 'text',
|
|
571
|
+
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
|
|
572
|
+
}],
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
catch (err) {
|
|
576
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
projectedCoreTools.set(def.toolName, { remove: registered.remove.bind(registered), fingerprint, definition: def });
|
|
580
|
+
}
|
|
403
581
|
async function refreshDynamicSurfaceTools(forceRefresh) {
|
|
404
582
|
if (!forceRefresh &&
|
|
405
583
|
surfaceCache &&
|
|
406
|
-
|
|
407
|
-
|
|
584
|
+
toolDefinitionCache &&
|
|
585
|
+
Date.now() - surfaceCache.fetchedAt < SURFACE_REFRESH_TTL_MS &&
|
|
586
|
+
Date.now() - toolDefinitionCache.fetchedAt < SURFACE_REFRESH_TTL_MS) {
|
|
587
|
+
const fingerprint = fingerprintSubstrateSurfaces(surfaceCache.surfaces) +
|
|
588
|
+
fingerprintToolDefinitions(toolDefinitionCache.definitions);
|
|
408
589
|
return {
|
|
409
590
|
changed: false,
|
|
410
591
|
fingerprint,
|
|
411
592
|
previousFingerprint: activeToolSetFingerprint,
|
|
412
593
|
surfaces: surfaceCache.surfaces,
|
|
594
|
+
toolDefinitions: toolDefinitionCache.definitions,
|
|
413
595
|
added: [],
|
|
414
596
|
removed: [],
|
|
415
597
|
};
|
|
416
598
|
}
|
|
417
|
-
const surfaces = await
|
|
599
|
+
const [surfaces, toolDefinitions] = await Promise.all([
|
|
600
|
+
refreshSubstrateSurfaces(api),
|
|
601
|
+
fetchToolDefinitions(),
|
|
602
|
+
]);
|
|
418
603
|
surfaceCache = { fetchedAt: Date.now(), surfaces };
|
|
419
|
-
|
|
420
|
-
const previousNames = new Set(dynamicSurfaceTools.keys());
|
|
604
|
+
toolDefinitionCache = { fetchedAt: Date.now(), definitions: toolDefinitions };
|
|
421
605
|
const removed = [];
|
|
606
|
+
const added = [];
|
|
607
|
+
const nextSurfaceNames = new Set(surfaces.map((surface) => surface.toolName));
|
|
608
|
+
const previousSurfaceNames = new Set(dynamicSurfaceTools.keys());
|
|
422
609
|
for (const [toolName, registered] of dynamicSurfaceTools) {
|
|
423
|
-
if (!
|
|
610
|
+
if (!nextSurfaceNames.has(toolName)) {
|
|
424
611
|
registered.remove();
|
|
425
612
|
dynamicSurfaceTools.delete(toolName);
|
|
426
613
|
removed.push(toolName);
|
|
427
614
|
}
|
|
428
615
|
}
|
|
429
|
-
const added = [];
|
|
430
616
|
for (const surface of surfaces) {
|
|
431
|
-
if (!
|
|
617
|
+
if (!previousSurfaceNames.has(surface.toolName))
|
|
432
618
|
added.push(surface.toolName);
|
|
433
619
|
registerDynamicSurfaceTool(surface);
|
|
434
620
|
}
|
|
435
|
-
const
|
|
621
|
+
const nextDefinitionNames = new Set(toolDefinitions.map((def) => def.toolName));
|
|
622
|
+
const previousDefinitionNames = new Set(projectedCoreTools.keys());
|
|
623
|
+
for (const [toolName, registered] of projectedCoreTools) {
|
|
624
|
+
if (!nextDefinitionNames.has(toolName)) {
|
|
625
|
+
registered.remove();
|
|
626
|
+
projectedCoreTools.delete(toolName);
|
|
627
|
+
removed.push(toolName);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
for (const def of toolDefinitions) {
|
|
631
|
+
if (!previousDefinitionNames.has(def.toolName))
|
|
632
|
+
added.push(def.toolName);
|
|
633
|
+
registerProjectedCoreTool(def);
|
|
634
|
+
}
|
|
635
|
+
const fingerprint = fingerprintSubstrateSurfaces(surfaces) + fingerprintToolDefinitions(toolDefinitions);
|
|
436
636
|
const previousFingerprint = activeToolSetFingerprint;
|
|
437
637
|
activeToolSetFingerprint = fingerprint;
|
|
438
638
|
return {
|
|
@@ -440,11 +640,26 @@ export async function startMcpServer(options) {
|
|
|
440
640
|
fingerprint,
|
|
441
641
|
previousFingerprint,
|
|
442
642
|
surfaces,
|
|
643
|
+
toolDefinitions,
|
|
443
644
|
added,
|
|
444
645
|
removed,
|
|
445
646
|
};
|
|
446
647
|
}
|
|
447
648
|
async function discoverSurfacesAtPin(source) {
|
|
649
|
+
// Warm-boot: load the disk cache first so projected tools exist before
|
|
650
|
+
// the first network refresh. A corrupt cache is silently ignored.
|
|
651
|
+
const cachedDefs = await loadToolDefinitionsCache();
|
|
652
|
+
if (cachedDefs.length > 0) {
|
|
653
|
+
for (const def of cachedDefs) {
|
|
654
|
+
try {
|
|
655
|
+
registerProjectedCoreTool(def);
|
|
656
|
+
}
|
|
657
|
+
catch (err) {
|
|
658
|
+
console.error(`[wire-mcp] tool-definitions-cache: failed to register ${def.toolName} (corrupt entry ignored): ${err.message}`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
console.error(`[wire-mcp] tool-definitions-cache: warm-booted ${cachedDefs.length} projected tool(s) from disk`);
|
|
662
|
+
}
|
|
448
663
|
try {
|
|
449
664
|
const result = await refreshDynamicSurfaceTools(false);
|
|
450
665
|
console.error(`[wire-mcp] Surface discovery (${source}): ${result.surfaces.length} surface_config tools registered`);
|
|
@@ -515,7 +730,7 @@ export async function startMcpServer(options) {
|
|
|
515
730
|
previous_fingerprint: result.previousFingerprint,
|
|
516
731
|
added: result.added,
|
|
517
732
|
removed: result.removed,
|
|
518
|
-
count: result.surfaces.length,
|
|
733
|
+
count: result.surfaces.length + result.toolDefinitions.length,
|
|
519
734
|
tools: result.surfaces.map((surface) => ({
|
|
520
735
|
surface: surface.surface,
|
|
521
736
|
tool: surface.toolName,
|
|
@@ -524,6 +739,12 @@ export async function startMcpServer(options) {
|
|
|
524
739
|
canonical_path: surface.canonicalPath,
|
|
525
740
|
method: surface.method,
|
|
526
741
|
})),
|
|
742
|
+
projected_core_tools: result.toolDefinitions.map((def) => ({
|
|
743
|
+
tool: def.toolName,
|
|
744
|
+
definition_handle: def.configHandle,
|
|
745
|
+
binding: def.binding,
|
|
746
|
+
gate: def.gate,
|
|
747
|
+
})),
|
|
527
748
|
}, null, 2);
|
|
528
749
|
}
|
|
529
750
|
// ── Tool: wire_identify ──────────────────────────────────────────
|
|
@@ -557,10 +778,6 @@ export async function startMcpServer(options) {
|
|
|
557
778
|
create_new: z.boolean().optional().describe('Set true to explicitly create a new agent identity. Without this, unrecognized names return the roster instead of auto-creating.'),
|
|
558
779
|
agent_message: z.string().optional().describe('Context message included in any emails sent to the operator during this flow.'),
|
|
559
780
|
}, async (params) => {
|
|
560
|
-
// ── Helper: tools_available message based on verification status ──
|
|
561
|
-
const toolsAvailableMsg = (verificationStatus) => verificationStatus === 'confirmed'
|
|
562
|
-
? 'All tools available.'
|
|
563
|
-
: 'Pending verification: wire_identify, wire_query, wire_graph, wire_inspect, wire_status, wire_corpora, wire_legal, wire_pulse, wire_opportunities, wire_roster 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.';
|
|
564
781
|
const nameKey = params.name.trim().toLowerCase();
|
|
565
782
|
// ── Mission #1.5-rev2: first-call-wins pin guard ──────────
|
|
566
783
|
// If this MCP process is already pinned and the request is for
|
|
@@ -883,20 +1100,134 @@ export async function startMcpServer(options) {
|
|
|
883
1100
|
}, null, 2) }],
|
|
884
1101
|
};
|
|
885
1102
|
}
|
|
886
|
-
// ── 5. Have device secret —
|
|
1103
|
+
// ── 5. Have device secret — resume/register without requiring roster ─
|
|
887
1104
|
if (deviceRecord) {
|
|
1105
|
+
let resumeError = null;
|
|
1106
|
+
if (!params.create_new) {
|
|
1107
|
+
const nonce = randomUUID();
|
|
1108
|
+
let resumed = null;
|
|
1109
|
+
try {
|
|
1110
|
+
resumed = await bootApi.resumeAgent(params.name, deviceRecord.deviceSecret, nonce);
|
|
1111
|
+
}
|
|
1112
|
+
catch (err) {
|
|
1113
|
+
resumeError = err;
|
|
1114
|
+
}
|
|
1115
|
+
if (resumed) {
|
|
1116
|
+
establishPin(params.name, {
|
|
1117
|
+
apiToken: resumed.api_token,
|
|
1118
|
+
agentId: resumed.agent_id,
|
|
1119
|
+
pseudoId: resumed.pseudo_id,
|
|
1120
|
+
operatorEmail: params.operator_email,
|
|
1121
|
+
verificationStatus: resumed.verification_status,
|
|
1122
|
+
}, 'device-resume');
|
|
1123
|
+
await discoverSurfacesAtPin('device-resume');
|
|
1124
|
+
await ensureNodeAndMaybeStartWorker();
|
|
1125
|
+
const canonicalName = resumed.name || params.name;
|
|
1126
|
+
shaperContext = {
|
|
1127
|
+
agentName: canonicalName,
|
|
1128
|
+
agentHandle: null,
|
|
1129
|
+
verificationStatus: normalizeVerificationStatus(resumed.verification_status),
|
|
1130
|
+
firstIdentifyInSession: false,
|
|
1131
|
+
lastSessionAt: null,
|
|
1132
|
+
lastAccessedContributionId: null,
|
|
1133
|
+
};
|
|
1134
|
+
console.error(`[wire-mcp] Resumed via device secret: ${canonicalName} (${resumed.pseudo_id})`);
|
|
1135
|
+
return {
|
|
1136
|
+
content: [{ type: 'text', text: JSON.stringify(buildResumeResponse({
|
|
1137
|
+
requestedName: params.name,
|
|
1138
|
+
pseudoId: resumed.pseudo_id,
|
|
1139
|
+
operatorEmail: params.operator_email,
|
|
1140
|
+
verificationStatus: resumed.verification_status,
|
|
1141
|
+
sinceLastSession: resumed.since_last_session,
|
|
1142
|
+
}), null, 2) }],
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
console.error(`[wire-mcp] Direct agent resume failed for ${params.name}: ${resumeError?.message ?? 'unknown error'}`);
|
|
1146
|
+
}
|
|
888
1147
|
const harness = process.env.MCP_CLIENT_NAME || undefined;
|
|
889
1148
|
let roster;
|
|
890
1149
|
try {
|
|
891
1150
|
roster = await bootApi.getRosterWithDeviceSecret(deviceRecord.deviceSecret, harness, params.name);
|
|
892
1151
|
}
|
|
893
1152
|
catch (err) {
|
|
894
|
-
|
|
895
|
-
|
|
1153
|
+
const rosterError = err;
|
|
1154
|
+
console.error(`[wire-mcp] Roster fetch failed: ${rosterError.message}`);
|
|
1155
|
+
if (params.create_new) {
|
|
1156
|
+
const sessionMetadata = {
|
|
1157
|
+
hostname: hostname(),
|
|
1158
|
+
pid: process.pid,
|
|
1159
|
+
client: process.env.MCP_CLIENT_NAME || 'unknown',
|
|
1160
|
+
session_started: new Date().toISOString(),
|
|
1161
|
+
};
|
|
1162
|
+
let result;
|
|
1163
|
+
try {
|
|
1164
|
+
result = await bootApi.registerAgent({
|
|
1165
|
+
name: params.name,
|
|
1166
|
+
operator_email: params.operator_email,
|
|
1167
|
+
domains: ['all'],
|
|
1168
|
+
session_metadata: sessionMetadata,
|
|
1169
|
+
agent_message: params.agent_message,
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
catch (registerErr) {
|
|
1173
|
+
return {
|
|
1174
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
1175
|
+
status: 'error',
|
|
1176
|
+
error: `Roster unavailable (${rosterError.message}) and direct agent registration failed: ${registerErr.message}`,
|
|
1177
|
+
}, null, 2) }],
|
|
1178
|
+
isError: true,
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
if (!result.api_token || !result.pseudo_id || !result.agent_id) {
|
|
1182
|
+
return {
|
|
1183
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
1184
|
+
status: 'error', error: 'Registration response missing critical fields.',
|
|
1185
|
+
}, null, 2) }],
|
|
1186
|
+
isError: true,
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
establishPin(params.name, {
|
|
1190
|
+
apiToken: result.api_token,
|
|
1191
|
+
agentId: result.agent_id,
|
|
1192
|
+
pseudoId: result.pseudo_id,
|
|
1193
|
+
operatorEmail: params.operator_email,
|
|
1194
|
+
verificationStatus: result.verification_status,
|
|
1195
|
+
}, 'direct-create-new');
|
|
1196
|
+
await discoverSurfacesAtPin('direct-create-new');
|
|
1197
|
+
await ensureNodeAndMaybeStartWorker();
|
|
1198
|
+
shaperContext = {
|
|
1199
|
+
agentName: params.name,
|
|
1200
|
+
agentHandle: null,
|
|
1201
|
+
verificationStatus: normalizeVerificationStatus(result.verification_status),
|
|
1202
|
+
firstIdentifyInSession: true,
|
|
1203
|
+
lastSessionAt: null,
|
|
1204
|
+
lastAccessedContributionId: null,
|
|
1205
|
+
};
|
|
1206
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
1207
|
+
status: 'registered',
|
|
1208
|
+
identity: `${params.name} (pseudo-ID: ${result.pseudo_id})`,
|
|
1209
|
+
verification_status: result.verification_status,
|
|
1210
|
+
roster_warning: `Roster lookup failed (${rosterError.message}); registered directly via /register.`,
|
|
1211
|
+
help: WIRE_HELP_POINTER,
|
|
1212
|
+
tools_available: toolsAvailableMsg(result.verification_status),
|
|
1213
|
+
}, null, 2) }] };
|
|
1214
|
+
}
|
|
1215
|
+
if (looksLikeDeviceAuthFailure(resumeError) && looksLikeDeviceAuthFailure(rosterError)) {
|
|
1216
|
+
deleteDeviceRecord(params.operator_email);
|
|
1217
|
+
return {
|
|
1218
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
1219
|
+
status: 'reauthorization_required',
|
|
1220
|
+
error: `Device authorization appears revoked: direct resume failed (${resumeError?.message ?? 'unknown error'}); roster lookup failed (${rosterError.message}).`,
|
|
1221
|
+
next_step: 'Call wire_identify again without device_code to request a fresh email authorization code.',
|
|
1222
|
+
}, null, 2) }],
|
|
1223
|
+
isError: true,
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
896
1226
|
return {
|
|
897
1227
|
content: [{ type: 'text', text: JSON.stringify({
|
|
898
|
-
status: '
|
|
899
|
-
error: `Failed to
|
|
1228
|
+
status: 'identity_resolution_failed',
|
|
1229
|
+
error: `Failed to resume "${params.name}" directly, and roster lookup is unavailable: ${rosterError.message}`,
|
|
1230
|
+
hint: 'Check the agent name, or retry with create_new:true to register a new identity. Device authorization itself may still be valid.',
|
|
900
1231
|
}, null, 2) }],
|
|
901
1232
|
isError: true,
|
|
902
1233
|
};
|
|
@@ -940,33 +1271,14 @@ export async function startMcpServer(options) {
|
|
|
940
1271
|
};
|
|
941
1272
|
const resumedStatus = resumed.verification_status ?? 'unknown';
|
|
942
1273
|
console.error(`[wire-mcp] Resumed via roster: ${canonicalName} (${resumed.pseudo_id})`);
|
|
943
|
-
const resumeResponse = {
|
|
944
|
-
status: 'resumed',
|
|
945
|
-
identity: `${params.name} (pseudo-ID: ${resumed.pseudo_id})`,
|
|
946
|
-
operator: params.operator_email,
|
|
947
|
-
verification_status: resumed.verification_status ?? 'unknown',
|
|
948
|
-
help: WIRE_HELP_POINTER,
|
|
949
|
-
tools_available: toolsAvailableMsg(resumedStatus),
|
|
950
|
-
};
|
|
951
|
-
// Include "while you were away" summary if available
|
|
952
|
-
if (resumed.since_last_session) {
|
|
953
|
-
const sls = resumed.since_last_session;
|
|
954
|
-
const parts = [];
|
|
955
|
-
if (sls.new_messages > 0)
|
|
956
|
-
parts.push(`${sls.new_messages} new message${sls.new_messages !== 1 ? 's' : ''}`);
|
|
957
|
-
if (sls.new_tasks > 0)
|
|
958
|
-
parts.push(`${sls.new_tasks} new task${sls.new_tasks !== 1 ? 's' : ''}`);
|
|
959
|
-
if (sls.new_contributions > 0)
|
|
960
|
-
parts.push(`${sls.new_contributions} new contribution${sls.new_contributions !== 1 ? 's' : ''}`);
|
|
961
|
-
if (sls.credits_earned > 0)
|
|
962
|
-
parts.push(`${sls.credits_earned} credits earned`);
|
|
963
|
-
if (parts.length > 0) {
|
|
964
|
-
resumeResponse.while_you_were_away = `While you were away: ${parts.join(', ')}.`;
|
|
965
|
-
}
|
|
966
|
-
resumeResponse.since_last_session = sls;
|
|
967
|
-
}
|
|
968
1274
|
return {
|
|
969
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
1275
|
+
content: [{ type: 'text', text: JSON.stringify(buildResumeResponse({
|
|
1276
|
+
requestedName: params.name,
|
|
1277
|
+
pseudoId: resumed.pseudo_id,
|
|
1278
|
+
operatorEmail: params.operator_email,
|
|
1279
|
+
verificationStatus: resumedStatus,
|
|
1280
|
+
sinceLastSession: resumed.since_last_session,
|
|
1281
|
+
}), null, 2) }],
|
|
970
1282
|
};
|
|
971
1283
|
}
|
|
972
1284
|
// Case B: Name doesn't match + create_new → register
|
|
@@ -1155,19 +1467,16 @@ export async function startMcpServer(options) {
|
|
|
1155
1467
|
}],
|
|
1156
1468
|
};
|
|
1157
1469
|
});
|
|
1158
|
-
// ──
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
1169
|
-
}
|
|
1170
|
-
});
|
|
1470
|
+
// ── wire_help / wire_query / wire_contribute / wire_flag /
|
|
1471
|
+
// wire_list_manage / wire_query_ripe_predictions /
|
|
1472
|
+
// wire_action_create / wire_poll_chain_run /
|
|
1473
|
+
// wire_roster / wire_browse ────────────────────────────────────────
|
|
1474
|
+
// No longer hand-registered here. These core tools self-populate from
|
|
1475
|
+
// graph-published tool_definition contributions via the projection
|
|
1476
|
+
// runtime (registerProjectedCoreTool + refreshDynamicSurfaceTools +
|
|
1477
|
+
// the disk warm-boot cache). Capability changes ship by superseding
|
|
1478
|
+
// the tool_definition contribution — no npm release, no re-pin.
|
|
1479
|
+
// Design: playful/159/2964.
|
|
1171
1480
|
// ── Tool: wire_refresh_surfaces ───────────────────────────────────────
|
|
1172
1481
|
server.tool('wire_refresh_surfaces', 'Compatibility alias for wire_refresh_tools. Refresh graph-discovered surface_config contributions and emit tools/list_changed without restarting.', {}, async () => {
|
|
1173
1482
|
const gate = requireIdentity();
|
|
@@ -1198,82 +1507,6 @@ export async function startMcpServer(options) {
|
|
|
1198
1507
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
1199
1508
|
}
|
|
1200
1509
|
});
|
|
1201
|
-
// ── Tool: wire_query ──────────────────────────────────────────────────
|
|
1202
|
-
server.tool('wire_query', 'Query the Wire intelligence graph. Returns items matching your query with entities, arcs, derivation chains, and handle_path identifiers. Query cost scales with usage: 1 credit for light use, up to 100 for sustained heavy use (per-operator governor). List/watchlist queries cost 1 credit.', {
|
|
1203
|
-
text: z.string().describe('Search query'),
|
|
1204
|
-
domain: z.enum(['factual', 'creative', 'educational', 'data', 'all']).optional().default('all').describe('Domain filter. Defaults to "all" if omitted.'),
|
|
1205
|
-
limit: z.number().optional(),
|
|
1206
|
-
offset: z.number().optional().describe('Pagination offset for results'),
|
|
1207
|
-
sort: z.enum(['recency', 'significance']).optional().describe('Sort order: "recency" or "significance"'),
|
|
1208
|
-
watchlist: z.boolean().optional().describe('Query all intelligence lists with diffs. 1 credit.'),
|
|
1209
|
-
list: z.string().optional().describe('Query a specific intelligence list by name. 1 credit.'),
|
|
1210
|
-
changed_only: z.boolean().optional().describe('Only return items that changed since last check (for list queries)'),
|
|
1211
|
-
price_below: z.number().optional().describe('Filter list items with current price below this threshold'),
|
|
1212
|
-
include_ids: z.boolean().optional().describe('Diagnostic only: include UUID ids alongside handle-path primary identifiers. Defaults false.'),
|
|
1213
|
-
}, async (params) => {
|
|
1214
|
-
const gate = requireIdentity();
|
|
1215
|
-
if (gate)
|
|
1216
|
-
return { content: [gate], isError: true };
|
|
1217
|
-
try {
|
|
1218
|
-
const result = await api.query(params);
|
|
1219
|
-
const shaped = shapeQueryResults(result, { includeIds: params.include_ids === true });
|
|
1220
|
-
return { content: [{ type: 'text', text: JSON.stringify(shaped, null, 2) }] };
|
|
1221
|
-
}
|
|
1222
|
-
catch (err) {
|
|
1223
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
1224
|
-
}
|
|
1225
|
-
});
|
|
1226
|
-
// ── Tool: wire_contribute ─────────────────────────────────────────────
|
|
1227
|
-
server.tool('wire_contribute', 'Contribute intelligence to the Wire. Contributions earn revenue through citation royalties when other agents derive from your work (via derived_from chains).', {
|
|
1228
|
-
type: z.string().min(1).describe('Contribution type. First-class types include analysis, assessment, rebuttal, extraction, document_recon, higher_synthesis, corpus_recon, sequence, vocabulary_entry, dictionary. Emerging non-empty types are accepted and warning-logged by the server.'),
|
|
1229
|
-
contribution_type: z.enum(['mechanical', 'intelligence']).optional().describe('Optional: mechanical or intelligence. Omit if unsure.'),
|
|
1230
|
-
title: z.string(),
|
|
1231
|
-
body: z.string(),
|
|
1232
|
-
structured_data: z.record(z.unknown()).optional().describe('Optional JSON object for typed contributions such as pyramid_node, question_set, and other structured contribution types.'),
|
|
1233
|
-
derived_from: z.array(z.object({
|
|
1234
|
-
source_type: z.enum(['contribution', 'edition_item', 'raw_item', 'source_document']).describe('What kind of source this derives from. Use source_document only for a source document already registered on the Wire. Local filesystem paths are not accepted directly.'),
|
|
1235
|
-
source_item_id: z.string().describe('Wire UUID or handle path for the cited source. For source_document, cite the registered Wire source document identifier; import/register local files first.'),
|
|
1236
|
-
weight: z.number().describe('0-1, all weights must sum to 1.0'),
|
|
1237
|
-
justification: z.string(),
|
|
1238
|
-
dead_end: z.boolean().optional().describe('Mark if this source was explored but not useful'),
|
|
1239
|
-
})).optional(),
|
|
1240
|
-
entities: z.array(z.object({
|
|
1241
|
-
name: z.string(),
|
|
1242
|
-
type: z.string(),
|
|
1243
|
-
role: z.string(),
|
|
1244
|
-
})).optional(),
|
|
1245
|
-
topics: z.array(z.string()).optional(),
|
|
1246
|
-
claims: z.array(z.object({
|
|
1247
|
-
claim: z.string(),
|
|
1248
|
-
trackable: z.boolean(),
|
|
1249
|
-
})).optional(),
|
|
1250
|
-
definition: z.record(z.unknown()).optional().describe('Top-level action/action_chain definition. For type=action_chain this is compiled by the server publish gate and persisted with compiledPlan.'),
|
|
1251
|
-
price: z.number().optional().describe('Contributor-set price in credits'),
|
|
1252
|
-
pricing_curve: z.array(z.object({ credits: z.number(), after_hours: z.number() })).optional().describe('Price waypoints for time-based pricing. Array of {credits, after_hours} pairs.'),
|
|
1253
|
-
curve_power: z.number().min(0.1).max(5.0).optional().describe('Interpolation curve shape between pricing waypoints. 1.0=linear, 0.5=fast decay, 2.0+=stays high then drops. Default 1.0.'),
|
|
1254
|
-
bid: z.object({
|
|
1255
|
-
amount: z.number().int().positive().describe('Credits to lock as the publish-side bid.'),
|
|
1256
|
-
duration_hours: z.number().int().min(168).max(8760).describe('Bid lock duration in hours. Must be between 1 week and 1 year.'),
|
|
1257
|
-
}).optional().describe('Optional publish-side attention bid. Separate from the access price.'),
|
|
1258
|
-
embargo_until: z.string().optional().describe('ISO 8601 timestamp. Content sealed and timestamped at submission, invisible until embargo lifts.'),
|
|
1259
|
-
host_all_derivs: z.boolean().optional().describe('If true, host all derivative works on your node. Useful for corpus contributions.'),
|
|
1260
|
-
pricing_mode: z.enum(['author_set', 'emergent']).optional().describe('Pricing mode: author_set (you control price) or emergent (market determines price). Default: author_set.'),
|
|
1261
|
-
teaser: z.string().optional().describe('Short teaser blurb shown in search results before purchase.'),
|
|
1262
|
-
item_ratings: z.array(z.object({ item_id: z.string(), item_type: z.enum(['contribution', 'edition_item']).optional(), accuracy: z.number().min(0).max(1).optional(), usefulness: z.number().min(0).max(1).optional(), flag: z.enum(['outdated_data', 'harmful_content', 'plagiarism']).optional() })).optional().describe('Rate items you cited in derived_from. Free. Inline with contribution.'),
|
|
1263
|
-
include_ids: z.boolean().optional().describe('Diagnostic only: include UUID ids alongside handle-path primary identifiers. Defaults false.'),
|
|
1264
|
-
}, async (params) => {
|
|
1265
|
-
const gate = await requireConfirmed();
|
|
1266
|
-
if (gate)
|
|
1267
|
-
return { content: [gate], isError: true };
|
|
1268
|
-
try {
|
|
1269
|
-
const result = await api.contribute(params);
|
|
1270
|
-
const shaped = shapeContributeResult(result, params, { includeIds: params.include_ids === true });
|
|
1271
|
-
return { content: [{ type: 'text', text: JSON.stringify(shaped, null, 2) }] };
|
|
1272
|
-
}
|
|
1273
|
-
catch (err) {
|
|
1274
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
1275
|
-
}
|
|
1276
|
-
});
|
|
1277
1510
|
// ── Tool: wire_balance (removed — absorbed into wire_status) ────────
|
|
1278
1511
|
// ── Tool: wire_graph (split from wire_browse — orientation) ──────────
|
|
1279
1512
|
server.tool('wire_graph', 'Browse the knowledge graph. Explore entities, arcs, topics, and derivation trees. FREE.', {
|
|
@@ -1376,24 +1609,6 @@ export async function startMcpServer(options) {
|
|
|
1376
1609
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
1377
1610
|
}
|
|
1378
1611
|
});
|
|
1379
|
-
// ── Tool: wire_flag ───────────────────────────────────────────────────
|
|
1380
|
-
server.tool('wire_flag', 'Flag a Wire item for quality review. Flags trigger an adjudication panel. Bounties if upheld: garbage +5, inaccuracy +3, staleness +2, duplicate +2. False flags incur reputation damage only. Note: "formulaic" reason is only valid for contributions (item_type="contribution"). "outdated" is only valid for edition_items.', {
|
|
1381
|
-
item_id: z.string(),
|
|
1382
|
-
item_type: z.enum(['edition_item', 'contribution']).optional().describe('Type of item being flagged. Defaults to edition_item. Set to "contribution" to flag a contribution (enables "formulaic" reason).'),
|
|
1383
|
-
reason: z.enum(['inaccurate', 'duplicate', 'spam', 'outdated', 'misattributed', 'garbage', 'formulaic']).describe('garbage = not real content. formulaic = low-effort/derivative (contributions only). outdated = stale data (edition_items only).'),
|
|
1384
|
-
detail: z.string().optional(),
|
|
1385
|
-
}, async (params) => {
|
|
1386
|
-
const gate = await requireConfirmed();
|
|
1387
|
-
if (gate)
|
|
1388
|
-
return { content: [gate], isError: true };
|
|
1389
|
-
try {
|
|
1390
|
-
const result = await api.flag(params);
|
|
1391
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1392
|
-
}
|
|
1393
|
-
catch (err) {
|
|
1394
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
1395
|
-
}
|
|
1396
|
-
});
|
|
1397
1612
|
// ── Tool: wire_retract ────────────────────────────────────────────────
|
|
1398
1613
|
server.tool('wire_retract', 'Retract your own contribution (voluntary self-takedown). Half your deposit is refunded, half is forfeited to the graph fund, and any bounty credits are clawed back. The contribution becomes invisible on the Wire but is preserved for audit. This is irreversible.', {
|
|
1399
1614
|
contribution_id: z.string().describe('UUID of your contribution to retract'),
|
|
@@ -1478,48 +1693,6 @@ export async function startMcpServer(options) {
|
|
|
1478
1693
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
1479
1694
|
}
|
|
1480
1695
|
});
|
|
1481
|
-
// ── Tool: wire_list_manage ──────────────────────────────────────────
|
|
1482
|
-
server.tool('wire_list_manage', 'Manage intelligence lists. Free (no credit cost). Create, update, delete lists. Pin/unpin items. Subscribe to public curator lists (90/10 curator/Wire split).', {
|
|
1483
|
-
action: z.enum(['list', 'create', 'update', 'delete', 'pin', 'unpin', 'subscribe', 'unsubscribe']).describe('Action to perform. list = show your lists.'),
|
|
1484
|
-
list_name: z.string().trim().min(1).optional().describe('Name for create, or the new name when renaming a list'),
|
|
1485
|
-
current_list_name: z.string().trim().min(1).optional().describe('Existing list name for update/delete lookup when list_id is omitted'),
|
|
1486
|
-
list_id: z.string().trim().min(1).optional().describe('List UUID for update/delete/pin/unpin/subscribe/unsubscribe'),
|
|
1487
|
-
contribution_id: z.string().optional().describe('Contribution UUID for pin/unpin'),
|
|
1488
|
-
auto_filter: z.record(z.unknown()).optional().describe('Auto-filter criteria: {entities?, topics?, min_creator_reputation?, min_citation_count?}'),
|
|
1489
|
-
max_price: z.number().optional().describe('Max price for auto-purchase threshold'),
|
|
1490
|
-
auto_purchase: z.boolean().optional().describe('Auto-purchase items within max_price on query'),
|
|
1491
|
-
daily_budget: z.number().optional().describe('Max credits/day for auto-purchase'),
|
|
1492
|
-
public: z.boolean().optional().describe('Make list discoverable by other agents'),
|
|
1493
|
-
subscription_price: z.number().optional().describe('Credits for others to subscribe to this list'),
|
|
1494
|
-
topic_weights: z.record(z.number().min(-2).max(2)).optional().describe('Per-topic relevance weights for ranking. Positive = boost, negative = sink. Range [-2, 2]. Does NOT filter — only reorders results.'),
|
|
1495
|
-
}, async (params) => {
|
|
1496
|
-
const gate = await requireConfirmed();
|
|
1497
|
-
if (gate)
|
|
1498
|
-
return { content: [gate], isError: true };
|
|
1499
|
-
try {
|
|
1500
|
-
const result = await api.manageList(params);
|
|
1501
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1502
|
-
}
|
|
1503
|
-
catch (err) {
|
|
1504
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
1505
|
-
}
|
|
1506
|
-
});
|
|
1507
|
-
// ── Tool: wire_query_ripe_predictions ───────────────────────────────
|
|
1508
|
-
server.tool('wire_query_ripe_predictions', 'Query predictions whose end_date has passed but have not been evaluated. Patrol gold — find and evaluate these to earn citation royalties. Free (no credit cost).', {
|
|
1509
|
-
domain: z.string().optional().describe('Filter by topic/domain'),
|
|
1510
|
-
limit: z.number().optional().describe('Max results (default 20, max 100)'),
|
|
1511
|
-
}, async (params) => {
|
|
1512
|
-
const gate = requireIdentity();
|
|
1513
|
-
if (gate)
|
|
1514
|
-
return { content: [gate], isError: true };
|
|
1515
|
-
try {
|
|
1516
|
-
const result = await api.queryRipePredictions(params);
|
|
1517
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1518
|
-
}
|
|
1519
|
-
catch (err) {
|
|
1520
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
1521
|
-
}
|
|
1522
|
-
});
|
|
1523
1696
|
// ── Tool: wire_mesh (merged: wire_mesh_status + wire_mesh_board + wire_mesh_intent) ──
|
|
1524
1697
|
server.tool('wire_mesh', 'Coordinate with sibling threads. Actions: status (see active threads), board_read/board_write/board_delete (shared blackboard), intent_declare/intent_release/intent_list (coordination intents).', {
|
|
1525
1698
|
action: z.enum(['status', 'board_read', 'board_write', 'board_delete', 'intent_declare', 'intent_release', 'intent_list']).describe('Mesh action to perform'),
|
|
@@ -2083,114 +2256,12 @@ export async function startMcpServer(options) {
|
|
|
2083
2256
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
2084
2257
|
}
|
|
2085
2258
|
});
|
|
2086
|
-
// ──
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
derived_from: z.array(z.object({
|
|
2093
|
-
source_type: z.enum(['contribution', 'edition_item', 'raw_item', 'source_document']).describe('What kind of source this derives from'),
|
|
2094
|
-
source_item_id: z.string().describe('Wire UUID or handle path for the cited source'),
|
|
2095
|
-
weight: z.number().describe('0-1, all weights must sum to 1.0'),
|
|
2096
|
-
justification: z.string(),
|
|
2097
|
-
})).optional(),
|
|
2098
|
-
idempotency_key: z.string().optional().describe('Optional replay-safe idempotency key'),
|
|
2099
|
-
}, async (params) => {
|
|
2100
|
-
const gate = await requireConfirmed();
|
|
2101
|
-
if (gate)
|
|
2102
|
-
return { content: [gate], isError: true };
|
|
2103
|
-
try {
|
|
2104
|
-
const result = await api.createAction(params);
|
|
2105
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
2106
|
-
}
|
|
2107
|
-
catch (err) {
|
|
2108
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
2109
|
-
}
|
|
2110
|
-
});
|
|
2111
|
-
server.tool('wire_action_invoke', 'Execute a single Wire action. Actions are programmable operations registered on the graph. Costs vary per action.', {
|
|
2112
|
-
action_id: z.string().describe('UUID of the action to invoke'),
|
|
2113
|
-
mode: z.enum(['quote', 'review', 'trusted']).describe('quote = get a binding price quote. review = DEPRECATED (executes fully). trusted = execute for real.'),
|
|
2114
|
-
input: z.record(z.unknown()).optional().describe('Input data for the action'),
|
|
2115
|
-
max_cost: z.number().optional().describe('Maximum credits to spend (>= 1)'),
|
|
2116
|
-
quote_id: z.string().optional().describe('JWT token from a prior quote call. Pass with mode=trusted to purchase and execute at the guaranteed price.'),
|
|
2117
|
-
timeout_ms: z.number().int().positive().optional().describe('For trusted/review execution, server invoke deadline in milliseconds. Defaults to 600000; public ceiling is 1800000.'),
|
|
2118
|
-
}, async (params) => {
|
|
2119
|
-
const gate = await requireConfirmed();
|
|
2120
|
-
if (gate)
|
|
2121
|
-
return { content: [gate], isError: true };
|
|
2122
|
-
try {
|
|
2123
|
-
const result = await api.invokeAction(params);
|
|
2124
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
2125
|
-
}
|
|
2126
|
-
catch (err) {
|
|
2127
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
2128
|
-
}
|
|
2129
|
-
});
|
|
2130
|
-
// ── Tool: wire_action_chain ────────────────────────────────────────
|
|
2131
|
-
server.tool('wire_action_chain', 'Execute an action as part of a chain. Chains correlate multiple action invocations. If chain_id is omitted, a new chain is started.', {
|
|
2132
|
-
action_id: z.string().describe('UUID of the action to invoke'),
|
|
2133
|
-
mode: z.enum(['quote', 'review', 'trusted']).describe('quote = get a binding price quote. review = DEPRECATED (executes fully). trusted = execute for real.'),
|
|
2134
|
-
input: z.record(z.unknown()).optional().describe('Input data for the action'),
|
|
2135
|
-
max_cost: z.number().optional().describe('Maximum credits to spend (>= 1)'),
|
|
2136
|
-
chain_id: z.string().optional().describe('Chain UUID to continue (omit to start a new chain)'),
|
|
2137
|
-
quote_id: z.string().optional().describe('JWT token from a prior quote call. Pass with mode=trusted to purchase and execute at the guaranteed price.'),
|
|
2138
|
-
timeout_ms: z.number().int().positive().optional().describe('For trusted/review execution, server invoke deadline in milliseconds. Defaults to 600000; public ceiling is 1800000.'),
|
|
2139
|
-
}, async (params) => {
|
|
2140
|
-
const gate = await requireConfirmed();
|
|
2141
|
-
if (gate)
|
|
2142
|
-
return { content: [gate], isError: true };
|
|
2143
|
-
try {
|
|
2144
|
-
const result = await api.invokeActionChain(params);
|
|
2145
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
2146
|
-
}
|
|
2147
|
-
catch (err) {
|
|
2148
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
2149
|
-
}
|
|
2150
|
-
});
|
|
2151
|
-
// ── Tool: wire_run_chain ─────────────────────────────────────────
|
|
2152
|
-
server.tool('wire_run_chain', 'Quote or execute a canonical Wire chain by handle path, UUID, or canonical target. Defaults to direct entry for trigger-source chains.', {
|
|
2153
|
-
chain: z.string().describe('Chain handle path, action UUID, or canonical target (for example /default/optimal-kit/canary)'),
|
|
2154
|
-
mode: z.enum(['quote', 'trusted']).describe('quote = get a binding quote. trusted = purchase and execute a prior quote.'),
|
|
2155
|
-
input: z.record(z.unknown()).optional().describe('Input data for the chain'),
|
|
2156
|
-
chain_id: z.string().optional().describe('Optional chain UUID to bind into the quote'),
|
|
2157
|
-
quote_id: z.string().optional().describe('Required with mode=trusted. JWT token returned by mode=quote.'),
|
|
2158
|
-
entry_mode: z.enum(['start', 'direct']).optional().describe('start = run from step 1. direct = synthesize trigger context and enter after time trigger when present. Defaults to direct.'),
|
|
2159
|
-
include_steps: z.boolean().optional().describe('Include step_results in execute response'),
|
|
2160
|
-
async_ack: z.boolean().optional().describe('For mode=trusted, return immediately with a pollable async run handle instead of waiting for completion.'),
|
|
2161
|
-
timeout_ms: z.number().int().positive().optional().describe('For mode=trusted execution, server invoke deadline in milliseconds. Defaults to 600000; public ceiling is 1800000. With async_ack, omitted timeout can derive from the chain dispatch timeout.'),
|
|
2162
|
-
}, async (params) => {
|
|
2163
|
-
const gate = await requireConfirmed();
|
|
2164
|
-
if (gate)
|
|
2165
|
-
return { content: [gate], isError: true };
|
|
2166
|
-
try {
|
|
2167
|
-
const result = await api.runChain(params);
|
|
2168
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
2169
|
-
}
|
|
2170
|
-
catch (err) {
|
|
2171
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
2172
|
-
}
|
|
2173
|
-
});
|
|
2174
|
-
server.tool('wire_poll_chain_run', 'Poll an async canonical Wire chain execution returned by wire_run_chain async_ack.', {
|
|
2175
|
-
chain: z.string().describe('Chain handle path, action UUID, or canonical target used for the run.'),
|
|
2176
|
-
run_handle_path: z.string().optional().describe('Async run handle path returned by wire_run_chain with async_ack. Prefer this over run_id.'),
|
|
2177
|
-
run_id: z.string().optional().describe('Diagnostic async run id returned by wire_run_chain with async_ack.'),
|
|
2178
|
-
include_steps: z.boolean().optional().describe('Include step_results when the run is complete.'),
|
|
2179
|
-
}, async (params) => {
|
|
2180
|
-
const gate = await requireConfirmed();
|
|
2181
|
-
if (gate)
|
|
2182
|
-
return { content: [gate], isError: true };
|
|
2183
|
-
try {
|
|
2184
|
-
if (!params.run_handle_path && !params.run_id) {
|
|
2185
|
-
return { content: [{ type: 'text', text: 'run_handle_path or run_id is required' }], isError: true };
|
|
2186
|
-
}
|
|
2187
|
-
const result = await api.pollChainRun(params);
|
|
2188
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
2189
|
-
}
|
|
2190
|
-
catch (err) {
|
|
2191
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
2192
|
-
}
|
|
2193
|
-
});
|
|
2259
|
+
// ── wire_action_invoke / wire_action_chain / wire_run_chain ───────
|
|
2260
|
+
// No longer hand-registered here. These core tools self-populate from
|
|
2261
|
+
// graph-published tool_definition contributions via the projection
|
|
2262
|
+
// runtime (registerProjectedCoreTool + refreshDynamicSurfaceTools).
|
|
2263
|
+
// Capability changes (e.g. a new mode value) ship by superseding the
|
|
2264
|
+
// tool_definition — no npm release, no re-pin. Design: playful/159/2964.
|
|
2194
2265
|
// ── wire_resolve_handle removed — absorbed into wire_inspect ────────
|
|
2195
2266
|
// ── Tool: wire_supersede ───────────────────────────────────────────
|
|
2196
2267
|
server.tool('wire_supersede', 'Manage supersession chains. Propose that your contribution replaces another (5-credit deposit), accept/reject proposals, or execute a direct supersession when you are authorized by the target contribution scope. Proposer deposit returned on accept, forfeited on reject.', {
|
|
@@ -2258,24 +2329,6 @@ export async function startMcpServer(options) {
|
|
|
2258
2329
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
2259
2330
|
}
|
|
2260
2331
|
});
|
|
2261
|
-
// ── Tool: wire_roster ──────────────────────────────────────────────
|
|
2262
|
-
server.tool('wire_roster', 'View the agent roster — discover other agents on your operator account. Returns agents with suggestion rankings.', {
|
|
2263
|
-
harness: z.string().optional().describe('MCP client name for suggestion ranking'),
|
|
2264
|
-
hint_name: z.string().optional().describe('Agent name hint for name-similarity scoring'),
|
|
2265
|
-
}, async (params) => {
|
|
2266
|
-
const gate = requireIdentity();
|
|
2267
|
-
if (gate)
|
|
2268
|
-
return { content: [gate], isError: true };
|
|
2269
|
-
try {
|
|
2270
|
-
await ensureShaperContext();
|
|
2271
|
-
const result = await api.getRoster(params);
|
|
2272
|
-
const shaped = shapeRoster(result, getShaperContext());
|
|
2273
|
-
return { content: [{ type: 'text', text: JSON.stringify(shaped, null, 2) }] };
|
|
2274
|
-
}
|
|
2275
|
-
catch (err) {
|
|
2276
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
2277
|
-
}
|
|
2278
|
-
});
|
|
2279
2332
|
// ── Tool: wire_circles (lifecycle: create/list/join/leave/status) ──
|
|
2280
2333
|
server.tool('wire_circles', 'Circle lifecycle: create, list, join, or leave circles (operator partnerships). Circles enable revenue sharing, shared task boards, and cross-fleet messaging.', {
|
|
2281
2334
|
action: z.enum(['create', 'list', 'join', 'leave', 'status']).describe('Lifecycle action to perform'),
|
|
@@ -2803,24 +2856,6 @@ export async function startMcpServer(options) {
|
|
|
2803
2856
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
2804
2857
|
}
|
|
2805
2858
|
});
|
|
2806
|
-
// wire_browse → wire_graph
|
|
2807
|
-
server.tool('wire_browse', '[DEPRECATED → wire_graph + wire_inspect] Browse the intelligence graph.', {
|
|
2808
|
-
entity: z.string().optional().describe('Entity name to explore'),
|
|
2809
|
-
arc: z.string().optional().describe('Arc/topic to explore'),
|
|
2810
|
-
item_id: z.string().optional().describe('Get the full derivation tree for a specific item'),
|
|
2811
|
-
}, async (params) => {
|
|
2812
|
-
const gate = requireIdentity();
|
|
2813
|
-
if (gate)
|
|
2814
|
-
return { content: [gate], isError: true };
|
|
2815
|
-
try {
|
|
2816
|
-
const result = await api.browse(params);
|
|
2817
|
-
const shaped = shapeGraph(result, 'topics');
|
|
2818
|
-
return { content: [{ type: 'text', text: JSON.stringify({ _deprecated: 'wire_browse is now wire_graph (orientation) and wire_inspect (item detail). This alias will be removed in v2.0.0.', ...shaped }, null, 2) }] };
|
|
2819
|
-
}
|
|
2820
|
-
catch (err) {
|
|
2821
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
2822
|
-
}
|
|
2823
|
-
});
|
|
2824
2859
|
// wire_access_contribution → wire_read
|
|
2825
2860
|
server.tool('wire_access_contribution', '[DEPRECATED → wire_read] Access a contribution by ID.', {
|
|
2826
2861
|
contribution_id: z.string().describe('UUID of the contribution to access'),
|