@ryuhq/sdk 0.1.13 → 0.2.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/README.md +19 -6
- package/dist/agent-plugin.cjs +46 -0
- package/dist/agent-plugin.d.cts +42 -32
- package/dist/agent-plugin.d.ts +42 -32
- package/dist/agent-plugin.js +1 -1
- package/dist/agent.cjs +9 -0
- package/dist/agent.d.cts +377 -1
- package/dist/agent.d.ts +377 -1
- package/dist/agent.js +4 -2
- package/dist/app-Bkw7LlCK.d.ts +129 -0
- package/dist/app-DNaGmLVf.d.cts +129 -0
- package/dist/builder.cjs +1095 -0
- package/dist/builder.d.cts +212 -0
- package/dist/builder.d.ts +212 -0
- package/dist/builder.js +28 -0
- package/dist/chunk-A3RGEPDG.js +250 -0
- package/dist/chunk-FZSFZOIN.js +200 -0
- package/dist/{chunk-ODFEUVPW.js → chunk-HACAGK65.js} +1 -18
- package/dist/{chunk-AO2KJRDD.js → chunk-IEUQ3CDG.js} +125 -2
- package/dist/chunk-IKEDLLFY.js +19 -0
- package/dist/chunk-IOLP5FFE.js +354 -0
- package/dist/{chunk-G6FLVEC4.js → chunk-JX6DDRXV.js} +46 -0
- package/dist/chunk-T5676WL2.js +240 -0
- package/dist/chunk-TLDPEGC7.js +21 -0
- package/dist/chunk-TXSHHZF2.js +0 -0
- package/dist/{chunk-MTUBUPIV.js → chunk-ULSVL7EC.js} +8 -227
- package/dist/chunk-VLIRNNAE.js +154 -0
- package/dist/chunk-W3KPP4WN.js +135 -0
- package/dist/cli.cjs +258 -14
- package/dist/cli.js +96 -16
- package/dist/client-D5U6ssPc.d.cts +84 -0
- package/dist/client-D5U6ssPc.d.ts +84 -0
- package/dist/index.cjs +519 -5
- package/dist/index.d.cts +16 -632
- package/dist/index.d.ts +16 -632
- package/dist/index.js +63 -695
- package/dist/manifest.cjs +129 -3
- package/dist/manifest.d.cts +105 -2
- package/dist/manifest.d.ts +105 -2
- package/dist/manifest.js +7 -1
- package/dist/mcp/client.cjs +180 -0
- package/dist/mcp/client.d.cts +49 -0
- package/dist/mcp/client.d.ts +49 -0
- package/dist/mcp/client.js +10 -0
- package/dist/mcp/server.cjs +370 -0
- package/dist/mcp/server.d.cts +126 -0
- package/dist/mcp/server.d.ts +126 -0
- package/dist/mcp/server.js +9 -0
- package/dist/mcp.cjs +376 -0
- package/dist/mcp.d.cts +2 -0
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +17 -0
- package/dist/model.cjs +141 -0
- package/dist/model.d.cts +33 -0
- package/dist/model.d.ts +33 -0
- package/dist/model.js +18 -0
- package/dist/plugin.cjs +46 -0
- package/dist/plugin.d.cts +215 -0
- package/dist/plugin.d.ts +215 -0
- package/dist/plugin.js +8 -0
- package/dist/runnable.cjs +1230 -0
- package/dist/runnable.d.cts +271 -0
- package/dist/runnable.d.ts +271 -0
- package/dist/runnable.js +28 -0
- package/dist/{index-B6SkaAjJ.d.ts → tool-DSx2bFx8.d.ts} +35 -458
- package/dist/{index-BvAB5eMk.d.cts → tool-u-VR0fLF.d.cts} +35 -458
- package/package.json +38 -2
- package/src/agent/loop.test.ts +4 -4
- package/src/agent/tools.ts +3 -3
- package/src/agent-plugin.test.ts +58 -0
- package/src/agent-plugin.ts +108 -35
- package/src/cli/dev.test.ts +26 -47
- package/src/cli/dev.ts +10 -2
- package/src/cli.ts +98 -15
- package/src/exports-lockstep.test.ts +92 -0
- package/src/generated/plugin-manifest.ts +243 -27
- package/src/index.ts +35 -0
- package/src/manifest-schema.test.ts +30 -1
- package/src/manifest.fixtures.test.ts +13 -3
- package/src/manifest.test.ts +96 -10
- package/src/manifest.ts +334 -187
- package/src/mcp/index.ts +18 -0
- package/src/model/index.ts +22 -0
- package/src/plugin/ryu-plugin.ts +82 -0
- package/src/runnable/app.test.ts +2 -0
- package/src/runnable/app.ts +5 -2
- package/src/runnable/index.ts +2 -0
- package/src/runnable/primitives.ts +57 -0
- package/src/runnable/tool.ts +1 -1
- package/src/runnable/turn-hook.ts +4 -1
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,10 @@ __export(src_exports, {
|
|
|
37
37
|
CapabilityReqSchema: () => CapabilityReqSchema,
|
|
38
38
|
CompanionSurfaceSchema: () => CompanionSurfaceSchema,
|
|
39
39
|
DEFAULT_GATEWAY_URL: () => DEFAULT_GATEWAY_URL,
|
|
40
|
+
MCP_PROTOCOL_VERSION: () => MCP_PROTOCOL_VERSION,
|
|
41
|
+
McpServer: () => McpServer,
|
|
42
|
+
McpServerAuthSchema: () => McpServerAuthSchema,
|
|
43
|
+
McpServerDeclSchema: () => McpServerDeclSchema,
|
|
40
44
|
ModelClient: () => ModelClient2,
|
|
41
45
|
PRIMITIVE_BINDINGS: () => PRIMITIVE_BINDINGS,
|
|
42
46
|
PluginBuilder: () => PluginBuilder,
|
|
@@ -54,6 +58,7 @@ __export(src_exports, {
|
|
|
54
58
|
app: () => app,
|
|
55
59
|
appToolId: () => appToolId,
|
|
56
60
|
assertAllowedEgressUrl: () => assertAllowedEgressUrl,
|
|
61
|
+
callTool: () => callTool,
|
|
57
62
|
coreManifestJsonSchema: () => coreManifestJsonSchema,
|
|
58
63
|
createAgent: () => createAgent,
|
|
59
64
|
createPrimitives: () => createPrimitives,
|
|
@@ -67,19 +72,22 @@ __export(src_exports, {
|
|
|
67
72
|
defineWorkflow: () => defineWorkflow,
|
|
68
73
|
httpPrimitiveTransport: () => httpPrimitiveTransport,
|
|
69
74
|
inlineToolRunnable: () => inlineToolRunnable,
|
|
75
|
+
listTools: () => listTools,
|
|
70
76
|
query: () => query,
|
|
71
77
|
resolveGatewayToken: () => resolveGatewayToken2,
|
|
72
78
|
resolveGatewayUrl: () => resolveGatewayUrl3,
|
|
73
79
|
ryuTool: () => ryuTool,
|
|
74
80
|
skill: () => skill,
|
|
81
|
+
toDisposable: () => toDisposable,
|
|
75
82
|
tool: () => tool,
|
|
83
|
+
unwrapContent: () => unwrapContent,
|
|
76
84
|
validateManifestStrict: () => validateManifestStrict,
|
|
77
85
|
validatePluginId: () => validatePluginId,
|
|
78
86
|
workflow: () => workflow
|
|
79
87
|
});
|
|
80
88
|
module.exports = __toCommonJS(src_exports);
|
|
81
89
|
|
|
82
|
-
// ../../node_modules/.bun/tsup@8.5.1+
|
|
90
|
+
// ../../node_modules/.bun/tsup@8.5.1+276f2dd5f44b8d39/node_modules/tsup/assets/cjs_shims.js
|
|
83
91
|
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
84
92
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
85
93
|
|
|
@@ -317,6 +325,16 @@ var PRIMITIVE_BINDINGS = {
|
|
|
317
325
|
method: "model.complete",
|
|
318
326
|
grant: "hook:side-model"
|
|
319
327
|
},
|
|
328
|
+
"background.list": {
|
|
329
|
+
transport: "bridge",
|
|
330
|
+
method: "background.list",
|
|
331
|
+
grant: "background:control"
|
|
332
|
+
},
|
|
333
|
+
"background.stop": {
|
|
334
|
+
transport: "bridge",
|
|
335
|
+
method: "background.stop",
|
|
336
|
+
grant: "background:control"
|
|
337
|
+
},
|
|
320
338
|
// Host-direct media data-path (the host holds the node token; returns data: URLs).
|
|
321
339
|
"image.generate": {
|
|
322
340
|
transport: "direct",
|
|
@@ -350,6 +368,15 @@ function brokerCall(transport, cap, op, input) {
|
|
|
350
368
|
}
|
|
351
369
|
function createPrimitives(transport) {
|
|
352
370
|
return {
|
|
371
|
+
background: {
|
|
372
|
+
list: (input) => transport.bridge("background.list", {
|
|
373
|
+
producer: input?.producer,
|
|
374
|
+
running_only: input?.runningOnly
|
|
375
|
+
}),
|
|
376
|
+
stop: (input) => transport.bridge("background.stop", {
|
|
377
|
+
process_id: input.processId
|
|
378
|
+
})
|
|
379
|
+
},
|
|
353
380
|
rag: {
|
|
354
381
|
retrieve: (input) => brokerCall(transport, "rag", "retrieve", input),
|
|
355
382
|
embed: (input) => brokerCall(transport, "rag", "embed", input),
|
|
@@ -976,8 +1003,30 @@ var WidgetContributionSchema = import_zod.z.object({
|
|
|
976
1003
|
/** Default display mode (`inline` | `fullscreen` | `pip`). */
|
|
977
1004
|
default_display_mode: import_zod.z.string().default(DEFAULT_WIDGET_DISPLAY_MODE)
|
|
978
1005
|
});
|
|
1006
|
+
var ChatWidgetTemplateSchema = import_zod.z.object({
|
|
1007
|
+
id: import_zod.z.string().regex(/^[a-z0-9][a-z0-9._:-]*$/),
|
|
1008
|
+
title: import_zod.z.string().min(1),
|
|
1009
|
+
description: import_zod.z.string().optional(),
|
|
1010
|
+
triggers: import_zod.z.array(import_zod.z.string()).default([]),
|
|
1011
|
+
examples: import_zod.z.array(import_zod.z.string()).default([]),
|
|
1012
|
+
backing: import_zod.z.object({
|
|
1013
|
+
tool_id: import_zod.z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:/-]*$/).optional(),
|
|
1014
|
+
view_id: import_zod.z.string().regex(/^[a-z0-9][a-z0-9._:-]*$/).optional()
|
|
1015
|
+
}),
|
|
1016
|
+
display_mode: import_zod.z.string().min(1),
|
|
1017
|
+
safe_action_ids: import_zod.z.array(import_zod.z.string().regex(/^[a-z0-9][a-z0-9._-]*$/)).default([]),
|
|
1018
|
+
availability: import_zod.z.string().default("available")
|
|
1019
|
+
}).superRefine((value, ctx) => {
|
|
1020
|
+
const count = Number(Boolean(value.backing.tool_id)) + Number(Boolean(value.backing.view_id));
|
|
1021
|
+
if (count !== 1 && value.availability === "available") {
|
|
1022
|
+
ctx.addIssue({ code: "custom", path: ["backing"], message: "available templates need exactly one backing tool_id or view_id" });
|
|
1023
|
+
}
|
|
1024
|
+
if (count > 1) {
|
|
1025
|
+
ctx.addIssue({ code: "custom", path: ["backing"], message: "backing must declare at most one of tool_id or view_id" });
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
979
1028
|
var ToolAppConfigSchema = import_zod.z.object({
|
|
980
|
-
/** MCP tool slug this runnable wraps — the fully-qualified `<server
|
|
1029
|
+
/** MCP tool slug this runnable wraps — the fully-qualified `<server>.<name>` id. */
|
|
981
1030
|
slug: import_zod.z.string().min(1),
|
|
982
1031
|
/** The tool description the model reads when choosing it. Carried here because a
|
|
983
1032
|
* packed app's manifest is the only channel (there is no `generated.rs`); Core's
|
|
@@ -1004,6 +1053,11 @@ var ContributesSchema = import_zod.z.object({
|
|
|
1004
1053
|
* signing, leaving an app that emits events nothing is allowed to subscribe to. */
|
|
1005
1054
|
hook_events: import_zod.z.array(HookEventContributionSchema).default([]),
|
|
1006
1055
|
composer_controls: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
|
|
1056
|
+
/** Chat feature descriptors whose behavior is implemented by the host shell.
|
|
1057
|
+
* Mirrors the Rust-side `Contributes.chat_features`; keeping this field in the
|
|
1058
|
+
* authoring schema prevents `ryu pack` from silently deleting a plugin's chat
|
|
1059
|
+
* feature declaration before signing. */
|
|
1060
|
+
chat_features: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
|
|
1007
1061
|
settings_tabs: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
|
|
1008
1062
|
slash_commands: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
|
|
1009
1063
|
/** App widgets (Ryu Apps). Each binds a render tool id to its
|
|
@@ -1011,6 +1065,8 @@ var ContributesSchema = import_zod.z.object({
|
|
|
1011
1065
|
* `Contributes.widgets` field, without which the CLI's zod parse would strip
|
|
1012
1066
|
* every widget an app authored here declares. */
|
|
1013
1067
|
widgets: import_zod.z.array(WidgetContributionSchema).default([]),
|
|
1068
|
+
/** Metadata-only chat widget templates. */
|
|
1069
|
+
chat_widget_templates: import_zod.z.array(ChatWidgetTemplateSchema).optional(),
|
|
1014
1070
|
/** App-registered sidebar sections (header + live list) and buttons (single nav
|
|
1015
1071
|
* rows). Loosely typed here — the shell owns the spec vocabulary — matching how
|
|
1016
1072
|
* `composer_controls`/`settings_tabs` are declared. Mirrors the Rust-side
|
|
@@ -1023,6 +1079,13 @@ var ContributesSchema = import_zod.z.object({
|
|
|
1023
1079
|
* Rust-side `Contributes.dock_panels`; without it the CLI's zod parse would
|
|
1024
1080
|
* strip the dock panel an app declares here. */
|
|
1025
1081
|
dock_panels: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
|
|
1082
|
+
/** App-registered live activities (the desktop "Dynamic Island" cards). Loosely
|
|
1083
|
+
* typed for the same reason as the surfaces above — the shell owns the
|
|
1084
|
+
* `spec` vocabulary. Mirrors the Rust-side `Contributes.live_activities`;
|
|
1085
|
+
* without it the CLI's zod parse would strip every live activity an app
|
|
1086
|
+
* declares here, so a packed bundle would ship a dock that silently stays
|
|
1087
|
+
* empty. */
|
|
1088
|
+
live_activities: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
|
|
1026
1089
|
/** Deletable data categories the app owns — one "Delete all X" row in Settings
|
|
1027
1090
|
* → Danger Zone. Mirrors the Rust-side `Contributes.data_categories`; without
|
|
1028
1091
|
* it the CLI's zod parse would strip the declaration before signing, and the
|
|
@@ -1060,7 +1123,10 @@ var ContributesSchema = import_zod.z.object({
|
|
|
1060
1123
|
* bug: the styles' `.md` files are not carried by the bundle either, so there
|
|
1061
1124
|
* would be no residue to notice — the plugin would install clean and contribute
|
|
1062
1125
|
* nothing. */
|
|
1063
|
-
output_styles: import_zod.z.array(OutputStyleContributionSchema).default([])
|
|
1126
|
+
output_styles: import_zod.z.array(OutputStyleContributionSchema).default([]),
|
|
1127
|
+
/** Per-message actions contributed by an enabled plugin. Kept as loose records
|
|
1128
|
+
* so renderer-specific `kind`/`args` payloads survive `ryu pack` unchanged. */
|
|
1129
|
+
message_actions: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([])
|
|
1064
1130
|
});
|
|
1065
1131
|
var SetupStepSchema = import_zod.z.object({
|
|
1066
1132
|
/** Card heading (e.g. the companion app name). */
|
|
@@ -1141,6 +1207,61 @@ var EnginesReqSchema = import_zod.z.object({
|
|
|
1141
1207
|
/** Floor for the Next.js web app. */
|
|
1142
1208
|
web: import_zod.z.string().optional()
|
|
1143
1209
|
});
|
|
1210
|
+
var McpServerAuthSchema = import_zod.z.object({
|
|
1211
|
+
client_id: import_zod.z.string().min(1).optional(),
|
|
1212
|
+
type: import_zod.z.literal("oauth")
|
|
1213
|
+
}).strict();
|
|
1214
|
+
var McpServerDeclSchema = import_zod.z.object({
|
|
1215
|
+
args: import_zod.z.array(import_zod.z.string()).default([]),
|
|
1216
|
+
auth: McpServerAuthSchema.optional(),
|
|
1217
|
+
command: import_zod.z.string().optional(),
|
|
1218
|
+
command_env: import_zod.z.string().optional(),
|
|
1219
|
+
description: import_zod.z.string().optional(),
|
|
1220
|
+
enabled: import_zod.z.boolean().default(true),
|
|
1221
|
+
env: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).default({}),
|
|
1222
|
+
headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).default({}),
|
|
1223
|
+
type: import_zod.z.enum(["stdio", "http", "streamable-http", "streamable_http", "sse"]).optional(),
|
|
1224
|
+
url: import_zod.z.url().optional()
|
|
1225
|
+
}).superRefine((server, context) => {
|
|
1226
|
+
if (!(server.command || server.url)) {
|
|
1227
|
+
context.addIssue({
|
|
1228
|
+
code: "custom",
|
|
1229
|
+
message: "an MCP server requires command or url"
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
if (!server.auth) {
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
if (server.command || server.type === "stdio" || !server.url) {
|
|
1236
|
+
context.addIssue({
|
|
1237
|
+
code: "custom",
|
|
1238
|
+
message: "OAuth is supported only for remote HTTP MCP servers"
|
|
1239
|
+
});
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
if (Object.keys(server.headers).some(
|
|
1243
|
+
(name) => name.toLowerCase() === "authorization"
|
|
1244
|
+
)) {
|
|
1245
|
+
context.addIssue({
|
|
1246
|
+
code: "custom",
|
|
1247
|
+
message: "OAuth cannot be combined with a static Authorization header"
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
const url = new URL(server.url);
|
|
1251
|
+
const loopback = url.hostname === "localhost" || url.hostname.startsWith("127.") || url.hostname === "[::1]" || url.hostname === "::1";
|
|
1252
|
+
if (url.username || url.password || url.hash) {
|
|
1253
|
+
context.addIssue({
|
|
1254
|
+
code: "custom",
|
|
1255
|
+
message: "OAuth MCP URLs cannot contain credentials or fragments"
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
|
|
1259
|
+
context.addIssue({
|
|
1260
|
+
code: "custom",
|
|
1261
|
+
message: "OAuth MCP URLs must use HTTPS except on loopback"
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1144
1265
|
var PluginManifestSchema = import_zod.z.object({
|
|
1145
1266
|
/** Reverse-domain unique identifier (e.g. `"com.example.my-plugin"`). */
|
|
1146
1267
|
id: import_zod.z.string().min(1, "id is required"),
|
|
@@ -1172,6 +1293,8 @@ var PluginManifestSchema = import_zod.z.object({
|
|
|
1172
1293
|
* Declarations only — grant enforcement is the Gateway's responsibility.
|
|
1173
1294
|
*/
|
|
1174
1295
|
permission_grants: import_zod.z.array(import_zod.z.string()).default([]),
|
|
1296
|
+
/** Remote or stdio MCP servers registered by this plugin. */
|
|
1297
|
+
mcp_servers: import_zod.z.record(import_zod.z.string(), McpServerDeclSchema).optional(),
|
|
1175
1298
|
/**
|
|
1176
1299
|
* Optional Companion surface (an in-desktop overlay or sidebar panel).
|
|
1177
1300
|
* Absent when the plugin has no Companion surface.
|
|
@@ -1265,10 +1388,16 @@ var PluginManifestSchema = import_zod.z.object({
|
|
|
1265
1388
|
url: import_zod.z.string().optional()
|
|
1266
1389
|
})
|
|
1267
1390
|
]).optional(),
|
|
1391
|
+
/** Public source repository URL (Claude/Codex `repository`). */
|
|
1392
|
+
repository: import_zod.z.string().url().optional(),
|
|
1393
|
+
/** True when the provider operates outside the local Ryu runtime. */
|
|
1394
|
+
external: import_zod.z.boolean().optional(),
|
|
1268
1395
|
/** Project/marketing homepage — maps to the listing `website` (Claude field). */
|
|
1269
1396
|
homepage: import_zod.z.string().optional(),
|
|
1270
1397
|
/** Free-text search keywords (Claude field). */
|
|
1271
1398
|
keywords: import_zod.z.array(import_zod.z.string()).optional(),
|
|
1399
|
+
/** Stable Marketplace filter labels (Ryu extension). */
|
|
1400
|
+
tags: import_zod.z.array(import_zod.z.string()).optional(),
|
|
1272
1401
|
/** Taxonomy category beyond the runnable kinds (Claude field). */
|
|
1273
1402
|
category: import_zod.z.string().optional(),
|
|
1274
1403
|
/** SPDX-ish license identifier (Claude field). */
|
|
@@ -1282,6 +1411,8 @@ var PluginManifestSchema = import_zod.z.object({
|
|
|
1282
1411
|
* from `iconUrl` (a raster logo). Falls back to `iconUrl` when omitted.
|
|
1283
1412
|
*/
|
|
1284
1413
|
icon: import_zod.z.string().optional(),
|
|
1414
|
+
/** Optional detail-page hero banner metadata forwarded to the marketplace. */
|
|
1415
|
+
banner: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
|
|
1285
1416
|
/**
|
|
1286
1417
|
* Dithered-gradient background for the card's icon square (Ryu extension),
|
|
1287
1418
|
* mirroring dither-kit's `DitherGradient` props. `from`/`to` are a palette-colour
|
|
@@ -1313,6 +1444,22 @@ var PluginManifestSchema = import_zod.z.object({
|
|
|
1313
1444
|
* array of steps guiding the user through post-install configuration.
|
|
1314
1445
|
*/
|
|
1315
1446
|
setup: import_zod.z.union([SetupStepSchema, import_zod.z.array(SetupStepSchema)]).optional()
|
|
1447
|
+
}).superRefine((manifest, context) => {
|
|
1448
|
+
const hasOAuthServer = Object.values(manifest.mcp_servers ?? {}).some(
|
|
1449
|
+
(server) => server.auth?.type === "oauth"
|
|
1450
|
+
);
|
|
1451
|
+
if (!hasOAuthServer) {
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
for (const grant of ["mcp:server", "identity.read"]) {
|
|
1455
|
+
if (!manifest.permission_grants.includes(grant)) {
|
|
1456
|
+
context.addIssue({
|
|
1457
|
+
code: "custom",
|
|
1458
|
+
message: `OAuth MCP servers require the ${grant} permission grant`,
|
|
1459
|
+
path: ["permission_grants"]
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1316
1463
|
});
|
|
1317
1464
|
function validatePluginId(id) {
|
|
1318
1465
|
loadNative().validatePluginId(id);
|
|
@@ -1336,7 +1483,7 @@ function withWidgetRenderGrant(grants, widgets) {
|
|
|
1336
1483
|
return out;
|
|
1337
1484
|
}
|
|
1338
1485
|
function appToolId(server, name) {
|
|
1339
|
-
return `${server}
|
|
1486
|
+
return `${server}.${name}`;
|
|
1340
1487
|
}
|
|
1341
1488
|
function defineApp(options) {
|
|
1342
1489
|
const server = options.server ?? options.slug;
|
|
@@ -1376,6 +1523,7 @@ function defineApp(options) {
|
|
|
1376
1523
|
}
|
|
1377
1524
|
const contributes = {
|
|
1378
1525
|
turn_hooks: [],
|
|
1526
|
+
chat_features: [],
|
|
1379
1527
|
// This builder synthesises an app from its runnables; an app that emits
|
|
1380
1528
|
// events declares them in a hand-authored `manifest.json`, same as
|
|
1381
1529
|
// `lsp_servers` below.
|
|
@@ -1386,6 +1534,7 @@ function defineApp(options) {
|
|
|
1386
1534
|
sidebar_sections: [],
|
|
1387
1535
|
sidebar_buttons: [],
|
|
1388
1536
|
dock_panels: [],
|
|
1537
|
+
live_activities: [],
|
|
1389
1538
|
// Empty for the same reason as every sibling family above: this builder
|
|
1390
1539
|
// synthesises `widgets` from the app's own runnables and nothing else, and
|
|
1391
1540
|
// takes no `contributes` passthrough. An app that wants to declare language
|
|
@@ -1398,6 +1547,7 @@ function defineApp(options) {
|
|
|
1398
1547
|
data_categories: [],
|
|
1399
1548
|
pi_extensions: [],
|
|
1400
1549
|
output_styles: [],
|
|
1550
|
+
message_actions: [],
|
|
1401
1551
|
widgets
|
|
1402
1552
|
};
|
|
1403
1553
|
const raw = {
|
|
@@ -1781,6 +1931,359 @@ var AppBuilder = class {
|
|
|
1781
1931
|
};
|
|
1782
1932
|
var app = () => new AppBuilder();
|
|
1783
1933
|
|
|
1934
|
+
// src/mcp/client.ts
|
|
1935
|
+
var import_node_child_process = require("child_process");
|
|
1936
|
+
var import_node_readline = require("readline");
|
|
1937
|
+
var MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
1938
|
+
var RPC_TIMEOUT_MS = 6e4;
|
|
1939
|
+
var McpConnection = class _McpConnection {
|
|
1940
|
+
proc;
|
|
1941
|
+
nextId = 1;
|
|
1942
|
+
closed = false;
|
|
1943
|
+
waiters = [];
|
|
1944
|
+
constructor(proc) {
|
|
1945
|
+
this.proc = proc;
|
|
1946
|
+
}
|
|
1947
|
+
/** Spawn the server and complete the MCP `initialize` handshake. */
|
|
1948
|
+
static async connect(cmd) {
|
|
1949
|
+
const env2 = { ...process.env, ...cmd.env ?? {} };
|
|
1950
|
+
const proc = (0, import_node_child_process.spawn)(cmd.command, cmd.args ?? [], {
|
|
1951
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1952
|
+
env: env2
|
|
1953
|
+
});
|
|
1954
|
+
if (!(proc.stdin && proc.stdout)) {
|
|
1955
|
+
proc.kill();
|
|
1956
|
+
throw new Error(`MCP server '${cmd.command}' stdin/stdout unavailable`);
|
|
1957
|
+
}
|
|
1958
|
+
if (proc.stderr) {
|
|
1959
|
+
proc.stderr.on("data", (chunk) => {
|
|
1960
|
+
process.stderr.write(`[mcp-server] ${chunk.toString()}`);
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
const conn = new _McpConnection(proc);
|
|
1964
|
+
const rl = (0, import_node_readline.createInterface)({
|
|
1965
|
+
input: proc.stdout,
|
|
1966
|
+
crlfDelay: Number.POSITIVE_INFINITY
|
|
1967
|
+
});
|
|
1968
|
+
rl.on("line", (rawLine) => {
|
|
1969
|
+
const trimmed = rawLine.trim();
|
|
1970
|
+
if (!trimmed) {
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
let parsed;
|
|
1974
|
+
try {
|
|
1975
|
+
parsed = JSON.parse(trimmed);
|
|
1976
|
+
} catch {
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
if (parsed.id === void 0 || parsed.id === null) {
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
const idx = conn.waiters.findIndex((w) => w.id === parsed.id);
|
|
1983
|
+
if (idx === -1) {
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
const [waiter] = conn.waiters.splice(idx, 1);
|
|
1987
|
+
if (!waiter) {
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
clearTimeout(waiter.timer);
|
|
1991
|
+
if (parsed.error) {
|
|
1992
|
+
waiter.reject(new Error(`MCP error: ${JSON.stringify(parsed.error)}`));
|
|
1993
|
+
} else {
|
|
1994
|
+
waiter.resolve(parsed.result ?? null);
|
|
1995
|
+
}
|
|
1996
|
+
});
|
|
1997
|
+
rl.on("close", () => {
|
|
1998
|
+
for (const waiter of conn.waiters.splice(0)) {
|
|
1999
|
+
clearTimeout(waiter.timer);
|
|
2000
|
+
waiter.reject(new Error("MCP server closed the connection"));
|
|
2001
|
+
}
|
|
2002
|
+
});
|
|
2003
|
+
await conn.request("initialize", {
|
|
2004
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
2005
|
+
capabilities: {},
|
|
2006
|
+
clientInfo: { name: "ryu-sdk", version: "0.0.1" }
|
|
2007
|
+
});
|
|
2008
|
+
conn.notify("notifications/initialized", {});
|
|
2009
|
+
return conn;
|
|
2010
|
+
}
|
|
2011
|
+
/** Send a JSON-RPC request and return the `result` field. */
|
|
2012
|
+
request(method, params) {
|
|
2013
|
+
const id = this.nextId++;
|
|
2014
|
+
const frame = JSON.stringify({
|
|
2015
|
+
jsonrpc: "2.0",
|
|
2016
|
+
id,
|
|
2017
|
+
method,
|
|
2018
|
+
params
|
|
2019
|
+
});
|
|
2020
|
+
this.write(frame);
|
|
2021
|
+
return new Promise((resolve, reject) => {
|
|
2022
|
+
const timer = setTimeout(
|
|
2023
|
+
() => reject(new Error(`MCP request '${method}' timed out`)),
|
|
2024
|
+
RPC_TIMEOUT_MS
|
|
2025
|
+
);
|
|
2026
|
+
this.waiters.push({ id, resolve, reject, timer });
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
/** Send a JSON-RPC notification (no response expected). */
|
|
2030
|
+
notify(method, params) {
|
|
2031
|
+
const frame = JSON.stringify({ jsonrpc: "2.0", method, params });
|
|
2032
|
+
this.write(frame);
|
|
2033
|
+
}
|
|
2034
|
+
write(frame) {
|
|
2035
|
+
if (this.closed) {
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
this.proc.stdin?.write(`${frame}
|
|
2039
|
+
`);
|
|
2040
|
+
}
|
|
2041
|
+
/** Graceful shutdown: close stdin, then kill. */
|
|
2042
|
+
async shutdown() {
|
|
2043
|
+
this.closed = true;
|
|
2044
|
+
this.proc.stdin?.end();
|
|
2045
|
+
await new Promise((resolve) => {
|
|
2046
|
+
this.proc.once("exit", () => resolve());
|
|
2047
|
+
this.proc.kill();
|
|
2048
|
+
setTimeout(resolve, 500);
|
|
2049
|
+
});
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
2052
|
+
async function listTools(cmd) {
|
|
2053
|
+
const conn = await McpConnection.connect(cmd);
|
|
2054
|
+
let result;
|
|
2055
|
+
try {
|
|
2056
|
+
result = await conn.request("tools/list", {});
|
|
2057
|
+
} finally {
|
|
2058
|
+
await conn.shutdown();
|
|
2059
|
+
}
|
|
2060
|
+
const tools = result?.tools ?? [];
|
|
2061
|
+
return tools.map((t) => {
|
|
2062
|
+
const tool2 = t;
|
|
2063
|
+
const name = tool2.name;
|
|
2064
|
+
if (typeof name !== "string") {
|
|
2065
|
+
return null;
|
|
2066
|
+
}
|
|
2067
|
+
return {
|
|
2068
|
+
name,
|
|
2069
|
+
description: typeof tool2.description === "string" ? tool2.description : void 0,
|
|
2070
|
+
inputSchema: tool2.inputSchema
|
|
2071
|
+
};
|
|
2072
|
+
}).filter((t) => t !== null);
|
|
2073
|
+
}
|
|
2074
|
+
async function callTool(cmd, tool2, args) {
|
|
2075
|
+
const conn = await McpConnection.connect(cmd);
|
|
2076
|
+
try {
|
|
2077
|
+
return await conn.request("tools/call", { name: tool2, arguments: args });
|
|
2078
|
+
} finally {
|
|
2079
|
+
await conn.shutdown();
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// src/mcp/server.ts
|
|
2084
|
+
var import_node_readline2 = require("readline");
|
|
2085
|
+
function respond(id, result) {
|
|
2086
|
+
return { jsonrpc: "2.0", id: id ?? null, result };
|
|
2087
|
+
}
|
|
2088
|
+
function respondError(id, code, message) {
|
|
2089
|
+
return { jsonrpc: "2.0", id: id ?? null, error: { code, message } };
|
|
2090
|
+
}
|
|
2091
|
+
function wrapContent(value) {
|
|
2092
|
+
return {
|
|
2093
|
+
content: [
|
|
2094
|
+
{
|
|
2095
|
+
type: "text",
|
|
2096
|
+
text: typeof value === "string" ? value : JSON.stringify(value)
|
|
2097
|
+
}
|
|
2098
|
+
]
|
|
2099
|
+
};
|
|
2100
|
+
}
|
|
2101
|
+
function unwrapContent(raw) {
|
|
2102
|
+
const r = raw;
|
|
2103
|
+
const text = r?.content?.[0]?.text;
|
|
2104
|
+
if (text === void 0) {
|
|
2105
|
+
return raw;
|
|
2106
|
+
}
|
|
2107
|
+
try {
|
|
2108
|
+
return JSON.parse(text);
|
|
2109
|
+
} catch {
|
|
2110
|
+
return text;
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
var McpServer = class {
|
|
2114
|
+
runnables = /* @__PURE__ */ new Map();
|
|
2115
|
+
passthroughs = [];
|
|
2116
|
+
/**
|
|
2117
|
+
* Register an SDK Runnable as an MCP tool.
|
|
2118
|
+
* Returns `this` for chaining.
|
|
2119
|
+
*/
|
|
2120
|
+
register(runnable) {
|
|
2121
|
+
this.runnables.set(runnable.name, runnable);
|
|
2122
|
+
return this;
|
|
2123
|
+
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Register a passthrough to an external MCP stdio server (e.g. Ghost or
|
|
2126
|
+
* Shadow). Tools from that server are fetched lazily and re-advertised.
|
|
2127
|
+
* Returns `this` for chaining.
|
|
2128
|
+
*/
|
|
2129
|
+
passthrough(registration) {
|
|
2130
|
+
this.passthroughs.push(registration);
|
|
2131
|
+
return this;
|
|
2132
|
+
}
|
|
2133
|
+
/** Fetch all tools: local Runnables + passthrough tools. */
|
|
2134
|
+
async allTools() {
|
|
2135
|
+
const local = [...this.runnables.values()].map((r) => ({
|
|
2136
|
+
name: r.name,
|
|
2137
|
+
description: r.description,
|
|
2138
|
+
inputSchema: r.inputSchema
|
|
2139
|
+
}));
|
|
2140
|
+
const remote = [];
|
|
2141
|
+
for (const pt of this.passthroughs) {
|
|
2142
|
+
try {
|
|
2143
|
+
const tools = await listTools(pt.command);
|
|
2144
|
+
remote.push(...tools);
|
|
2145
|
+
} catch (err) {
|
|
2146
|
+
process.stderr.write(
|
|
2147
|
+
`[mcp-server] passthrough '${pt.label}' list_tools failed: ${err}
|
|
2148
|
+
`
|
|
2149
|
+
);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
return [...local, ...remote];
|
|
2153
|
+
}
|
|
2154
|
+
/** Handle a `tools/call` request. */
|
|
2155
|
+
async handleCallTool(name, args) {
|
|
2156
|
+
const local = this.runnables.get(name);
|
|
2157
|
+
if (local) {
|
|
2158
|
+
const result = await local.run(args);
|
|
2159
|
+
return wrapContent(result);
|
|
2160
|
+
}
|
|
2161
|
+
for (const pt of this.passthroughs) {
|
|
2162
|
+
try {
|
|
2163
|
+
const tools = await listTools(pt.command);
|
|
2164
|
+
if (tools.some((t) => t.name === name)) {
|
|
2165
|
+
return await callTool(pt.command, name, args);
|
|
2166
|
+
}
|
|
2167
|
+
} catch {
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
2171
|
+
}
|
|
2172
|
+
/**
|
|
2173
|
+
* Handle a single parsed JSON-RPC request line. Returns the response to write
|
|
2174
|
+
* (or null for notifications that require no response).
|
|
2175
|
+
*/
|
|
2176
|
+
async handleRequest(req, initialized, write) {
|
|
2177
|
+
const { id, method, params } = req;
|
|
2178
|
+
if (method === "initialize") {
|
|
2179
|
+
initialized.value = true;
|
|
2180
|
+
write(
|
|
2181
|
+
respond(id, {
|
|
2182
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
2183
|
+
capabilities: { tools: {} },
|
|
2184
|
+
serverInfo: { name: "ryu-sdk-server", version: "0.0.1" }
|
|
2185
|
+
})
|
|
2186
|
+
);
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
if (method === "notifications/initialized") {
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
if (!initialized.value) {
|
|
2193
|
+
write(respondError(id, -32002, "Server not initialized"));
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
if (method === "tools/list") {
|
|
2197
|
+
try {
|
|
2198
|
+
const tools = await this.allTools();
|
|
2199
|
+
write(respond(id, { tools }));
|
|
2200
|
+
} catch (err) {
|
|
2201
|
+
write(respondError(id, -32603, String(err)));
|
|
2202
|
+
}
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2205
|
+
if (method === "tools/call") {
|
|
2206
|
+
await this.handleToolsCall(id, params, write);
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2209
|
+
write(respondError(id, -32601, `Method not found: ${method}`));
|
|
2210
|
+
}
|
|
2211
|
+
/** Handle a `tools/call` JSON-RPC request. */
|
|
2212
|
+
async handleToolsCall(id, params, write) {
|
|
2213
|
+
const p = params;
|
|
2214
|
+
const toolName = p?.name;
|
|
2215
|
+
const toolArgs = p?.arguments ?? {};
|
|
2216
|
+
if (typeof toolName !== "string") {
|
|
2217
|
+
write(respondError(id, -32602, "tools/call requires 'name'"));
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
try {
|
|
2221
|
+
const result = await this.handleCallTool(toolName, toolArgs);
|
|
2222
|
+
write(respond(id, result));
|
|
2223
|
+
} catch (err) {
|
|
2224
|
+
write(
|
|
2225
|
+
respond(id, {
|
|
2226
|
+
content: [{ type: "text", text: String(err) }],
|
|
2227
|
+
isError: true
|
|
2228
|
+
})
|
|
2229
|
+
);
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
/**
|
|
2233
|
+
* Start reading JSON-RPC requests from the given readable stream and writing
|
|
2234
|
+
* responses to the given writable stream.
|
|
2235
|
+
*
|
|
2236
|
+
* Defaults to `process.stdin` / `process.stdout`. Passing explicit streams
|
|
2237
|
+
* lets tests inject a pair of in-process pipes.
|
|
2238
|
+
*
|
|
2239
|
+
* Resolves when the input stream ends (EOF).
|
|
2240
|
+
*/
|
|
2241
|
+
serve(input = process.stdin, output = process.stdout) {
|
|
2242
|
+
const write = (obj) => {
|
|
2243
|
+
output.write(`${JSON.stringify(obj)}
|
|
2244
|
+
`);
|
|
2245
|
+
};
|
|
2246
|
+
const initialized = { value: false };
|
|
2247
|
+
return new Promise((resolve) => {
|
|
2248
|
+
const rl = (0, import_node_readline2.createInterface)({
|
|
2249
|
+
input,
|
|
2250
|
+
crlfDelay: Number.POSITIVE_INFINITY
|
|
2251
|
+
});
|
|
2252
|
+
rl.on("line", (rawLine) => {
|
|
2253
|
+
const line = rawLine.trim();
|
|
2254
|
+
if (!line) {
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
let req;
|
|
2258
|
+
try {
|
|
2259
|
+
req = JSON.parse(line);
|
|
2260
|
+
} catch {
|
|
2261
|
+
write(respondError(null, -32700, "Parse error"));
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
this.handleRequest(req, initialized, write).catch((err) => {
|
|
2265
|
+
write(respondError(req.id, -32603, String(err)));
|
|
2266
|
+
});
|
|
2267
|
+
});
|
|
2268
|
+
rl.on("close", resolve);
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
};
|
|
2272
|
+
|
|
2273
|
+
// src/plugin/ryu-plugin.ts
|
|
2274
|
+
function toDisposable(teardown) {
|
|
2275
|
+
let done = false;
|
|
2276
|
+
return {
|
|
2277
|
+
dispose() {
|
|
2278
|
+
if (done) {
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
done = true;
|
|
2282
|
+
teardown();
|
|
2283
|
+
}
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
|
|
1784
2287
|
// src/runnable/agent.ts
|
|
1785
2288
|
var CAPABILITY_SLOTS = ["rag", "memory", "tts", "stt"];
|
|
1786
2289
|
function lowerChatSlot(chat) {
|
|
@@ -2014,6 +2517,7 @@ function definePlugin(options) {
|
|
|
2014
2517
|
turn_hooks: options.turnHooks ?? [],
|
|
2015
2518
|
hook_events: options.hookEvents ?? [],
|
|
2016
2519
|
composer_controls: options.composerControls ?? [],
|
|
2520
|
+
chat_features: [],
|
|
2017
2521
|
settings_tabs: options.settingsTabs ?? [],
|
|
2018
2522
|
slash_commands: options.slashCommands ?? [],
|
|
2019
2523
|
lsp_servers: options.lspServers ?? {},
|
|
@@ -2025,9 +2529,11 @@ function definePlugin(options) {
|
|
|
2025
2529
|
sidebar_sections: [],
|
|
2026
2530
|
sidebar_buttons: [],
|
|
2027
2531
|
dock_panels: [],
|
|
2532
|
+
live_activities: [],
|
|
2028
2533
|
data_categories: [],
|
|
2029
2534
|
pi_extensions: [],
|
|
2030
|
-
output_styles: []
|
|
2535
|
+
output_styles: [],
|
|
2536
|
+
message_actions: []
|
|
2031
2537
|
};
|
|
2032
2538
|
const tools = options.tools ?? [];
|
|
2033
2539
|
const runnables = tools.map((t) => inlineToolRunnable(t));
|
|
@@ -2076,6 +2582,10 @@ function defineWorkflow(options) {
|
|
|
2076
2582
|
CapabilityReqSchema,
|
|
2077
2583
|
CompanionSurfaceSchema,
|
|
2078
2584
|
DEFAULT_GATEWAY_URL,
|
|
2585
|
+
MCP_PROTOCOL_VERSION,
|
|
2586
|
+
McpServer,
|
|
2587
|
+
McpServerAuthSchema,
|
|
2588
|
+
McpServerDeclSchema,
|
|
2079
2589
|
ModelClient,
|
|
2080
2590
|
PRIMITIVE_BINDINGS,
|
|
2081
2591
|
PluginBuilder,
|
|
@@ -2093,6 +2603,7 @@ function defineWorkflow(options) {
|
|
|
2093
2603
|
app,
|
|
2094
2604
|
appToolId,
|
|
2095
2605
|
assertAllowedEgressUrl,
|
|
2606
|
+
callTool,
|
|
2096
2607
|
coreManifestJsonSchema,
|
|
2097
2608
|
createAgent,
|
|
2098
2609
|
createPrimitives,
|
|
@@ -2106,12 +2617,15 @@ function defineWorkflow(options) {
|
|
|
2106
2617
|
defineWorkflow,
|
|
2107
2618
|
httpPrimitiveTransport,
|
|
2108
2619
|
inlineToolRunnable,
|
|
2620
|
+
listTools,
|
|
2109
2621
|
query,
|
|
2110
2622
|
resolveGatewayToken,
|
|
2111
2623
|
resolveGatewayUrl,
|
|
2112
2624
|
ryuTool,
|
|
2113
2625
|
skill,
|
|
2626
|
+
toDisposable,
|
|
2114
2627
|
tool,
|
|
2628
|
+
unwrapContent,
|
|
2115
2629
|
validateManifestStrict,
|
|
2116
2630
|
validatePluginId,
|
|
2117
2631
|
workflow
|