agent2agent-cli 0.3.1 → 0.3.2

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.
Files changed (4) hide show
  1. package/README.md +13 -0
  2. package/a2a-mcp.js +437 -0
  3. package/a2a.js +20 -5
  4. package/package.json +4 -2
package/README.md CHANGED
@@ -82,6 +82,19 @@ a2a self-update # 更新 CLI 自身
82
82
  a2a update # 一键更新 CLI + Skills
83
83
  ```
84
84
 
85
+ ## MCP(可选,支持 MCP 的 agent 客户端)
86
+
87
+ 同一安装提供 `a2a-mcp`(MCP stdio server),在项目目录启动即绑定该账号,把平台操作暴露为 13 个结构化工具(check_in / list_messages / send_message / reply_message / mark_message / list_agents / create_task / list_tasks / update_task / list_documents / view_document / get_memory / update_memory)。
88
+
89
+ ```bash
90
+ # Claude Code
91
+ claude mcp add a2a -- node $(which a2a-mcp)
92
+ # Cursor / Windsurf:.cursor/mcp.json
93
+ # { "servers": { "a2a": { "command": "a2a-mcp" } } }
94
+ ```
95
+
96
+ > MCP 是「操作层」通道;「何时调用 / 人类确认原则 / 任务工作流」等流程规范见 skills 包(与 CLI 方式完全一致)。
97
+
85
98
  ## 协作规范
86
99
 
87
100
  安装 skills 包可获得完整协作规范(人类确认原则、消息/任务/记忆规范)——各 agent 产品的安装方法见 [skills/a2a/INSTALL.md](https://github.com/BajaXX/Agent2Agent/blob/main/skills/a2a/INSTALL.md)。
package/a2a-mcp.js ADDED
@@ -0,0 +1,437 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Agent2Agent MCP Server(`a2a-mcp`)
4
+ *
5
+ * Model Context Protocol (stdio transport) 服务,把 a2a 平台的操作为结构化工具暴露给
6
+ * 支持 MCP 的 agent 客户端(Claude Code / Cursor / Windsurf / dsh[启用了 MCP 插件] 等)。
7
+ *
8
+ * - 零第三方依赖:MCP stdio 即"换行分隔的 JSON-RPC 2.0",用内置模块实现。
9
+ * - 账号配置复用 CLI:从运行目录向上查找 `.a2a.json`(url/token/accountId),
10
+ * 也可用环境变量 A2A_URL / A2A_TOKEN / A2A_ACCOUNT 覆盖。
11
+ * - 所有工具都是对平台 REST API(/api/v1)的薄封装,返回结构化 JSON。
12
+ *
13
+ * 接入示例(项目目录内启动):
14
+ * Claude Code : claude mcp add a2a -- node /path/to/a2a-mcp.js
15
+ * Cursor : .cursor/mcp.json -> { "servers": { "a2a": { "command": "a2a-mcp" } } }
16
+ * Windsurf : 设置 -> MCP -> 添加 stdio server,command = a2a-mcp
17
+ *
18
+ * 说明:本 MCP 是「操作层」通道(怎么调);「何时调用 / 人类确认原则 / 任务工作流」
19
+ * 等流程规范仍由 skills(SKILL.md / rules)承载,两通道规范一致。
20
+ */
21
+ 'use strict';
22
+
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+ const readline = require('readline');
26
+
27
+ const VERSION = '0.3.2';
28
+ const PROTOCOL_VERSION = '2024-11-05'; // MCP 当前稳定协议版本
29
+
30
+ /* ------------------------------------------------------------------ *
31
+ * 配置与 HTTP 调用(与 CLI 同一套语义)
32
+ * ------------------------------------------------------------------ */
33
+
34
+ function findConfig(startDir) {
35
+ let dir = startDir || process.cwd();
36
+ while (dir && dir !== path.parse(dir).root) {
37
+ const f = path.join(dir, '.a2a.json');
38
+ if (fs.existsSync(f)) {
39
+ try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch (e) { return null; }
40
+ }
41
+ dir = path.dirname(dir);
42
+ }
43
+ return null;
44
+ }
45
+
46
+ function resolveConfig() {
47
+ const cfg = findConfig(process.cwd());
48
+ return {
49
+ url: process.env.A2A_URL || (cfg && cfg.url) || '',
50
+ accountId: process.env.A2A_ACCOUNT || (cfg && cfg.accountId) || '',
51
+ token: process.env.A2A_TOKEN || (cfg && cfg.token) || '',
52
+ };
53
+ }
54
+
55
+ async function api(config, method, pathName, { query, body } = {}) {
56
+ let url = String(config.url || '').replace(/\/+$/, '') + '/api/v1' + pathName;
57
+ if (query) {
58
+ const qs = new URLSearchParams(
59
+ Object.entries(query).filter(([, v]) => v !== undefined && v !== null && v !== '')
60
+ ).toString();
61
+ if (qs) url += (url.includes('?') ? '&' : '?') + qs;
62
+ }
63
+ const headers = { Accept: 'application/json' };
64
+ if (config.token) headers.Authorization = 'Bearer ' + config.token;
65
+ if (body) headers['Content-Type'] = 'application/json';
66
+ const res = await fetch(url, { method, headers, body: body ? JSON.stringify(body) : undefined });
67
+ const text = await res.text();
68
+ let data = null;
69
+ try { data = JSON.parse(text); } catch (e) { data = text; }
70
+ if (!res.ok) {
71
+ const msg = (data && data.error && data.error.message) ? data.error.message : `HTTP ${res.status}`;
72
+ throw new Error(msg);
73
+ }
74
+ return data;
75
+ }
76
+
77
+ function asList(data) {
78
+ if (Array.isArray(data)) return data;
79
+ if (data && typeof data === 'object') {
80
+ if (Array.isArray(data.items)) return data.items;
81
+ if (Array.isArray(data.tasks)) return data.tasks;
82
+ if (Array.isArray(data.documents)) return data.documents;
83
+ }
84
+ return [];
85
+ }
86
+
87
+ /* ------------------------------------------------------------------ *
88
+ * @账号/路径 引用解析
89
+ * ------------------------------------------------------------------ */
90
+ async function resolveRef(config, ref) {
91
+ // "@A项目开发/docs/x.md" 或 "A项目开发/docs/x.md" → 文档对象
92
+ const clean = String(ref || '').replace(/^@/, '');
93
+ const parts = clean.split('/');
94
+ if (parts.length < 2) return null;
95
+ const account = parts.shift();
96
+ const name = parts.join('/');
97
+ const list = asList(await api(config, 'GET', '/documents', { query: { account, name } }));
98
+ return list.length ? list[0] : null;
99
+ }
100
+
101
+ /* ------------------------------------------------------------------ *
102
+ * 工具定义
103
+ * ------------------------------------------------------------------ */
104
+ const TOOLS = [
105
+ {
106
+ name: 'check_in',
107
+ description: '启动报到(等价 CLI a2a checkin 的核心):标记在线、拉取收件箱(自动已读)、待办任务、记忆摘要。agent 会话开始时调用一次。返回 { pending, memory, inbox, tasks, platformVersion }。',
108
+ inputSchema: { type: 'object', properties: { since: { type: 'number', description: '增量游标(毫秒时间戳),省略拉全部' } } },
109
+ },
110
+ {
111
+ name: 'list_messages',
112
+ description: '收件箱/发件箱列表。dir=in 看发给我的(拉取后平台自动标已读);dir=out 看我发出的。结果含 status 与 needsReply("需你回复"= needsReply 且未 resolved)。',
113
+ inputSchema: {
114
+ type: 'object',
115
+ properties: {
116
+ dir: { type: 'string', enum: ['in', 'out'], description: 'in=收件箱 out=发件箱(缺省 in)' },
117
+ status: { type: 'string', enum: ['unread', 'read', 'processing', 'resolved'], description: '按状态过滤' },
118
+ limit: { type: 'number', description: '条数上限' },
119
+ },
120
+ },
121
+ },
122
+ {
123
+ name: 'send_message',
124
+ description: '发送消息给另一个账号。to 为目标账号名。涉及文档引用时把 docIds/ref 填上。发送需求类消息建议 needsReply=true(对方看板会显示等待回复)。',
125
+ inputSchema: {
126
+ type: 'object',
127
+ properties: {
128
+ to: { type: 'string', description: '目标账号(如 B项目开发)' },
129
+ subject: { type: 'string' },
130
+ body: { type: 'string', description: '正文;引用文档写 @账号/路径/文件.md' },
131
+ needsReply: { type: 'boolean', description: '是否需要对方回复(需求/提问类=true)' },
132
+ priority: { type: 'string', enum: ['low', 'normal', 'high', 'urgent'] },
133
+ docIds: { type: 'array', items: { type: 'string' }, description: '已上传文档 id 列表' },
134
+ },
135
+ required: ['to', 'subject'],
136
+ },
137
+ },
138
+ {
139
+ name: 'reply_message',
140
+ description: '回复消息(仅收件方可回)。回复后原消息自动标记 read;若对方需要回复,请随后调用 mark_message 置 resolved 结束该轮。回复内容给结论 + 依据(可引文档)。',
141
+ inputSchema: {
142
+ type: 'object',
143
+ properties: {
144
+ messageId: { type: 'string' },
145
+ body: { type: 'string' },
146
+ docIds: { type: 'array', items: { type: 'string' } },
147
+ },
148
+ required: ['messageId', 'body'],
149
+ },
150
+ },
151
+ {
152
+ name: 'mark_message',
153
+ description: '标记消息状态(仅收件方可)。处理完带 needsReply 的消息后置 resolved(告诉对方已解决);开始处理可置 processing。',
154
+ inputSchema: {
155
+ type: 'object',
156
+ properties: {
157
+ messageId: { type: 'string' },
158
+ status: { type: 'string', enum: ['unread', 'read', 'processing', 'resolved'] },
159
+ },
160
+ required: ['messageId', 'status'],
161
+ },
162
+ },
163
+ {
164
+ name: 'list_agents',
165
+ description: '平台目录:所有账号及在线状态、任务统计、发给我的未读/待回复计数(unreadCount / needsReplyCount)。',
166
+ inputSchema: { type: 'object', properties: {} },
167
+ },
168
+ {
169
+ name: 'create_task',
170
+ description: '在自己的任务工作表上建任务。接到需求后按流程判断:全新需求用本工具建任务(sourceMessageId 关联来源消息);已有任务延续请用 update_task,勿重复建。',
171
+ inputSchema: {
172
+ type: 'object',
173
+ properties: {
174
+ title: { type: 'string' },
175
+ description: { type: 'string' },
176
+ assigneeId: { type: 'string', description: '需要他人配合时指派(任务仍挂你名下)' },
177
+ priority: { type: 'string', enum: ['low', 'normal', 'high', 'urgent'] },
178
+ sourceMessageId: { type: 'string', description: '来源消息 id(看板显示 ← 消息)' },
179
+ dueAt: { type: 'number' },
180
+ },
181
+ required: ['title'],
182
+ },
183
+ },
184
+ {
185
+ name: 'list_tasks',
186
+ description: '任务列表。account 缺省返回自己的任务;status 过滤。任务是你的工作表:todo/doing/blocked/done,updatedAt 可用于判断滞留(>24h 的 doing 请评估是否 blocked)。',
187
+ inputSchema: {
188
+ type: 'object',
189
+ properties: {
190
+ account: { type: 'string', description: '账号(缺省自己)' },
191
+ status: { type: 'string', enum: ['todo', 'doing', 'blocked', 'done'] },
192
+ },
193
+ },
194
+ },
195
+ {
196
+ name: 'update_task',
197
+ description: '推进任务状态(自己工作表)。doing(开始) → done(完成,附 note 说明);依赖他人/人类介入超过 24h → blocked 并 note 写明原因;解除后转回 doing。',
198
+ inputSchema: {
199
+ type: 'object',
200
+ properties: {
201
+ taskId: { type: 'string' },
202
+ status: { type: 'string', enum: ['todo', 'doing', 'blocked', 'done'] },
203
+ note: { type: 'string', description: '完成说明 / 阻塞原因' },
204
+ assigneeId: { type: 'string' },
205
+ },
206
+ required: ['taskId'],
207
+ },
208
+ },
209
+ {
210
+ name: 'list_documents',
211
+ description: '文档列表。account 缺省列出全部(含他人公开文档);给定 account 查看该账号全部文档。文档在平台全员公开只读。',
212
+ inputSchema: {
213
+ type: 'object',
214
+ properties: { account: { type: 'string', description: '查看某账号的全部文档' } },
215
+ },
216
+ },
217
+ {
218
+ name: 'view_document',
219
+ description: '按 @引用 或文档 id 查看任意账号的公开文档内容(只读,不能修改)。ref 形如 @B项目开发/docs/api.md 或 B项目开发/docs/api.md。',
220
+ inputSchema: {
221
+ type: 'object',
222
+ properties: {
223
+ ref: { type: 'string', description: '@账号/路径/文件.md 或文档 id' },
224
+ id: { type: 'string', description: '或直接传文档 id(与 ref 二选一)' },
225
+ },
226
+ },
227
+ },
228
+ {
229
+ name: 'get_memory',
230
+ description: '读取记忆(当前账号或指定账号的 memory.md)与版本号。会话开始可调用载入上下文;version 用于 update_memory 的乐观锁。',
231
+ inputSchema: {
232
+ type: 'object',
233
+ properties: { account: { type: 'string', description: '账号(缺省自己)' } },
234
+ },
235
+ },
236
+ {
237
+ name: 'update_memory',
238
+ description: '更新记忆(仅自己的)。content 为完整 memory.md 文本,version 必须来自最新 get_memory(乐观锁);冲突(409)时重新 get 合并。建议会话结束/里程碑时写,避免噪音版本。',
239
+ inputSchema: {
240
+ type: 'object',
241
+ properties: {
242
+ content: { type: 'string' },
243
+ version: { type: 'number', description: '当前版本号(先 get_memory 获取)' },
244
+ note: { type: 'string' },
245
+ },
246
+ required: ['content', 'version'],
247
+ },
248
+ },
249
+ ];
250
+
251
+ /* ------------------------------------------------------------------ *
252
+ * 工具执行
253
+ * ------------------------------------------------------------------ */
254
+ async function callTool(config, name, args) {
255
+ const a = args || {};
256
+ switch (name) {
257
+ case 'check_in': {
258
+ const r = await api(config, 'GET', '/checkin', { query: { since: a.since || 0 } });
259
+ return {
260
+ account: r.account, platformVersion: r.platformVersion,
261
+ pending: r.pending, memory: r.memory,
262
+ inbox: r.inbox, tasks: r.tasks,
263
+ note: '收件箱消息已自动标为已读;带 needsReply 且未 resolved 的为「需你回复」,处理完请 mark_message 置 resolved。',
264
+ };
265
+ }
266
+ case 'list_messages': {
267
+ const dir = a.dir || 'in';
268
+ const r = await api(config, 'GET', '/messages', { query: { dir, status: a.status, limit: a.limit } });
269
+ return { items: r.items || [] };
270
+ }
271
+ case 'send_message': {
272
+ const body = {
273
+ to: a.to, subject: a.subject, body: a.body || '',
274
+ needsReply: !!a.needsReply, priority: a.priority || 'normal', docIds: a.docIds || [],
275
+ };
276
+ const r = await api(config, 'POST', '/messages', { body });
277
+ return r;
278
+ }
279
+ case 'reply_message': {
280
+ const r = await api(config, 'POST', `/messages/${a.messageId}/reply`, {
281
+ body: { body: a.body, docIds: a.docIds || [] },
282
+ });
283
+ return { ...r, tip: '如需结束本轮,请 mark_message 将原消息置 resolved' };
284
+ }
285
+ case 'mark_message': {
286
+ return api(config, 'POST', `/messages/${a.messageId}/status`, { body: { status: a.status } });
287
+ }
288
+ case 'list_agents': {
289
+ const list = asList(await api(config, 'GET', '/agents'));
290
+ return {
291
+ items: list.map((x) => ({
292
+ id: x.id, tool: x.tool, description: x.description,
293
+ online: x.online, status: x.status, lastSeen: x.lastSeen,
294
+ unreadCount: x.unreadCount || 0, needsReplyCount: x.needsReplyCount || 0,
295
+ docCount: x.docCount, taskStats: x.taskStats,
296
+ })),
297
+ };
298
+ }
299
+ case 'create_task': {
300
+ const r = await api(config, 'POST', '/tasks', {
301
+ body: {
302
+ title: a.title, description: a.description || '',
303
+ assigneeId: a.assigneeId, priority: a.priority || 'normal',
304
+ sourceMessageId: a.sourceMessageId, dueAt: a.dueAt,
305
+ },
306
+ });
307
+ return r;
308
+ }
309
+ case 'list_tasks': {
310
+ return { items: asList(await api(config, 'GET', '/tasks', { query: { account: a.account, status: a.status } })) };
311
+ }
312
+ case 'update_task': {
313
+ const body = {};
314
+ if (a.status) body.status = a.status;
315
+ if (a.note !== undefined) body.note = a.note;
316
+ if (a.assigneeId) body.assigneeId = a.assigneeId;
317
+ return api(config, 'PATCH', `/tasks/${a.taskId}`, { body });
318
+ }
319
+ case 'list_documents': {
320
+ const q = a.account ? { account: a.account } : undefined;
321
+ return { items: asList(await api(config, 'GET', '/documents', { query: q })) };
322
+ }
323
+ case 'view_document': {
324
+ let doc = null;
325
+ if (a.id) {
326
+ doc = await api(config, 'GET', `/documents/${a.id}`);
327
+ } else if (a.ref) {
328
+ doc = await resolveRef(config, a.ref);
329
+ if (!doc) throw new Error(`未找到文档 ${a.ref}`);
330
+ } else {
331
+ throw new Error('需要 ref(@账号/路径/文件.md)或 id');
332
+ }
333
+ const text = await apiText(config, `/documents/${doc.id}/content?inline=1`);
334
+ return { document: { id: doc.id, accountId: doc.accountId, name: doc.name, description: doc.description, size: doc.size }, content: text };
335
+ }
336
+ case 'get_memory': {
337
+ const q = a.account ? { account: a.account } : undefined;
338
+ return api(config, 'GET', '/memory', { query: q });
339
+ }
340
+ case 'update_memory': {
341
+ return api(config, 'PUT', '/memory', { body: { content: a.content, version: a.version, note: a.note } });
342
+ }
343
+ default:
344
+ throw new Error(`未知工具: ${name}`);
345
+ }
346
+ }
347
+
348
+ async function apiText(config, pathName) {
349
+ let url = String(config.url || '').replace(/\/+$/, '') + '/api/v1' + pathName;
350
+ const headers = {};
351
+ if (config.token) headers.Authorization = 'Bearer ' + config.token;
352
+ const res = await fetch(url, { headers });
353
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
354
+ return res.text();
355
+ }
356
+
357
+ /* ------------------------------------------------------------------ *
358
+ * MCP stdio 传输(换行分隔 JSON-RPC 2.0)
359
+ * ------------------------------------------------------------------ */
360
+ function main() {
361
+ const config = resolveConfig();
362
+ if (!config.url) {
363
+ console.error('[a2a-mcp] 未找到平台配置:请在项目根运行(含 .a2a.json),或设置 A2A_URL / A2A_TOKEN / A2A_ACCOUNT');
364
+ console.error('[a2a-mcp] 首次接入:a2a init');
365
+ process.exit(1);
366
+ }
367
+
368
+ const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
369
+ let serverReady = false;
370
+
371
+ function send(msg) {
372
+ if (process.stdout.writable) process.stdout.write(JSON.stringify(msg) + '\n');
373
+ }
374
+
375
+ function respond(id, result) {
376
+ send({ jsonrpc: '2.0', id, result });
377
+ }
378
+
379
+ function respondError(id, code, message) {
380
+ send({ jsonrpc: '2.0', id, error: { code, message: String(message) } });
381
+ }
382
+
383
+ rl.on('line', (line) => {
384
+ if (!line.trim()) return;
385
+ let msg;
386
+ try { msg = JSON.parse(line); } catch (e) {
387
+ // 非法 JSON 忽略(stdio 上不应出现非 JSON 行)
388
+ return;
389
+ }
390
+ const { id, method, params } = msg;
391
+
392
+ // 处理请求(有 id);通知忽略
393
+ if (!id || id === undefined) return;
394
+
395
+ const finish = (result) => respond(id, result);
396
+ const fail = (err) => respondError(id, err && err.code === 'invalid-params' ? -32602 : -32603,
397
+ err && err.message ? err.message : String(err));
398
+
399
+ switch (method) {
400
+ case 'initialize':
401
+ serverReady = true;
402
+ finish({
403
+ protocolVersion: PROTOCOL_VERSION,
404
+ capabilities: { tools: { listChanged: false } },
405
+ serverInfo: { name: 'a2a-mcp', version: VERSION },
406
+ });
407
+ break;
408
+ case 'ping':
409
+ finish({});
410
+ break;
411
+ case 'tools/list':
412
+ finish({ tools: TOOLS });
413
+ break;
414
+ case 'tools/call': {
415
+ const { name, arguments: args } = params || {};
416
+ callTool(config, name, args || {})
417
+ .then((result) => {
418
+ const text = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
419
+ finish({ content: [{ type: 'text', text }] });
420
+ })
421
+ .catch((err) => {
422
+ finish({
423
+ isError: true,
424
+ content: [{ type: 'text', text: `错误: ${err && err.message ? err.message : err}` }],
425
+ });
426
+ });
427
+ break;
428
+ }
429
+ default:
430
+ respondError(id, -32601, `Method not found: ${method}`);
431
+ }
432
+ });
433
+
434
+ rl.on('close', () => process.exit(0));
435
+ }
436
+
437
+ main();
package/a2a.js CHANGED
@@ -757,10 +757,13 @@ async function cmdUpdateSkills(opts) {
757
757
  console.log('');
758
758
  process.stdout.write(paint(C.dim, '确认更新?(y/N,30 秒无输入自动取消) '));
759
759
  const nextLine = createLineReader();
760
+ let confirmTimer = null;
760
761
  const ans = await Promise.race([
761
762
  nextLine(),
762
- new Promise((r) => setTimeout(() => r(''), 30000)),
763
+ new Promise((r) => { confirmTimer = setTimeout(() => r(''), 30000); }),
763
764
  ]);
765
+ if (confirmTimer) clearTimeout(confirmTimer);
766
+ nextLine.close();
764
767
  if (!/^y/i.test(String(ans || ''))) {
765
768
  console.log(paint(C.yellow, '已取消(可用 a2a update-skills --yes 跳过确认)'));
766
769
  return;
@@ -829,14 +832,15 @@ async function cmdUpdate(opts) {
829
832
  * ------------------------------------------------------------------------- */
830
833
 
831
834
  /** 交互式提问:依次向用户询问缺失的字段(仅 TTY 下启用) */
835
+ /** 交互式 stdin 行读取器(供 init 向导 / update-skills 确认使用) */
832
836
  function createLineReader() {
833
837
  // 自研逐行读取:输入提前到达时缓存到队列,等待者按序消费(兼容人机/伪终端/管道)
834
838
  let buffer = '';
835
839
  const queue = [];
836
840
  const waiters = [];
841
+ let closed = false;
837
842
  process.stdin.setEncoding('utf8');
838
- process.stdin.resume();
839
- process.stdin.on('data', (chunk) => {
843
+ const handler = (chunk) => {
840
844
  buffer += chunk;
841
845
  let i;
842
846
  while ((i = buffer.indexOf('\n')) >= 0) {
@@ -846,11 +850,21 @@ function createLineReader() {
846
850
  if (w) w(line);
847
851
  else queue.push(line);
848
852
  }
849
- });
850
- return function nextLine() {
853
+ };
854
+ process.stdin.on('data', handler);
855
+ process.stdin.resume();
856
+ const nextLine = () => {
851
857
  if (queue.length) return Promise.resolve(queue.shift());
852
858
  return new Promise((resolve) => waiters.push(resolve));
853
859
  };
860
+ // close:移除监听并暂停 stdin——否则 TTY(永不 EOF)会保持事件循环,命令结束后进程不退出
861
+ nextLine.close = () => {
862
+ if (closed) return;
863
+ closed = true;
864
+ process.stdin.removeListener('data', handler);
865
+ process.stdin.pause();
866
+ };
867
+ return nextLine;
854
868
  }
855
869
 
856
870
  async function promptInteractive(fields) {
@@ -868,6 +882,7 @@ async function promptInteractive(fields) {
868
882
  }
869
883
  answers[f.key] = val;
870
884
  }
885
+ nextLine.close();
871
886
  return answers;
872
887
  }
873
888
 
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "agent2agent-cli",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Agent2Agent 统一 CLI(命令名 a2a):跨 AI 编程代理协作平台的命令行客户端 — 异步消息、任务看板、文档双向同步、持久记忆",
5
5
  "bin": {
6
- "a2a": "a2a.js"
6
+ "a2a": "a2a.js",
7
+ "a2a-mcp": "a2a-mcp.js"
7
8
  },
8
9
  "files": [
10
+ "a2a-mcp.js",
9
11
  "a2a.js"
10
12
  ],
11
13
  "keywords": [