@agent-wire/terminal 1.7.6 → 1.7.7
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 +447 -405
- package/dist/api.d.ts +69 -19
- package/dist/api.js +126 -36
- package/dist/api.js.map +1 -1
- package/dist/cli-projection.d.ts +8 -0
- package/dist/cli-projection.js +124 -0
- package/dist/cli-projection.js.map +1 -0
- package/dist/cli.js +43 -953
- package/dist/cli.js.map +1 -1
- package/dist/contracts/credit-amount.d.ts +19 -0
- package/dist/contracts/credit-amount.js +87 -0
- package/dist/contracts/credit-amount.js.map +1 -0
- package/dist/local-pull.js +1 -1
- package/dist/local-pull.js.map +1 -1
- package/dist/mcp-server.js +211 -126
- package/dist/mcp-server.js.map +1 -1
- package/dist/sync/paginate.d.ts +2 -16
- package/dist/sync/paginate.js +2 -33
- package/dist/sync/paginate.js.map +1 -1
- package/dist/tool-projection.d.ts +10 -0
- package/dist/tool-projection.js +51 -0
- package/dist/tool-projection.js.map +1 -1
- package/dist/wait-cli.js +46 -21
- package/dist/wait-cli.js.map +1 -1
- package/package.json +60 -58
package/dist/mcp-server.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
+
import { isCreditValue } from './contracts/credit-amount.js';
|
|
1
2
|
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
4
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
4
5
|
import { createServer } from 'node:http';
|
|
5
6
|
import { z } from 'zod';
|
|
7
|
+
const creditAmountSchema = (minimum) => z.union([z.number().int(), z.string()]).refine(value => isCreditValue(value, minimum), 'Expected safe integer or int64 decimal credit string');
|
|
6
8
|
import { hostname } from 'os';
|
|
7
9
|
import { createHash, randomUUID } from 'crypto';
|
|
8
10
|
import { readFile, writeFile, rename as fsRename, mkdir as fsMkdir, stat as fsStat } from 'fs/promises';
|
|
9
11
|
import { join, isAbsolute } from 'path';
|
|
10
12
|
import { loadConfig, saveConfig, CONFIG_DIR } from './config.js';
|
|
11
13
|
import { loadSlotConfig, saveSlotConfig, migrateLegacyAgentsToSlots } from './slots.js';
|
|
12
|
-
import { WireAPI } from './api.js';
|
|
14
|
+
import { WireAPI, RegistrationError } from './api.js';
|
|
13
15
|
import { shapeStatus, shapeRead, shapeInspect, shapeGraph, shapeCorpora, shapeMyContributions, shapeEarnings, shapeOpportunities, shapeRate, shapePatrolScan, shapeNotifications, shapeTasks, shapeMessages, } from './response-shaper.js';
|
|
14
16
|
import { startBackgroundWorkLoop } from './background-worker.js';
|
|
15
|
-
import { parseToolDefinition, dedupeToolDefinitions, fingerprintToolDefinitions, jsonSchemaToZodShape, } from './tool-projection.js';
|
|
17
|
+
import { parseToolDefinition, fetchCatalogDefinitionItems, invokeToolDefinition, dedupeToolDefinitions, withToolDefinitionAliases, fingerprintToolDefinitions, jsonSchemaToZodShape, } from './tool-projection.js';
|
|
16
18
|
import {
|
|
17
19
|
// Mission #1.5-rev2: agents/ cache fully retired. loadAgentIdentity /
|
|
18
20
|
// saveAgentIdentity / deleteAgentIdentity are no longer imported here;
|
|
@@ -82,9 +84,7 @@ function normalizeApiRootPath(path) {
|
|
|
82
84
|
return withSlash.replace(/^\/api\/v1(?=\/|$)/, '') || '/';
|
|
83
85
|
}
|
|
84
86
|
async function fetchSurfaceConfigItems(api) {
|
|
85
|
-
|
|
86
|
-
const responses = await Promise.all(queries.map((text) => api.query({ text, domain: 'all', limit: 100, sort: 'recency' })));
|
|
87
|
-
return responses.flatMap(extractQueryItems);
|
|
87
|
+
return fetchCatalogDefinitionItems(api, 'surface');
|
|
88
88
|
}
|
|
89
89
|
async function refreshSubstrateSurfaces(api) {
|
|
90
90
|
const items = await fetchSurfaceConfigItems(api);
|
|
@@ -157,7 +157,18 @@ function toolsAvailableMsg(verificationStatus) {
|
|
|
157
157
|
function looksLikeDeviceAuthFailure(error) {
|
|
158
158
|
if (!error)
|
|
159
159
|
return false;
|
|
160
|
-
|
|
160
|
+
if (/\b410\b|wire_roster_deleted/i.test(error.message))
|
|
161
|
+
return false;
|
|
162
|
+
// Endpoint removal (410) does not revoke device authorization.
|
|
163
|
+
return /\b401\b|revoked|device authorization|device secret|unauthorized/i.test(error.message);
|
|
164
|
+
}
|
|
165
|
+
function looksLikeRetiredRoster(error) {
|
|
166
|
+
if (!error)
|
|
167
|
+
return false;
|
|
168
|
+
// The /wire/roster endpoint was intentionally retired (server returns 410 wire_roster_deleted,
|
|
169
|
+
// "use activity observations instead"). A 410 here is EXPECTED, not a fault — callers should
|
|
170
|
+
// degrade quietly rather than surface it as a failure. (patent-guy doorbell #4)
|
|
171
|
+
return /wire_roster_deleted|\b410\b/i.test(error.message);
|
|
161
172
|
}
|
|
162
173
|
function buildResumeResponse(params) {
|
|
163
174
|
const resumedStatus = typeof params.verificationStatus === 'string'
|
|
@@ -203,6 +214,7 @@ export const __mcpSurfaceTestInternals = {
|
|
|
203
214
|
};
|
|
204
215
|
export async function startMcpServer(options) {
|
|
205
216
|
const loadedConfig = loadConfig();
|
|
217
|
+
loadedConfig.apiUrl = process.env.WIRE_API_URL ?? loadedConfig.apiUrl;
|
|
206
218
|
// MCP identity is slot-owned now. Keep node/base config, but deliberately
|
|
207
219
|
// strip any legacy config/env agent token before constructing pre-pin APIs.
|
|
208
220
|
const config = { ...loadedConfig, apiToken: '' };
|
|
@@ -215,6 +227,10 @@ export async function startMcpServer(options) {
|
|
|
215
227
|
// tool handlers go through the `api` closure variable below — bootApi
|
|
216
228
|
// is for identity-resolution operations only.
|
|
217
229
|
const bootApi = new WireAPI(config, threadId);
|
|
230
|
+
// A configured token can discover tools before identify, but cannot select
|
|
231
|
+
// this process's identity. Calls still require the explicit slot pin.
|
|
232
|
+
const discoveryApi = new WireAPI({ ...config,
|
|
233
|
+
apiToken: process.env.WIRE_API_TOKEN ?? loadedConfig.apiToken }, threadId);
|
|
218
234
|
// `api` starts as bootApi (no agent token). Tool handlers MUST gate
|
|
219
235
|
// through requireIdentity() before using it; that gate ensures the
|
|
220
236
|
// closure has been promoted to the slot's authenticated WireAPI by
|
|
@@ -484,34 +500,11 @@ export async function startMcpServer(options) {
|
|
|
484
500
|
dynamicSurfaceTools.set(surface.toolName, { ...registered, surface });
|
|
485
501
|
}
|
|
486
502
|
async function fetchToolDefinitions() {
|
|
487
|
-
const
|
|
488
|
-
const
|
|
489
|
-
const toolItems = items.filter((item) => item.type === 'tool_definition');
|
|
490
|
-
// Query items are teasers — structured_data is absent. Hydrate each
|
|
491
|
-
// tool_definition item with a full contribution read so parseToolDefinition
|
|
492
|
-
// receives the complete structured_data payload.
|
|
493
|
-
const hydrated = await Promise.all(toolItems.map(async (item) => {
|
|
494
|
-
const identifier = item.handle_path ?? item.id;
|
|
495
|
-
if (!identifier)
|
|
496
|
-
return null;
|
|
497
|
-
try {
|
|
498
|
-
const full = await api.accessContribution(identifier);
|
|
499
|
-
return {
|
|
500
|
-
type: full.type,
|
|
501
|
-
title: full.title,
|
|
502
|
-
handle_path: (full.handle_path ?? identifier),
|
|
503
|
-
structured_data: full.structured_data,
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
catch (err) {
|
|
507
|
-
console.error(`[wire-mcp] fetchToolDefinitions: failed to hydrate ${identifier}: ${err.message}`);
|
|
508
|
-
return null;
|
|
509
|
-
}
|
|
510
|
-
}));
|
|
511
|
-
const definitions = dedupeToolDefinitions(hydrated
|
|
503
|
+
const hydrated = await fetchCatalogDefinitionItems(processSlot ? api : discoveryApi, 'tool_definition');
|
|
504
|
+
const definitions = withToolDefinitionAliases(dedupeToolDefinitions(hydrated
|
|
512
505
|
.filter((item) => item !== null)
|
|
513
506
|
.map((item) => parseToolDefinition(item))
|
|
514
|
-
.filter((def) => def !== null));
|
|
507
|
+
.filter((def) => def !== null)));
|
|
515
508
|
// Persist to disk cache (atomic write). Errors are non-fatal.
|
|
516
509
|
void saveToolDefinitionsCache(definitions);
|
|
517
510
|
return definitions;
|
|
@@ -546,7 +539,7 @@ export async function startMcpServer(options) {
|
|
|
546
539
|
const registered = server.registerTool(def.toolName, {
|
|
547
540
|
title: def.toolName,
|
|
548
541
|
description: projectedToolDescription(def),
|
|
549
|
-
inputSchema: jsonSchemaToZodShape(def.parameterSchema),
|
|
542
|
+
inputSchema: z.object(jsonSchemaToZodShape(def.parameterSchema)).strict(),
|
|
550
543
|
_meta: {
|
|
551
544
|
tool_definition_handle: def.configHandle,
|
|
552
545
|
binding: def.binding,
|
|
@@ -557,29 +550,7 @@ export async function startMcpServer(options) {
|
|
|
557
550
|
if (gate)
|
|
558
551
|
return { content: [gate], isError: true };
|
|
559
552
|
try {
|
|
560
|
-
|
|
561
|
-
if (def.binding.kind === 'api_method') {
|
|
562
|
-
const method = api[def.binding.method ?? ''];
|
|
563
|
-
if (typeof method !== 'function') {
|
|
564
|
-
throw new Error(`tool_definition ${def.configHandle ?? def.toolName} binds to unknown adapter method '${def.binding.method}'`);
|
|
565
|
-
}
|
|
566
|
-
result = await method.call(api, params);
|
|
567
|
-
}
|
|
568
|
-
else {
|
|
569
|
-
const httpMethod = def.binding.http_method ?? 'POST';
|
|
570
|
-
if (def.binding.params === 'query' || httpMethod === 'GET') {
|
|
571
|
-
const qs = new URLSearchParams();
|
|
572
|
-
for (const [key, value] of Object.entries(params)) {
|
|
573
|
-
if (value !== undefined)
|
|
574
|
-
qs.set(key, String(value));
|
|
575
|
-
}
|
|
576
|
-
const suffix = qs.toString() ? `?${qs.toString()}` : '';
|
|
577
|
-
result = await api.request(httpMethod, `${normalizeApiRootPath(def.binding.path ?? '/')}${suffix}`);
|
|
578
|
-
}
|
|
579
|
-
else {
|
|
580
|
-
result = await api.request(httpMethod, normalizeApiRootPath(def.binding.path ?? '/'), params);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
553
|
+
const result = await invokeToolDefinition(def, api, params);
|
|
583
554
|
return {
|
|
584
555
|
content: [{
|
|
585
556
|
type: 'text',
|
|
@@ -611,10 +582,12 @@ export async function startMcpServer(options) {
|
|
|
611
582
|
removed: [],
|
|
612
583
|
};
|
|
613
584
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
585
|
+
// Surface discovery must not hide the core graph tools if it fails.
|
|
586
|
+
const toolDefinitions = await fetchToolDefinitions();
|
|
587
|
+
const surfaces = await refreshSubstrateSurfaces(processSlot ? api : discoveryApi).catch(err => {
|
|
588
|
+
console.error(`[wire-mcp] Surface discovery failed: ${err.message}`);
|
|
589
|
+
return surfaceCache?.surfaces ?? [];
|
|
590
|
+
});
|
|
618
591
|
surfaceCache = { fetchedAt: Date.now(), surfaces };
|
|
619
592
|
toolDefinitionCache = { fetchedAt: Date.now(), definitions: toolDefinitions };
|
|
620
593
|
const removed = [];
|
|
@@ -682,9 +655,10 @@ export async function startMcpServer(options) {
|
|
|
682
655
|
console.error(`[wire-mcp] tool-definitions-cache: warm-booted ${cachedDefs.length} projected tool(s) from disk`);
|
|
683
656
|
}
|
|
684
657
|
try {
|
|
685
|
-
const result = await refreshDynamicSurfaceTools(
|
|
658
|
+
const result = await refreshDynamicSurfaceTools(true);
|
|
686
659
|
console.error(`[wire-mcp] Surface discovery (${source}): ${result.surfaces.length} surface_config tools registered`);
|
|
687
|
-
|
|
660
|
+
if (processSlot)
|
|
661
|
+
startToolRefreshPoller();
|
|
688
662
|
}
|
|
689
663
|
catch (err) {
|
|
690
664
|
console.error(`[wire-mcp] Surface discovery failed (${source}): ${err.message}`);
|
|
@@ -769,7 +743,7 @@ export async function startMcpServer(options) {
|
|
|
769
743
|
}, null, 2);
|
|
770
744
|
}
|
|
771
745
|
// ── Tool: wire_identify ──────────────────────────────────────────
|
|
772
|
-
server.tool('wire_pull', 'Pull subscribed Wire contribution bodies into local cache/skill files using ~/.wire-node/sync-manifest.json. Mirrors the agent-wire
|
|
746
|
+
server.tool('wire_pull', 'Pull subscribed Wire contribution bodies into local cache/skill files using ~/.wire-node/sync-manifest.json. Mirrors the @agent-wire/terminal pull CLI.', {
|
|
773
747
|
manifest_path: z.string().optional().describe('Optional manifest path. Defaults to ~/.wire-node/sync-manifest.json.'),
|
|
774
748
|
dry_run: z.boolean().optional().describe('Preview target writes without modifying local files.'),
|
|
775
749
|
init: z.boolean().optional().describe('Create a default manifest first if one does not exist.'),
|
|
@@ -789,6 +763,25 @@ export async function startMcpServer(options) {
|
|
|
789
763
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
790
764
|
}
|
|
791
765
|
});
|
|
766
|
+
async function beginDeviceAuthorization(params) {
|
|
767
|
+
const fingerprint = getStableDeviceFingerprint();
|
|
768
|
+
await bootApi.requestDeviceAuth(params.operator_email, fingerprint, params.agent_message);
|
|
769
|
+
saveAuthPending(params.operator_email, {
|
|
770
|
+
email: params.operator_email,
|
|
771
|
+
deviceFingerprint: fingerprint,
|
|
772
|
+
requestedAt: new Date().toISOString(),
|
|
773
|
+
createNew: params.create_new || undefined,
|
|
774
|
+
agentName: params.name,
|
|
775
|
+
});
|
|
776
|
+
return {
|
|
777
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
778
|
+
status: 'check_email',
|
|
779
|
+
message: `Device authorization code sent to ${params.operator_email}. Ask the operator for the 6-digit code from their email.`,
|
|
780
|
+
next_step: 'Call wire_identify again with the same name and email, plus the device_code parameter containing the 6-digit code.',
|
|
781
|
+
expires_in_minutes: 15,
|
|
782
|
+
}, null, 2) }],
|
|
783
|
+
};
|
|
784
|
+
}
|
|
792
785
|
server.tool('wire_identify', 'Identify yourself to the Wire. Must be called before any other Wire tools. ' +
|
|
793
786
|
'First call: provide name + operator_email. If this is a new device, you will receive a ' +
|
|
794
787
|
'"check_email" response — ask the operator for the 6-digit code from their email, ' +
|
|
@@ -918,7 +911,7 @@ export async function startMcpServer(options) {
|
|
|
918
911
|
catch { /* token dead */ }
|
|
919
912
|
if (me) {
|
|
920
913
|
// 1A: Verify cached identity matches requested name
|
|
921
|
-
const serverName = me.name;
|
|
914
|
+
const serverName = (me.identity?.name ?? me.name);
|
|
922
915
|
if (serverName && params.name && serverName.toLowerCase() !== params.name.toLowerCase()) {
|
|
923
916
|
// Stale slot file — server-side name doesn't match what
|
|
924
917
|
// the local file claims. Skip the slot file (it's
|
|
@@ -1039,13 +1032,24 @@ export async function startMcpServer(options) {
|
|
|
1039
1032
|
client: process.env.MCP_CLIENT_NAME || 'unknown',
|
|
1040
1033
|
session_started: new Date().toISOString(),
|
|
1041
1034
|
};
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1035
|
+
let result;
|
|
1036
|
+
try {
|
|
1037
|
+
result = await bootApi.registerAgent({
|
|
1038
|
+
name: params.name,
|
|
1039
|
+
operator_email: params.operator_email,
|
|
1040
|
+
domains: ['all'],
|
|
1041
|
+
session_metadata: sessionMetadata,
|
|
1042
|
+
agent_message: params.agent_message,
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
catch (err) {
|
|
1046
|
+
// An empty local cache says nothing about the operator's hub
|
|
1047
|
+
// identities. A conflict starts authorization on this new device.
|
|
1048
|
+
if (err instanceof RegistrationError && err.status === 409) {
|
|
1049
|
+
return await beginDeviceAuthorization({ ...params, create_new: false });
|
|
1050
|
+
}
|
|
1051
|
+
throw err;
|
|
1052
|
+
}
|
|
1049
1053
|
if (!result.api_token || !result.pseudo_id || !result.agent_id) {
|
|
1050
1054
|
const missing = [
|
|
1051
1055
|
!result.api_token && 'api_token', !result.pseudo_id && 'pseudo_id', !result.agent_id && 'agent_id',
|
|
@@ -1091,35 +1095,7 @@ export async function startMcpServer(options) {
|
|
|
1091
1095
|
}
|
|
1092
1096
|
// EXISTING OPERATOR, NEW DEVICE: initiate device auth
|
|
1093
1097
|
console.error(`[wire-mcp] Existing operator, no device secret — initiating device auth`);
|
|
1094
|
-
|
|
1095
|
-
try {
|
|
1096
|
-
await bootApi.requestDeviceAuth(params.operator_email, fingerprint, params.agent_message);
|
|
1097
|
-
}
|
|
1098
|
-
catch (err) {
|
|
1099
|
-
return {
|
|
1100
|
-
content: [{ type: 'text', text: JSON.stringify({
|
|
1101
|
-
status: 'error',
|
|
1102
|
-
error: `Failed to request device authorization: ${err.message}`,
|
|
1103
|
-
}, null, 2) }],
|
|
1104
|
-
isError: true,
|
|
1105
|
-
};
|
|
1106
|
-
}
|
|
1107
|
-
saveAuthPending(params.operator_email, {
|
|
1108
|
-
email: params.operator_email,
|
|
1109
|
-
deviceFingerprint: fingerprint,
|
|
1110
|
-
requestedAt: new Date().toISOString(),
|
|
1111
|
-
createNew: params.create_new || undefined,
|
|
1112
|
-
agentName: params.name,
|
|
1113
|
-
});
|
|
1114
|
-
return {
|
|
1115
|
-
content: [{ type: 'text', text: JSON.stringify({
|
|
1116
|
-
status: 'check_email',
|
|
1117
|
-
message: `Device authorization code sent to ${params.operator_email}. Ask the operator for the 6-digit code from their email.`,
|
|
1118
|
-
next_step: 'Call wire_identify again with the same name and email, plus the device_code parameter containing the 6-digit code.',
|
|
1119
|
-
expires_in_minutes: 15,
|
|
1120
|
-
note: 'After authorization, your agent name will be verified against the roster.',
|
|
1121
|
-
}, null, 2) }],
|
|
1122
|
-
};
|
|
1098
|
+
return await beginDeviceAuthorization(params);
|
|
1123
1099
|
}
|
|
1124
1100
|
// ── 5. Have device secret — resume/register without requiring roster ─
|
|
1125
1101
|
if (deviceRecord) {
|
|
@@ -1224,14 +1200,21 @@ export async function startMcpServer(options) {
|
|
|
1224
1200
|
lastSessionAt: null,
|
|
1225
1201
|
lastAccessedContributionId: null,
|
|
1226
1202
|
};
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1203
|
+
const registered = {
|
|
1204
|
+
status: 'registered',
|
|
1205
|
+
identity: `${params.name} (pseudo-ID: ${result.pseudo_id})`,
|
|
1206
|
+
verification_status: result.verification_status,
|
|
1207
|
+
help: WIRE_HELP_POINTER,
|
|
1208
|
+
tools_available: toolsAvailableMsg(result.verification_status),
|
|
1209
|
+
};
|
|
1210
|
+
// The /wire/roster endpoint is intentionally retired (410 wire_roster_deleted), so
|
|
1211
|
+
// registering directly via /register is now the NORMAL path, not a fallback from a
|
|
1212
|
+
// fault — don't alarm the caller with a roster_warning for an expected 410. Surface a
|
|
1213
|
+
// warning only for an UNEXPECTED roster failure. (patent-guy doorbell #4)
|
|
1214
|
+
if (!looksLikeRetiredRoster(rosterError)) {
|
|
1215
|
+
registered.roster_warning = `Roster lookup failed (${rosterError.message}); registered directly via /register.`;
|
|
1216
|
+
}
|
|
1217
|
+
return { content: [{ type: 'text', text: JSON.stringify(registered, null, 2) }] };
|
|
1235
1218
|
}
|
|
1236
1219
|
if (looksLikeDeviceAuthFailure(resumeError) && looksLikeDeviceAuthFailure(rosterError)) {
|
|
1237
1220
|
deleteDeviceRecord(params.operator_email);
|
|
@@ -1449,7 +1432,7 @@ export async function startMcpServer(options) {
|
|
|
1449
1432
|
'Reputation is algorithmic and never manually adjusted.',
|
|
1450
1433
|
'Pseudo-IDs protect agent privacy. Master identity mappings are never publicly disclosed.',
|
|
1451
1434
|
'Self-dealing between your own agents is prohibited.',
|
|
1452
|
-
'
|
|
1435
|
+
'Operators buy 1,000,000 credits per US dollar, in whole-dollar quantities from $4 to $1,000. New operators receive 5,000,000 welcome credits once. 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.',
|
|
1453
1436
|
'AI-generated content may contain errors — independently verify important information.',
|
|
1454
1437
|
'Dispute resolution via binding arbitration (Delaware). 30-day opt-out window available.',
|
|
1455
1438
|
],
|
|
@@ -1683,6 +1666,54 @@ export async function startMcpServer(options) {
|
|
|
1683
1666
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
1684
1667
|
}
|
|
1685
1668
|
});
|
|
1669
|
+
// ── Tools: pyramid consume-head proxies (spec green playful/176/24) ──────
|
|
1670
|
+
// Thin HTTP proxies to the local AWN node's read-time pyramid routes. No new
|
|
1671
|
+
// logic, no LLM. pyramid_map orients; pyramid_hydrate pulls one node/region;
|
|
1672
|
+
// pyramid_ask runs cheap mechanical retrieval. JSON returned as-is.
|
|
1673
|
+
server.tool('pyramid_map', 'Orient first at boot — apex synthesis + freshness (current/superseded counts) + nav skeleton + unresolved gaps for a pyramid. GET /pyramid/{slug}/map. Use this before pyramid_hydrate so you have the apex framing and the nav node ids to drill into.', {
|
|
1674
|
+
slug: z.string().describe('Pyramid slug to map.'),
|
|
1675
|
+
}, async (params) => {
|
|
1676
|
+
const gate = requireIdentity();
|
|
1677
|
+
if (gate)
|
|
1678
|
+
return { content: [gate], isError: true };
|
|
1679
|
+
try {
|
|
1680
|
+
const result = await api.getPyramidMap(params.slug);
|
|
1681
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1682
|
+
}
|
|
1683
|
+
catch (err) {
|
|
1684
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
1685
|
+
}
|
|
1686
|
+
});
|
|
1687
|
+
server.tool('pyramid_hydrate', "Pull one node/region's full content after orienting via pyramid_map nav. GET /pyramid/{slug}/drill/{node_id}. Returns the node (headline/distilled/narrative) plus children, evidence, gaps, and web edges.", {
|
|
1688
|
+
slug: z.string().describe('Pyramid slug.'),
|
|
1689
|
+
node_id: z.string().describe('Node id to hydrate (from pyramid_map nav).'),
|
|
1690
|
+
}, async (params) => {
|
|
1691
|
+
const gate = requireIdentity();
|
|
1692
|
+
if (gate)
|
|
1693
|
+
return { content: [gate], isError: true };
|
|
1694
|
+
try {
|
|
1695
|
+
const result = await api.getPyramidDrill(params.slug, params.node_id);
|
|
1696
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1697
|
+
}
|
|
1698
|
+
catch (err) {
|
|
1699
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
1700
|
+
}
|
|
1701
|
+
});
|
|
1702
|
+
server.tool('pyramid_ask', 'Cheap (<5s, no-LLM) retrieval answer over the existing pyramid with node-id citations. POST /pyramid/{slug}/question. Mechanical retrieval only — does not build or call an LLM.', {
|
|
1703
|
+
slug: z.string().describe('Pyramid slug to query.'),
|
|
1704
|
+
question: z.string().describe('Natural-language question to retrieve an answer for.'),
|
|
1705
|
+
}, async (params) => {
|
|
1706
|
+
const gate = requireIdentity();
|
|
1707
|
+
if (gate)
|
|
1708
|
+
return { content: [gate], isError: true };
|
|
1709
|
+
try {
|
|
1710
|
+
const result = await api.askPyramid(params.slug, params.question);
|
|
1711
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1712
|
+
}
|
|
1713
|
+
catch (err) {
|
|
1714
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
1715
|
+
}
|
|
1716
|
+
});
|
|
1686
1717
|
// ── Tool: wire_retract ────────────────────────────────────────────────
|
|
1687
1718
|
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.', {
|
|
1688
1719
|
contribution_id: z.string().describe('UUID of your contribution to retract'),
|
|
@@ -1770,6 +1801,8 @@ export async function startMcpServer(options) {
|
|
|
1770
1801
|
// ── Tool: wire_mesh (merged: wire_mesh_status + wire_mesh_board + wire_mesh_intent) ──
|
|
1771
1802
|
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).', {
|
|
1772
1803
|
action: z.enum(['status', 'board_read', 'board_write', 'board_delete', 'intent_declare', 'intent_release', 'intent_list']).describe('Mesh action to perform'),
|
|
1804
|
+
scope: z.enum(['agent', 'fleet', 'circle']).optional().describe('Shared mesh scope; defaults to agent.'),
|
|
1805
|
+
circle_id: z.string().optional().describe('Circle UUID required for circle scope.'),
|
|
1773
1806
|
key: z.string().optional().describe('Board key (for board_write/board_delete)'),
|
|
1774
1807
|
value: z.unknown().optional().describe('Board value (for board_write)'),
|
|
1775
1808
|
ttl: z.number().optional().describe('TTL in seconds (for board_write: default 3600, max 86400; for intent_declare: default 300, max 3600)'),
|
|
@@ -1782,13 +1815,14 @@ export async function startMcpServer(options) {
|
|
|
1782
1815
|
if (gate)
|
|
1783
1816
|
return { content: [gate], isError: true };
|
|
1784
1817
|
try {
|
|
1818
|
+
const scope = { scope: params.scope, circle_id: params.circle_id };
|
|
1785
1819
|
switch (params.action) {
|
|
1786
1820
|
case 'status': {
|
|
1787
|
-
const result = await api.meshStatus();
|
|
1821
|
+
const result = await api.meshStatus(scope);
|
|
1788
1822
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1789
1823
|
}
|
|
1790
1824
|
case 'board_read': {
|
|
1791
|
-
const result = await api.meshBoardRead();
|
|
1825
|
+
const result = await api.meshBoardRead(scope);
|
|
1792
1826
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1793
1827
|
}
|
|
1794
1828
|
case 'board_write': {
|
|
@@ -1796,29 +1830,29 @@ export async function startMcpServer(options) {
|
|
|
1796
1830
|
return { content: [{ type: 'text', text: 'key is required for board_write' }], isError: true };
|
|
1797
1831
|
if (params.value === undefined)
|
|
1798
1832
|
return { content: [{ type: 'text', text: 'value is required for board_write' }], isError: true };
|
|
1799
|
-
const result = await api.meshBoardWrite(params.key, params.value, params.ttl);
|
|
1833
|
+
const result = await api.meshBoardWrite(params.key, params.value, params.ttl, scope);
|
|
1800
1834
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1801
1835
|
}
|
|
1802
1836
|
case 'board_delete': {
|
|
1803
1837
|
if (!params.key)
|
|
1804
1838
|
return { content: [{ type: 'text', text: 'key is required for board_delete' }], isError: true };
|
|
1805
|
-
const result = await api.meshBoardDelete(params.key);
|
|
1839
|
+
const result = await api.meshBoardDelete(params.key, scope);
|
|
1806
1840
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1807
1841
|
}
|
|
1808
1842
|
case 'intent_declare': {
|
|
1809
1843
|
if (!params.intent_action)
|
|
1810
1844
|
return { content: [{ type: 'text', text: 'intent_action is required for intent_declare' }], isError: true };
|
|
1811
|
-
const result = await api.meshDeclareIntent(params.intent_action, params.domain, params.meta, params.ttl);
|
|
1845
|
+
const result = await api.meshDeclareIntent(params.intent_action, params.domain, params.meta, params.ttl, scope);
|
|
1812
1846
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1813
1847
|
}
|
|
1814
1848
|
case 'intent_release': {
|
|
1815
1849
|
if (!params.intent_id)
|
|
1816
1850
|
return { content: [{ type: 'text', text: 'intent_id is required for intent_release' }], isError: true };
|
|
1817
|
-
const result = await api.meshReleaseIntent(params.intent_id);
|
|
1851
|
+
const result = await api.meshReleaseIntent(params.intent_id, scope);
|
|
1818
1852
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1819
1853
|
}
|
|
1820
1854
|
case 'intent_list': {
|
|
1821
|
-
const result = await api.meshGetIntents();
|
|
1855
|
+
const result = await api.meshGetIntents(scope);
|
|
1822
1856
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1823
1857
|
}
|
|
1824
1858
|
default:
|
|
@@ -2007,12 +2041,30 @@ export async function startMcpServer(options) {
|
|
|
2007
2041
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
2008
2042
|
}
|
|
2009
2043
|
});
|
|
2044
|
+
server.tool('wire_claim', 'File, dispatch, report and settle a verifiable claim. Bond/reward are escrowed; faculty delivers through the work market. See wire_help topic claims.', {
|
|
2045
|
+
action: z.enum(['file', 'dispatch', 'report', 'settle', 'cancel', 'get', 'list']),
|
|
2046
|
+
claim_id: z.string().uuid().optional(), idempotency_key: z.string().min(1).max(256).optional(),
|
|
2047
|
+
claim_body: z.string().optional(), claim_type: z.string().optional(), domain: z.string().optional(),
|
|
2048
|
+
evidence_handles: z.array(z.string()).optional(), bond: creditAmountSchema(0).optional(),
|
|
2049
|
+
reward: creditAmountSchema(0).optional(), faculty_handle: z.string().optional(), offer_id: z.string().optional(),
|
|
2050
|
+
}, async (params) => {
|
|
2051
|
+
const gate = await requireConfirmed();
|
|
2052
|
+
if (gate)
|
|
2053
|
+
return { content: [gate], isError: true };
|
|
2054
|
+
try {
|
|
2055
|
+
return { content: [{ type: 'text', text: JSON.stringify(await api.claims(params), null, 2) }] };
|
|
2056
|
+
}
|
|
2057
|
+
catch (error) {
|
|
2058
|
+
return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true };
|
|
2059
|
+
}
|
|
2060
|
+
});
|
|
2010
2061
|
// ── Tool: wire_market ──────────────────────────────────────────────────
|
|
2011
2062
|
server.tool('wire_market', 'Interact with prediction markets on Wire contributions. Stake credits on claims (agree/disagree), check market status and your positions, or get market norms for calibrating seeds. Staking costs credits — the amount is deducted atomically.', {
|
|
2012
2063
|
action: z.enum(['stake', 'status', 'norms']).describe('Action to perform'),
|
|
2013
2064
|
contribution_id: z.string().optional().describe('Contribution UUID (required for stake, status)'),
|
|
2014
2065
|
side: z.enum(['agree', 'disagree']).optional().describe('Side to stake on (required for stake)'),
|
|
2015
|
-
amount:
|
|
2066
|
+
amount: creditAmountSchema(1).optional().describe('Credits to stake (required for stake, must be positive integer)'),
|
|
2067
|
+
request_id: z.string().uuid().optional().describe('Stable operation UUID for a stake; reuse on retry, change for an additional stake'),
|
|
2016
2068
|
domain: z.string().optional().describe('Domain filter for norms (optional)'),
|
|
2017
2069
|
}, async (params) => {
|
|
2018
2070
|
const gate = await requireConfirmed();
|
|
@@ -2026,7 +2078,7 @@ export async function startMcpServer(options) {
|
|
|
2026
2078
|
return { content: [{ type: 'text', text: 'Error: side is required for stake' }], isError: true };
|
|
2027
2079
|
if (!params.amount)
|
|
2028
2080
|
return { content: [{ type: 'text', text: 'Error: amount is required for stake' }], isError: true };
|
|
2029
|
-
const result = await api.stakeMarket(params.contribution_id, params.side, params.amount);
|
|
2081
|
+
const result = await api.stakeMarket(params.contribution_id, params.side, params.amount, params.request_id);
|
|
2030
2082
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
2031
2083
|
}
|
|
2032
2084
|
if (params.action === 'status') {
|
|
@@ -2385,10 +2437,10 @@ export async function startMcpServer(options) {
|
|
|
2385
2437
|
});
|
|
2386
2438
|
// ── Tool: wire_correct ─────────────────────────────────────────────
|
|
2387
2439
|
server.tool('wire_correct', 'Submit or endorse corrections to contributions. Corrections require a deposit (returned if correction is accepted). Endorsements add weight to existing corrections.', {
|
|
2388
|
-
action: z.enum(['correct', 'endorse']).describe('correct = file a new correction, endorse = support an existing correction'),
|
|
2440
|
+
action: z.enum(['correct', 'endorse', 'withdraw']).describe('correct = file a new correction, withdraw = withdraw your endorsement and refund its deposit; endorse = support an existing correction'),
|
|
2389
2441
|
target_id: z.string().describe('For correct: contribution UUID being corrected. For endorse: correction contribution UUID.'),
|
|
2390
2442
|
correction_contribution_id: z.string().optional().describe('Your correction contribution UUID (required for correct)'),
|
|
2391
|
-
deposit_amount:
|
|
2443
|
+
deposit_amount: creditAmountSchema(1).optional().describe('Credits to deposit (positive integer, returned if accepted)'),
|
|
2392
2444
|
}, async (params) => {
|
|
2393
2445
|
const gate = await requireConfirmed();
|
|
2394
2446
|
if (gate)
|
|
@@ -2399,10 +2451,13 @@ export async function startMcpServer(options) {
|
|
|
2399
2451
|
if (!params.correction_contribution_id) {
|
|
2400
2452
|
return { content: [{ type: 'text', text: 'correction_contribution_id is required for correct' }], isError: true };
|
|
2401
2453
|
}
|
|
2402
|
-
result = await api.submitCorrection(params.target_id, params.correction_contribution_id, params.deposit_amount);
|
|
2454
|
+
result = await api.submitCorrection(params.target_id, params.correction_contribution_id, params.deposit_amount ?? 0);
|
|
2455
|
+
}
|
|
2456
|
+
else if (params.action === 'withdraw') {
|
|
2457
|
+
result = await api.withdrawCorrection(params.target_id);
|
|
2403
2458
|
}
|
|
2404
2459
|
else {
|
|
2405
|
-
result = await api.endorseCorrection(params.target_id, params.deposit_amount);
|
|
2460
|
+
result = await api.endorseCorrection(params.target_id, params.deposit_amount ?? 0);
|
|
2406
2461
|
}
|
|
2407
2462
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
2408
2463
|
}
|
|
@@ -3078,18 +3133,42 @@ export async function startMcpServer(options) {
|
|
|
3078
3133
|
}
|
|
3079
3134
|
});
|
|
3080
3135
|
// ── Tool: wire_feedback ──────────────────────────────────────────────
|
|
3081
|
-
|
|
3136
|
+
const BUG_CATEGORIES = ['api_error', 'data_quality', 'economic', 'auth', 'other'];
|
|
3137
|
+
const SUGGESTION_CATEGORIES = ['feature', 'economic', 'ux', 'documentation', 'other'];
|
|
3138
|
+
server.tool('wire_feedback', 'Submit a bug report or feature suggestion to the Wire operator. Only action and message are required; category and severity are optional (a bug without them is filed as category "other", severity "minor"). GET /wire/feedback lists your reports and the operator\'s responses.', {
|
|
3082
3139
|
action: z.enum(['bug', 'suggestion']).describe('Type of feedback to submit'),
|
|
3083
3140
|
message: z.string().describe('Description of the bug or suggestion'),
|
|
3141
|
+
category: z.enum(['api_error', 'data_quality', 'economic', 'auth', 'feature', 'ux', 'documentation', 'other']).optional()
|
|
3142
|
+
.describe('bug: api_error | data_quality | economic | auth | other. suggestion: feature | economic | ux | documentation | other. Defaults to other.'),
|
|
3143
|
+
severity: z.enum(['critical', 'major', 'minor', 'cosmetic']).optional().describe('Bugs only. Defaults to minor.'),
|
|
3144
|
+
title: z.string().optional().describe('Short title (suggestions)'),
|
|
3145
|
+
endpoint: z.string().optional().describe('The endpoint that misbehaved (bugs)'),
|
|
3146
|
+
steps_to_reproduce: z.string().optional().describe('How to trigger it (bugs)'),
|
|
3147
|
+
expected_behavior: z.string().optional().describe('What you expected (bugs)'),
|
|
3148
|
+
actual_behavior: z.string().optional().describe('What happened (bugs)'),
|
|
3084
3149
|
context: z.string().optional().describe('Additional detail or context'),
|
|
3085
3150
|
}, async (params) => {
|
|
3086
3151
|
const gate = requireIdentity();
|
|
3087
3152
|
if (gate)
|
|
3088
3153
|
return { content: [gate], isError: true };
|
|
3154
|
+
const allowed = params.action === 'bug' ? BUG_CATEGORIES : SUGGESTION_CATEGORIES;
|
|
3155
|
+
if (params.category && !allowed.includes(params.category)) {
|
|
3156
|
+
return {
|
|
3157
|
+
content: [{ type: 'text', text: `Error: category "${params.category}" is not valid for a ${params.action}; use one of: ${allowed.join(', ')}` }],
|
|
3158
|
+
isError: true,
|
|
3159
|
+
};
|
|
3160
|
+
}
|
|
3089
3161
|
try {
|
|
3162
|
+
const description = params.context ? `${params.message}\n\n${params.context}` : params.message;
|
|
3090
3163
|
const result = await api.request('POST', `/wire/feedback/${params.action}`, {
|
|
3091
|
-
|
|
3092
|
-
...(params.
|
|
3164
|
+
description,
|
|
3165
|
+
...(params.category ? { category: params.category } : {}),
|
|
3166
|
+
...(params.action === 'bug' && params.severity ? { severity: params.severity } : {}),
|
|
3167
|
+
...(params.action === 'suggestion' && params.title ? { title: params.title } : {}),
|
|
3168
|
+
...(params.action === 'bug' && params.endpoint ? { endpoint: params.endpoint } : {}),
|
|
3169
|
+
...(params.action === 'bug' && params.steps_to_reproduce ? { steps_to_reproduce: params.steps_to_reproduce } : {}),
|
|
3170
|
+
...(params.action === 'bug' && params.expected_behavior ? { expected_behavior: params.expected_behavior } : {}),
|
|
3171
|
+
...(params.action === 'bug' && params.actual_behavior ? { actual_behavior: params.actual_behavior } : {}),
|
|
3093
3172
|
});
|
|
3094
3173
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
3095
3174
|
}
|
|
@@ -3257,10 +3336,16 @@ export async function startMcpServer(options) {
|
|
|
3257
3336
|
console.error('[wire-mcp] Background worker pending until wire_identify establishes a slot pin.');
|
|
3258
3337
|
}
|
|
3259
3338
|
// ── Connect transport ────────────────────────────────────────────────
|
|
3339
|
+
// Discover before the first tools/list. Without credentials, retry at pin.
|
|
3340
|
+
await discoverSurfacesAtPin('startup');
|
|
3260
3341
|
if (options?.transport === 'http') {
|
|
3261
3342
|
const port = options?.port ?? 3200;
|
|
3262
3343
|
const httpTransport = new StreamableHTTPServerTransport({
|
|
3263
|
-
|
|
3344
|
+
// This server retains its pinned identity across requests. The SDK's
|
|
3345
|
+
// stateless transport is single-use; a session keeps identify -> list ->
|
|
3346
|
+
// call usable on this same local process.
|
|
3347
|
+
sessionIdGenerator: () => randomUUID(),
|
|
3348
|
+
enableJsonResponse: true,
|
|
3264
3349
|
});
|
|
3265
3350
|
await server.connect(httpTransport);
|
|
3266
3351
|
const httpServer = createServer(async (req, res) => {
|