agent-relay 8.9.0 → 8.9.1
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/dist/cli/agent-relay-mcp.d.ts +2 -53
- package/dist/cli/agent-relay-mcp.d.ts.map +1 -1
- package/dist/cli/agent-relay-mcp.js +11 -943
- package/dist/cli/agent-relay-mcp.js.map +1 -1
- package/dist/cli/commands/fleet.d.ts.map +1 -1
- package/dist/cli/commands/fleet.js +62 -13
- package/dist/cli/commands/fleet.js.map +1 -1
- package/dist/cli/lib/broker-dashboard.d.ts +12 -0
- package/dist/cli/lib/broker-dashboard.d.ts.map +1 -0
- package/dist/cli/lib/broker-dashboard.js +431 -0
- package/dist/cli/lib/broker-dashboard.js.map +1 -0
- package/dist/cli/lib/broker-lifecycle.d.ts.map +1 -1
- package/dist/cli/lib/broker-lifecycle.js +1 -428
- package/dist/cli/lib/broker-lifecycle.js.map +1 -1
- package/dist/cli/mcp/action-schema.d.ts +20 -0
- package/dist/cli/mcp/action-schema.d.ts.map +1 -0
- package/dist/cli/mcp/action-schema.js +132 -0
- package/dist/cli/mcp/action-schema.js.map +1 -0
- package/dist/cli/mcp/action-tools.d.ts +10 -0
- package/dist/cli/mcp/action-tools.d.ts.map +1 -0
- package/dist/cli/mcp/action-tools.js +114 -0
- package/dist/cli/mcp/action-tools.js.map +1 -0
- package/dist/cli/mcp/inbox.d.ts +9 -0
- package/dist/cli/mcp/inbox.d.ts.map +1 -0
- package/dist/cli/mcp/inbox.js +49 -0
- package/dist/cli/mcp/inbox.js.map +1 -0
- package/dist/cli/mcp/messaging-tools.d.ts +9 -0
- package/dist/cli/mcp/messaging-tools.d.ts.map +1 -0
- package/dist/cli/mcp/messaging-tools.js +315 -0
- package/dist/cli/mcp/messaging-tools.js.map +1 -0
- package/dist/cli/mcp/resources.d.ts +39 -0
- package/dist/cli/mcp/resources.d.ts.map +1 -0
- package/dist/cli/mcp/resources.js +158 -0
- package/dist/cli/mcp/resources.js.map +1 -0
- package/dist/cli/mcp/telemetry.d.ts +9 -0
- package/dist/cli/mcp/telemetry.d.ts.map +1 -0
- package/dist/cli/mcp/telemetry.js +176 -0
- package/dist/cli/mcp/telemetry.js.map +1 -0
- package/dist/cli/mcp/tool-results.d.ts +29 -0
- package/dist/cli/mcp/tool-results.d.ts.map +1 -0
- package/dist/cli/mcp/tool-results.js +39 -0
- package/dist/cli/mcp/tool-results.js.map +1 -0
- package/dist/cli/mcp/tool-shapes.d.ts +13 -0
- package/dist/cli/mcp/tool-shapes.d.ts.map +1 -0
- package/dist/cli/mcp/tool-shapes.js +16 -0
- package/dist/cli/mcp/tool-shapes.js.map +1 -0
- package/dist/cli/mcp/types.d.ts +38 -0
- package/dist/cli/mcp/types.d.ts.map +1 -0
- package/dist/cli/mcp/types.js +2 -0
- package/dist/cli/mcp/types.js.map +1 -0
- package/dist/cli/mcp/workspace.d.ts +10 -0
- package/dist/cli/mcp/workspace.d.ts.map +1 -0
- package/dist/cli/mcp/workspace.js +36 -0
- package/dist/cli/mcp/workspace.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/package.json +8 -8
|
@@ -2,16 +2,21 @@
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { McpServer
|
|
5
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
6
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
7
|
import { ListToolsRequestSchema, SubscribeRequestSchema, UnsubscribeRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
8
8
|
import { RelayCast, SDK_VERSION, WsClient } from '@relaycast/sdk';
|
|
9
|
-
import { INVALID_AGENT_TOKEN_CODE, agentTokenRecoveryMessage, isInvalidAgentTokenError, isInvalidAgentTokenToolResult, } from '@agent-relay/sdk';
|
|
10
9
|
import { AgentRelay } from '@agent-relay/sdk';
|
|
11
10
|
import { z } from 'zod';
|
|
12
|
-
import { initTelemetry, shutdown as shutdownTelemetry
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
11
|
+
import { initTelemetry, shutdown as shutdownTelemetry } from './telemetry/index.js';
|
|
12
|
+
import { withRelaycastTelemetry } from './lib/relaycast-telemetry.js';
|
|
13
|
+
import { RealtimeResourceBridge, SubscriptionManager, registerResourceDefinitions } from './mcp/resources.js';
|
|
14
|
+
import { jsonContent, jsonResult, textContent } from './mcp/tool-results.js';
|
|
15
|
+
import { createWorkspace, extractWorkspaceKey, extractWorkspaceName, requireWorkspaceKey, } from './mcp/workspace.js';
|
|
16
|
+
import { enableInboxPiggyback } from './mcp/telemetry.js';
|
|
17
|
+
import { registerAgentRelayActionTools } from './mcp/action-tools.js';
|
|
18
|
+
import { registerMessagingTools } from './mcp/messaging-tools.js';
|
|
19
|
+
import { identityOverrideInputShape, messageResult } from './mcp/tool-shapes.js';
|
|
15
20
|
const DEFAULT_BASE_URL = 'https://gateway.relaycast.dev';
|
|
16
21
|
export const AGENT_RELAY_MCP_VERSION = process.env.AGENT_RELAY_CLI_VERSION ?? SDK_VERSION ?? 'unknown';
|
|
17
22
|
let mcpTelemetryExitHookInstalled = false;
|
|
@@ -46,16 +51,6 @@ const DEFAULT_SYSTEM_PROMPT = `You are an AI agent in a collaborative workspace
|
|
|
46
51
|
- Use threads for detailed discussions to keep channels organized
|
|
47
52
|
- React with emoji to acknowledge messages
|
|
48
53
|
- Keep messages concise and actionable`;
|
|
49
|
-
const jsonResult = z.object({}).passthrough();
|
|
50
|
-
const messageResult = {
|
|
51
|
-
message: z.string().describe('Human-readable confirmation message'),
|
|
52
|
-
};
|
|
53
|
-
const identityOverrideInputShape = {
|
|
54
|
-
as: z
|
|
55
|
-
.string()
|
|
56
|
-
.optional()
|
|
57
|
-
.describe('Registered agent identity to act as when multiple identities have been registered'),
|
|
58
|
-
};
|
|
59
54
|
/** Return env var value, or undefined if missing / an unresolved ${...} template. */
|
|
60
55
|
function resolveEnv(key) {
|
|
61
56
|
const v = process.env[key];
|
|
@@ -217,630 +212,6 @@ function createInitialSession(options) {
|
|
|
217
212
|
wsInitAttempted: false,
|
|
218
213
|
};
|
|
219
214
|
}
|
|
220
|
-
class SubscriptionManager {
|
|
221
|
-
subscriptions = new Set();
|
|
222
|
-
subscribe(uri) {
|
|
223
|
-
this.subscriptions.add(uri);
|
|
224
|
-
}
|
|
225
|
-
unsubscribe(uri) {
|
|
226
|
-
this.subscriptions.delete(uri);
|
|
227
|
-
}
|
|
228
|
-
getMatchingSubscriptions(uris) {
|
|
229
|
-
return uris.filter((uri) => this.subscriptions.has(uri));
|
|
230
|
-
}
|
|
231
|
-
getAll() {
|
|
232
|
-
return [...this.subscriptions];
|
|
233
|
-
}
|
|
234
|
-
clear() {
|
|
235
|
-
this.subscriptions.clear();
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
function getStringEventField(event, field) {
|
|
239
|
-
if (typeof event !== 'object' || event === null) {
|
|
240
|
-
return null;
|
|
241
|
-
}
|
|
242
|
-
const candidate = event[field];
|
|
243
|
-
return typeof candidate === 'string' ? candidate : null;
|
|
244
|
-
}
|
|
245
|
-
function eventToResourceUris(event) {
|
|
246
|
-
const type = getStringEventField(event, 'type');
|
|
247
|
-
switch (type) {
|
|
248
|
-
case 'message.created': {
|
|
249
|
-
const channel = getStringEventField(event, 'channel');
|
|
250
|
-
return channel ? ['relay://inbox', `relay://channels/${channel}/messages`] : ['relay://inbox'];
|
|
251
|
-
}
|
|
252
|
-
case 'message.updated': {
|
|
253
|
-
const channel = getStringEventField(event, 'channel');
|
|
254
|
-
return channel ? [`relay://channels/${channel}/messages`] : [];
|
|
255
|
-
}
|
|
256
|
-
case 'thread.reply': {
|
|
257
|
-
const parentId = getStringEventField(event, 'parentId');
|
|
258
|
-
return parentId ? ['relay://inbox', `relay://messages/${parentId}/thread`] : ['relay://inbox'];
|
|
259
|
-
}
|
|
260
|
-
case 'dm.received':
|
|
261
|
-
case 'group_dm.received': {
|
|
262
|
-
const conversationId = getStringEventField(event, 'conversationId');
|
|
263
|
-
return conversationId ? ['relay://inbox', `relay://dm/${conversationId}`] : ['relay://inbox'];
|
|
264
|
-
}
|
|
265
|
-
case 'agent.online':
|
|
266
|
-
case 'agent.offline':
|
|
267
|
-
return ['relay://agents'];
|
|
268
|
-
case 'channel.created':
|
|
269
|
-
case 'channel.updated':
|
|
270
|
-
case 'channel.archived':
|
|
271
|
-
case 'member.joined':
|
|
272
|
-
case 'member.left':
|
|
273
|
-
return ['relay://channels'];
|
|
274
|
-
case 'webhook.received':
|
|
275
|
-
case 'command.invoked': {
|
|
276
|
-
const channel = getStringEventField(event, 'channel');
|
|
277
|
-
return channel ? [`relay://channels/${channel}/messages`] : [];
|
|
278
|
-
}
|
|
279
|
-
case 'reaction.added':
|
|
280
|
-
case 'reaction.removed':
|
|
281
|
-
return ['relay://inbox'];
|
|
282
|
-
default:
|
|
283
|
-
return [];
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
class RealtimeResourceBridge {
|
|
287
|
-
wsClient;
|
|
288
|
-
subscriptions;
|
|
289
|
-
notifyCallback;
|
|
290
|
-
unsubscribeFn = null;
|
|
291
|
-
constructor(wsClient, subscriptions, notifyCallback) {
|
|
292
|
-
this.wsClient = wsClient;
|
|
293
|
-
this.subscriptions = subscriptions;
|
|
294
|
-
this.notifyCallback = notifyCallback;
|
|
295
|
-
}
|
|
296
|
-
start() {
|
|
297
|
-
this.unsubscribeFn = this.wsClient.on('*', (event) => {
|
|
298
|
-
const type = getStringEventField(event, 'type');
|
|
299
|
-
if (type === 'open' ||
|
|
300
|
-
type === 'close' ||
|
|
301
|
-
type === 'error' ||
|
|
302
|
-
type === 'reconnecting' ||
|
|
303
|
-
type === 'permanently_disconnected') {
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
const matched = this.subscriptions.getMatchingSubscriptions(eventToResourceUris(event));
|
|
307
|
-
for (const uri of matched) {
|
|
308
|
-
this.notifyCallback(uri);
|
|
309
|
-
}
|
|
310
|
-
});
|
|
311
|
-
this.wsClient.connect();
|
|
312
|
-
}
|
|
313
|
-
stop() {
|
|
314
|
-
if (this.unsubscribeFn) {
|
|
315
|
-
this.unsubscribeFn();
|
|
316
|
-
this.unsubscribeFn = null;
|
|
317
|
-
}
|
|
318
|
-
this.wsClient.disconnect();
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
function registerResourceDefinitions(server, getAgentClient, getRelay) {
|
|
322
|
-
server.registerResource('inbox', 'relay://inbox', { title: 'Inbox', description: 'Unread messages, mentions, and DMs', mimeType: 'application/json' }, async (uri) => {
|
|
323
|
-
const inbox = await getAgentClient().inbox();
|
|
324
|
-
return { contents: [{ uri: uri.href, text: JSON.stringify(inbox) }] };
|
|
325
|
-
});
|
|
326
|
-
server.registerResource('agents', 'relay://agents', {
|
|
327
|
-
title: 'Agents',
|
|
328
|
-
description: 'Online and offline agents in the workspace',
|
|
329
|
-
mimeType: 'application/json',
|
|
330
|
-
}, async (uri) => {
|
|
331
|
-
const agents = await getRelay().agents.list();
|
|
332
|
-
return { contents: [{ uri: uri.href, text: JSON.stringify(agents) }] };
|
|
333
|
-
});
|
|
334
|
-
server.registerResource('channels', 'relay://channels', { title: 'Channels', description: 'Available channels in the workspace', mimeType: 'application/json' }, async (uri) => {
|
|
335
|
-
const channels = await getAgentClient().channels.list();
|
|
336
|
-
return { contents: [{ uri: uri.href, text: JSON.stringify(channels) }] };
|
|
337
|
-
});
|
|
338
|
-
server.registerResource('channel-messages', new ResourceTemplate('relay://channels/{name}/messages', { list: undefined }), {
|
|
339
|
-
title: 'Channel Messages',
|
|
340
|
-
description: 'Messages in a specific channel',
|
|
341
|
-
mimeType: 'application/json',
|
|
342
|
-
}, async (uri, params) => {
|
|
343
|
-
const messages = await getAgentClient().messages(String(params.name));
|
|
344
|
-
return { contents: [{ uri: uri.href, text: JSON.stringify(messages) }] };
|
|
345
|
-
});
|
|
346
|
-
server.registerResource('message-thread', new ResourceTemplate('relay://messages/{id}/thread', { list: undefined }), { title: 'Message Thread', description: 'Thread replies on a message', mimeType: 'application/json' }, async (uri, params) => {
|
|
347
|
-
const thread = await getAgentClient().thread(String(params.id));
|
|
348
|
-
return { contents: [{ uri: uri.href, text: JSON.stringify(thread) }] };
|
|
349
|
-
});
|
|
350
|
-
server.registerResource('dm-conversation', new ResourceTemplate('relay://dm/{conversation_id}', { list: undefined }), {
|
|
351
|
-
title: 'DM Conversation',
|
|
352
|
-
description: 'Direct message conversation',
|
|
353
|
-
mimeType: 'application/json',
|
|
354
|
-
}, async (uri, params) => {
|
|
355
|
-
const messages = await getAgentClient().dms.messages(String(params.conversation_id));
|
|
356
|
-
return { contents: [{ uri: uri.href, text: JSON.stringify(messages) }] };
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
function hasContentArray(value) {
|
|
360
|
-
return (typeof value === 'object' && value !== null && Array.isArray(value.content));
|
|
361
|
-
}
|
|
362
|
-
const SKIP_PIGGYBACK = new Set(['check_inbox', 'create_workspace', 'set_workspace_key', 'register_agent']);
|
|
363
|
-
function formatInbox(inbox, selfName) {
|
|
364
|
-
const norm = (s) => s.trim().replace(/^@/, '').toLowerCase();
|
|
365
|
-
const selfNorm = selfName ? norm(selfName) : null;
|
|
366
|
-
const isSelf = (name) => selfNorm != null && norm(name) === selfNorm;
|
|
367
|
-
const lines = ['--- Pending Messages ---'];
|
|
368
|
-
if (inbox.unreadChannels?.length) {
|
|
369
|
-
lines.push('Unread channels:');
|
|
370
|
-
for (const ch of inbox.unreadChannels) {
|
|
371
|
-
lines.push(` #${ch.channelName}: ${ch.unreadCount} unread`);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
const mentions = selfNorm ? inbox.mentions?.filter((m) => !isSelf(m.agentName)) : inbox.mentions;
|
|
375
|
-
if (mentions?.length) {
|
|
376
|
-
lines.push('Mentions:');
|
|
377
|
-
for (const m of mentions) {
|
|
378
|
-
lines.push(` @${m.agentName} in #${m.channelName}: "${m.text}"`);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
const dms = selfNorm ? inbox.unreadDms?.filter((dm) => !isSelf(dm.from)) : inbox.unreadDms;
|
|
382
|
-
if (dms?.length) {
|
|
383
|
-
lines.push('Unread DMs:');
|
|
384
|
-
for (const dm of dms) {
|
|
385
|
-
lines.push(` From ${dm.from}: ${dm.unreadCount} unread`);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
const reactions = selfNorm
|
|
389
|
-
? inbox.recentReactions?.filter((reaction) => !isSelf(reaction.agentName))
|
|
390
|
-
: inbox.recentReactions;
|
|
391
|
-
if (reactions?.length) {
|
|
392
|
-
lines.push('Reactions (informational; no response required):');
|
|
393
|
-
for (const reaction of reactions) {
|
|
394
|
-
lines.push(` :${reaction.emoji}: on your message in #${reaction.channelName} by @${reaction.agentName}`);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
return lines.length === 1 ? '' : lines.join('\n');
|
|
398
|
-
}
|
|
399
|
-
function readAsIdentity(args) {
|
|
400
|
-
const [input] = args;
|
|
401
|
-
if (typeof input !== 'object' || input === null)
|
|
402
|
-
return undefined;
|
|
403
|
-
const as = input.as;
|
|
404
|
-
return typeof as === 'string' ? as : undefined;
|
|
405
|
-
}
|
|
406
|
-
function invalidAgentTokenToolResult() {
|
|
407
|
-
const text = agentTokenRecoveryMessage();
|
|
408
|
-
return {
|
|
409
|
-
content: [{ type: 'text', text }],
|
|
410
|
-
structuredContent: {
|
|
411
|
-
error: { code: INVALID_AGENT_TOKEN_CODE, message: text },
|
|
412
|
-
},
|
|
413
|
-
isError: true,
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
function isErrorToolResult(value) {
|
|
417
|
-
return Boolean(value && typeof value === 'object' && value.isError === true);
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* Owned tools that delegate to the actions surface (`actions.invoke(...)`)
|
|
421
|
-
* rather than the agents/messaging APIs. Together with the dynamic per-action
|
|
422
|
-
* tools (tracked via `actionToolNames`), these intentionally skip per-tool
|
|
423
|
-
* telemetry so the same underlying action is not counted differently depending
|
|
424
|
-
* on which MCP surface the caller used (e.g. `spawn` vs `invoke_action`).
|
|
425
|
-
*/
|
|
426
|
-
const ACTION_ROUTED_TOOL_NAMES = new Set(['invoke_action', 'spawn']);
|
|
427
|
-
/**
|
|
428
|
-
* Coarse type/category metadata for the statically-registered ("owned") MCP
|
|
429
|
-
* tools. Action-routed calls (see `ACTION_ROUTED_TOOL_NAMES`) and the dynamic
|
|
430
|
-
* per-action tools surfaced from the actions registry are intentionally
|
|
431
|
-
* excluded from per-tool telemetry (see the skip in `enableInboxPiggyback`),
|
|
432
|
-
* so they have no entry.
|
|
433
|
-
*/
|
|
434
|
-
const AGENT_RELAY_TOOL_CALL_METADATA = {
|
|
435
|
-
add_agent: { toolType: 'agent.create', toolCategory: 'spawn' },
|
|
436
|
-
remove_agent: { toolType: 'agent.release', toolCategory: 'release' },
|
|
437
|
-
list_actions: { toolType: 'action.list', toolCategory: 'action' },
|
|
438
|
-
submit_result: { toolType: 'result.submit', toolCategory: 'result' },
|
|
439
|
-
create_workspace: { toolType: 'workspace.create', toolCategory: 'workspace' },
|
|
440
|
-
set_workspace_key: { toolType: 'workspace.set_key', toolCategory: 'workspace' },
|
|
441
|
-
register_agent: { toolType: 'agent.register', toolCategory: 'agent' },
|
|
442
|
-
list_agents: { toolType: 'agent.list', toolCategory: 'agent' },
|
|
443
|
-
post_message: { toolType: 'message.post', toolCategory: 'message' },
|
|
444
|
-
send_dm: { toolType: 'message.dm', toolCategory: 'message' },
|
|
445
|
-
send_group_dm: { toolType: 'message.group_dm', toolCategory: 'message' },
|
|
446
|
-
list_dms: { toolType: 'message.dm_list', toolCategory: 'message' },
|
|
447
|
-
list_messages: { toolType: 'message.list', toolCategory: 'message' },
|
|
448
|
-
reply_to_thread: { toolType: 'message.reply', toolCategory: 'message' },
|
|
449
|
-
get_message_thread: { toolType: 'message.thread', toolCategory: 'message' },
|
|
450
|
-
search_messages: { toolType: 'message.search', toolCategory: 'message' },
|
|
451
|
-
create_channel: { toolType: 'channel.create', toolCategory: 'channel' },
|
|
452
|
-
list_channels: { toolType: 'channel.list', toolCategory: 'channel' },
|
|
453
|
-
join_channel: { toolType: 'channel.join', toolCategory: 'channel' },
|
|
454
|
-
leave_channel: { toolType: 'channel.leave', toolCategory: 'channel' },
|
|
455
|
-
set_channel_topic: { toolType: 'channel.set_topic', toolCategory: 'channel' },
|
|
456
|
-
archive_channel: { toolType: 'channel.archive', toolCategory: 'channel' },
|
|
457
|
-
invite_to_channel: { toolType: 'channel.invite', toolCategory: 'channel' },
|
|
458
|
-
add_reaction: { toolType: 'reaction.add', toolCategory: 'reaction' },
|
|
459
|
-
remove_reaction: { toolType: 'reaction.remove', toolCategory: 'reaction' },
|
|
460
|
-
check_inbox: { toolType: 'inbox.check', toolCategory: 'inbox' },
|
|
461
|
-
mark_message_read: { toolType: 'inbox.mark_read', toolCategory: 'inbox' },
|
|
462
|
-
get_message_readers: { toolType: 'inbox.reader_list', toolCategory: 'inbox' },
|
|
463
|
-
};
|
|
464
|
-
function agentRelayToolCallMetadata(name) {
|
|
465
|
-
const known = AGENT_RELAY_TOOL_CALL_METADATA[name];
|
|
466
|
-
return known ?? { toolType: name, toolCategory: 'tool' };
|
|
467
|
-
}
|
|
468
|
-
function trackAgentRelayToolCall(input) {
|
|
469
|
-
track('agent_relay_tool_call', {
|
|
470
|
-
tool_name: input.toolName,
|
|
471
|
-
tool_type: input.toolType,
|
|
472
|
-
tool_category: input.toolCategory,
|
|
473
|
-
transport: input.transport ?? 'unknown',
|
|
474
|
-
success: input.success,
|
|
475
|
-
duration_ms: Date.now() - input.startedAt,
|
|
476
|
-
...(input.errorClass ? { error_class: input.errorClass } : {}),
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
function enableInboxPiggyback(mcpServer, getSession, getAgentClient, invalidateAgentToken, telemetryTransport, actionToolNames = new Set()) {
|
|
480
|
-
const original = mcpServer.registerTool.bind(mcpServer);
|
|
481
|
-
const mutableServer = mcpServer;
|
|
482
|
-
mutableServer.registerTool = (name, config, handler) => {
|
|
483
|
-
if (!handler) {
|
|
484
|
-
return original(name, config, handler);
|
|
485
|
-
}
|
|
486
|
-
const wrapped = async (...args) => {
|
|
487
|
-
const asIdentity = readAsIdentity(args);
|
|
488
|
-
const startedAt = Date.now();
|
|
489
|
-
// Action-routed calls (`invoke_action`, `spawn`, and the dynamic
|
|
490
|
-
// per-action tools) run through the actions surface and deliberately skip
|
|
491
|
-
// per-tool telemetry; only the owned tools emit `agent_relay_tool_call`.
|
|
492
|
-
const toolMetadata = !ACTION_ROUTED_TOOL_NAMES.has(name) && !actionToolNames.has(name)
|
|
493
|
-
? agentRelayToolCallMetadata(name)
|
|
494
|
-
: undefined;
|
|
495
|
-
let result;
|
|
496
|
-
try {
|
|
497
|
-
result = await handler(...args);
|
|
498
|
-
}
|
|
499
|
-
catch (err) {
|
|
500
|
-
if (name !== 'register_agent' && isInvalidAgentTokenError(err)) {
|
|
501
|
-
invalidateAgentToken(asIdentity);
|
|
502
|
-
if (toolMetadata) {
|
|
503
|
-
trackAgentRelayToolCall({
|
|
504
|
-
toolName: name,
|
|
505
|
-
toolType: toolMetadata.toolType,
|
|
506
|
-
toolCategory: toolMetadata.toolCategory,
|
|
507
|
-
transport: telemetryTransport,
|
|
508
|
-
startedAt,
|
|
509
|
-
success: false,
|
|
510
|
-
errorClass: errorClassName(err) ?? 'InvalidAgentToken',
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
return invalidAgentTokenToolResult();
|
|
514
|
-
}
|
|
515
|
-
if (toolMetadata) {
|
|
516
|
-
trackAgentRelayToolCall({
|
|
517
|
-
toolName: name,
|
|
518
|
-
toolType: toolMetadata.toolType,
|
|
519
|
-
toolCategory: toolMetadata.toolCategory,
|
|
520
|
-
transport: telemetryTransport,
|
|
521
|
-
startedAt,
|
|
522
|
-
success: false,
|
|
523
|
-
errorClass: errorClassName(err),
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
throw err;
|
|
527
|
-
}
|
|
528
|
-
if (name !== 'register_agent' && isInvalidAgentTokenToolResult(result)) {
|
|
529
|
-
invalidateAgentToken(asIdentity);
|
|
530
|
-
if (toolMetadata) {
|
|
531
|
-
trackAgentRelayToolCall({
|
|
532
|
-
toolName: name,
|
|
533
|
-
toolType: toolMetadata.toolType,
|
|
534
|
-
toolCategory: toolMetadata.toolCategory,
|
|
535
|
-
transport: telemetryTransport,
|
|
536
|
-
startedAt,
|
|
537
|
-
success: false,
|
|
538
|
-
errorClass: 'InvalidAgentToken',
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
if (hasContentArray(result)) {
|
|
542
|
-
result.content.push({ type: 'text', text: agentTokenRecoveryMessage() });
|
|
543
|
-
}
|
|
544
|
-
return result;
|
|
545
|
-
}
|
|
546
|
-
if (!SKIP_PIGGYBACK.has(name) && getSession().agentToken && hasContentArray(result)) {
|
|
547
|
-
try {
|
|
548
|
-
const inbox = await getAgentClient(asIdentity).inbox();
|
|
549
|
-
const inboxText = formatInbox(inbox, asIdentity ?? getSession().agentName);
|
|
550
|
-
if (inboxText) {
|
|
551
|
-
result.content.push({ type: 'text', text: inboxText });
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
catch (err) {
|
|
555
|
-
if (isInvalidAgentTokenError(err)) {
|
|
556
|
-
invalidateAgentToken(asIdentity);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
if (toolMetadata) {
|
|
561
|
-
const resultIsError = isErrorToolResult(result);
|
|
562
|
-
trackAgentRelayToolCall({
|
|
563
|
-
toolName: name,
|
|
564
|
-
toolType: toolMetadata.toolType,
|
|
565
|
-
toolCategory: toolMetadata.toolCategory,
|
|
566
|
-
transport: telemetryTransport,
|
|
567
|
-
startedAt,
|
|
568
|
-
success: !resultIsError,
|
|
569
|
-
...(resultIsError ? { errorClass: 'ToolResultError' } : {}),
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
return result;
|
|
573
|
-
};
|
|
574
|
-
return original(name, config, wrapped);
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
async function createWorkspace(name, baseUrl) {
|
|
578
|
-
return (await RelayCast.createWorkspace(name, {
|
|
579
|
-
baseUrl,
|
|
580
|
-
...relaycastWorkspaceTelemetryOptions(),
|
|
581
|
-
}));
|
|
582
|
-
}
|
|
583
|
-
function extractWorkspaceKey(payload) {
|
|
584
|
-
const data = payload.data && typeof payload.data === 'object' ? payload.data : {};
|
|
585
|
-
const value = payload.workspaceKey ??
|
|
586
|
-
payload.workspace_key ??
|
|
587
|
-
payload.apiKey ??
|
|
588
|
-
payload.api_key ??
|
|
589
|
-
data.workspaceKey ??
|
|
590
|
-
data.workspace_key ??
|
|
591
|
-
data.apiKey ??
|
|
592
|
-
data.api_key;
|
|
593
|
-
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
594
|
-
}
|
|
595
|
-
function extractWorkspaceName(payload, fallback) {
|
|
596
|
-
const data = payload.data && typeof payload.data === 'object' ? payload.data : {};
|
|
597
|
-
const value = payload.workspaceName ?? payload.workspace_name ?? payload.name ?? data.workspaceName;
|
|
598
|
-
return typeof value === 'string' && value.trim() ? value : fallback;
|
|
599
|
-
}
|
|
600
|
-
function requireWorkspaceKey(session) {
|
|
601
|
-
if (session.workspaceKey) {
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
throw new Error('Workspace key not configured. Call "create_workspace" first, or "set_workspace_key" if someone shared a workspace key.');
|
|
605
|
-
}
|
|
606
|
-
function jsonContent(value) {
|
|
607
|
-
const structuredContent = typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
608
|
-
? value
|
|
609
|
-
: { value };
|
|
610
|
-
return {
|
|
611
|
-
content: [{ type: 'text', text: JSON.stringify(value, null, 2) }],
|
|
612
|
-
structuredContent,
|
|
613
|
-
};
|
|
614
|
-
}
|
|
615
|
-
function textContent(message, structuredContent = { message }) {
|
|
616
|
-
return {
|
|
617
|
-
content: [{ type: 'text', text: message }],
|
|
618
|
-
structuredContent,
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
function isSchemaObject(schema) {
|
|
622
|
-
return Boolean(schema &&
|
|
623
|
-
typeof schema === 'object' &&
|
|
624
|
-
!Array.isArray(schema) &&
|
|
625
|
-
typeof schema.safeParse !== 'function');
|
|
626
|
-
}
|
|
627
|
-
function getSchemaDescription(schema) {
|
|
628
|
-
return isSchemaObject(schema) && typeof schema.description === 'string' ? schema.description : undefined;
|
|
629
|
-
}
|
|
630
|
-
function zodFromJsonSchema(schema) {
|
|
631
|
-
if (schema === false) {
|
|
632
|
-
return z.never();
|
|
633
|
-
}
|
|
634
|
-
if (!isSchemaObject(schema)) {
|
|
635
|
-
return z.unknown();
|
|
636
|
-
}
|
|
637
|
-
let zodType;
|
|
638
|
-
const schemaType = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
639
|
-
switch (schemaType) {
|
|
640
|
-
case 'array':
|
|
641
|
-
zodType = z.array(zodFromJsonSchema(schema.items));
|
|
642
|
-
break;
|
|
643
|
-
case 'boolean':
|
|
644
|
-
zodType = z.boolean();
|
|
645
|
-
break;
|
|
646
|
-
case 'integer':
|
|
647
|
-
zodType = z.number().int();
|
|
648
|
-
break;
|
|
649
|
-
case 'number':
|
|
650
|
-
zodType = z.number();
|
|
651
|
-
break;
|
|
652
|
-
case 'object':
|
|
653
|
-
if (schema.properties) {
|
|
654
|
-
const required = new Set(schema.required ?? []);
|
|
655
|
-
const shape = {};
|
|
656
|
-
for (const [key, childSchema] of Object.entries(schema.properties)) {
|
|
657
|
-
const child = zodFromJsonSchema(childSchema);
|
|
658
|
-
shape[key] = required.has(key) ? child : child.optional();
|
|
659
|
-
}
|
|
660
|
-
zodType = z.object(shape).passthrough();
|
|
661
|
-
}
|
|
662
|
-
else {
|
|
663
|
-
zodType = z.record(z.string(), z.unknown());
|
|
664
|
-
}
|
|
665
|
-
break;
|
|
666
|
-
case 'string':
|
|
667
|
-
zodType = z.string();
|
|
668
|
-
break;
|
|
669
|
-
default:
|
|
670
|
-
zodType = z.unknown();
|
|
671
|
-
break;
|
|
672
|
-
}
|
|
673
|
-
const description = getSchemaDescription(schema);
|
|
674
|
-
return description ? zodType.describe(description) : zodType;
|
|
675
|
-
}
|
|
676
|
-
function actionToolInputSchema(schema) {
|
|
677
|
-
const zodShape = zodObjectShape(schema);
|
|
678
|
-
if (zodShape) {
|
|
679
|
-
return zodShape;
|
|
680
|
-
}
|
|
681
|
-
if (!isSchemaObject(schema) || schema.type !== 'object') {
|
|
682
|
-
return {
|
|
683
|
-
input: z.unknown().describe('Action input payload. The action registry performs final validation.'),
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
const required = new Set(schema.required ?? []);
|
|
687
|
-
const shape = {};
|
|
688
|
-
for (const [key, childSchema] of Object.entries(schema.properties ?? {})) {
|
|
689
|
-
const child = zodFromJsonSchema(childSchema);
|
|
690
|
-
shape[key] = required.has(key) ? child : child.optional();
|
|
691
|
-
}
|
|
692
|
-
return shape;
|
|
693
|
-
}
|
|
694
|
-
function actionInvocationInput(descriptor, args) {
|
|
695
|
-
const schema = descriptor.inputSchema;
|
|
696
|
-
if (zodObjectShape(schema)) {
|
|
697
|
-
return args;
|
|
698
|
-
}
|
|
699
|
-
if (!isSchemaObject(schema) || schema.type !== 'object') {
|
|
700
|
-
return typeof args === 'object' && args !== null && 'input' in args
|
|
701
|
-
? args.input
|
|
702
|
-
: args;
|
|
703
|
-
}
|
|
704
|
-
return args;
|
|
705
|
-
}
|
|
706
|
-
function zodObjectShape(schema) {
|
|
707
|
-
if (schema instanceof z.ZodObject) {
|
|
708
|
-
return schema.shape;
|
|
709
|
-
}
|
|
710
|
-
return undefined;
|
|
711
|
-
}
|
|
712
|
-
function serializableActionDescriptor(descriptor) {
|
|
713
|
-
return {
|
|
714
|
-
name: descriptor.name,
|
|
715
|
-
description: descriptor.description,
|
|
716
|
-
visibility: descriptor.visibility,
|
|
717
|
-
...(descriptor.inputSchema ? { inputSchema: serializableActionSchema(descriptor.inputSchema) } : {}),
|
|
718
|
-
...(descriptor.outputSchema ? { outputSchema: serializableActionSchema(descriptor.outputSchema) } : {}),
|
|
719
|
-
};
|
|
720
|
-
}
|
|
721
|
-
function serializableActionSchema(schema) {
|
|
722
|
-
if (isSchemaObject(schema)) {
|
|
723
|
-
return schema;
|
|
724
|
-
}
|
|
725
|
-
if (isZodLikeSchema(schema)) {
|
|
726
|
-
return {
|
|
727
|
-
type: 'zod',
|
|
728
|
-
...(schema.description ? { description: schema.description } : {}),
|
|
729
|
-
};
|
|
730
|
-
}
|
|
731
|
-
return schema;
|
|
732
|
-
}
|
|
733
|
-
function isZodLikeSchema(schema) {
|
|
734
|
-
return Boolean(schema &&
|
|
735
|
-
typeof schema === 'object' &&
|
|
736
|
-
!Array.isArray(schema) &&
|
|
737
|
-
typeof schema.safeParse === 'function');
|
|
738
|
-
}
|
|
739
|
-
function registerAgentRelayActionTools(server, actions, getSession, onAuditEvent, getAgentClient, actionToolNames) {
|
|
740
|
-
if (!actions) {
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
/**
|
|
744
|
-
* Fire-and-forget invocation through the relay: returns an immediate ack
|
|
745
|
-
* (with an `invocation_id`) and does NOT run the handler inline. Falls back to
|
|
746
|
-
* the local in-process registry when the relay action surface is unavailable.
|
|
747
|
-
*/
|
|
748
|
-
const invokeAction = async (name, input) => {
|
|
749
|
-
const relayActions = getRelayAgentActions(getAgentClient);
|
|
750
|
-
if (relayActions) {
|
|
751
|
-
try {
|
|
752
|
-
const ack = await relayActions.invoke(name, asInputRecord(input));
|
|
753
|
-
return jsonContent({ ok: true, status: 'invoked', invocation: ack });
|
|
754
|
-
}
|
|
755
|
-
catch (error) {
|
|
756
|
-
return { ...jsonContent({ ok: false, error: errorMessage(error) }), isError: true };
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
const session = getSession();
|
|
760
|
-
const result = await actions.invoke({
|
|
761
|
-
name,
|
|
762
|
-
input,
|
|
763
|
-
context: {
|
|
764
|
-
caller: { name: session.agentName ?? 'mcp', type: 'agent' },
|
|
765
|
-
emit: onAuditEvent,
|
|
766
|
-
},
|
|
767
|
-
});
|
|
768
|
-
return result.ok ? jsonContent(result) : { ...jsonContent(result), isError: true };
|
|
769
|
-
};
|
|
770
|
-
server.registerTool('list_actions', {
|
|
771
|
-
title: 'List Actions',
|
|
772
|
-
description: 'List Agent Relay actions available to this agent.',
|
|
773
|
-
inputSchema: {},
|
|
774
|
-
outputSchema: jsonResult,
|
|
775
|
-
annotations: {
|
|
776
|
-
readOnlyHint: true,
|
|
777
|
-
destructiveHint: false,
|
|
778
|
-
idempotentHint: true,
|
|
779
|
-
openWorldHint: false,
|
|
780
|
-
},
|
|
781
|
-
}, async () => jsonContent({
|
|
782
|
-
actions: (await actions.list({ visibility: 'agent' })).map(serializableActionDescriptor),
|
|
783
|
-
}));
|
|
784
|
-
server.registerTool('invoke_action', {
|
|
785
|
-
title: 'Invoke Action',
|
|
786
|
-
description: 'Invoke a registered Agent Relay action by name. Fire-and-forget: returns an ack with an invocation id; the result arrives asynchronously to the action handler.',
|
|
787
|
-
inputSchema: {
|
|
788
|
-
name: z.string().describe('Registered action name'),
|
|
789
|
-
input: z.unknown().describe('Action input payload'),
|
|
790
|
-
},
|
|
791
|
-
outputSchema: jsonResult,
|
|
792
|
-
annotations: {
|
|
793
|
-
readOnlyHint: false,
|
|
794
|
-
destructiveHint: false,
|
|
795
|
-
idempotentHint: false,
|
|
796
|
-
openWorldHint: false,
|
|
797
|
-
},
|
|
798
|
-
}, async ({ name, input }) => invokeAction(name, input));
|
|
799
|
-
void actions
|
|
800
|
-
.list({ visibility: 'agent' })
|
|
801
|
-
.then((descriptors) => {
|
|
802
|
-
for (const descriptor of descriptors) {
|
|
803
|
-
actionToolNames?.add(descriptor.name);
|
|
804
|
-
server.registerTool(descriptor.name, {
|
|
805
|
-
title: descriptor.name,
|
|
806
|
-
description: descriptor.description,
|
|
807
|
-
inputSchema: actionToolInputSchema(descriptor.inputSchema),
|
|
808
|
-
outputSchema: jsonResult,
|
|
809
|
-
annotations: {
|
|
810
|
-
readOnlyHint: false,
|
|
811
|
-
destructiveHint: false,
|
|
812
|
-
idempotentHint: false,
|
|
813
|
-
openWorldHint: false,
|
|
814
|
-
},
|
|
815
|
-
}, async (args) => invokeAction(descriptor.name, actionInvocationInput(descriptor, args)));
|
|
816
|
-
}
|
|
817
|
-
})
|
|
818
|
-
.catch(() => undefined);
|
|
819
|
-
}
|
|
820
|
-
/** The relay-backed action surface on the live agent client, when available. */
|
|
821
|
-
function getRelayAgentActions(getAgentClient) {
|
|
822
|
-
if (!getAgentClient) {
|
|
823
|
-
return undefined;
|
|
824
|
-
}
|
|
825
|
-
try {
|
|
826
|
-
return getAgentClient().actions;
|
|
827
|
-
}
|
|
828
|
-
catch {
|
|
829
|
-
return undefined;
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
function asInputRecord(input) {
|
|
833
|
-
if (input === undefined || input === null) {
|
|
834
|
-
return undefined;
|
|
835
|
-
}
|
|
836
|
-
if (typeof input === 'object' && !Array.isArray(input)) {
|
|
837
|
-
return input;
|
|
838
|
-
}
|
|
839
|
-
return { input };
|
|
840
|
-
}
|
|
841
|
-
function errorMessage(error) {
|
|
842
|
-
return error instanceof Error ? error.message : String(error);
|
|
843
|
-
}
|
|
844
215
|
function createRegisteredAgent(agentName, agentToken) {
|
|
845
216
|
return { agentName, agentToken };
|
|
846
217
|
}
|
|
@@ -887,21 +258,6 @@ export async function registerAgentWithRebind({ session, setSession, getRelay, n
|
|
|
887
258
|
warnings,
|
|
888
259
|
};
|
|
889
260
|
}
|
|
890
|
-
function resolveEmoji(input) {
|
|
891
|
-
const normalized = input.trim().replace(/^:/, '').replace(/:$/, '').toLowerCase();
|
|
892
|
-
const aliases = {
|
|
893
|
-
'+1': '👍',
|
|
894
|
-
thumbsup: '👍',
|
|
895
|
-
thumbs_up: '👍',
|
|
896
|
-
check: '✅',
|
|
897
|
-
white_check_mark: '✅',
|
|
898
|
-
rocket: '🚀',
|
|
899
|
-
eyes: '👀',
|
|
900
|
-
heart: '❤️',
|
|
901
|
-
clap: '👏',
|
|
902
|
-
};
|
|
903
|
-
return aliases[normalized] ?? input;
|
|
904
|
-
}
|
|
905
261
|
function registerAgentRelayTools(server, getRelay, getAgentClient, getSession, setSession, baseUrl, strictAgentName, preferredAgentName, forcedAgentType) {
|
|
906
262
|
server.registerTool('create_workspace', {
|
|
907
263
|
title: 'Create Workspace',
|
|
@@ -1046,295 +402,7 @@ function registerAgentRelayTools(server, getRelay, getAgentClient, getSession, s
|
|
|
1046
402
|
const relay = new AgentRelay({ workspaceKey: session.workspaceKey ?? undefined, baseUrl });
|
|
1047
403
|
return jsonContent({ nodes: await relay.nodes.list({ capability, name }) });
|
|
1048
404
|
});
|
|
1049
|
-
server
|
|
1050
|
-
title: 'Create Channel',
|
|
1051
|
-
description: 'Create a new workspace channel.',
|
|
1052
|
-
inputSchema: {
|
|
1053
|
-
name: z.string().describe('Unique channel name'),
|
|
1054
|
-
topic: z.string().optional().describe('Optional channel topic'),
|
|
1055
|
-
...identityOverrideInputShape,
|
|
1056
|
-
},
|
|
1057
|
-
outputSchema: jsonResult,
|
|
1058
|
-
annotations: {
|
|
1059
|
-
readOnlyHint: false,
|
|
1060
|
-
destructiveHint: false,
|
|
1061
|
-
idempotentHint: false,
|
|
1062
|
-
openWorldHint: true,
|
|
1063
|
-
},
|
|
1064
|
-
}, async ({ name, topic, as }) => jsonContent(await getAgentClient(as).channels.create({ name, topic })));
|
|
1065
|
-
server.registerTool('list_channels', {
|
|
1066
|
-
title: 'List Channels',
|
|
1067
|
-
description: 'List channels available in the workspace.',
|
|
1068
|
-
inputSchema: {
|
|
1069
|
-
include_archived: z.boolean().optional().describe('Include archived channels'),
|
|
1070
|
-
...identityOverrideInputShape,
|
|
1071
|
-
},
|
|
1072
|
-
outputSchema: {
|
|
1073
|
-
channels: z.array(z.object({}).passthrough()).describe('Channels'),
|
|
1074
|
-
},
|
|
1075
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1076
|
-
}, async ({ include_archived, as }) => {
|
|
1077
|
-
const channels = await getAgentClient(as).channels.list(include_archived ? { includeArchived: include_archived } : undefined);
|
|
1078
|
-
return jsonContent({ channels });
|
|
1079
|
-
});
|
|
1080
|
-
server.registerTool('join_channel', {
|
|
1081
|
-
title: 'Join Channel',
|
|
1082
|
-
description: 'Join an existing channel.',
|
|
1083
|
-
inputSchema: {
|
|
1084
|
-
channel: z.string().describe('Channel name'),
|
|
1085
|
-
...identityOverrideInputShape,
|
|
1086
|
-
},
|
|
1087
|
-
outputSchema: messageResult,
|
|
1088
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1089
|
-
}, async ({ channel, as }) => {
|
|
1090
|
-
await getAgentClient(as).channels.join(channel);
|
|
1091
|
-
return textContent(`Joined channel #${channel}`);
|
|
1092
|
-
});
|
|
1093
|
-
server.registerTool('leave_channel', {
|
|
1094
|
-
title: 'Leave Channel',
|
|
1095
|
-
description: 'Leave a channel.',
|
|
1096
|
-
inputSchema: {
|
|
1097
|
-
channel: z.string().describe('Channel name'),
|
|
1098
|
-
...identityOverrideInputShape,
|
|
1099
|
-
},
|
|
1100
|
-
outputSchema: messageResult,
|
|
1101
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1102
|
-
}, async ({ channel, as }) => {
|
|
1103
|
-
await getAgentClient(as).channels.leave(channel);
|
|
1104
|
-
return textContent(`Left channel #${channel}`);
|
|
1105
|
-
});
|
|
1106
|
-
server.registerTool('invite_to_channel', {
|
|
1107
|
-
title: 'Invite to Channel',
|
|
1108
|
-
description: 'Invite another agent to a channel.',
|
|
1109
|
-
inputSchema: {
|
|
1110
|
-
channel: z.string().describe('Channel name'),
|
|
1111
|
-
agent: z.string().describe('Agent name to invite'),
|
|
1112
|
-
...identityOverrideInputShape,
|
|
1113
|
-
},
|
|
1114
|
-
outputSchema: messageResult,
|
|
1115
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1116
|
-
}, async ({ channel, agent, as }) => {
|
|
1117
|
-
await getAgentClient(as).channels.invite(channel, agent);
|
|
1118
|
-
return textContent(`Invited ${agent} to #${channel}`);
|
|
1119
|
-
});
|
|
1120
|
-
server.registerTool('set_channel_topic', {
|
|
1121
|
-
title: 'Set Channel Topic',
|
|
1122
|
-
description: 'Update a channel topic.',
|
|
1123
|
-
inputSchema: {
|
|
1124
|
-
channel: z.string().describe('Channel name'),
|
|
1125
|
-
topic: z.string().describe('New topic'),
|
|
1126
|
-
...identityOverrideInputShape,
|
|
1127
|
-
},
|
|
1128
|
-
outputSchema: jsonResult,
|
|
1129
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1130
|
-
}, async ({ channel, topic, as }) => jsonContent(await getAgentClient(as).channels.setTopic(channel, topic)));
|
|
1131
|
-
server.registerTool('archive_channel', {
|
|
1132
|
-
title: 'Archive Channel',
|
|
1133
|
-
description: 'Archive a channel.',
|
|
1134
|
-
inputSchema: {
|
|
1135
|
-
channel: z.string().describe('Channel name'),
|
|
1136
|
-
...identityOverrideInputShape,
|
|
1137
|
-
},
|
|
1138
|
-
outputSchema: messageResult,
|
|
1139
|
-
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
1140
|
-
}, async ({ channel, as }) => {
|
|
1141
|
-
await getAgentClient(as).channels.archive(channel);
|
|
1142
|
-
return textContent(`Archived channel #${channel}`);
|
|
1143
|
-
});
|
|
1144
|
-
server.registerTool('post_message', {
|
|
1145
|
-
title: 'Post Message',
|
|
1146
|
-
description: 'Post a new message to a channel as the current agent.',
|
|
1147
|
-
inputSchema: {
|
|
1148
|
-
channel: z.string().describe('Channel name'),
|
|
1149
|
-
text: z.string().describe('Message text'),
|
|
1150
|
-
attachments: z.array(z.string()).optional().describe('File attachment IDs'),
|
|
1151
|
-
mode: z.enum(['wait', 'steer']).optional().describe('Delivery mode'),
|
|
1152
|
-
...identityOverrideInputShape,
|
|
1153
|
-
},
|
|
1154
|
-
outputSchema: jsonResult,
|
|
1155
|
-
annotations: {
|
|
1156
|
-
readOnlyHint: false,
|
|
1157
|
-
destructiveHint: false,
|
|
1158
|
-
idempotentHint: false,
|
|
1159
|
-
openWorldHint: true,
|
|
1160
|
-
},
|
|
1161
|
-
}, async ({ channel, text, attachments, mode, as }) => jsonContent(await getAgentClient(as).send(channel, text, { attachments, mode })));
|
|
1162
|
-
server.registerTool('list_messages', {
|
|
1163
|
-
title: 'Get Messages',
|
|
1164
|
-
description: 'Retrieve message history from a channel.',
|
|
1165
|
-
inputSchema: {
|
|
1166
|
-
channel: z.string().describe('Channel name'),
|
|
1167
|
-
limit: z.number().optional().describe('Maximum messages to return'),
|
|
1168
|
-
before: z.string().optional().describe('Older-than cursor'),
|
|
1169
|
-
after: z.string().optional().describe('Newer-than cursor'),
|
|
1170
|
-
...identityOverrideInputShape,
|
|
1171
|
-
},
|
|
1172
|
-
outputSchema: {
|
|
1173
|
-
messages: z.array(z.object({}).passthrough()).describe('Messages'),
|
|
1174
|
-
},
|
|
1175
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1176
|
-
}, async ({ channel, limit, before, after, as }) => {
|
|
1177
|
-
const messages = await getAgentClient(as).messages(channel, { limit, before, after });
|
|
1178
|
-
return jsonContent({ messages });
|
|
1179
|
-
});
|
|
1180
|
-
server.registerTool('reply_to_thread', {
|
|
1181
|
-
title: 'Reply to Thread',
|
|
1182
|
-
description: 'Reply to an existing message thread.',
|
|
1183
|
-
inputSchema: {
|
|
1184
|
-
message_id: z.string().describe('Parent message ID'),
|
|
1185
|
-
text: z.string().describe('Reply text'),
|
|
1186
|
-
...identityOverrideInputShape,
|
|
1187
|
-
},
|
|
1188
|
-
outputSchema: jsonResult,
|
|
1189
|
-
annotations: {
|
|
1190
|
-
readOnlyHint: false,
|
|
1191
|
-
destructiveHint: false,
|
|
1192
|
-
idempotentHint: false,
|
|
1193
|
-
openWorldHint: true,
|
|
1194
|
-
},
|
|
1195
|
-
}, async ({ message_id, text, as }) => jsonContent(await getAgentClient(as).reply(message_id, text)));
|
|
1196
|
-
server.registerTool('get_message_thread', {
|
|
1197
|
-
title: 'Get Thread',
|
|
1198
|
-
description: 'Retrieve a message thread.',
|
|
1199
|
-
inputSchema: {
|
|
1200
|
-
message_id: z.string().describe('Parent message ID'),
|
|
1201
|
-
limit: z.number().optional().describe('Maximum replies to return'),
|
|
1202
|
-
...identityOverrideInputShape,
|
|
1203
|
-
},
|
|
1204
|
-
outputSchema: jsonResult,
|
|
1205
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1206
|
-
}, async ({ message_id, limit, as }) => jsonContent(await getAgentClient(as).thread(message_id, limit ? { limit } : undefined)));
|
|
1207
|
-
server.registerTool('send_dm', {
|
|
1208
|
-
title: 'Send Direct Message',
|
|
1209
|
-
description: 'Send a private direct message to another agent.',
|
|
1210
|
-
inputSchema: {
|
|
1211
|
-
to: z.string().describe('Recipient agent name'),
|
|
1212
|
-
text: z.string().describe('DM text'),
|
|
1213
|
-
mode: z.enum(['wait', 'steer']).optional().describe('Delivery mode'),
|
|
1214
|
-
attachments: z.array(z.string()).optional().describe('File attachment IDs'),
|
|
1215
|
-
...identityOverrideInputShape,
|
|
1216
|
-
},
|
|
1217
|
-
outputSchema: jsonResult,
|
|
1218
|
-
annotations: {
|
|
1219
|
-
readOnlyHint: false,
|
|
1220
|
-
destructiveHint: false,
|
|
1221
|
-
idempotentHint: false,
|
|
1222
|
-
openWorldHint: true,
|
|
1223
|
-
},
|
|
1224
|
-
}, async ({ to, text, mode, attachments, as }) => jsonContent(await getAgentClient(as).dm(to, text, { mode, attachments })));
|
|
1225
|
-
server.registerTool('list_dms', {
|
|
1226
|
-
title: 'List DM Conversations',
|
|
1227
|
-
description: 'List direct message conversations for the current agent.',
|
|
1228
|
-
inputSchema: {
|
|
1229
|
-
...identityOverrideInputShape,
|
|
1230
|
-
},
|
|
1231
|
-
outputSchema: {
|
|
1232
|
-
conversations: z.array(z.object({}).passthrough()).describe('DM conversations'),
|
|
1233
|
-
},
|
|
1234
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1235
|
-
}, async ({ as }) => jsonContent({ conversations: await getAgentClient(as).dms.conversations() }));
|
|
1236
|
-
server.registerTool('send_group_dm', {
|
|
1237
|
-
title: 'Send Group DM',
|
|
1238
|
-
description: 'Create a group DM and send the first message.',
|
|
1239
|
-
inputSchema: {
|
|
1240
|
-
participants: z.array(z.string()).describe('Participant agent names'),
|
|
1241
|
-
name: z.string().optional().describe('Optional group name'),
|
|
1242
|
-
text: z.string().describe('Initial message'),
|
|
1243
|
-
...identityOverrideInputShape,
|
|
1244
|
-
},
|
|
1245
|
-
outputSchema: jsonResult,
|
|
1246
|
-
annotations: {
|
|
1247
|
-
readOnlyHint: false,
|
|
1248
|
-
destructiveHint: false,
|
|
1249
|
-
idempotentHint: false,
|
|
1250
|
-
openWorldHint: true,
|
|
1251
|
-
},
|
|
1252
|
-
}, async ({ participants, name, text, as }) => {
|
|
1253
|
-
const client = getAgentClient(as);
|
|
1254
|
-
const conversation = await client.dms.createGroup({ participants, name });
|
|
1255
|
-
const message = await client.dms.sendMessage(conversation.id, text);
|
|
1256
|
-
return jsonContent({ conversation, message });
|
|
1257
|
-
});
|
|
1258
|
-
server.registerTool('add_reaction', {
|
|
1259
|
-
title: 'Add Reaction',
|
|
1260
|
-
description: 'Add an emoji reaction to a message.',
|
|
1261
|
-
inputSchema: {
|
|
1262
|
-
message_id: z.string().describe('Message ID'),
|
|
1263
|
-
emoji: z.string().describe('Emoji character or shortcode'),
|
|
1264
|
-
...identityOverrideInputShape,
|
|
1265
|
-
},
|
|
1266
|
-
outputSchema: messageResult,
|
|
1267
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1268
|
-
}, async ({ message_id, emoji, as }) => {
|
|
1269
|
-
const resolved = resolveEmoji(emoji);
|
|
1270
|
-
await getAgentClient(as).react(message_id, resolved);
|
|
1271
|
-
return textContent(`Reacted with ${resolved}`);
|
|
1272
|
-
});
|
|
1273
|
-
server.registerTool('remove_reaction', {
|
|
1274
|
-
title: 'Remove Reaction',
|
|
1275
|
-
description: 'Remove an emoji reaction from a message.',
|
|
1276
|
-
inputSchema: {
|
|
1277
|
-
message_id: z.string().describe('Message ID'),
|
|
1278
|
-
emoji: z.string().describe('Emoji character or shortcode'),
|
|
1279
|
-
...identityOverrideInputShape,
|
|
1280
|
-
},
|
|
1281
|
-
outputSchema: messageResult,
|
|
1282
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1283
|
-
}, async ({ message_id, emoji, as }) => {
|
|
1284
|
-
const resolved = resolveEmoji(emoji);
|
|
1285
|
-
await getAgentClient(as).unreact(message_id, resolved);
|
|
1286
|
-
return textContent(`Removed reaction ${resolved}`);
|
|
1287
|
-
});
|
|
1288
|
-
server.registerTool('search_messages', {
|
|
1289
|
-
title: 'Search Messages',
|
|
1290
|
-
description: 'Search messages across the workspace.',
|
|
1291
|
-
inputSchema: {
|
|
1292
|
-
query: z.string().describe('Text search query'),
|
|
1293
|
-
channel: z.string().optional().describe('Optional channel filter'),
|
|
1294
|
-
from: z.string().optional().describe('Optional sender filter'),
|
|
1295
|
-
limit: z.number().optional().describe('Maximum results'),
|
|
1296
|
-
...identityOverrideInputShape,
|
|
1297
|
-
},
|
|
1298
|
-
outputSchema: {
|
|
1299
|
-
results: z.array(z.object({}).passthrough()).describe('Search results'),
|
|
1300
|
-
},
|
|
1301
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1302
|
-
}, async ({ query, channel, from, limit, as }) => jsonContent({ results: await getAgentClient(as).search(query, { channel, from, limit }) }));
|
|
1303
|
-
server.registerTool('check_inbox', {
|
|
1304
|
-
title: 'Check Inbox',
|
|
1305
|
-
description: 'Check unread messages, mentions, DMs, and reactions for the current agent.',
|
|
1306
|
-
inputSchema: {
|
|
1307
|
-
limit: z.number().optional().describe('Maximum inbox items'),
|
|
1308
|
-
...identityOverrideInputShape,
|
|
1309
|
-
},
|
|
1310
|
-
outputSchema: jsonResult,
|
|
1311
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1312
|
-
}, async ({ limit, as }) => jsonContent(await getAgentClient(as).inbox(limit != null ? { limit } : undefined)));
|
|
1313
|
-
server.registerTool('mark_message_read', {
|
|
1314
|
-
title: 'Mark as Read',
|
|
1315
|
-
description: 'Mark a message as read for the current agent.',
|
|
1316
|
-
inputSchema: {
|
|
1317
|
-
message_id: z.string().describe('Message ID'),
|
|
1318
|
-
...identityOverrideInputShape,
|
|
1319
|
-
},
|
|
1320
|
-
outputSchema: messageResult,
|
|
1321
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1322
|
-
}, async ({ message_id, as }) => {
|
|
1323
|
-
await getAgentClient(as).markRead(message_id);
|
|
1324
|
-
return textContent(`Marked message ${message_id} as read`);
|
|
1325
|
-
});
|
|
1326
|
-
server.registerTool('get_message_readers', {
|
|
1327
|
-
title: 'Get Readers',
|
|
1328
|
-
description: 'List agents who have read a message.',
|
|
1329
|
-
inputSchema: {
|
|
1330
|
-
message_id: z.string().describe('Message ID'),
|
|
1331
|
-
...identityOverrideInputShape,
|
|
1332
|
-
},
|
|
1333
|
-
outputSchema: {
|
|
1334
|
-
readers: z.array(z.object({}).passthrough()).describe('Readers'),
|
|
1335
|
-
},
|
|
1336
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1337
|
-
}, async ({ message_id, as }) => jsonContent({ readers: await getAgentClient(as).readers(message_id) }));
|
|
405
|
+
registerMessagingTools(server, getAgentClient);
|
|
1338
406
|
server.registerTool('add_agent', {
|
|
1339
407
|
title: 'Add Agent',
|
|
1340
408
|
description: 'Spawn another AI agent (relay worker) to delegate a task to. This is how you ' +
|