@zetaloop/chappie 0.5.0 → 1.0.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/.codex-plugin/plugin.json +13 -0
- package/README.md +26 -29
- package/dist/chunk-5CKVVDZ2.js +1042 -0
- package/dist/chunk-DM523QRR.js +148 -0
- package/dist/chunk-DN7OHZK6.js +110 -0
- package/dist/chunk-DQWC5GFN.js +1745 -0
- package/dist/chunk-EIPDEA3I.js +286 -0
- package/dist/chunk-GWYP7B66.js +8479 -0
- package/dist/chunk-GX4JWLHH.js +98 -0
- package/dist/chunk-K2VE2MDU.js +14916 -0
- package/dist/chunk-OWAAWZNT.js +56 -0
- package/dist/chunk-UOFCCHBS.js +94 -0
- package/dist/cli.js +47 -0
- package/dist/codex-RI2XL4HV.js +5144 -0
- package/dist/codex-plugin-NSKWB3WS.js +179 -0
- package/dist/codex.json +23 -0
- package/dist/index.js +4752 -0
- package/dist/instructions.md +13 -0
- package/dist/omp.js +240 -0
- package/dist/opencode.js +904 -0
- package/dist/promise-OHH5J44X.js +72 -0
- package/dist/stdio-KJVCYPXJ.js +1279 -0
- package/docs/setup.md +82 -0
- package/docs/tools.md +106 -96
- package/package.json +100 -56
- package/src/activity.ts +0 -21
- package/src/broker.ts +0 -780
- package/src/config.ts +0 -20
- package/src/delivery.ts +0 -54
- package/src/history.ts +0 -136
- package/src/index.ts +0 -42
- package/src/instructions.md +0 -17
- package/src/ipc.ts +0 -492
- package/src/provider.ts +0 -194
- package/src/questions.ts +0 -87
- package/src/resources.ts +0 -209
- package/src/server.ts +0 -566
- package/src/session.ts +0 -848
- package/src/state.ts +0 -163
- package/src/stdio.ts +0 -58
- package/src/tools.ts +0 -91
- package/src/transfer.ts +0 -313
- /package/{src → dist}/question.html +0 -0
package/src/server.ts
DELETED
|
@@ -1,566 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/server";
|
|
3
|
-
import * as z from "zod";
|
|
4
|
-
import packageJson from "../package.json" with { type: "json" };
|
|
5
|
-
import type { Broker } from "./broker.ts";
|
|
6
|
-
import { deliveryContent } from "./delivery.ts";
|
|
7
|
-
import { historyInput } from "./history.ts";
|
|
8
|
-
import {
|
|
9
|
-
answerContent,
|
|
10
|
-
answerInput,
|
|
11
|
-
questionInput,
|
|
12
|
-
questionInstructions,
|
|
13
|
-
questionOutput,
|
|
14
|
-
} from "./questions.ts";
|
|
15
|
-
import {
|
|
16
|
-
directTools,
|
|
17
|
-
inputContent,
|
|
18
|
-
type ToolInput,
|
|
19
|
-
toolResult,
|
|
20
|
-
} from "./tools.ts";
|
|
21
|
-
|
|
22
|
-
const instructions = readFileSync(
|
|
23
|
-
new URL("./instructions.md", import.meta.url),
|
|
24
|
-
"utf8",
|
|
25
|
-
).trim();
|
|
26
|
-
|
|
27
|
-
const outputSchema = z.object({
|
|
28
|
-
text: z
|
|
29
|
-
.string()
|
|
30
|
-
.describe(
|
|
31
|
-
"Complete text output, including Pi user input, submitted webpage answers, and deferred results. Images and file resources accompany it as native content blocks.",
|
|
32
|
-
),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const questionTemplate = "ui://chappie/question.html";
|
|
36
|
-
const questionSchema = outputSchema.extend({ question: questionOutput });
|
|
37
|
-
const toolAnnotations = {
|
|
38
|
-
readOnlyHint: true,
|
|
39
|
-
destructiveHint: false,
|
|
40
|
-
idempotentHint: true,
|
|
41
|
-
openWorldHint: false,
|
|
42
|
-
} as const;
|
|
43
|
-
|
|
44
|
-
interface RequestContext {
|
|
45
|
-
mcpReq: {
|
|
46
|
-
_meta?: Record<string, unknown>;
|
|
47
|
-
signal: AbortSignal;
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function createServer(broker: Broker): McpServer {
|
|
52
|
-
const server = new McpServer(
|
|
53
|
-
{
|
|
54
|
-
name: "chappie",
|
|
55
|
-
version: packageJson.version,
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
instructions: [
|
|
59
|
-
instructions,
|
|
60
|
-
...(broker.askEnabled ? [questionInstructions] : []),
|
|
61
|
-
].join("\n\n"),
|
|
62
|
-
},
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
function handle<Args, Result>(
|
|
66
|
-
callback: (args: Args, context: RequestContext) => Promise<Result>,
|
|
67
|
-
) {
|
|
68
|
-
return (args: Args, context: RequestContext): Promise<Result> => {
|
|
69
|
-
requireChatId(context);
|
|
70
|
-
context.mcpReq.signal.throwIfAborted();
|
|
71
|
-
return callback(args, context);
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
server.registerTool(
|
|
76
|
-
"init",
|
|
77
|
-
{
|
|
78
|
-
title: "Connect to Pi",
|
|
79
|
-
description:
|
|
80
|
-
"Select this chat's default Pi session and return its environment, tool catalog, and participation instructions. Use the task's sessionId to resume, or find it by cwd/name with sessions. For a task without a specified target, omit sessionId to reuse the default or select the first online, unbound session. Read recent history when resuming work.",
|
|
81
|
-
outputSchema,
|
|
82
|
-
inputSchema: z.object({
|
|
83
|
-
sessionId: z
|
|
84
|
-
.string()
|
|
85
|
-
.optional()
|
|
86
|
-
.describe("Default Pi session ID; may be shared with other chats"),
|
|
87
|
-
}),
|
|
88
|
-
annotations: toolAnnotations,
|
|
89
|
-
},
|
|
90
|
-
handle(async (args, context) => {
|
|
91
|
-
const chatId = requireChatId(context);
|
|
92
|
-
const { inputs, ...initialized } = await broker.initialize(
|
|
93
|
-
chatId,
|
|
94
|
-
args.sessionId,
|
|
95
|
-
context.mcpReq._meta?.["otunnel/requestId"],
|
|
96
|
-
context.mcpReq.signal,
|
|
97
|
-
);
|
|
98
|
-
return finishResult(broker, context, textResult(initialized, inputs));
|
|
99
|
-
}),
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
server.registerTool(
|
|
103
|
-
"chat",
|
|
104
|
-
{
|
|
105
|
-
title: "Reply in Pi",
|
|
106
|
-
description: "Send a Markdown assistant message to Pi.",
|
|
107
|
-
outputSchema,
|
|
108
|
-
inputSchema: z.object({
|
|
109
|
-
text: z.string().min(1).describe("Assistant message in Markdown"),
|
|
110
|
-
sessionId: z
|
|
111
|
-
.string()
|
|
112
|
-
.optional()
|
|
113
|
-
.describe(
|
|
114
|
-
"Pi session for this operation; becomes the default if none is set",
|
|
115
|
-
),
|
|
116
|
-
}),
|
|
117
|
-
annotations: toolAnnotations,
|
|
118
|
-
},
|
|
119
|
-
handle(async (args, context) => {
|
|
120
|
-
const chatId = requireChatId(context);
|
|
121
|
-
const { sessionId, cwd, inputs, initialization } = await broker.chat(
|
|
122
|
-
chatId,
|
|
123
|
-
args.sessionId,
|
|
124
|
-
args.text,
|
|
125
|
-
context.mcpReq._meta?.["otunnel/requestId"],
|
|
126
|
-
context.mcpReq.signal,
|
|
127
|
-
);
|
|
128
|
-
return finishResult(
|
|
129
|
-
broker,
|
|
130
|
-
context,
|
|
131
|
-
textResult(
|
|
132
|
-
{ sessionId, cwd, ...(initialization ? { initialization } : {}) },
|
|
133
|
-
inputs,
|
|
134
|
-
),
|
|
135
|
-
);
|
|
136
|
-
}),
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
if (broker.askEnabled) {
|
|
140
|
-
server.registerTool(
|
|
141
|
-
"ask",
|
|
142
|
-
{
|
|
143
|
-
title: "Ask in ChatGPT",
|
|
144
|
-
description:
|
|
145
|
-
"Request a question widget in ChatGPT and return its ID immediately. Display depends on the host; call ask_assert next with question.id to confirm loading. Answers, revisions, and skips arrive as webAnswer in later tool results.",
|
|
146
|
-
inputSchema: questionInput.extend({
|
|
147
|
-
sessionId: z
|
|
148
|
-
.string()
|
|
149
|
-
.optional()
|
|
150
|
-
.describe(
|
|
151
|
-
"Pi session for this question; defaults to this chat's session",
|
|
152
|
-
),
|
|
153
|
-
}),
|
|
154
|
-
outputSchema: questionSchema,
|
|
155
|
-
annotations: toolAnnotations,
|
|
156
|
-
_meta: { ui: { resourceUri: questionTemplate } },
|
|
157
|
-
},
|
|
158
|
-
handle(async ({ sessionId, ...input }, context) => {
|
|
159
|
-
const { initialization, ...question } = await broker.ask(
|
|
160
|
-
requireChatId(context),
|
|
161
|
-
sessionId,
|
|
162
|
-
input,
|
|
163
|
-
context.mcpReq._meta?.["otunnel/requestId"],
|
|
164
|
-
context.mcpReq.signal,
|
|
165
|
-
);
|
|
166
|
-
const result = await finishResult(broker, context, {
|
|
167
|
-
content: [
|
|
168
|
-
...(initialization ? textResult({ initialization }).content : []),
|
|
169
|
-
{
|
|
170
|
-
type: "text",
|
|
171
|
-
text: `Question widget requested. Call ask_assert({"questionId":"${question.id}"}) next.`,
|
|
172
|
-
},
|
|
173
|
-
],
|
|
174
|
-
});
|
|
175
|
-
return {
|
|
176
|
-
...result,
|
|
177
|
-
structuredContent: { ...result.structuredContent, question },
|
|
178
|
-
};
|
|
179
|
-
}),
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
server.registerTool(
|
|
183
|
-
"ask_assert",
|
|
184
|
-
{
|
|
185
|
-
title: "Assert question display",
|
|
186
|
-
description:
|
|
187
|
-
"Confirm that an ask widget loaded in ChatGPT. Call immediately after ask with question.id. Fails after 10 seconds without loading and records the question as skipped. Use a Pi interactive tool if an answer is needed. User answers arrive separately as webAnswer.",
|
|
188
|
-
inputSchema: z.object({
|
|
189
|
-
questionId: z.string().describe("question.id returned by ask"),
|
|
190
|
-
}),
|
|
191
|
-
outputSchema: questionSchema,
|
|
192
|
-
annotations: toolAnnotations,
|
|
193
|
-
},
|
|
194
|
-
handle(async ({ questionId }, context) => {
|
|
195
|
-
const question = await broker.assertQuestion(
|
|
196
|
-
requireChatId(context),
|
|
197
|
-
questionId,
|
|
198
|
-
context.mcpReq.signal,
|
|
199
|
-
);
|
|
200
|
-
const result = await finishResult(broker, context, {
|
|
201
|
-
content: [{ type: "text", text: "Question widget loaded." }],
|
|
202
|
-
});
|
|
203
|
-
return {
|
|
204
|
-
...result,
|
|
205
|
-
structuredContent: { ...result.structuredContent, question },
|
|
206
|
-
};
|
|
207
|
-
}),
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
server.registerTool(
|
|
211
|
-
"answer",
|
|
212
|
-
{
|
|
213
|
-
title: "Question state",
|
|
214
|
-
description:
|
|
215
|
-
"Read a saved question, report widget loading, or save an answer, revision, or skip.",
|
|
216
|
-
inputSchema: z.object({
|
|
217
|
-
questionId: z.string(),
|
|
218
|
-
answer: answerInput.optional(),
|
|
219
|
-
loaded: z
|
|
220
|
-
.literal(true)
|
|
221
|
-
.optional()
|
|
222
|
-
.describe("The question widget has loaded"),
|
|
223
|
-
}),
|
|
224
|
-
outputSchema: questionSchema,
|
|
225
|
-
annotations: toolAnnotations,
|
|
226
|
-
_meta: { ui: { visibility: ["app"] }, "openai/widgetAccessible": true },
|
|
227
|
-
},
|
|
228
|
-
async ({ questionId, answer, loaded = false }, context) => {
|
|
229
|
-
const question = await broker.answer(
|
|
230
|
-
requireChatId(context),
|
|
231
|
-
questionId,
|
|
232
|
-
answer,
|
|
233
|
-
loaded,
|
|
234
|
-
);
|
|
235
|
-
const text = answer
|
|
236
|
-
? answer.skipped
|
|
237
|
-
? "Question skipped."
|
|
238
|
-
: "Answer saved."
|
|
239
|
-
: loaded
|
|
240
|
-
? "Question widget loaded."
|
|
241
|
-
: "Question state.";
|
|
242
|
-
return {
|
|
243
|
-
content: [{ type: "text", text }],
|
|
244
|
-
structuredContent: { text, question },
|
|
245
|
-
};
|
|
246
|
-
},
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
server.registerResource(
|
|
250
|
-
"question",
|
|
251
|
-
questionTemplate,
|
|
252
|
-
{ title: "Chappie question", mimeType: "text/html;profile=mcp-app" },
|
|
253
|
-
async () => ({
|
|
254
|
-
contents: [
|
|
255
|
-
{
|
|
256
|
-
uri: questionTemplate,
|
|
257
|
-
mimeType: "text/html;profile=mcp-app",
|
|
258
|
-
text: readFileSync(
|
|
259
|
-
new URL("./question.html", import.meta.url),
|
|
260
|
-
"utf8",
|
|
261
|
-
),
|
|
262
|
-
_meta: {
|
|
263
|
-
ui: {
|
|
264
|
-
prefersBorder: true,
|
|
265
|
-
csp: { connectDomains: [], resourceDomains: [] },
|
|
266
|
-
},
|
|
267
|
-
"openai/widgetDescription":
|
|
268
|
-
"A question the user can answer or revise.",
|
|
269
|
-
},
|
|
270
|
-
},
|
|
271
|
-
],
|
|
272
|
-
}),
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
server.registerTool(
|
|
277
|
-
"tools",
|
|
278
|
-
{
|
|
279
|
-
title: "Pi tools",
|
|
280
|
-
description:
|
|
281
|
-
"Get full definitions of Pi tools for call. Filter by names, or omit names to list all active tools.",
|
|
282
|
-
outputSchema,
|
|
283
|
-
inputSchema: z.object({
|
|
284
|
-
names: z
|
|
285
|
-
.array(z.string())
|
|
286
|
-
.min(1)
|
|
287
|
-
.optional()
|
|
288
|
-
.describe("Tool names to describe; omit to return every active tool"),
|
|
289
|
-
sessionId: z
|
|
290
|
-
.string()
|
|
291
|
-
.optional()
|
|
292
|
-
.describe(
|
|
293
|
-
"Pi session for this operation; becomes the default if none is set",
|
|
294
|
-
),
|
|
295
|
-
}),
|
|
296
|
-
annotations: toolAnnotations,
|
|
297
|
-
},
|
|
298
|
-
handle(async (args, context) => {
|
|
299
|
-
const { inputs, ...inspected } = await broker.tools(
|
|
300
|
-
requireChatId(context),
|
|
301
|
-
args.sessionId,
|
|
302
|
-
args.names,
|
|
303
|
-
context.mcpReq._meta?.["otunnel/requestId"],
|
|
304
|
-
context.mcpReq.signal,
|
|
305
|
-
);
|
|
306
|
-
return finishResult(
|
|
307
|
-
broker,
|
|
308
|
-
context,
|
|
309
|
-
textResult(
|
|
310
|
-
{
|
|
311
|
-
session: inspected.session,
|
|
312
|
-
tools: inspected.tools,
|
|
313
|
-
...(inspected.initialization
|
|
314
|
-
? { initialization: inspected.initialization }
|
|
315
|
-
: {}),
|
|
316
|
-
},
|
|
317
|
-
inputs,
|
|
318
|
-
),
|
|
319
|
-
);
|
|
320
|
-
}),
|
|
321
|
-
);
|
|
322
|
-
|
|
323
|
-
server.registerTool(
|
|
324
|
-
"call",
|
|
325
|
-
{
|
|
326
|
-
title: "Call Pi tools",
|
|
327
|
-
description:
|
|
328
|
-
"Execute Pi tools using the definitions returned by tools. Each calls array is one native Pi batch.",
|
|
329
|
-
outputSchema,
|
|
330
|
-
inputSchema: z.object({
|
|
331
|
-
calls: z
|
|
332
|
-
.array(
|
|
333
|
-
z.object({
|
|
334
|
-
name: z.string(),
|
|
335
|
-
arguments: z.record(z.string(), z.unknown()),
|
|
336
|
-
}),
|
|
337
|
-
)
|
|
338
|
-
.min(1),
|
|
339
|
-
sessionId: z
|
|
340
|
-
.string()
|
|
341
|
-
.optional()
|
|
342
|
-
.describe(
|
|
343
|
-
"Pi session for this operation; becomes the default if none is set",
|
|
344
|
-
),
|
|
345
|
-
}),
|
|
346
|
-
annotations: toolAnnotations,
|
|
347
|
-
},
|
|
348
|
-
handle(async (args, context) => {
|
|
349
|
-
const result = await broker.call(
|
|
350
|
-
requireChatId(context),
|
|
351
|
-
args.sessionId,
|
|
352
|
-
args.calls,
|
|
353
|
-
context.mcpReq._meta?.["otunnel/requestId"],
|
|
354
|
-
context.mcpReq.signal,
|
|
355
|
-
);
|
|
356
|
-
return finishResult(
|
|
357
|
-
broker,
|
|
358
|
-
context,
|
|
359
|
-
toolResult(
|
|
360
|
-
result.toolResults,
|
|
361
|
-
result.sessionId,
|
|
362
|
-
result.cwd,
|
|
363
|
-
result.inputs,
|
|
364
|
-
result.initialization,
|
|
365
|
-
),
|
|
366
|
-
);
|
|
367
|
-
}),
|
|
368
|
-
);
|
|
369
|
-
|
|
370
|
-
for (const tool of directTools) {
|
|
371
|
-
server.registerTool(
|
|
372
|
-
tool.name,
|
|
373
|
-
{
|
|
374
|
-
title: tool.name,
|
|
375
|
-
description: tool.description,
|
|
376
|
-
outputSchema,
|
|
377
|
-
inputSchema: tool.inputSchema,
|
|
378
|
-
annotations: toolAnnotations,
|
|
379
|
-
...(tool.fileParams
|
|
380
|
-
? { _meta: { "openai/fileParams": tool.fileParams } }
|
|
381
|
-
: {}),
|
|
382
|
-
},
|
|
383
|
-
handle(async (args, context) => {
|
|
384
|
-
const input = { ...args } as Record<string, unknown> & {
|
|
385
|
-
sessionId?: string;
|
|
386
|
-
};
|
|
387
|
-
const sessionId = input.sessionId;
|
|
388
|
-
delete input.sessionId;
|
|
389
|
-
const calls: ToolInput[] = [{ name: tool.name, arguments: input }];
|
|
390
|
-
const result = await broker.call(
|
|
391
|
-
requireChatId(context),
|
|
392
|
-
sessionId,
|
|
393
|
-
calls,
|
|
394
|
-
context.mcpReq._meta?.["otunnel/requestId"],
|
|
395
|
-
context.mcpReq.signal,
|
|
396
|
-
);
|
|
397
|
-
return finishResult(
|
|
398
|
-
broker,
|
|
399
|
-
context,
|
|
400
|
-
toolResult(
|
|
401
|
-
result.toolResults,
|
|
402
|
-
result.sessionId,
|
|
403
|
-
result.cwd,
|
|
404
|
-
result.inputs,
|
|
405
|
-
result.initialization,
|
|
406
|
-
),
|
|
407
|
-
);
|
|
408
|
-
}),
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
server.registerTool(
|
|
413
|
-
"history",
|
|
414
|
-
{
|
|
415
|
-
title: "Session history",
|
|
416
|
-
description:
|
|
417
|
-
"Read Pi history with entry IDs and timestamps. Use before/after to page the current branch, and wait to follow new progress when caught up. Set observer when reading as an observer. An explicit sessionId applies only to this read.",
|
|
418
|
-
inputSchema: historyInput.extend({
|
|
419
|
-
sessionId: z
|
|
420
|
-
.string()
|
|
421
|
-
.optional()
|
|
422
|
-
.describe("Pi session to read; defaults to this chat's session"),
|
|
423
|
-
}),
|
|
424
|
-
outputSchema,
|
|
425
|
-
annotations: toolAnnotations,
|
|
426
|
-
},
|
|
427
|
-
handle(async ({ sessionId, ...range }, context) => {
|
|
428
|
-
const { history, ...session } = await broker.history(
|
|
429
|
-
requireChatId(context),
|
|
430
|
-
sessionId,
|
|
431
|
-
range,
|
|
432
|
-
context.mcpReq._meta?.["otunnel/requestId"],
|
|
433
|
-
context.mcpReq.signal,
|
|
434
|
-
);
|
|
435
|
-
const { content, ...page } = history;
|
|
436
|
-
return formatResult(
|
|
437
|
-
{
|
|
438
|
-
content: [
|
|
439
|
-
...textResult({ ...session, history: page }).content,
|
|
440
|
-
...content,
|
|
441
|
-
],
|
|
442
|
-
},
|
|
443
|
-
requireChatId(context),
|
|
444
|
-
);
|
|
445
|
-
}),
|
|
446
|
-
);
|
|
447
|
-
|
|
448
|
-
server.registerTool(
|
|
449
|
-
"sessions",
|
|
450
|
-
{
|
|
451
|
-
title: "Local sessions",
|
|
452
|
-
description:
|
|
453
|
-
"List online Pi sessions with their IDs, devices, cwd, names, execution status, and saved binding counts. Also returns this chat's default.",
|
|
454
|
-
outputSchema,
|
|
455
|
-
inputSchema: z.object({
|
|
456
|
-
sessionId: z
|
|
457
|
-
.string()
|
|
458
|
-
.optional()
|
|
459
|
-
.describe("Filter the online list to this Pi session"),
|
|
460
|
-
}),
|
|
461
|
-
annotations: toolAnnotations,
|
|
462
|
-
},
|
|
463
|
-
handle(async (args, context) => {
|
|
464
|
-
const chatId = requestChatId(context);
|
|
465
|
-
const inputs = chatId
|
|
466
|
-
? await broker.inputs(chatId, args.sessionId, context.mcpReq.signal)
|
|
467
|
-
: [];
|
|
468
|
-
const result = textResult(
|
|
469
|
-
{
|
|
470
|
-
binding: chatId ? (broker.binding(chatId) ?? null) : null,
|
|
471
|
-
sessions: broker.listSessions(args.sessionId),
|
|
472
|
-
},
|
|
473
|
-
inputs,
|
|
474
|
-
);
|
|
475
|
-
return finishResult(broker, context, result);
|
|
476
|
-
}),
|
|
477
|
-
);
|
|
478
|
-
|
|
479
|
-
server.registerResource(
|
|
480
|
-
"Pi resource",
|
|
481
|
-
new ResourceTemplate(
|
|
482
|
-
"chappie://session/{sessionId}/{kind}/{id}/{name}{?chatId}",
|
|
483
|
-
{
|
|
484
|
-
list: undefined,
|
|
485
|
-
},
|
|
486
|
-
),
|
|
487
|
-
{ title: "Pi resource" },
|
|
488
|
-
async (uri, _variables, context) => {
|
|
489
|
-
const resource = await broker.readResource(
|
|
490
|
-
uri.href,
|
|
491
|
-
context.mcpReq.signal,
|
|
492
|
-
);
|
|
493
|
-
return {
|
|
494
|
-
contents: [
|
|
495
|
-
{
|
|
496
|
-
uri: resource.uri,
|
|
497
|
-
mimeType: resource.mimeType,
|
|
498
|
-
blob: resource.blob,
|
|
499
|
-
},
|
|
500
|
-
],
|
|
501
|
-
};
|
|
502
|
-
},
|
|
503
|
-
);
|
|
504
|
-
|
|
505
|
-
return server;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function textResult(
|
|
509
|
-
value: unknown,
|
|
510
|
-
inputs: Parameters<typeof inputContent>[0] = [],
|
|
511
|
-
) {
|
|
512
|
-
return {
|
|
513
|
-
content: [
|
|
514
|
-
{ type: "text" as const, text: JSON.stringify(value) },
|
|
515
|
-
...inputContent(inputs),
|
|
516
|
-
],
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
async function finishResult<
|
|
521
|
-
T extends { content: ReturnType<typeof toolResult>["content"] },
|
|
522
|
-
>(broker: Broker, context: RequestContext, result: T) {
|
|
523
|
-
context.mcpReq.signal.throwIfAborted();
|
|
524
|
-
const chatId = requestChatId(context);
|
|
525
|
-
const deliveries = chatId ? broker.deliveries(chatId) : [];
|
|
526
|
-
const answers = chatId ? broker.answers(chatId) : [];
|
|
527
|
-
const content = [
|
|
528
|
-
...result.content,
|
|
529
|
-
...deliveryContent(deliveries),
|
|
530
|
-
...answerContent(answers),
|
|
531
|
-
];
|
|
532
|
-
await broker.acknowledge(deliveries, answers, context.mcpReq.signal);
|
|
533
|
-
return formatResult({ ...result, content }, chatId);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function formatResult<
|
|
537
|
-
T extends { content: ReturnType<typeof toolResult>["content"] },
|
|
538
|
-
>(result: T, chatId?: string) {
|
|
539
|
-
const content = result.content.map((block) => {
|
|
540
|
-
if (block.type !== "resource_link" || !chatId) return block;
|
|
541
|
-
const uri = new URL(block.uri);
|
|
542
|
-
uri.searchParams.set("chatId", chatId);
|
|
543
|
-
return { ...block, uri: uri.href };
|
|
544
|
-
});
|
|
545
|
-
return {
|
|
546
|
-
...result,
|
|
547
|
-
content,
|
|
548
|
-
structuredContent: {
|
|
549
|
-
text: content
|
|
550
|
-
.flatMap((block) => (block.type === "text" ? [block.text] : []))
|
|
551
|
-
.join("\n"),
|
|
552
|
-
},
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
function requestChatId(context: RequestContext): string | undefined {
|
|
557
|
-
const value = context.mcpReq._meta?.["openai/session"];
|
|
558
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
function requireChatId(context: RequestContext): string {
|
|
562
|
-
const chatId = requestChatId(context);
|
|
563
|
-
if (!chatId)
|
|
564
|
-
throw new Error("ChatGPT did not provide openai/session metadata");
|
|
565
|
-
return chatId;
|
|
566
|
-
}
|