@ryuhq/sdk 0.1.14 → 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.
Files changed (90) hide show
  1. package/README.md +19 -6
  2. package/dist/agent-plugin.cjs +46 -0
  3. package/dist/agent-plugin.d.cts +42 -32
  4. package/dist/agent-plugin.d.ts +42 -32
  5. package/dist/agent-plugin.js +1 -1
  6. package/dist/agent.cjs +9 -0
  7. package/dist/agent.d.cts +377 -1
  8. package/dist/agent.d.ts +377 -1
  9. package/dist/agent.js +4 -2
  10. package/dist/app-Bkw7LlCK.d.ts +129 -0
  11. package/dist/app-DNaGmLVf.d.cts +129 -0
  12. package/dist/builder.cjs +1095 -0
  13. package/dist/builder.d.cts +212 -0
  14. package/dist/builder.d.ts +212 -0
  15. package/dist/builder.js +28 -0
  16. package/dist/chunk-A3RGEPDG.js +250 -0
  17. package/dist/chunk-FZSFZOIN.js +200 -0
  18. package/dist/{chunk-ODFEUVPW.js → chunk-HACAGK65.js} +1 -18
  19. package/dist/{chunk-AO2KJRDD.js → chunk-IEUQ3CDG.js} +125 -2
  20. package/dist/chunk-IKEDLLFY.js +19 -0
  21. package/dist/chunk-IOLP5FFE.js +354 -0
  22. package/dist/{chunk-G6FLVEC4.js → chunk-JX6DDRXV.js} +46 -0
  23. package/dist/chunk-T5676WL2.js +240 -0
  24. package/dist/chunk-TLDPEGC7.js +21 -0
  25. package/dist/chunk-TXSHHZF2.js +0 -0
  26. package/dist/{chunk-MTUBUPIV.js → chunk-ULSVL7EC.js} +8 -227
  27. package/dist/chunk-VLIRNNAE.js +154 -0
  28. package/dist/chunk-W3KPP4WN.js +135 -0
  29. package/dist/cli.cjs +258 -14
  30. package/dist/cli.js +96 -16
  31. package/dist/client-D5U6ssPc.d.cts +84 -0
  32. package/dist/client-D5U6ssPc.d.ts +84 -0
  33. package/dist/index.cjs +519 -5
  34. package/dist/index.d.cts +16 -632
  35. package/dist/index.d.ts +16 -632
  36. package/dist/index.js +63 -695
  37. package/dist/manifest.cjs +129 -3
  38. package/dist/manifest.d.cts +105 -2
  39. package/dist/manifest.d.ts +105 -2
  40. package/dist/manifest.js +7 -1
  41. package/dist/mcp/client.cjs +180 -0
  42. package/dist/mcp/client.d.cts +49 -0
  43. package/dist/mcp/client.d.ts +49 -0
  44. package/dist/mcp/client.js +10 -0
  45. package/dist/mcp/server.cjs +370 -0
  46. package/dist/mcp/server.d.cts +126 -0
  47. package/dist/mcp/server.d.ts +126 -0
  48. package/dist/mcp/server.js +9 -0
  49. package/dist/mcp.cjs +376 -0
  50. package/dist/mcp.d.cts +2 -0
  51. package/dist/mcp.d.ts +2 -0
  52. package/dist/mcp.js +17 -0
  53. package/dist/model.cjs +141 -0
  54. package/dist/model.d.cts +33 -0
  55. package/dist/model.d.ts +33 -0
  56. package/dist/model.js +18 -0
  57. package/dist/plugin.cjs +46 -0
  58. package/dist/plugin.d.cts +215 -0
  59. package/dist/plugin.d.ts +215 -0
  60. package/dist/plugin.js +8 -0
  61. package/dist/runnable.cjs +1230 -0
  62. package/dist/runnable.d.cts +271 -0
  63. package/dist/runnable.d.ts +271 -0
  64. package/dist/runnable.js +28 -0
  65. package/dist/{index-B6SkaAjJ.d.ts → tool-DSx2bFx8.d.ts} +35 -458
  66. package/dist/{index-BvAB5eMk.d.cts → tool-u-VR0fLF.d.cts} +35 -458
  67. package/package.json +38 -2
  68. package/src/agent/loop.test.ts +4 -4
  69. package/src/agent/tools.ts +3 -3
  70. package/src/agent-plugin.test.ts +58 -0
  71. package/src/agent-plugin.ts +108 -35
  72. package/src/cli/dev.test.ts +26 -47
  73. package/src/cli/dev.ts +10 -2
  74. package/src/cli.ts +98 -15
  75. package/src/exports-lockstep.test.ts +92 -0
  76. package/src/generated/plugin-manifest.ts +243 -27
  77. package/src/index.ts +35 -0
  78. package/src/manifest-schema.test.ts +30 -1
  79. package/src/manifest.fixtures.test.ts +13 -3
  80. package/src/manifest.test.ts +96 -10
  81. package/src/manifest.ts +334 -187
  82. package/src/mcp/index.ts +18 -0
  83. package/src/model/index.ts +22 -0
  84. package/src/plugin/ryu-plugin.ts +82 -0
  85. package/src/runnable/app.test.ts +2 -0
  86. package/src/runnable/app.ts +5 -2
  87. package/src/runnable/index.ts +2 -0
  88. package/src/runnable/primitives.ts +57 -0
  89. package/src/runnable/tool.ts +1 -1
  90. package/src/runnable/turn-hook.ts +4 -1
@@ -0,0 +1,126 @@
1
+ import { McpStdioCommand } from './client.cjs';
2
+
3
+ /**
4
+ * SDK MCP stdio server — exposes a set of SDK Runnables (and optional
5
+ * passthrough registrations from Ghost / Shadow) as MCP tools over stdio,
6
+ * so any MCP host can discover and call them.
7
+ *
8
+ * The server speaks the same JSON-RPC 2.0 / newline-delimited protocol as
9
+ * `apps/core/src/sidecar/mcp/client.rs` and as `client.ts` in this package.
10
+ *
11
+ * POLICY NOTE: this server does NOT implement tool-approval, permission grants,
12
+ * or any Gateway-level policy. Approval stays in the chat layer; policy stays
13
+ * in the Gateway (per issue #86). Callers must not route policy decisions
14
+ * through this server.
15
+ *
16
+ * ## Minimal Runnable contract
17
+ *
18
+ * The full `defineAgent / defineWorkflow / defineTool / defineSkill` authoring
19
+ * API is delivered by issue #205. This unit only needs the *consume side*:
20
+ * an executable object with a name and a `run()` method. Once #205 ships, its
21
+ * builders will produce objects that satisfy this same interface.
22
+ */
23
+
24
+ /** JSON Schema fragment — enough to describe a tool's input arguments. */
25
+ interface JsonSchema {
26
+ description?: string;
27
+ properties?: Record<string, JsonSchema>;
28
+ required?: string[];
29
+ type?: string;
30
+ [key: string]: unknown;
31
+ }
32
+ /**
33
+ * The minimal executable Runnable interface consumed by this bridge.
34
+ *
35
+ * When #205 ships its `defineAgent / defineTool / ...` API, those builders must
36
+ * return objects that satisfy this interface so they plug straight in here
37
+ * without any adapter.
38
+ */
39
+ interface SdkRunnable {
40
+ /** Human-readable description shown to MCP hosts. */
41
+ description?: string;
42
+ /** JSON Schema for the tool's input arguments. */
43
+ inputSchema?: JsonSchema;
44
+ /** Stable, unique tool name (no spaces; used as the MCP tool name). */
45
+ name: string;
46
+ /**
47
+ * Execute the runnable with the given arguments and return a result.
48
+ * The result is JSON-encoded into an MCP `text` content block.
49
+ */
50
+ run(args: unknown): Promise<unknown>;
51
+ }
52
+ /**
53
+ * A passthrough registration forwards `tools/list` + `tools/call` to a remote
54
+ * MCP server (e.g. Ghost at its stdio command, or Shadow at :3030). The tools
55
+ * are re-advertised under their original names; calls are forwarded verbatim.
56
+ *
57
+ * This is the mechanism by which orphaned Ghost (29 computer-use tools) and
58
+ * Shadow (:3030 capture/search tools) can be advertised to any MCP host without
59
+ * embedding their implementation in the SDK.
60
+ */
61
+ interface PassthroughRegistration {
62
+ /** Command descriptor for the upstream MCP stdio server. */
63
+ command: McpStdioCommand;
64
+ /** Label used in error messages (e.g. "ghost", "shadow"). */
65
+ label: string;
66
+ }
67
+ /**
68
+ * Unwrap an MCP `tools/call` content-block envelope back to a plain value.
69
+ * If the text is valid JSON it is parsed; otherwise the raw string is returned.
70
+ *
71
+ * This is exported so tests can verify that `decode(wrapContent(x))` round-trips
72
+ * back to `x` and that `client.callTool()` output matches a direct `run()`.
73
+ */
74
+ declare function unwrapContent(raw: unknown): unknown;
75
+ /**
76
+ * An MCP stdio server that exposes SDK Runnables and optional passthrough
77
+ * registrations as MCP tools.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * import { McpServer } from "@ryuhq/sdk/mcp/server"
82
+ *
83
+ * const server = new McpServer()
84
+ * .register({ name: "greet", run: (a) => Promise.resolve(`Hello!`) })
85
+ *
86
+ * await server.serve() // reads stdin, writes stdout until EOF
87
+ * ```
88
+ */
89
+ declare class McpServer {
90
+ private readonly runnables;
91
+ private readonly passthroughs;
92
+ /**
93
+ * Register an SDK Runnable as an MCP tool.
94
+ * Returns `this` for chaining.
95
+ */
96
+ register(runnable: SdkRunnable): this;
97
+ /**
98
+ * Register a passthrough to an external MCP stdio server (e.g. Ghost or
99
+ * Shadow). Tools from that server are fetched lazily and re-advertised.
100
+ * Returns `this` for chaining.
101
+ */
102
+ passthrough(registration: PassthroughRegistration): this;
103
+ /** Fetch all tools: local Runnables + passthrough tools. */
104
+ private allTools;
105
+ /** Handle a `tools/call` request. */
106
+ private handleCallTool;
107
+ /**
108
+ * Handle a single parsed JSON-RPC request line. Returns the response to write
109
+ * (or null for notifications that require no response).
110
+ */
111
+ private handleRequest;
112
+ /** Handle a `tools/call` JSON-RPC request. */
113
+ private handleToolsCall;
114
+ /**
115
+ * Start reading JSON-RPC requests from the given readable stream and writing
116
+ * responses to the given writable stream.
117
+ *
118
+ * Defaults to `process.stdin` / `process.stdout`. Passing explicit streams
119
+ * lets tests inject a pair of in-process pipes.
120
+ *
121
+ * Resolves when the input stream ends (EOF).
122
+ */
123
+ serve(input?: NodeJS.ReadableStream, output?: NodeJS.WritableStream): Promise<void>;
124
+ }
125
+
126
+ export { type JsonSchema, McpServer, type PassthroughRegistration, type SdkRunnable, unwrapContent };
@@ -0,0 +1,126 @@
1
+ import { McpStdioCommand } from './client.js';
2
+
3
+ /**
4
+ * SDK MCP stdio server — exposes a set of SDK Runnables (and optional
5
+ * passthrough registrations from Ghost / Shadow) as MCP tools over stdio,
6
+ * so any MCP host can discover and call them.
7
+ *
8
+ * The server speaks the same JSON-RPC 2.0 / newline-delimited protocol as
9
+ * `apps/core/src/sidecar/mcp/client.rs` and as `client.ts` in this package.
10
+ *
11
+ * POLICY NOTE: this server does NOT implement tool-approval, permission grants,
12
+ * or any Gateway-level policy. Approval stays in the chat layer; policy stays
13
+ * in the Gateway (per issue #86). Callers must not route policy decisions
14
+ * through this server.
15
+ *
16
+ * ## Minimal Runnable contract
17
+ *
18
+ * The full `defineAgent / defineWorkflow / defineTool / defineSkill` authoring
19
+ * API is delivered by issue #205. This unit only needs the *consume side*:
20
+ * an executable object with a name and a `run()` method. Once #205 ships, its
21
+ * builders will produce objects that satisfy this same interface.
22
+ */
23
+
24
+ /** JSON Schema fragment — enough to describe a tool's input arguments. */
25
+ interface JsonSchema {
26
+ description?: string;
27
+ properties?: Record<string, JsonSchema>;
28
+ required?: string[];
29
+ type?: string;
30
+ [key: string]: unknown;
31
+ }
32
+ /**
33
+ * The minimal executable Runnable interface consumed by this bridge.
34
+ *
35
+ * When #205 ships its `defineAgent / defineTool / ...` API, those builders must
36
+ * return objects that satisfy this interface so they plug straight in here
37
+ * without any adapter.
38
+ */
39
+ interface SdkRunnable {
40
+ /** Human-readable description shown to MCP hosts. */
41
+ description?: string;
42
+ /** JSON Schema for the tool's input arguments. */
43
+ inputSchema?: JsonSchema;
44
+ /** Stable, unique tool name (no spaces; used as the MCP tool name). */
45
+ name: string;
46
+ /**
47
+ * Execute the runnable with the given arguments and return a result.
48
+ * The result is JSON-encoded into an MCP `text` content block.
49
+ */
50
+ run(args: unknown): Promise<unknown>;
51
+ }
52
+ /**
53
+ * A passthrough registration forwards `tools/list` + `tools/call` to a remote
54
+ * MCP server (e.g. Ghost at its stdio command, or Shadow at :3030). The tools
55
+ * are re-advertised under their original names; calls are forwarded verbatim.
56
+ *
57
+ * This is the mechanism by which orphaned Ghost (29 computer-use tools) and
58
+ * Shadow (:3030 capture/search tools) can be advertised to any MCP host without
59
+ * embedding their implementation in the SDK.
60
+ */
61
+ interface PassthroughRegistration {
62
+ /** Command descriptor for the upstream MCP stdio server. */
63
+ command: McpStdioCommand;
64
+ /** Label used in error messages (e.g. "ghost", "shadow"). */
65
+ label: string;
66
+ }
67
+ /**
68
+ * Unwrap an MCP `tools/call` content-block envelope back to a plain value.
69
+ * If the text is valid JSON it is parsed; otherwise the raw string is returned.
70
+ *
71
+ * This is exported so tests can verify that `decode(wrapContent(x))` round-trips
72
+ * back to `x` and that `client.callTool()` output matches a direct `run()`.
73
+ */
74
+ declare function unwrapContent(raw: unknown): unknown;
75
+ /**
76
+ * An MCP stdio server that exposes SDK Runnables and optional passthrough
77
+ * registrations as MCP tools.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * import { McpServer } from "@ryuhq/sdk/mcp/server"
82
+ *
83
+ * const server = new McpServer()
84
+ * .register({ name: "greet", run: (a) => Promise.resolve(`Hello!`) })
85
+ *
86
+ * await server.serve() // reads stdin, writes stdout until EOF
87
+ * ```
88
+ */
89
+ declare class McpServer {
90
+ private readonly runnables;
91
+ private readonly passthroughs;
92
+ /**
93
+ * Register an SDK Runnable as an MCP tool.
94
+ * Returns `this` for chaining.
95
+ */
96
+ register(runnable: SdkRunnable): this;
97
+ /**
98
+ * Register a passthrough to an external MCP stdio server (e.g. Ghost or
99
+ * Shadow). Tools from that server are fetched lazily and re-advertised.
100
+ * Returns `this` for chaining.
101
+ */
102
+ passthrough(registration: PassthroughRegistration): this;
103
+ /** Fetch all tools: local Runnables + passthrough tools. */
104
+ private allTools;
105
+ /** Handle a `tools/call` request. */
106
+ private handleCallTool;
107
+ /**
108
+ * Handle a single parsed JSON-RPC request line. Returns the response to write
109
+ * (or null for notifications that require no response).
110
+ */
111
+ private handleRequest;
112
+ /** Handle a `tools/call` JSON-RPC request. */
113
+ private handleToolsCall;
114
+ /**
115
+ * Start reading JSON-RPC requests from the given readable stream and writing
116
+ * responses to the given writable stream.
117
+ *
118
+ * Defaults to `process.stdin` / `process.stdout`. Passing explicit streams
119
+ * lets tests inject a pair of in-process pipes.
120
+ *
121
+ * Resolves when the input stream ends (EOF).
122
+ */
123
+ serve(input?: NodeJS.ReadableStream, output?: NodeJS.WritableStream): Promise<void>;
124
+ }
125
+
126
+ export { type JsonSchema, McpServer, type PassthroughRegistration, type SdkRunnable, unwrapContent };
@@ -0,0 +1,9 @@
1
+ import {
2
+ McpServer,
3
+ unwrapContent
4
+ } from "../chunk-FZSFZOIN.js";
5
+ import "../chunk-VLIRNNAE.js";
6
+ export {
7
+ McpServer,
8
+ unwrapContent
9
+ };
package/dist/mcp.cjs ADDED
@@ -0,0 +1,376 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/mcp/index.ts
21
+ var mcp_exports = {};
22
+ __export(mcp_exports, {
23
+ MCP_PROTOCOL_VERSION: () => MCP_PROTOCOL_VERSION,
24
+ McpServer: () => McpServer,
25
+ callTool: () => callTool,
26
+ listTools: () => listTools,
27
+ unwrapContent: () => unwrapContent
28
+ });
29
+ module.exports = __toCommonJS(mcp_exports);
30
+
31
+ // src/mcp/client.ts
32
+ var import_node_child_process = require("child_process");
33
+ var import_node_readline = require("readline");
34
+ var MCP_PROTOCOL_VERSION = "2024-11-05";
35
+ var RPC_TIMEOUT_MS = 6e4;
36
+ var McpConnection = class _McpConnection {
37
+ proc;
38
+ nextId = 1;
39
+ closed = false;
40
+ waiters = [];
41
+ constructor(proc) {
42
+ this.proc = proc;
43
+ }
44
+ /** Spawn the server and complete the MCP `initialize` handshake. */
45
+ static async connect(cmd) {
46
+ const env = { ...process.env, ...cmd.env ?? {} };
47
+ const proc = (0, import_node_child_process.spawn)(cmd.command, cmd.args ?? [], {
48
+ stdio: ["pipe", "pipe", "pipe"],
49
+ env
50
+ });
51
+ if (!(proc.stdin && proc.stdout)) {
52
+ proc.kill();
53
+ throw new Error(`MCP server '${cmd.command}' stdin/stdout unavailable`);
54
+ }
55
+ if (proc.stderr) {
56
+ proc.stderr.on("data", (chunk) => {
57
+ process.stderr.write(`[mcp-server] ${chunk.toString()}`);
58
+ });
59
+ }
60
+ const conn = new _McpConnection(proc);
61
+ const rl = (0, import_node_readline.createInterface)({
62
+ input: proc.stdout,
63
+ crlfDelay: Number.POSITIVE_INFINITY
64
+ });
65
+ rl.on("line", (rawLine) => {
66
+ const trimmed = rawLine.trim();
67
+ if (!trimmed) {
68
+ return;
69
+ }
70
+ let parsed;
71
+ try {
72
+ parsed = JSON.parse(trimmed);
73
+ } catch {
74
+ return;
75
+ }
76
+ if (parsed.id === void 0 || parsed.id === null) {
77
+ return;
78
+ }
79
+ const idx = conn.waiters.findIndex((w) => w.id === parsed.id);
80
+ if (idx === -1) {
81
+ return;
82
+ }
83
+ const [waiter] = conn.waiters.splice(idx, 1);
84
+ if (!waiter) {
85
+ return;
86
+ }
87
+ clearTimeout(waiter.timer);
88
+ if (parsed.error) {
89
+ waiter.reject(new Error(`MCP error: ${JSON.stringify(parsed.error)}`));
90
+ } else {
91
+ waiter.resolve(parsed.result ?? null);
92
+ }
93
+ });
94
+ rl.on("close", () => {
95
+ for (const waiter of conn.waiters.splice(0)) {
96
+ clearTimeout(waiter.timer);
97
+ waiter.reject(new Error("MCP server closed the connection"));
98
+ }
99
+ });
100
+ await conn.request("initialize", {
101
+ protocolVersion: MCP_PROTOCOL_VERSION,
102
+ capabilities: {},
103
+ clientInfo: { name: "ryu-sdk", version: "0.0.1" }
104
+ });
105
+ conn.notify("notifications/initialized", {});
106
+ return conn;
107
+ }
108
+ /** Send a JSON-RPC request and return the `result` field. */
109
+ request(method, params) {
110
+ const id = this.nextId++;
111
+ const frame = JSON.stringify({
112
+ jsonrpc: "2.0",
113
+ id,
114
+ method,
115
+ params
116
+ });
117
+ this.write(frame);
118
+ return new Promise((resolve, reject) => {
119
+ const timer = setTimeout(
120
+ () => reject(new Error(`MCP request '${method}' timed out`)),
121
+ RPC_TIMEOUT_MS
122
+ );
123
+ this.waiters.push({ id, resolve, reject, timer });
124
+ });
125
+ }
126
+ /** Send a JSON-RPC notification (no response expected). */
127
+ notify(method, params) {
128
+ const frame = JSON.stringify({ jsonrpc: "2.0", method, params });
129
+ this.write(frame);
130
+ }
131
+ write(frame) {
132
+ if (this.closed) {
133
+ return;
134
+ }
135
+ this.proc.stdin?.write(`${frame}
136
+ `);
137
+ }
138
+ /** Graceful shutdown: close stdin, then kill. */
139
+ async shutdown() {
140
+ this.closed = true;
141
+ this.proc.stdin?.end();
142
+ await new Promise((resolve) => {
143
+ this.proc.once("exit", () => resolve());
144
+ this.proc.kill();
145
+ setTimeout(resolve, 500);
146
+ });
147
+ }
148
+ };
149
+ async function listTools(cmd) {
150
+ const conn = await McpConnection.connect(cmd);
151
+ let result;
152
+ try {
153
+ result = await conn.request("tools/list", {});
154
+ } finally {
155
+ await conn.shutdown();
156
+ }
157
+ const tools = result?.tools ?? [];
158
+ return tools.map((t) => {
159
+ const tool = t;
160
+ const name = tool.name;
161
+ if (typeof name !== "string") {
162
+ return null;
163
+ }
164
+ return {
165
+ name,
166
+ description: typeof tool.description === "string" ? tool.description : void 0,
167
+ inputSchema: tool.inputSchema
168
+ };
169
+ }).filter((t) => t !== null);
170
+ }
171
+ async function callTool(cmd, tool, args) {
172
+ const conn = await McpConnection.connect(cmd);
173
+ try {
174
+ return await conn.request("tools/call", { name: tool, arguments: args });
175
+ } finally {
176
+ await conn.shutdown();
177
+ }
178
+ }
179
+
180
+ // src/mcp/server.ts
181
+ var import_node_readline2 = require("readline");
182
+ function respond(id, result) {
183
+ return { jsonrpc: "2.0", id: id ?? null, result };
184
+ }
185
+ function respondError(id, code, message) {
186
+ return { jsonrpc: "2.0", id: id ?? null, error: { code, message } };
187
+ }
188
+ function wrapContent(value) {
189
+ return {
190
+ content: [
191
+ {
192
+ type: "text",
193
+ text: typeof value === "string" ? value : JSON.stringify(value)
194
+ }
195
+ ]
196
+ };
197
+ }
198
+ function unwrapContent(raw) {
199
+ const r = raw;
200
+ const text = r?.content?.[0]?.text;
201
+ if (text === void 0) {
202
+ return raw;
203
+ }
204
+ try {
205
+ return JSON.parse(text);
206
+ } catch {
207
+ return text;
208
+ }
209
+ }
210
+ var McpServer = class {
211
+ runnables = /* @__PURE__ */ new Map();
212
+ passthroughs = [];
213
+ /**
214
+ * Register an SDK Runnable as an MCP tool.
215
+ * Returns `this` for chaining.
216
+ */
217
+ register(runnable) {
218
+ this.runnables.set(runnable.name, runnable);
219
+ return this;
220
+ }
221
+ /**
222
+ * Register a passthrough to an external MCP stdio server (e.g. Ghost or
223
+ * Shadow). Tools from that server are fetched lazily and re-advertised.
224
+ * Returns `this` for chaining.
225
+ */
226
+ passthrough(registration) {
227
+ this.passthroughs.push(registration);
228
+ return this;
229
+ }
230
+ /** Fetch all tools: local Runnables + passthrough tools. */
231
+ async allTools() {
232
+ const local = [...this.runnables.values()].map((r) => ({
233
+ name: r.name,
234
+ description: r.description,
235
+ inputSchema: r.inputSchema
236
+ }));
237
+ const remote = [];
238
+ for (const pt of this.passthroughs) {
239
+ try {
240
+ const tools = await listTools(pt.command);
241
+ remote.push(...tools);
242
+ } catch (err) {
243
+ process.stderr.write(
244
+ `[mcp-server] passthrough '${pt.label}' list_tools failed: ${err}
245
+ `
246
+ );
247
+ }
248
+ }
249
+ return [...local, ...remote];
250
+ }
251
+ /** Handle a `tools/call` request. */
252
+ async handleCallTool(name, args) {
253
+ const local = this.runnables.get(name);
254
+ if (local) {
255
+ const result = await local.run(args);
256
+ return wrapContent(result);
257
+ }
258
+ for (const pt of this.passthroughs) {
259
+ try {
260
+ const tools = await listTools(pt.command);
261
+ if (tools.some((t) => t.name === name)) {
262
+ return await callTool(pt.command, name, args);
263
+ }
264
+ } catch {
265
+ }
266
+ }
267
+ throw new Error(`Unknown tool: ${name}`);
268
+ }
269
+ /**
270
+ * Handle a single parsed JSON-RPC request line. Returns the response to write
271
+ * (or null for notifications that require no response).
272
+ */
273
+ async handleRequest(req, initialized, write) {
274
+ const { id, method, params } = req;
275
+ if (method === "initialize") {
276
+ initialized.value = true;
277
+ write(
278
+ respond(id, {
279
+ protocolVersion: MCP_PROTOCOL_VERSION,
280
+ capabilities: { tools: {} },
281
+ serverInfo: { name: "ryu-sdk-server", version: "0.0.1" }
282
+ })
283
+ );
284
+ return;
285
+ }
286
+ if (method === "notifications/initialized") {
287
+ return;
288
+ }
289
+ if (!initialized.value) {
290
+ write(respondError(id, -32002, "Server not initialized"));
291
+ return;
292
+ }
293
+ if (method === "tools/list") {
294
+ try {
295
+ const tools = await this.allTools();
296
+ write(respond(id, { tools }));
297
+ } catch (err) {
298
+ write(respondError(id, -32603, String(err)));
299
+ }
300
+ return;
301
+ }
302
+ if (method === "tools/call") {
303
+ await this.handleToolsCall(id, params, write);
304
+ return;
305
+ }
306
+ write(respondError(id, -32601, `Method not found: ${method}`));
307
+ }
308
+ /** Handle a `tools/call` JSON-RPC request. */
309
+ async handleToolsCall(id, params, write) {
310
+ const p = params;
311
+ const toolName = p?.name;
312
+ const toolArgs = p?.arguments ?? {};
313
+ if (typeof toolName !== "string") {
314
+ write(respondError(id, -32602, "tools/call requires 'name'"));
315
+ return;
316
+ }
317
+ try {
318
+ const result = await this.handleCallTool(toolName, toolArgs);
319
+ write(respond(id, result));
320
+ } catch (err) {
321
+ write(
322
+ respond(id, {
323
+ content: [{ type: "text", text: String(err) }],
324
+ isError: true
325
+ })
326
+ );
327
+ }
328
+ }
329
+ /**
330
+ * Start reading JSON-RPC requests from the given readable stream and writing
331
+ * responses to the given writable stream.
332
+ *
333
+ * Defaults to `process.stdin` / `process.stdout`. Passing explicit streams
334
+ * lets tests inject a pair of in-process pipes.
335
+ *
336
+ * Resolves when the input stream ends (EOF).
337
+ */
338
+ serve(input = process.stdin, output = process.stdout) {
339
+ const write = (obj) => {
340
+ output.write(`${JSON.stringify(obj)}
341
+ `);
342
+ };
343
+ const initialized = { value: false };
344
+ return new Promise((resolve) => {
345
+ const rl = (0, import_node_readline2.createInterface)({
346
+ input,
347
+ crlfDelay: Number.POSITIVE_INFINITY
348
+ });
349
+ rl.on("line", (rawLine) => {
350
+ const line = rawLine.trim();
351
+ if (!line) {
352
+ return;
353
+ }
354
+ let req;
355
+ try {
356
+ req = JSON.parse(line);
357
+ } catch {
358
+ write(respondError(null, -32700, "Parse error"));
359
+ return;
360
+ }
361
+ this.handleRequest(req, initialized, write).catch((err) => {
362
+ write(respondError(req.id, -32603, String(err)));
363
+ });
364
+ });
365
+ rl.on("close", resolve);
366
+ });
367
+ }
368
+ };
369
+ // Annotate the CommonJS export names for ESM import in node:
370
+ 0 && (module.exports = {
371
+ MCP_PROTOCOL_VERSION,
372
+ McpServer,
373
+ callTool,
374
+ listTools,
375
+ unwrapContent
376
+ });
package/dist/mcp.d.cts ADDED
@@ -0,0 +1,2 @@
1
+ export { MCP_PROTOCOL_VERSION, McpStdioCommand, McpTool, callTool, listTools } from './mcp/client.cjs';
2
+ export { JsonSchema, McpServer, PassthroughRegistration, SdkRunnable, unwrapContent } from './mcp/server.cjs';
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { MCP_PROTOCOL_VERSION, McpStdioCommand, McpTool, callTool, listTools } from './mcp/client.js';
2
+ export { JsonSchema, McpServer, PassthroughRegistration, SdkRunnable, unwrapContent } from './mcp/server.js';
package/dist/mcp.js ADDED
@@ -0,0 +1,17 @@
1
+ import "./chunk-TXSHHZF2.js";
2
+ import {
3
+ McpServer,
4
+ unwrapContent
5
+ } from "./chunk-FZSFZOIN.js";
6
+ import {
7
+ MCP_PROTOCOL_VERSION,
8
+ callTool,
9
+ listTools
10
+ } from "./chunk-VLIRNNAE.js";
11
+ export {
12
+ MCP_PROTOCOL_VERSION,
13
+ McpServer,
14
+ callTool,
15
+ listTools,
16
+ unwrapContent
17
+ };