relay-companion 0.1.0
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/bin/relay.js +262 -0
- package/overlay/inbox.html +1398 -0
- package/overlay/main.cjs +762 -0
- package/overlay/preload.cjs +37 -0
- package/overlay/sounds/tink.wav +0 -0
- package/package.json +25 -0
- package/src/claude-materializer.js +85 -0
- package/src/claude-session-writer.js +629 -0
- package/src/client.js +168 -0
- package/src/codex-app-server.js +120 -0
- package/src/codex-desktop.js +276 -0
- package/src/codex-session-writer.js +170 -0
- package/src/codex-state.js +114 -0
- package/src/config.js +62 -0
- package/src/host-json.js +14 -0
- package/src/host-paths.js +67 -0
- package/src/install.js +142 -0
- package/src/materializer.js +378 -0
- package/src/mcp.js +419 -0
- package/src/notifications.js +412 -0
- package/src/pinning.js +43 -0
- package/src/relay-briefing.js +344 -0
- package/src/runtime.js +1141 -0
- package/src/task-daemon.js +216 -0
package/src/mcp.js
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { RelayClient } from "./client.js";
|
|
5
|
+
|
|
6
|
+
export const TOOLS = [
|
|
7
|
+
{
|
|
8
|
+
name: "relay_task_create",
|
|
9
|
+
description:
|
|
10
|
+
"Create a Relay task so this agent can coordinate with other people's agents. Use this when the user asks you to do work that needs another person's approval, calendar, files, email, Slack context, or agent. You MUST provide a concise approvalSummary that can be shown to invited humans before they accept. Do not include private source material in the invitation unless the user explicitly asked to reveal it. The task will wait up to 60 minutes for invitees; if at least one accepts it starts with acceptors only, otherwise it cancels.",
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
title: { type: "string", description: "Short task title." },
|
|
15
|
+
objective: { type: "string", description: "The work the agents should coordinate to complete." },
|
|
16
|
+
approvalSummary: { type: "string", description: "One or two sentences shown to invited humans." },
|
|
17
|
+
invitees: {
|
|
18
|
+
type: "array",
|
|
19
|
+
items: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
email: { type: "string" },
|
|
23
|
+
name: { type: "string" },
|
|
24
|
+
contactId: { type: "string" },
|
|
25
|
+
relayUserId: { type: "string" },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
requiredCapabilities: {
|
|
30
|
+
type: "array",
|
|
31
|
+
items: { type: "string" },
|
|
32
|
+
description: "Connector/tool capabilities likely needed, e.g. read_calendar, read_email.",
|
|
33
|
+
},
|
|
34
|
+
idempotencyKey: { type: "string" },
|
|
35
|
+
},
|
|
36
|
+
required: ["title", "objective", "approvalSummary", "invitees", "idempotencyKey"],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "relay_task_status",
|
|
41
|
+
description:
|
|
42
|
+
"Inspect a Relay task's current state, participants, pending approvals visible to this human, recent messages, scoped results, and latest events. Use this before sending follow-up messages or ending a task.",
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: "object",
|
|
45
|
+
properties: { taskId: { type: "string" } },
|
|
46
|
+
required: ["taskId"],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "relay_to_agent",
|
|
51
|
+
description:
|
|
52
|
+
"Request to send a task-scoped message to another human's agent. This NEVER delivers immediately in v1. Relay creates a ShareApproval for this human to review. The approval is bound to the exact recipients, body, attachments, provenance, destination, and payload hash. If you edit the payload you must call this tool again. Include shareSummary in one or two sentences explaining what information would be shared and with whom. Do not put private rationale in decline-handling logic; Relay declines are intentionally non-revealing.",
|
|
53
|
+
inputSchema: {
|
|
54
|
+
type: "object",
|
|
55
|
+
properties: {
|
|
56
|
+
taskId: { type: "string" },
|
|
57
|
+
recipientParticipantIds: { type: "array", items: { type: "string" } },
|
|
58
|
+
bodyMarkdown: { type: "string" },
|
|
59
|
+
shareSummary: { type: "string" },
|
|
60
|
+
attachments: { type: "array", items: { type: "object" } },
|
|
61
|
+
provenance: { type: "array", items: { type: "object" } },
|
|
62
|
+
senderAgentSessionId: { type: "string" },
|
|
63
|
+
idempotencyKey: { type: "string" },
|
|
64
|
+
},
|
|
65
|
+
required: ["taskId", "recipientParticipantIds", "bodyMarkdown", "shareSummary", "senderAgentSessionId", "idempotencyKey"],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "relay_to_human",
|
|
70
|
+
description:
|
|
71
|
+
"Request to send a human-visible Relay message. If the recipient is another person, Relay creates a ShareApproval before delivery. Use this for ordinary human-facing updates, non-final notices, or non-blocking questions. Do not use this to publish final task-scoped completion results; the creator agent must use relay_end_task for those, and Relay publishes those scoped results without a separate human approval step. If this task-run agent needs to ask its OWN human a question and cannot continue until they answer, set humanResponse.mode to required_before_resume; do not use that mode for another person. Example distinction: 'ask Sven what time he is going tonight' is a normal relay_to_human to Sven; 'I need my human to choose which slot I may offer' is relay_to_human with humanResponse.mode = required_before_resume. The same privacy rule applies: every cross-person visible word, filename, and connector-derived fact needs approval.",
|
|
72
|
+
inputSchema: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
taskId: { type: "string" },
|
|
76
|
+
recipientParticipantIds: { type: "array", items: { type: "string" } },
|
|
77
|
+
recipientUserIds: { type: "array", items: { type: "string" } },
|
|
78
|
+
bodyMarkdown: { type: "string" },
|
|
79
|
+
shareSummary: { type: "string" },
|
|
80
|
+
attachments: { type: "array", items: { type: "object" } },
|
|
81
|
+
provenance: { type: "array", items: { type: "object" } },
|
|
82
|
+
senderAgentSessionId: { type: "string" },
|
|
83
|
+
humanResponse: {
|
|
84
|
+
type: "object",
|
|
85
|
+
description:
|
|
86
|
+
"Only set this when this agent is asking its own human a blocking task-run question. Relay will show it as an urgent relay with quick reply UI, then resume the specified agent session after the human answers.",
|
|
87
|
+
properties: {
|
|
88
|
+
mode: { type: "string", enum: ["required_before_resume"] },
|
|
89
|
+
question: { type: "string" },
|
|
90
|
+
replyFormat: { type: "string", enum: ["plain_text", "yes_no", "datetime", "choice"] },
|
|
91
|
+
choices: { type: "array", items: { type: "string" } },
|
|
92
|
+
urgency: { type: "string", enum: ["normal", "high"] },
|
|
93
|
+
resumeAgentSessionId: { type: "string" },
|
|
94
|
+
expiresAt: { type: "string" },
|
|
95
|
+
},
|
|
96
|
+
required: ["mode", "question"],
|
|
97
|
+
},
|
|
98
|
+
idempotencyKey: { type: "string" },
|
|
99
|
+
},
|
|
100
|
+
required: ["taskId", "bodyMarkdown", "senderAgentSessionId", "idempotencyKey"],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "relay_answer_human_question",
|
|
105
|
+
description:
|
|
106
|
+
"Submit this human's answer to a blocking Relay question. Use this from the Codex/Claude session opened by a Relay pill question item or Relays row. This updates the original relay, wakes the waiting task agent session, and causes Relay to inject the answer back into that run. Do not use it for normal cross-person relays or outbound share approvals.",
|
|
107
|
+
inputSchema: {
|
|
108
|
+
type: "object",
|
|
109
|
+
properties: {
|
|
110
|
+
taskId: { type: "string" },
|
|
111
|
+
messageId: { type: "string", description: "The relay_to_human task message that is waiting for an answer." },
|
|
112
|
+
answerMarkdown: { type: "string" },
|
|
113
|
+
idempotencyKey: { type: "string" },
|
|
114
|
+
},
|
|
115
|
+
required: ["taskId", "messageId", "answerMarkdown", "idempotencyKey"],
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "relay_file_prepare_upload",
|
|
120
|
+
description:
|
|
121
|
+
"Register a task attachment owned by this human and get a short-lived upload URL. Use this before attaching a local/generated file to relay_to_agent, relay_to_human, or relay_end_task output. The returned attachment object contains a Relay fileId; pass that attachment object unchanged when sending the message. Never include signed upload/download URLs in cross-person messages. Cross-person filenames, file metadata, and file contents are only revealed after the exact message/share approval succeeds. For cross-person sharing, provide sha256 so Relay can bind approval to the file checksum.",
|
|
122
|
+
inputSchema: {
|
|
123
|
+
type: "object",
|
|
124
|
+
properties: {
|
|
125
|
+
taskId: { type: "string", description: "Task the file is being prepared for." },
|
|
126
|
+
filename: { type: "string" },
|
|
127
|
+
contentType: { type: "string" },
|
|
128
|
+
sizeBytes: { type: "number" },
|
|
129
|
+
sha256: {
|
|
130
|
+
type: "string",
|
|
131
|
+
description: "Hex/base64 SHA-256 checksum of the bytes. Required before this file can cross a person boundary.",
|
|
132
|
+
},
|
|
133
|
+
metadataScanStatus: { type: "string", enum: ["pending", "clean", "warning", "blocked"] },
|
|
134
|
+
idempotencyKey: { type: "string" },
|
|
135
|
+
},
|
|
136
|
+
required: ["taskId", "filename", "idempotencyKey"],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "relay_file_download",
|
|
141
|
+
description:
|
|
142
|
+
"Get an authorized short-lived download URL for a Relay file visible to this human/agent. Relay only mints the URL after checking file ownership or task visibility. Do not paste this signed URL into a message to another person; attach the Relay fileId through relay_to_agent or relay_to_human so approval and visibility rules can run.",
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: {
|
|
146
|
+
fileId: { type: "string" },
|
|
147
|
+
},
|
|
148
|
+
required: ["fileId"],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: "relay_end_task",
|
|
153
|
+
description:
|
|
154
|
+
"End a Relay task as the creator's agent. You MUST include a creatorResult for the creator human and one participantResult for every accepted participant. Relay publishes these final scoped results without a separate human approval step, so you must get the audience boundaries right before calling this tool. Each participant result must be scoped to that participant only and must not disclose information learned privately from other participants unless that information was explicitly approved for that participant. If any accepted participant has no useful interaction, include a scoped no-interaction result for them.",
|
|
155
|
+
inputSchema: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {
|
|
158
|
+
taskId: { type: "string" },
|
|
159
|
+
creatorResult: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
status: { type: "string", enum: ["success", "partial_success", "failed", "cancelled"] },
|
|
163
|
+
title: { type: "string" },
|
|
164
|
+
bodyMarkdown: { type: "string" },
|
|
165
|
+
sourceApprovalIds: { type: "array", items: { type: "string" } },
|
|
166
|
+
sourceResultIds: { type: "array", items: { type: "string" } },
|
|
167
|
+
},
|
|
168
|
+
required: ["status", "title", "bodyMarkdown"],
|
|
169
|
+
},
|
|
170
|
+
participantResults: {
|
|
171
|
+
type: "array",
|
|
172
|
+
items: {
|
|
173
|
+
type: "object",
|
|
174
|
+
properties: {
|
|
175
|
+
participantId: { type: "string" },
|
|
176
|
+
status: { type: "string", enum: ["success", "partial_success", "failed", "cancelled"] },
|
|
177
|
+
title: { type: "string" },
|
|
178
|
+
bodyMarkdown: { type: "string" },
|
|
179
|
+
sourceApprovalIds: { type: "array", items: { type: "string" } },
|
|
180
|
+
sourceResultIds: { type: "array", items: { type: "string" } },
|
|
181
|
+
},
|
|
182
|
+
required: ["participantId", "status", "title", "bodyMarkdown"],
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
expectedVersion: { type: "number" },
|
|
186
|
+
senderAgentSessionId: { type: "string" },
|
|
187
|
+
idempotencyKey: { type: "string" },
|
|
188
|
+
},
|
|
189
|
+
required: ["taskId", "creatorResult", "participantResults", "senderAgentSessionId", "idempotencyKey"],
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "share_results_with_human",
|
|
194
|
+
description:
|
|
195
|
+
"Participant-side helper: turn the scoped result context injected into this participant agent's conversation into a human-visible Relay result for this agent's own human. Only use the result context intended for this human. Do not include other participants' private summaries.",
|
|
196
|
+
inputSchema: {
|
|
197
|
+
type: "object",
|
|
198
|
+
properties: {
|
|
199
|
+
taskId: { type: "string" },
|
|
200
|
+
bodyMarkdown: { type: "string" },
|
|
201
|
+
senderAgentSessionId: { type: "string" },
|
|
202
|
+
idempotencyKey: { type: "string" },
|
|
203
|
+
},
|
|
204
|
+
required: ["taskId", "bodyMarkdown", "senderAgentSessionId", "idempotencyKey"],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: "relay_connector_list_tools",
|
|
209
|
+
description:
|
|
210
|
+
"List provider tools available through Relay connectors. Relay only exposes tools allowed by the human's granted scopes and Relay policy. Direct Codex/Claude tools connected outside Relay are not listed here; use them natively if the host exposes them.",
|
|
211
|
+
inputSchema: { type: "object", properties: {} },
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: "relay_connector_request_approval",
|
|
215
|
+
description:
|
|
216
|
+
"Request human approval for one exact connector tool execution before calling a state-changing provider tool. Use this for calendar creates/updates/deletes and any other connector tool whose catalog policy/capability tags indicate write or approval_sensitive behavior. The approval is bound to this taskId, provider, toolName, arguments, provenance, destination, and payload hash. If you change the event time, attendees, title, body, file target, or any material argument, request a new approval. After the human approves, call relay_connector_call_tool once with the returned approval.id and the exact same provider/toolName/arguments/provenance. Do not use this for read-only availability/email/file lookups unless you are about to reveal the results to another person; cross-person disclosure still goes through relay_to_agent or relay_to_human.",
|
|
217
|
+
inputSchema: {
|
|
218
|
+
type: "object",
|
|
219
|
+
properties: {
|
|
220
|
+
taskId: { type: "string" },
|
|
221
|
+
senderAgentSessionId: { type: "string" },
|
|
222
|
+
provider: { type: "string" },
|
|
223
|
+
toolName: { type: "string" },
|
|
224
|
+
arguments: { type: "object" },
|
|
225
|
+
provenance: { type: "array", items: { type: "object" } },
|
|
226
|
+
approvalSummary: {
|
|
227
|
+
type: "string",
|
|
228
|
+
description:
|
|
229
|
+
"One or two sentences for the human explaining the exact external action, visible fields, recipients/attendees if any, and why it is needed.",
|
|
230
|
+
},
|
|
231
|
+
idempotencyKey: { type: "string" },
|
|
232
|
+
},
|
|
233
|
+
required: ["taskId", "provider", "toolName", "approvalSummary", "senderAgentSessionId", "idempotencyKey"],
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: "relay_connector_call_tool",
|
|
238
|
+
description:
|
|
239
|
+
"Execute a provider tool through Relay's server-side connector gateway. Tool outputs include provenance. Read-only tools may be called directly, including outside a Relay task so this host can use Relay-connected Gmail, calendar, Slack, and file tools. When calling from a Relay task run, include taskId and senderAgentSessionId. Calendar writes and other state-changing connector tools require approvalId from relay_connector_request_approval, and the call must exactly match the approved payload. You may use connector outputs for the task, but sharing connector-derived facts across people still requires relay_to_agent or relay_to_human approval.",
|
|
240
|
+
inputSchema: {
|
|
241
|
+
type: "object",
|
|
242
|
+
properties: {
|
|
243
|
+
taskId: { type: "string" },
|
|
244
|
+
provider: { type: "string" },
|
|
245
|
+
toolName: { type: "string" },
|
|
246
|
+
arguments: { type: "object" },
|
|
247
|
+
provenance: { type: "array", items: { type: "object" } },
|
|
248
|
+
approvalId: { type: "string" },
|
|
249
|
+
senderAgentSessionId: { type: "string" },
|
|
250
|
+
idempotencyKey: { type: "string" },
|
|
251
|
+
},
|
|
252
|
+
required: ["provider", "toolName", "idempotencyKey"],
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
];
|
|
256
|
+
|
|
257
|
+
function text(obj) {
|
|
258
|
+
return { content: [{ type: "text", text: typeof obj === "string" ? obj : JSON.stringify(obj, null, 2) }] };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export async function handleCall(client, name, args) {
|
|
262
|
+
switch (name) {
|
|
263
|
+
case "relay_task_create":
|
|
264
|
+
return text(
|
|
265
|
+
await client.createTask({
|
|
266
|
+
title: args.title,
|
|
267
|
+
objective: args.objective,
|
|
268
|
+
approvalSummary: args.approvalSummary,
|
|
269
|
+
invitees: args.invitees || [],
|
|
270
|
+
requiredCapabilities: args.requiredCapabilities || [],
|
|
271
|
+
source: { host: "relay-mcp" },
|
|
272
|
+
idempotencyKey: args.idempotencyKey,
|
|
273
|
+
}),
|
|
274
|
+
);
|
|
275
|
+
case "relay_task_status":
|
|
276
|
+
return text(await client.getTask(args.taskId));
|
|
277
|
+
case "relay_to_agent":
|
|
278
|
+
return text(
|
|
279
|
+
await client.createTaskMessage(args.taskId, {
|
|
280
|
+
kind: "relay_to_agent",
|
|
281
|
+
bodyMarkdown: args.bodyMarkdown,
|
|
282
|
+
recipientParticipantIds: args.recipientParticipantIds || [],
|
|
283
|
+
shareSummary: args.shareSummary,
|
|
284
|
+
destinationKind: "agent",
|
|
285
|
+
attachments: args.attachments || [],
|
|
286
|
+
provenance: args.provenance || [],
|
|
287
|
+
senderAgentSessionId: args.senderAgentSessionId,
|
|
288
|
+
idempotencyKey: args.idempotencyKey,
|
|
289
|
+
}),
|
|
290
|
+
);
|
|
291
|
+
case "relay_to_human":
|
|
292
|
+
{
|
|
293
|
+
const result = await client.createTaskMessage(args.taskId, {
|
|
294
|
+
kind: "relay_to_human",
|
|
295
|
+
bodyMarkdown: args.bodyMarkdown,
|
|
296
|
+
recipientParticipantIds: args.recipientParticipantIds || [],
|
|
297
|
+
recipientUserIds: args.recipientUserIds || [],
|
|
298
|
+
shareSummary: args.shareSummary,
|
|
299
|
+
destinationKind: "human",
|
|
300
|
+
attachments: args.attachments || [],
|
|
301
|
+
provenance: args.provenance || [],
|
|
302
|
+
humanResponse: args.humanResponse,
|
|
303
|
+
senderAgentSessionId: args.senderAgentSessionId,
|
|
304
|
+
idempotencyKey: args.idempotencyKey,
|
|
305
|
+
});
|
|
306
|
+
if (args.humanResponse?.mode === "required_before_resume") {
|
|
307
|
+
return text({
|
|
308
|
+
...result,
|
|
309
|
+
instruction:
|
|
310
|
+
"The question has been delivered to your human as a Relay. Stop task work now and wait for Relay to resume this task session with the human's answer.",
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
return text(result);
|
|
314
|
+
}
|
|
315
|
+
case "relay_answer_human_question":
|
|
316
|
+
return text(
|
|
317
|
+
await client.answerHumanQuestion(args.taskId, args.messageId, {
|
|
318
|
+
answerMarkdown: args.answerMarkdown,
|
|
319
|
+
idempotencyKey: args.idempotencyKey,
|
|
320
|
+
}),
|
|
321
|
+
);
|
|
322
|
+
case "relay_file_prepare_upload":
|
|
323
|
+
return text(
|
|
324
|
+
await client.createFileUpload({
|
|
325
|
+
taskId: args.taskId,
|
|
326
|
+
filename: args.filename,
|
|
327
|
+
contentType: args.contentType || "application/octet-stream",
|
|
328
|
+
sizeBytes: args.sizeBytes || 0,
|
|
329
|
+
sha256: args.sha256,
|
|
330
|
+
metadataScanStatus: args.metadataScanStatus || "pending",
|
|
331
|
+
idempotencyKey: args.idempotencyKey,
|
|
332
|
+
}),
|
|
333
|
+
);
|
|
334
|
+
case "relay_file_download":
|
|
335
|
+
return text(await client.fileDownload(args.fileId));
|
|
336
|
+
case "relay_end_task":
|
|
337
|
+
return text(
|
|
338
|
+
await client.completeTask(args.taskId, {
|
|
339
|
+
creatorResult: args.creatorResult,
|
|
340
|
+
participantResults: args.participantResults || [],
|
|
341
|
+
senderAgentSessionId: args.senderAgentSessionId,
|
|
342
|
+
expectedVersion: args.expectedVersion,
|
|
343
|
+
idempotencyKey: args.idempotencyKey,
|
|
344
|
+
}),
|
|
345
|
+
);
|
|
346
|
+
case "share_results_with_human":
|
|
347
|
+
return text(
|
|
348
|
+
await client.createTaskMessage(args.taskId, {
|
|
349
|
+
kind: "relay_to_human",
|
|
350
|
+
bodyMarkdown: args.bodyMarkdown,
|
|
351
|
+
recipientParticipantIds: [],
|
|
352
|
+
recipientUserIds: [],
|
|
353
|
+
visibilityScope: [],
|
|
354
|
+
shareSummary: "Share this task result with my own human.",
|
|
355
|
+
destinationKind: "human",
|
|
356
|
+
senderAgentSessionId: args.senderAgentSessionId,
|
|
357
|
+
idempotencyKey: args.idempotencyKey,
|
|
358
|
+
}),
|
|
359
|
+
);
|
|
360
|
+
case "relay_connector_list_tools":
|
|
361
|
+
return text(await client.toolCatalog());
|
|
362
|
+
case "relay_connector_request_approval":
|
|
363
|
+
return text(
|
|
364
|
+
await client.requestToolApproval({
|
|
365
|
+
taskId: args.taskId,
|
|
366
|
+
senderAgentSessionId: args.senderAgentSessionId,
|
|
367
|
+
provider: args.provider,
|
|
368
|
+
toolName: args.toolName,
|
|
369
|
+
arguments: args.arguments || {},
|
|
370
|
+
provenance: args.provenance || [],
|
|
371
|
+
approvalSummary: args.approvalSummary,
|
|
372
|
+
idempotencyKey: args.idempotencyKey,
|
|
373
|
+
}),
|
|
374
|
+
);
|
|
375
|
+
case "relay_connector_call_tool":
|
|
376
|
+
return text(
|
|
377
|
+
await client.callTool({
|
|
378
|
+
taskId: args.taskId,
|
|
379
|
+
provider: args.provider,
|
|
380
|
+
toolName: args.toolName,
|
|
381
|
+
arguments: args.arguments || {},
|
|
382
|
+
provenance: args.provenance || [],
|
|
383
|
+
approvalId: args.approvalId,
|
|
384
|
+
senderAgentSessionId: args.senderAgentSessionId,
|
|
385
|
+
idempotencyKey: args.idempotencyKey,
|
|
386
|
+
}),
|
|
387
|
+
);
|
|
388
|
+
default:
|
|
389
|
+
return text({ error: `Unknown tool ${name}` });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export async function runMcpServer() {
|
|
394
|
+
const client = new RelayClient();
|
|
395
|
+
const server = new Server(
|
|
396
|
+
{ name: "relay-companion", version: "0.2.0-agent-protocol" },
|
|
397
|
+
{ capabilities: { tools: {} } },
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
401
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
402
|
+
try {
|
|
403
|
+
return await handleCall(client, req.params.name, req.params.arguments || {});
|
|
404
|
+
} catch (err) {
|
|
405
|
+
return { content: [{ type: "text", text: `Relay error: ${err.message}` }], isError: true };
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const transport = new StdioServerTransport();
|
|
410
|
+
await server.connect(transport);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
414
|
+
runMcpServer().catch((err) => {
|
|
415
|
+
// eslint-disable-next-line no-console
|
|
416
|
+
console.error(err);
|
|
417
|
+
process.exit(1);
|
|
418
|
+
});
|
|
419
|
+
}
|