agoragentic-mcp 1.3.2 → 1.3.3

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/mcp-server.js CHANGED
@@ -1,52 +1,60 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const readline = require('readline');
5
- const crypto = require('crypto');
6
- const { version: PACKAGE_VERSION } = require('./package.json');
7
-
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const readline = require('readline');
5
+ const crypto = require('crypto');
6
+ const { version: PACKAGE_VERSION } = require('./package.json');
7
+
8
8
  const REMOTE_MCP_URL = process.env.AGORAGENTIC_MCP_URL || 'https://agoragentic.com/api/mcp';
9
9
  const AGORAGENTIC_BASE = process.env.AGORAGENTIC_BASE_URL || 'https://agoragentic.com';
10
- const API_KEY = process.env.AGORAGENTIC_API_KEY || '';
10
+ const RAW_API_KEY = process.env.AGORAGENTIC_API_KEY || '';
11
+ const PLACEHOLDER_API_KEYS = new Set([
12
+ 'amk_glama_test_placeholder',
13
+ 'amk_your_key',
14
+ 'amk_your_key_here',
15
+ 'amk_placeholder',
16
+ 'amk_test_placeholder',
17
+ ]);
18
+ const API_KEY = PLACEHOLDER_API_KEYS.has(RAW_API_KEY.trim()) ? '' : RAW_API_KEY.trim();
11
19
  const ACP_MODE = process.argv.includes('--acp');
12
-
20
+
13
21
  const ACP_TOOLS = [
14
- {
15
- name: 'agoragentic_execute',
16
- description: 'Route a task through Agent OS execute() with provider selection, fallback, receipts, and settlement.',
17
- },
18
- {
19
- name: 'agoragentic_match',
20
- description: 'Preview routed providers before execution.',
21
- },
22
- {
23
- name: 'agoragentic_quote',
24
- description: 'Create a bounded quote before paid execution.',
25
- },
26
- {
27
- name: 'agoragentic_status',
28
- description: 'Inspect execution status for an invocation.',
29
- },
30
- {
31
- name: 'agoragentic_receipt',
32
- description: 'Fetch normalized receipt and settlement metadata.',
33
- },
34
- {
35
- name: 'agoragentic_browse_services',
36
- description: 'Browse stable x402 edge resources.',
37
- },
38
- {
39
- name: 'agoragentic_call_service',
40
- description: 'Call a stable x402 edge resource after payment challenge handling.',
41
- },
42
- {
43
- name: 'agoragentic_edge_receipt',
44
- description: 'Inspect x402 edge receipt metadata.',
45
- },
46
- {
47
- name: 'agoragentic_x402_test',
48
- description: 'Exercise the free x402 pipeline canary.',
49
- },
22
+ {
23
+ name: 'agoragentic_execute',
24
+ description: 'Route a task through Agent OS execute() with provider selection, fallback, receipts, and settlement.',
25
+ },
26
+ {
27
+ name: 'agoragentic_match',
28
+ description: 'Preview routed providers before execution.',
29
+ },
30
+ {
31
+ name: 'agoragentic_quote',
32
+ description: 'Create a bounded quote before paid execution.',
33
+ },
34
+ {
35
+ name: 'agoragentic_status',
36
+ description: 'Inspect execution status for an invocation.',
37
+ },
38
+ {
39
+ name: 'agoragentic_receipt',
40
+ description: 'Fetch normalized receipt and settlement metadata.',
41
+ },
42
+ {
43
+ name: 'agoragentic_browse_services',
44
+ description: 'Browse stable x402 edge resources.',
45
+ },
46
+ {
47
+ name: 'agoragentic_call_service',
48
+ description: 'Call a stable x402 edge resource after payment challenge handling.',
49
+ },
50
+ {
51
+ name: 'agoragentic_edge_receipt',
52
+ description: 'Inspect x402 edge receipt metadata.',
53
+ },
54
+ {
55
+ name: 'agoragentic_x402_test',
56
+ description: 'Exercise the free x402 pipeline canary.',
57
+ },
50
58
  ];
51
59
 
52
60
  function buildJsonContent(data) {
@@ -108,65 +116,128 @@ function buildFallbackToolList() {
108
116
  return [
109
117
  {
110
118
  name: 'agoragentic_register',
111
- description: 'Register an agent with Agoragentic and receive an API key for routed execution.',
119
+ description:
120
+ 'Register a new agent with the Agoragentic marketplace and receive an API key. ' +
121
+ 'Use this as the first step before calling agoragentic_execute, agoragentic_match, or agoragentic_execute_status. ' +
122
+ 'This tool is idempotent: registering the same agent name returns the existing API key. ' +
123
+ 'No AGORAGENTIC_API_KEY environment variable is required to call this tool. ' +
124
+ 'Side effects: creates a persistent agent record on the Agoragentic server if one does not exist. ' +
125
+ 'Returns JSON with fields: ok (boolean), api_key (string), agent_id (string), and wallet address. ' +
126
+ 'On error, returns JSON with ok:false and an error message string.',
112
127
  inputSchema: {
113
128
  type: 'object',
114
129
  properties: {
115
- name: { type: 'string', description: 'Agent name' },
116
- agent_name: { type: 'string', description: 'Agent name, compatibility alias' },
117
- intent: { type: 'string', description: 'buyer, seller, or both', default: 'buyer' },
118
- description: { type: 'string', description: 'Short agent description' },
130
+ name: { type: 'string', description: 'Human-readable agent name, e.g. "my-research-agent"' },
131
+ agent_name: { type: 'string', description: 'Alias for the name field (backward compatibility). Prefer using name instead.' },
132
+ intent: {
133
+ type: 'string',
134
+ enum: ['buyer', 'seller', 'both'],
135
+ description: 'Agent marketplace role: "buyer" to consume services, "seller" to provide them, or "both"',
136
+ default: 'buyer',
137
+ },
138
+ description: { type: 'string', description: 'One-line summary of what this agent does, e.g. "Summarizes web pages on demand"' },
119
139
  },
120
140
  },
121
141
  },
122
142
  {
123
143
  name: 'agoragentic_search',
124
- description: 'Search public Agoragentic marketplace capabilities.',
144
+ description:
145
+ 'Search the public Agoragentic marketplace for available agent capabilities and services. ' +
146
+ 'Use this to discover what services exist before calling agoragentic_execute or agoragentic_match. ' +
147
+ 'This is a read-only operation with no side effects and no USDC spend. ' +
148
+ 'No AGORAGENTIC_API_KEY is required. ' +
149
+ 'Returns JSON with an array of matching capabilities, each containing id, name, description, category, and price_usdc. ' +
150
+ 'Returns an empty array when no capabilities match the query.',
125
151
  inputSchema: {
126
152
  type: 'object',
127
153
  properties: {
128
- query: { type: 'string', description: 'Search query' },
129
- category: { type: 'string', description: 'Optional category filter' },
130
- limit: { type: 'number', description: 'Maximum results to return', default: 10 },
154
+ query: { type: 'string', description: 'Free-text search query, e.g. "text summarization" or "web scraping"' },
155
+ category: { type: 'string', description: 'Filter results by category slug, e.g. "ai-ml", "data", or "web". Omit to search all categories.' },
156
+ limit: { type: 'number', description: 'Maximum number of results to return (1\u2013100)', default: 10 },
131
157
  },
132
158
  },
133
159
  },
134
160
  {
135
161
  name: 'agoragentic_match',
136
- description: 'Preview Router / Marketplace provider matches for a task. No spend.',
162
+ description:
163
+ 'Preview which providers the Agoragentic Router would select for a given task, without executing or spending USDC. ' +
164
+ 'Use this before agoragentic_execute to compare providers, check pricing, and verify availability. ' +
165
+ 'This is a read-only, non-destructive operation with no side effects. ' +
166
+ 'Requires the AGORAGENTIC_API_KEY environment variable to be set. Returns ok:false with error "missing_api_key" if the key is absent. ' +
167
+ 'Returns JSON with matched providers including their trust scores, estimated cost in USDC, and routing rationale.',
137
168
  inputSchema: {
138
169
  type: 'object',
139
170
  properties: {
140
- task: { type: 'string', description: 'Task to route' },
141
- max_cost: { type: 'number', description: 'Maximum USDC price per call' },
142
- category: { type: 'string', description: 'Optional category filter' },
143
- prefer_trusted: { type: 'boolean', description: 'Prefer trusted providers', default: true },
171
+ task: { type: 'string', description: 'Natural-language task description to match against providers, e.g. "summarize this article"' },
172
+ max_cost: { type: 'number', description: 'Maximum acceptable USDC price per call. Providers above this price are excluded from results.' },
173
+ category: { type: 'string', description: 'Optional category filter to narrow provider matches, e.g. "ai-ml"' },
174
+ prefer_trusted: { type: 'boolean', description: 'When true, rank verified and trusted providers higher in results', default: true },
144
175
  },
145
176
  required: ['task'],
146
177
  },
147
178
  },
148
179
  {
149
180
  name: 'agoragentic_execute',
150
- description: 'Execute a task through the hosted Agoragentic Router / Marketplace. May spend according to listing price and account balance.',
181
+ description:
182
+ 'Execute a task through the Agoragentic Router / Marketplace. The Router selects a provider, invokes it, and returns the result with a receipt. ' +
183
+ 'IMPORTANT: This tool MAY SPEND USDC from the authenticated agent wallet based on the matched provider listing price. ' +
184
+ 'This tool is NOT idempotent \u2014 each call creates a new invocation and may incur a charge. ' +
185
+ 'Prefer agoragentic_match first to preview providers and pricing without spending. ' +
186
+ 'Use agoragentic_search to discover available capabilities before executing. ' +
187
+ 'Do NOT call this tool for read-only discovery; use agoragentic_search or agoragentic_match instead. ' +
188
+ 'Requires the AGORAGENTIC_API_KEY environment variable. Returns ok:false with error "missing_api_key" if the key is absent. ' +
189
+ 'On success, returns JSON with: invocation_id (string), output (provider result object), cost_usdc (number), provider_id (string), and receipt metadata. ' +
190
+ 'On failure, returns JSON with ok:false, a status code, and error details describing routing or provider errors.',
151
191
  inputSchema: {
152
192
  type: 'object',
153
193
  properties: {
154
- task: { type: 'string', description: 'Task to execute' },
155
- input: { type: 'object', description: 'Provider input payload', default: {} },
156
- constraints: { type: 'object', description: 'Routing and budget constraints', default: {} },
157
- quote_id: { type: 'string', description: 'Optional quote ID' },
158
- intent_contract_id: { type: 'string', description: 'Optional Agent OS intent contract ID' },
194
+ task: {
195
+ type: 'string',
196
+ description: 'Natural-language task for the Router to match and execute, e.g. "summarize this article" or "scrape https://example.com"',
197
+ },
198
+ input: {
199
+ type: 'object',
200
+ description:
201
+ 'Structured input payload forwarded to the matched provider. Shape depends on the provider API. ' +
202
+ 'Example: {"text": "Hello world", "max_sentences": 3}',
203
+ default: {},
204
+ },
205
+ constraints: {
206
+ type: 'object',
207
+ description:
208
+ 'Optional routing and budget constraints. Supported fields: max_cost (number, maximum USDC per call), ' +
209
+ 'provider_id (string, pin to a specific provider), category (string). ' +
210
+ 'Example: {"max_cost": 0.05, "category": "ai-ml"}',
211
+ default: {},
212
+ },
213
+ quote_id: {
214
+ type: 'string',
215
+ description: 'ID from a prior agoragentic_quote call to lock in a pre-agreed price. Omit for standard dynamic pricing.',
216
+ },
217
+ intent_contract_id: {
218
+ type: 'string',
219
+ description: 'Agent OS intent contract ID for auditable intent-to-execution tracking. Omit if not using intent contracts.',
220
+ },
159
221
  },
160
222
  required: ['task'],
161
223
  },
162
224
  },
163
225
  {
164
226
  name: 'agoragentic_execute_status',
165
- description: 'Read status, output, cost, and receipt metadata for a routed execution.',
227
+ description:
228
+ 'Check the status, output, cost, and receipt of a previous agoragentic_execute invocation. ' +
229
+ 'Use this to poll for results of async executions or to retrieve receipt metadata after completion. ' +
230
+ 'This is a read-only operation with no side effects and no USDC spend. ' +
231
+ 'Requires the AGORAGENTIC_API_KEY environment variable. Returns ok:false with error "missing_api_key" if the key is absent. ' +
232
+ 'Returns JSON with: status ("pending", "completed", or "failed"), output (provider result), cost_usdc, provider_id, receipt_id, and timestamps. ' +
233
+ 'Returns ok:false with error "invalid_invocation_id" if the ID is empty or contains disallowed characters.',
166
234
  inputSchema: {
167
235
  type: 'object',
168
236
  properties: {
169
- invocation_id: { type: 'string', description: 'Invocation ID from agoragentic_execute' },
237
+ invocation_id: {
238
+ type: 'string',
239
+ description: 'The invocation_id string returned by a prior agoragentic_execute call, e.g. "inv_abc123def456"',
240
+ },
170
241
  },
171
242
  required: ['invocation_id'],
172
243
  },
@@ -185,6 +256,8 @@ function mergeFallbackTools(remoteTools = []) {
185
256
  return merged;
186
257
  }
187
258
 
259
+ const FALLBACK_TOOL_NAMES = new Set(buildFallbackToolList().map((tool) => tool.name));
260
+
188
261
  async function executeFallbackTool(name, args = {}) {
189
262
  if (name === 'agoragentic_register') {
190
263
  const agentName = args.agent_name || args.name || 'mcp-agent';
@@ -246,51 +319,51 @@ async function executeFallbackTool(name, args = {}) {
246
319
  tool: name,
247
320
  });
248
321
  }
249
-
250
- function buildRemoteTransport() {
251
- const { StreamableHTTPClientTransport } = require('@modelcontextprotocol/sdk/client/streamableHttp.js');
252
- const headers = {};
253
- if (API_KEY) {
254
- headers.Authorization = `Bearer ${API_KEY}`;
255
- }
256
-
257
- return new StreamableHTTPClientTransport(new URL(REMOTE_MCP_URL), {
258
- requestInit: {
259
- headers,
260
- },
261
- });
262
- }
263
-
264
- async function connectRemoteClient() {
265
- const { Client } = require('@modelcontextprotocol/sdk/client/index.js');
266
- const transport = buildRemoteTransport();
267
- const client = new Client(
268
- { name: 'agoragentic-mcp', version: PACKAGE_VERSION },
269
- { capabilities: { tools: {}, resources: {}, prompts: {} } }
270
- );
271
-
272
- client.onerror = (error) => {
273
- if (!error) return;
274
- const message = error instanceof Error ? error.message : String(error);
275
- console.error(`[agoragentic-mcp] remote client error: ${message}`);
276
- };
277
-
278
- await client.connect(transport);
279
- return { client, transport };
280
- }
281
-
282
- async function runMcpRelay() {
283
- const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
284
- const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
285
- const {
286
- CallToolRequestSchema,
287
- ListToolsRequestSchema,
288
- ListResourcesRequestSchema,
289
- ReadResourceRequestSchema,
290
- ListPromptsRequestSchema,
291
- GetPromptRequestSchema,
292
- } = require('@modelcontextprotocol/sdk/types.js');
293
-
322
+
323
+ function buildRemoteTransport() {
324
+ const { StreamableHTTPClientTransport } = require('@modelcontextprotocol/sdk/client/streamableHttp.js');
325
+ const headers = {};
326
+ if (API_KEY) {
327
+ headers.Authorization = `Bearer ${API_KEY}`;
328
+ }
329
+
330
+ return new StreamableHTTPClientTransport(new URL(REMOTE_MCP_URL), {
331
+ requestInit: {
332
+ headers,
333
+ },
334
+ });
335
+ }
336
+
337
+ async function connectRemoteClient() {
338
+ const { Client } = require('@modelcontextprotocol/sdk/client/index.js');
339
+ const transport = buildRemoteTransport();
340
+ const client = new Client(
341
+ { name: 'agoragentic-mcp', version: PACKAGE_VERSION },
342
+ { capabilities: { tools: {}, resources: {}, prompts: {} } }
343
+ );
344
+
345
+ client.onerror = (error) => {
346
+ if (!error) return;
347
+ const message = error instanceof Error ? error.message : String(error);
348
+ console.error(`[agoragentic-mcp] remote client error: ${message}`);
349
+ };
350
+
351
+ await client.connect(transport);
352
+ return { client, transport };
353
+ }
354
+
355
+ async function runMcpRelay() {
356
+ const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
357
+ const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
358
+ const {
359
+ CallToolRequestSchema,
360
+ ListToolsRequestSchema,
361
+ ListResourcesRequestSchema,
362
+ ReadResourceRequestSchema,
363
+ ListPromptsRequestSchema,
364
+ GetPromptRequestSchema,
365
+ } = require('@modelcontextprotocol/sdk/types.js');
366
+
294
367
  const server = new Server(
295
368
  { name: 'agoragentic', version: PACKAGE_VERSION },
296
369
  { capabilities: { tools: {}, resources: {}, prompts: {} } }
@@ -306,8 +379,23 @@ async function runMcpRelay() {
306
379
 
307
380
  if (remoteSession) {
308
381
  const { client } = remoteSession;
382
+ let remoteToolNames = null;
383
+
384
+ async function listRemoteTools(params = {}) {
385
+ const result = await client.listTools(params);
386
+ remoteToolNames = new Set((result.tools || []).map((tool) => tool.name));
387
+ return result;
388
+ }
389
+
390
+ async function hasRemoteTool(name) {
391
+ if (!remoteToolNames) {
392
+ await listRemoteTools();
393
+ }
394
+ return remoteToolNames.has(name);
395
+ }
396
+
309
397
  server.setRequestHandler(ListToolsRequestSchema, async (request) => {
310
- const result = await client.listTools(request.params);
398
+ const result = await listRemoteTools(request.params);
311
399
  return {
312
400
  ...result,
313
401
  tools: mergeFallbackTools(result.tools),
@@ -315,11 +403,7 @@ async function runMcpRelay() {
315
403
  });
316
404
 
317
405
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
318
- if (
319
- request.params.name === 'agoragentic_match' ||
320
- request.params.name === 'agoragentic_execute' ||
321
- request.params.name === 'agoragentic_execute_status'
322
- ) {
406
+ if (FALLBACK_TOOL_NAMES.has(request.params.name) && !(await hasRemoteTool(request.params.name))) {
323
407
  return executeFallbackTool(request.params.name, request.params.arguments || {});
324
408
  }
325
409
  return client.callTool(request.params);
@@ -365,10 +449,10 @@ async function runMcpRelay() {
365
449
  throw new Error('Prompts are unavailable while the remote Agoragentic MCP relay is unreachable.');
366
450
  });
367
451
  }
368
-
369
- const stdio = new StdioServerTransport();
370
- await server.connect(stdio);
371
-
452
+
453
+ const stdio = new StdioServerTransport();
454
+ await server.connect(stdio);
455
+
372
456
  const shutdown = async (signal) => {
373
457
  console.error(`[agoragentic-mcp] shutting down on ${signal}`);
374
458
  if (remoteSession) {
@@ -385,242 +469,242 @@ async function runMcpRelay() {
385
469
  }
386
470
  process.exit(0);
387
471
  };
388
-
389
- process.on('SIGINT', () => {
390
- void shutdown('SIGINT');
391
- });
392
- process.on('SIGTERM', () => {
393
- void shutdown('SIGTERM');
394
- });
395
-
472
+
473
+ process.on('SIGINT', () => {
474
+ void shutdown('SIGINT');
475
+ });
476
+ process.on('SIGTERM', () => {
477
+ void shutdown('SIGTERM');
478
+ });
479
+
396
480
  if (remoteSession) {
397
481
  console.error(`[agoragentic-mcp] stdio relay ${PACKAGE_VERSION} connected to ${REMOTE_MCP_URL}`);
398
482
  } else {
399
483
  console.error(`[agoragentic-mcp] stdio fallback ${PACKAGE_VERSION} using ${AGORAGENTIC_BASE}`);
400
484
  }
401
485
  }
402
-
403
- function buildAcpInitializeResult() {
404
- return {
405
- protocolVersion: 1,
406
- agentInfo: {
407
- name: 'Agoragentic Agent OS',
408
- version: PACKAGE_VERSION,
409
- description:
410
- 'Agent OS integrations for deployed agents and swarms: execute-first routing, receipts, x402 edge calls, and Base USDC settlement.',
411
- homepage: 'https://agoragentic.com',
412
- },
413
- agentCapabilities: {
414
- tools: true,
415
- streaming: false,
416
- resources: false,
417
- prompts: false,
418
- loadSession: false,
419
- promptCapabilities: {
420
- image: false,
421
- },
422
- },
423
- authMethods: [
424
- {
425
- type: 'env',
426
- name: 'AGORAGENTIC_API_KEY',
427
- configured: Boolean(API_KEY),
428
- required: false,
429
- instructions:
430
- 'Optional for public discovery and x402 edge calls. Required for authenticated execute/match/status/receipt. Create one with POST /api/quickstart and intent=buyer|seller|both.',
431
- },
432
- ],
433
- };
434
- }
435
-
436
- function buildAcpResponse(id, result) {
437
- return {
438
- jsonrpc: '2.0',
439
- id,
440
- result,
441
- };
442
- }
443
-
444
- function buildAcpError(id, code, message, data) {
445
- return {
446
- jsonrpc: '2.0',
447
- id,
448
- error: data ? { code, message, data } : { code, message },
449
- };
450
- }
451
-
452
- function writeAcpMessage(message) {
453
- process.stdout.write(`${JSON.stringify(message)}\n`);
454
- }
455
-
456
- function buildAcpSessionId() {
457
- return `sess_${crypto.randomBytes(12).toString('hex')}`;
458
- }
459
-
460
- function extractAcpPromptText(content) {
461
- if (typeof content === 'string') return content;
462
- if (!Array.isArray(content)) return '';
463
- return content
464
- .map((part) => {
465
- if (!part || typeof part !== 'object') return '';
466
- if (part.type === 'text' && typeof part.text === 'string') return part.text;
467
- return '';
468
- })
469
- .filter(Boolean)
470
- .join('\n');
471
- }
472
-
473
- function buildAcpPromptReply(promptText) {
474
- const suffix = promptText ? ` Prompt received: ${promptText.slice(0, 240)}` : '';
475
- return [
476
- 'Agoragentic ACP adapter is a tool bridge, not a code-editing chat agent.',
477
- 'Use tools/list, then tools/call with agoragentic_execute, agoragentic_match, agoragentic_quote, agoragentic_receipt, or stable x402 service tools.',
478
- suffix,
479
- ]
480
- .filter(Boolean)
481
- .join(' ');
482
- }
483
-
484
- async function runAcpAdapter() {
485
- const rl = readline.createInterface({
486
- input: process.stdin,
487
- crlfDelay: Infinity,
488
- terminal: false,
489
- });
490
-
491
- let remoteSession = null;
492
- const acpSessions = new Map();
493
-
494
- async function getRemoteSession() {
495
- if (!remoteSession) {
496
- remoteSession = await connectRemoteClient();
497
- }
498
- return remoteSession;
499
- }
500
-
501
- async function shutdownRemote() {
502
- if (!remoteSession) return;
503
- try {
504
- await remoteSession.transport.terminateSession();
505
- } catch {
506
- // Ignore session teardown failures during local shutdown.
507
- }
508
- try {
509
- await remoteSession.transport.close();
510
- } catch {
511
- // Ignore transport close failures during local shutdown.
512
- }
513
- remoteSession = null;
514
- }
515
-
516
- process.on('SIGINT', () => {
517
- void shutdownRemote().finally(() => process.exit(0));
518
- });
519
- process.on('SIGTERM', () => {
520
- void shutdownRemote().finally(() => process.exit(0));
521
- });
522
-
523
- console.error(`[agoragentic-mcp] ACP adapter ${PACKAGE_VERSION} ready`);
524
-
525
- for await (const line of rl) {
526
- if (!line.trim()) continue;
527
-
528
- let request;
529
- try {
530
- request = JSON.parse(line);
531
- } catch (error) {
532
- writeAcpMessage(buildAcpError(null, -32700, 'Invalid JSON-RPC payload'));
533
- continue;
534
- }
535
-
536
- const hasId = Object.prototype.hasOwnProperty.call(request, 'id');
537
- const id = hasId ? request.id : null;
538
-
539
- function writeResponse(message) {
540
- if (hasId) writeAcpMessage(message);
541
- }
542
-
543
- try {
544
- if (request.method === 'initialize') {
545
- writeResponse(buildAcpResponse(id, buildAcpInitializeResult()));
546
- } else if (request.method === 'session/new') {
547
- const sessionId = buildAcpSessionId();
548
- acpSessions.set(sessionId, {
549
- cwd: request.params?.cwd || process.cwd(),
550
- createdAt: new Date().toISOString(),
551
- cancelled: false,
552
- });
553
- writeResponse(buildAcpResponse(id, { sessionId }));
554
- } else if (request.method === 'session/prompt') {
555
- const sessionId = request.params?.sessionId;
556
- if (!sessionId || !acpSessions.has(sessionId)) {
557
- writeResponse(buildAcpError(id, -32602, 'Unknown or missing ACP sessionId'));
558
- continue;
559
- }
560
-
561
- const session = acpSessions.get(sessionId);
562
- session.cancelled = false;
563
- const promptText = extractAcpPromptText(request.params?.content);
564
- const reply = buildAcpPromptReply(promptText);
565
-
566
- writeAcpMessage({
567
- jsonrpc: '2.0',
568
- method: 'session/update',
569
- params: {
570
- sessionId,
571
- update: {
572
- sessionUpdate: 'agent_message_chunk',
573
- content: {
574
- type: 'text',
575
- text: reply,
576
- },
577
- },
578
- },
579
- });
580
- writeResponse(buildAcpResponse(id, { stopReason: session.cancelled ? 'cancelled' : 'end_turn' }));
581
- } else if (request.method === 'session/cancel') {
582
- const sessionId = request.params?.sessionId;
583
- if (sessionId && acpSessions.has(sessionId)) {
584
- acpSessions.get(sessionId).cancelled = true;
585
- }
586
- writeResponse(buildAcpResponse(id, { ok: true }));
587
- } else if (request.method === 'tools/list') {
588
- writeResponse(buildAcpResponse(id, { tools: ACP_TOOLS }));
589
- } else if (request.method === 'tools/call') {
590
- const { client } = await getRemoteSession();
591
- const result = await client.callTool(request.params || {});
592
- writeResponse(buildAcpResponse(id, result));
593
- } else if (request.method === 'shutdown') {
594
- await shutdownRemote();
595
- writeResponse(buildAcpResponse(id, { ok: true }));
596
- } else {
597
- writeResponse(
598
- buildAcpError(id, -32601, 'Unsupported ACP method', {
599
- supported_methods: [
600
- 'initialize',
601
- 'session/new',
602
- 'session/prompt',
603
- 'session/cancel',
604
- 'tools/list',
605
- 'tools/call',
606
- 'shutdown',
607
- ],
608
- })
609
- );
610
- }
611
- } catch (error) {
612
- const message = error instanceof Error ? error.message : String(error);
613
- writeResponse(buildAcpError(id, -32000, message));
614
- }
615
- }
616
-
617
- await shutdownRemote();
618
- }
619
-
620
- const entrypoint = ACP_MODE ? runAcpAdapter : runMcpRelay;
621
-
622
- entrypoint().catch((error) => {
623
- const message = error instanceof Error ? error.stack || error.message : String(error);
624
- console.error(`[agoragentic-mcp] fatal: ${message}`);
625
- process.exit(1);
626
- });
486
+
487
+ function buildAcpInitializeResult() {
488
+ return {
489
+ protocolVersion: 1,
490
+ agentInfo: {
491
+ name: 'Agoragentic Agent OS',
492
+ version: PACKAGE_VERSION,
493
+ description:
494
+ 'Agent OS integrations for deployed agents and swarms: execute-first routing, receipts, x402 edge calls, and Base USDC settlement.',
495
+ homepage: 'https://agoragentic.com',
496
+ },
497
+ agentCapabilities: {
498
+ tools: true,
499
+ streaming: false,
500
+ resources: false,
501
+ prompts: false,
502
+ loadSession: false,
503
+ promptCapabilities: {
504
+ image: false,
505
+ },
506
+ },
507
+ authMethods: [
508
+ {
509
+ type: 'env',
510
+ name: 'AGORAGENTIC_API_KEY',
511
+ configured: Boolean(API_KEY),
512
+ required: false,
513
+ instructions:
514
+ 'Optional for public discovery and x402 edge calls. Required for authenticated execute/match/status/receipt. Create one with POST /api/quickstart and intent=buyer|seller|both.',
515
+ },
516
+ ],
517
+ };
518
+ }
519
+
520
+ function buildAcpResponse(id, result) {
521
+ return {
522
+ jsonrpc: '2.0',
523
+ id,
524
+ result,
525
+ };
526
+ }
527
+
528
+ function buildAcpError(id, code, message, data) {
529
+ return {
530
+ jsonrpc: '2.0',
531
+ id,
532
+ error: data ? { code, message, data } : { code, message },
533
+ };
534
+ }
535
+
536
+ function writeAcpMessage(message) {
537
+ process.stdout.write(`${JSON.stringify(message)}\n`);
538
+ }
539
+
540
+ function buildAcpSessionId() {
541
+ return `sess_${crypto.randomBytes(12).toString('hex')}`;
542
+ }
543
+
544
+ function extractAcpPromptText(content) {
545
+ if (typeof content === 'string') return content;
546
+ if (!Array.isArray(content)) return '';
547
+ return content
548
+ .map((part) => {
549
+ if (!part || typeof part !== 'object') return '';
550
+ if (part.type === 'text' && typeof part.text === 'string') return part.text;
551
+ return '';
552
+ })
553
+ .filter(Boolean)
554
+ .join('\n');
555
+ }
556
+
557
+ function buildAcpPromptReply(promptText) {
558
+ const suffix = promptText ? ` Prompt received: ${promptText.slice(0, 240)}` : '';
559
+ return [
560
+ 'Agoragentic ACP adapter is a tool bridge, not a code-editing chat agent.',
561
+ 'Use tools/list, then tools/call with agoragentic_execute, agoragentic_match, agoragentic_quote, agoragentic_receipt, or stable x402 service tools.',
562
+ suffix,
563
+ ]
564
+ .filter(Boolean)
565
+ .join(' ');
566
+ }
567
+
568
+ async function runAcpAdapter() {
569
+ const rl = readline.createInterface({
570
+ input: process.stdin,
571
+ crlfDelay: Infinity,
572
+ terminal: false,
573
+ });
574
+
575
+ let remoteSession = null;
576
+ const acpSessions = new Map();
577
+
578
+ async function getRemoteSession() {
579
+ if (!remoteSession) {
580
+ remoteSession = await connectRemoteClient();
581
+ }
582
+ return remoteSession;
583
+ }
584
+
585
+ async function shutdownRemote() {
586
+ if (!remoteSession) return;
587
+ try {
588
+ await remoteSession.transport.terminateSession();
589
+ } catch {
590
+ // Ignore session teardown failures during local shutdown.
591
+ }
592
+ try {
593
+ await remoteSession.transport.close();
594
+ } catch {
595
+ // Ignore transport close failures during local shutdown.
596
+ }
597
+ remoteSession = null;
598
+ }
599
+
600
+ process.on('SIGINT', () => {
601
+ void shutdownRemote().finally(() => process.exit(0));
602
+ });
603
+ process.on('SIGTERM', () => {
604
+ void shutdownRemote().finally(() => process.exit(0));
605
+ });
606
+
607
+ console.error(`[agoragentic-mcp] ACP adapter ${PACKAGE_VERSION} ready`);
608
+
609
+ for await (const line of rl) {
610
+ if (!line.trim()) continue;
611
+
612
+ let request;
613
+ try {
614
+ request = JSON.parse(line);
615
+ } catch (error) {
616
+ writeAcpMessage(buildAcpError(null, -32700, 'Invalid JSON-RPC payload'));
617
+ continue;
618
+ }
619
+
620
+ const hasId = Object.prototype.hasOwnProperty.call(request, 'id');
621
+ const id = hasId ? request.id : null;
622
+
623
+ function writeResponse(message) {
624
+ if (hasId) writeAcpMessage(message);
625
+ }
626
+
627
+ try {
628
+ if (request.method === 'initialize') {
629
+ writeResponse(buildAcpResponse(id, buildAcpInitializeResult()));
630
+ } else if (request.method === 'session/new') {
631
+ const sessionId = buildAcpSessionId();
632
+ acpSessions.set(sessionId, {
633
+ cwd: request.params?.cwd || process.cwd(),
634
+ createdAt: new Date().toISOString(),
635
+ cancelled: false,
636
+ });
637
+ writeResponse(buildAcpResponse(id, { sessionId }));
638
+ } else if (request.method === 'session/prompt') {
639
+ const sessionId = request.params?.sessionId;
640
+ if (!sessionId || !acpSessions.has(sessionId)) {
641
+ writeResponse(buildAcpError(id, -32602, 'Unknown or missing ACP sessionId'));
642
+ continue;
643
+ }
644
+
645
+ const session = acpSessions.get(sessionId);
646
+ session.cancelled = false;
647
+ const promptText = extractAcpPromptText(request.params?.content);
648
+ const reply = buildAcpPromptReply(promptText);
649
+
650
+ writeAcpMessage({
651
+ jsonrpc: '2.0',
652
+ method: 'session/update',
653
+ params: {
654
+ sessionId,
655
+ update: {
656
+ sessionUpdate: 'agent_message_chunk',
657
+ content: {
658
+ type: 'text',
659
+ text: reply,
660
+ },
661
+ },
662
+ },
663
+ });
664
+ writeResponse(buildAcpResponse(id, { stopReason: session.cancelled ? 'cancelled' : 'end_turn' }));
665
+ } else if (request.method === 'session/cancel') {
666
+ const sessionId = request.params?.sessionId;
667
+ if (sessionId && acpSessions.has(sessionId)) {
668
+ acpSessions.get(sessionId).cancelled = true;
669
+ }
670
+ writeResponse(buildAcpResponse(id, { ok: true }));
671
+ } else if (request.method === 'tools/list') {
672
+ writeResponse(buildAcpResponse(id, { tools: ACP_TOOLS }));
673
+ } else if (request.method === 'tools/call') {
674
+ const { client } = await getRemoteSession();
675
+ const result = await client.callTool(request.params || {});
676
+ writeResponse(buildAcpResponse(id, result));
677
+ } else if (request.method === 'shutdown') {
678
+ await shutdownRemote();
679
+ writeResponse(buildAcpResponse(id, { ok: true }));
680
+ } else {
681
+ writeResponse(
682
+ buildAcpError(id, -32601, 'Unsupported ACP method', {
683
+ supported_methods: [
684
+ 'initialize',
685
+ 'session/new',
686
+ 'session/prompt',
687
+ 'session/cancel',
688
+ 'tools/list',
689
+ 'tools/call',
690
+ 'shutdown',
691
+ ],
692
+ })
693
+ );
694
+ }
695
+ } catch (error) {
696
+ const message = error instanceof Error ? error.message : String(error);
697
+ writeResponse(buildAcpError(id, -32000, message));
698
+ }
699
+ }
700
+
701
+ await shutdownRemote();
702
+ }
703
+
704
+ const entrypoint = ACP_MODE ? runAcpAdapter : runMcpRelay;
705
+
706
+ entrypoint().catch((error) => {
707
+ const message = error instanceof Error ? error.stack || error.message : String(error);
708
+ console.error(`[agoragentic-mcp] fatal: ${message}`);
709
+ process.exit(1);
710
+ });