@runa-cmd/mcp 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Runa CLI Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # @runa-cmd/mcp
2
+
3
+ Turn any Runa CLI into an [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) server with a single flag.
4
+
5
+ Every command becomes an MCP tool. Zod schemas pass directly to the MCP SDK — zero schema conversion needed.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @runa-cmd/mcp
11
+ ```
12
+
13
+ > Peer dependencies: `@runa-cmd/core` and `zod ^4.0.0`.
14
+ > Runtime dependency: `@modelcontextprotocol/sdk ^1.29.0`.
15
+
16
+ ## Quick Start
17
+
18
+ ```ts
19
+ import { defineCLI, defineCommand } from '@runa-cmd/core';
20
+ import { z } from '@runa-cmd/core/zod';
21
+ import { mcpPlugin } from '@runa-cmd/mcp';
22
+
23
+ const deploy = defineCommand({
24
+ meta: { name: 'deploy', description: 'Deploy a service' },
25
+ args: { service: z.string().describe('Service name') },
26
+ options: { env: z.enum(['staging', 'production']).describe('Target') },
27
+ output: z.object({ url: z.string(), status: z.string() }),
28
+ async run({ args, options }) {
29
+ return {
30
+ url: `https://${options.env}.example.com/${args.service}`,
31
+ status: 'deployed',
32
+ };
33
+ },
34
+ });
35
+
36
+ const cli = defineCLI({
37
+ meta: { name: 'my-tool', version: '1.0.0' },
38
+ commands: { deploy },
39
+ plugins: [mcpPlugin()],
40
+ });
41
+
42
+ cli.run();
43
+ ```
44
+
45
+ ```bash
46
+ # Normal CLI usage
47
+ $ my-tool deploy api --env staging
48
+
49
+ # MCP server mode — for AI agents (Claude, Cursor, etc.)
50
+ $ my-tool --mcp
51
+ ```
52
+
53
+ When `--mcp` is passed, the CLI starts a stdio-based MCP server instead of normal execution. AI agents can then discover and call your commands as tools.
54
+
55
+ ## How It Works
56
+
57
+ 1. The plugin adds a `--mcp` global boolean flag
58
+ 2. On `onGlobalFlags` hook, if `--mcp` is true:
59
+ - Introspects the CLI schema via `getSchema()`
60
+ - Gets the live command tree via `getCommands()`
61
+ - Maps each leaf command to an MCP tool
62
+ - Starts an MCP server over stdio transport
63
+ - Short-circuits normal CLI execution
64
+
65
+ ### Command → Tool Mapping
66
+
67
+ | CLI Command | MCP Tool Name | Why |
68
+ |---|---|---|
69
+ | `deploy` | `deploy` | Direct mapping |
70
+ | `config get` | `config_get` | Nested commands join with `_` |
71
+ | `db migrate` | `db_migrate` | Same pattern |
72
+
73
+ ### Schema Mapping
74
+
75
+ Args and options merge into a single MCP `inputSchema`:
76
+
77
+ ```
78
+ CLI: deploy <service> --env staging
79
+ MCP: { "service": "api", "env": "staging" }
80
+ ```
81
+
82
+ The plugin tracks which keys are args vs options internally, so it can reconstruct the proper `RunContext` when executing the command.
83
+
84
+ ### Output Handling
85
+
86
+ | Scenario | MCP Response |
87
+ |---|---|
88
+ | Command has `output` schema | `structuredContent` with typed data |
89
+ | Command returns a string (no output schema) | `text` content |
90
+ | Command returns an object (no output schema) | `text` content (JSON stringified) |
91
+ | Command returns void | `text: "ok"` |
92
+ | Command throws | `isError: true` with error message |
93
+
94
+ ## API
95
+
96
+ ### `mcpPlugin(options?)`
97
+
98
+ Creates the MCP plugin. Returns a `PluginConfig` compatible with `defineCLI`.
99
+
100
+ ```ts
101
+ import { mcpPlugin } from '@runa-cmd/mcp';
102
+
103
+ mcpPlugin({
104
+ name: 'my-server', // MCP server name (defaults to CLI name)
105
+ version: '2.0.0', // MCP server version (defaults to CLI version)
106
+ instructions: 'Use deploy before running tests', // Cross-tool workflow hints for AI
107
+ });
108
+ ```
109
+
110
+ ### `McpPluginOptions`
111
+
112
+ ```ts
113
+ interface McpPluginOptions {
114
+ name?: string; // Server name override
115
+ version?: string; // Server version override
116
+ instructions?: string; // Workflow instructions for AI agents
117
+ }
118
+ ```
119
+
120
+ ## With Help Plugin
121
+
122
+ MCP and help work together — just add both:
123
+
124
+ ```ts
125
+ import { helpPlugin } from '@runa-cmd/help';
126
+ import { mcpPlugin } from '@runa-cmd/mcp';
127
+
128
+ const cli = defineCLI({
129
+ meta: { name: 'my-tool', version: '1.0.0' },
130
+ commands: { deploy, init },
131
+ plugins: [helpPlugin(), mcpPlugin()],
132
+ });
133
+ ```
134
+
135
+ ```bash
136
+ $ my-tool --help # Human-readable help
137
+ $ my-tool --mcp # Machine-readable MCP server
138
+ ```
139
+
140
+ ## MCP Server Details
141
+
142
+ - **Transport**: stdio (stdin/stdout JSON-RPC)
143
+ - **Graceful shutdown**: Handles SIGINT and SIGTERM
144
+ - **SDK**: Uses `@modelcontextprotocol/sdk` v1.29+ with `McpServer` API
145
+ - **Zod v4 native**: Zod v4 implements [Standard Schema](https://github.com/standard-schema/standard-schema), so schemas pass directly to `registerTool()` — no conversion layer
146
+
147
+ ### Testing with MCP Inspector
148
+
149
+ You can test your MCP server with the [MCP Inspector](https://github.com/modelcontextprotocol/inspector):
150
+
151
+ ```bash
152
+ npx @modelcontextprotocol/inspector my-tool --mcp
153
+ ```
154
+
155
+ ### Configuring in Claude Desktop
156
+
157
+ Add to your Claude Desktop MCP config (`~/.config/claude/claude_desktop_config.json`):
158
+
159
+ ```json
160
+ {
161
+ "mcpServers": {
162
+ "my-tool": {
163
+ "command": "node",
164
+ "args": ["path/to/my-tool.js", "--mcp"]
165
+ }
166
+ }
167
+ }
168
+ ```
169
+
170
+ ## Architecture
171
+
172
+ ```
173
+ mcpPlugin()
174
+ │
175
+ ├── plugin.ts Runa plugin (adds --mcp flag, orchestrates lifecycle)
176
+ ├── schema.ts Builds tool registrations from command tree
177
+ ├── server.ts Creates MCP server, registers tools, handles execution
178
+ └── types.ts McpPluginOptions interface
179
+ ```
180
+
181
+ The package has a single runtime dependency (`@modelcontextprotocol/sdk`) and delegates everything else to `@runa-cmd/core`.
182
+
183
+ ## License
184
+
185
+ MIT
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @runa-cmd/mcp — Public API
3
+ *
4
+ * Barrel re-exports for the MCP plugin package.
5
+ */
6
+ export { mcpPlugin } from './plugin.js';
7
+ export type { McpPluginOptions } from './types.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @runa-cmd/mcp — Public API
3
+ *
4
+ * Barrel re-exports for the MCP plugin package.
5
+ */
6
+ export { mcpPlugin } from './plugin.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @runa-cmd/mcp — mcpPlugin()
3
+ *
4
+ * A Runa plugin that exposes CLI commands as MCP tools.
5
+ * When `--mcp` is passed, the plugin short-circuits normal CLI execution,
6
+ * starts an MCP server on stdio transport, and registers every CLI
7
+ * command as an MCP tool.
8
+ */
9
+ import type { PluginConfig } from '@runa-cmd/core';
10
+ import type { McpPluginOptions } from './types.js';
11
+ /**
12
+ * Create an MCP plugin that exposes CLI commands as MCP tools.
13
+ *
14
+ * @param opts - Optional server name, version, or instructions override
15
+ */
16
+ export declare function mcpPlugin(opts?: McpPluginOptions): PluginConfig;
17
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAe,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAKhE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,GAAE,gBAAqB,GAAG,YAAY,CAuCnE"}
package/dist/plugin.js ADDED
@@ -0,0 +1,42 @@
1
+ import { definePlugin } from '@runa-cmd/core';
2
+ import { z } from 'zod';
3
+ import { buildToolRegistrations } from './schema.js';
4
+ import { createMcpServer, registerTools, startServer } from './server.js';
5
+ /**
6
+ * Create an MCP plugin that exposes CLI commands as MCP tools.
7
+ *
8
+ * @param opts - Optional server name, version, or instructions override
9
+ */
10
+ export function mcpPlugin(opts = {}) {
11
+ return definePlugin({
12
+ meta: {
13
+ name: '@runa-cmd/mcp',
14
+ version: '0.1.0',
15
+ description: 'MCP server generation from CLI commands',
16
+ },
17
+ capabilities: { addGlobalOptions: true },
18
+ setup(api) {
19
+ // Add --mcp as a global boolean option
20
+ api.addGlobalOption('mcp', z.boolean().optional().default(false).describe('Start MCP server'));
21
+ // Intercept via onGlobalFlags hook
22
+ api.hook('onGlobalFlags', async (ctx) => {
23
+ if (!ctx.globalOptions?.mcp)
24
+ return;
25
+ const schema = api.getSchema();
26
+ const commands = api.getCommands();
27
+ const registrations = buildToolRegistrations(commands, schema);
28
+ const server = createMcpServer({
29
+ name: opts.name ?? schema.meta.name,
30
+ version: opts.version ?? schema.meta.version ?? '0.0.0',
31
+ instructions: opts.instructions,
32
+ });
33
+ registerTools(server, registrations);
34
+ await startServer(server);
35
+ // Short-circuit — MCP server now running, don't execute CLI command
36
+ // NOTE: Do NOT call process.exit() — server must stay alive for tool invocations
37
+ ctx.shortCircuit?.();
38
+ });
39
+ },
40
+ });
41
+ }
42
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1E;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,OAAyB,EAAE;IACpD,OAAO,YAAY,CAAC;QACnB,IAAI,EAAE;YACL,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,OAAO;YAChB,WAAW,EAAE,yCAAyC;SACtD;QACD,YAAY,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE;QACxC,KAAK,CAAC,GAAG;YACR,uCAAuC;YACvC,GAAG,CAAC,eAAe,CAClB,KAAK,EACL,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAClE,CAAC;YAEF,mCAAmC;YACnC,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,GAAgB,EAAE,EAAE;gBACpD,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,GAAG;oBAAE,OAAO;gBAEpC,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC;gBAC/B,MAAM,QAAQ,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;gBAEnC,MAAM,aAAa,GAAG,sBAAsB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;gBAE/D,MAAM,MAAM,GAAG,eAAe,CAAC;oBAC9B,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI;oBACnC,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,OAAO;oBACvD,YAAY,EAAE,IAAI,CAAC,YAAY;iBAC/B,CAAC,CAAC;gBAEH,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;gBACrC,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;gBAE1B,oEAAoE;gBACpE,iFAAiF;gBACjF,GAAG,CAAC,YAAY,EAAE,EAAE,CAAC;YACtB,CAAC,CAAC,CAAC;QACJ,CAAC;KACD,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @runa-cmd/mcp — Command → MCP tool schema mapping
3
+ *
4
+ * Walks the CommandTree and builds ToolRegistration objects
5
+ * that map each command to an MCP tool with merged input schema.
6
+ */
7
+ import type { CLISchema, Command, CommandTree } from '@runa-cmd/core';
8
+ import type { ZodType } from 'zod';
9
+ /**
10
+ * Represents a single MCP tool registration derived from a CLI command.
11
+ */
12
+ export interface ToolRegistration {
13
+ /** MCP tool name (e.g., 'config_set') */
14
+ name: string;
15
+ /** Tool description from command metadata */
16
+ description: string;
17
+ /** Merged args + options as a Zod shape (Record<string, ZodType>) */
18
+ inputShape: Record<string, ZodType>;
19
+ /** Command output schema if defined */
20
+ outputSchema?: ZodType;
21
+ /** Reference to the Command for executing run() */
22
+ command: Command;
23
+ /** Keys that belong to args (for splitting input back on invocation) */
24
+ argKeys: Set<string>;
25
+ /** Keys that belong to options */
26
+ optionKeys: Set<string>;
27
+ }
28
+ /**
29
+ * Build MCP tool registrations from a CommandTree and CLISchema.
30
+ *
31
+ * Walks the tree, maps each leaf command to a ToolRegistration with:
32
+ * - Merged args + options as inputShape
33
+ * - Output schema if defined
34
+ * - Arg/option key sets for splitting input on invocation
35
+ */
36
+ export declare function buildToolRegistrations(commands: CommandTree, schema: CLISchema): ToolRegistration[];
37
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAiB,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACrF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAInC;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,uCAAuC;IACvC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,mDAAmD;IACnD,OAAO,EAAE,OAAO,CAAC;IACjB,wEAAwE;IACxE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,kCAAkC;IAClC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACxB;AAyFD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACrC,QAAQ,EAAE,WAAW,EACrB,MAAM,EAAE,SAAS,GACf,gBAAgB,EAAE,CAEpB"}
package/dist/schema.js ADDED
@@ -0,0 +1,82 @@
1
+ // ─── Helpers ────────────────────────────────────────────────
2
+ function isCommand(value) {
3
+ return '_type' in value && value._type === 'runa:command';
4
+ }
5
+ /**
6
+ * Find the matching CommandSchema from CLISchema for a given fullPath.
7
+ */
8
+ function findCommandSchema(schemas, fullPath) {
9
+ for (const schema of schemas) {
10
+ if (pathsMatch(schema.fullPath, fullPath))
11
+ return schema;
12
+ if (schema.subcommands) {
13
+ const found = findCommandSchema(schema.subcommands, fullPath);
14
+ if (found)
15
+ return found;
16
+ }
17
+ }
18
+ return undefined;
19
+ }
20
+ function pathsMatch(a, b) {
21
+ if (a.length !== b.length)
22
+ return false;
23
+ return a.every((segment, i) => segment === b[i]);
24
+ }
25
+ // ─── Core ───────────────────────────────────────────────────
26
+ /**
27
+ * Walk a CommandTree and build ToolRegistration[] for all leaf commands.
28
+ * Group nodes (non-leaf, no run()) are skipped.
29
+ */
30
+ function walkTree(tree, parentPath, cliSchema) {
31
+ const registrations = [];
32
+ for (const [name, entry] of Object.entries(tree)) {
33
+ const fullPath = [...parentPath, name];
34
+ if (isCommand(entry)) {
35
+ const toolName = fullPath.join('_');
36
+ const cmdSchema = findCommandSchema(cliSchema.commands, fullPath);
37
+ // Merge args + options into a single input shape
38
+ const inputShape = {};
39
+ const argKeys = new Set();
40
+ const optionKeys = new Set();
41
+ if (entry.args) {
42
+ for (const [key, schema] of Object.entries(entry.args)) {
43
+ inputShape[key] = schema;
44
+ argKeys.add(key);
45
+ }
46
+ }
47
+ if (entry.options) {
48
+ for (const [key, schema] of Object.entries(entry.options)) {
49
+ inputShape[key] = schema;
50
+ optionKeys.add(key);
51
+ }
52
+ }
53
+ registrations.push({
54
+ name: toolName,
55
+ description: cmdSchema?.description ?? entry.meta.description,
56
+ inputShape,
57
+ outputSchema: entry.output,
58
+ command: entry,
59
+ argKeys,
60
+ optionKeys,
61
+ });
62
+ }
63
+ else {
64
+ // It's a subtree — recurse
65
+ registrations.push(...walkTree(entry, fullPath, cliSchema));
66
+ }
67
+ }
68
+ return registrations;
69
+ }
70
+ // ─── Public API ─────────────────────────────────────────────
71
+ /**
72
+ * Build MCP tool registrations from a CommandTree and CLISchema.
73
+ *
74
+ * Walks the tree, maps each leaf command to a ToolRegistration with:
75
+ * - Merged args + options as inputShape
76
+ * - Output schema if defined
77
+ * - Arg/option key sets for splitting input on invocation
78
+ */
79
+ export function buildToolRegistrations(commands, schema) {
80
+ return walkTree(commands, [], schema);
81
+ }
82
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @runa-cmd/mcp — MCP server creation and tool registration
3
+ *
4
+ * Creates an MCP server, registers tools from ToolRegistrations,
5
+ * and manages the server lifecycle (stdio transport, signal handlers).
6
+ */
7
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
+ import type { ToolRegistration } from './schema.js';
9
+ /**
10
+ * Create an MCP server with the given metadata.
11
+ */
12
+ export declare function createMcpServer(opts: {
13
+ name: string;
14
+ version: string;
15
+ instructions?: string;
16
+ }): McpServer;
17
+ /**
18
+ * Register MCP tools on the server from ToolRegistration objects.
19
+ *
20
+ * Each tool's handler:
21
+ * 1. Splits the input back into args/options using stored key sets
22
+ * 2. Calls command.run() with a constructed RunContext
23
+ * 3. Returns structured content if outputSchema exists, text otherwise
24
+ * 4. Catches errors and returns isError: true
25
+ */
26
+ export declare function registerTools(server: McpServer, registrations: ToolRegistration[]): void;
27
+ /**
28
+ * Start the MCP server on stdio transport.
29
+ * Registers signal handlers for graceful shutdown.
30
+ *
31
+ * Returns a close function for cleanup.
32
+ */
33
+ export declare function startServer(server: McpServer): Promise<{
34
+ close: () => Promise<void>;
35
+ }>;
36
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,SAAS,CAKZ;AAID;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,gBAAgB,EAAE,GAAG,IAAI,CA6BxF;AAmED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAiB5F"}
package/dist/server.js ADDED
@@ -0,0 +1,125 @@
1
+ /**
2
+ * @runa-cmd/mcp — MCP server creation and tool registration
3
+ *
4
+ * Creates an MCP server, registers tools from ToolRegistrations,
5
+ * and manages the server lifecycle (stdio transport, signal handlers).
6
+ */
7
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
+ // ─── Server Creation ────────────────────────────────────────
10
+ /**
11
+ * Create an MCP server with the given metadata.
12
+ */
13
+ export function createMcpServer(opts) {
14
+ return new McpServer({ name: opts.name, version: opts.version }, { instructions: opts.instructions });
15
+ }
16
+ // ─── Tool Registration ──────────────────────────────────────
17
+ /**
18
+ * Register MCP tools on the server from ToolRegistration objects.
19
+ *
20
+ * Each tool's handler:
21
+ * 1. Splits the input back into args/options using stored key sets
22
+ * 2. Calls command.run() with a constructed RunContext
23
+ * 3. Returns structured content if outputSchema exists, text otherwise
24
+ * 4. Catches errors and returns isError: true
25
+ */
26
+ export function registerTools(server, registrations) {
27
+ for (const reg of registrations) {
28
+ const hasInput = Object.keys(reg.inputShape).length > 0;
29
+ if (hasInput) {
30
+ server.registerTool(reg.name, {
31
+ description: reg.description,
32
+ inputSchema: reg.inputShape,
33
+ ...(reg.outputSchema ? { outputSchema: reg.outputSchema } : {}),
34
+ }, async (input) => {
35
+ return executeToolHandler(reg, input);
36
+ });
37
+ }
38
+ else {
39
+ server.registerTool(reg.name, {
40
+ description: reg.description,
41
+ ...(reg.outputSchema ? { outputSchema: reg.outputSchema } : {}),
42
+ }, async () => {
43
+ return executeToolHandler(reg, {});
44
+ });
45
+ }
46
+ }
47
+ }
48
+ /**
49
+ * Execute a tool handler — splits input into args/options, runs the command,
50
+ * and returns the appropriate MCP result.
51
+ */
52
+ async function executeToolHandler(reg, input) {
53
+ // Split input back into args and options
54
+ const runArgs = {};
55
+ const runOptions = {};
56
+ for (const [key, value] of Object.entries(input)) {
57
+ if (reg.argKeys.has(key)) {
58
+ runArgs[key] = value;
59
+ }
60
+ else {
61
+ runOptions[key] = value;
62
+ }
63
+ }
64
+ try {
65
+ const result = await reg.command.run({
66
+ args: runArgs,
67
+ options: runOptions,
68
+ globalOptions: {},
69
+ command: reg.command.meta,
70
+ rawArgs: [],
71
+ });
72
+ // Command has output schema — return structured content
73
+ if (reg.outputSchema) {
74
+ reg.outputSchema.parse(result);
75
+ return {
76
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
77
+ structuredContent: result,
78
+ };
79
+ }
80
+ // Command returned something (but no output schema)
81
+ if (result !== undefined && result !== null) {
82
+ const text = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
83
+ return {
84
+ content: [{ type: 'text', text }],
85
+ };
86
+ }
87
+ // Void command — success message
88
+ return {
89
+ content: [{ type: 'text', text: `Command '${reg.name}' executed successfully.` }],
90
+ };
91
+ }
92
+ catch (err) {
93
+ return {
94
+ isError: true,
95
+ content: [
96
+ {
97
+ type: 'text',
98
+ text: err instanceof Error ? err.message : String(err),
99
+ },
100
+ ],
101
+ };
102
+ }
103
+ }
104
+ // ─── Server Lifecycle ───────────────────────────────────────
105
+ /**
106
+ * Start the MCP server on stdio transport.
107
+ * Registers signal handlers for graceful shutdown.
108
+ *
109
+ * Returns a close function for cleanup.
110
+ */
111
+ export async function startServer(server) {
112
+ const transport = new StdioServerTransport();
113
+ await server.connect(transport);
114
+ const close = async () => {
115
+ await server.close();
116
+ };
117
+ const handleShutdown = async () => {
118
+ await close();
119
+ process.exit(0);
120
+ };
121
+ process.on('SIGINT', handleShutdown);
122
+ process.on('SIGTERM', handleShutdown);
123
+ return { close };
124
+ }
125
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @runa-cmd/mcp — Type definitions
3
+ *
4
+ * Plugin options for mcpPlugin().
5
+ */
6
+ /**
7
+ * Options for mcpPlugin().
8
+ */
9
+ export interface McpPluginOptions {
10
+ /** Custom server name override (defaults to CLI meta.name) */
11
+ name?: string;
12
+ /** Custom server version override (defaults to CLI meta.version) */
13
+ version?: string;
14
+ /** Server instructions for cross-tool workflows */
15
+ instructions?: string;
16
+ }
17
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB"}
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @runa-cmd/mcp — Type definitions
3
+ *
4
+ * Plugin options for mcpPlugin().
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@runa-cmd/mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP (Model Context Protocol) server mode for Runa CLIs. Turn commands into AI-callable tools.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "keywords": [
8
+ "cli",
9
+ "mcp",
10
+ "model-context-protocol",
11
+ "ai",
12
+ "llm",
13
+ "tools",
14
+ "runa"
15
+ ],
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ }
23
+ },
24
+ "dependencies": {
25
+ "@modelcontextprotocol/sdk": "^1.29.0"
26
+ },
27
+ "peerDependencies": {
28
+ "zod": "^4.0.0",
29
+ "@runa-cmd/core": "0.1.0"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.3.0"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/jrodrigopuca/runa-cmd.git",
40
+ "directory": "packages/mcp"
41
+ },
42
+ "homepage": "https://github.com/jrodrigopuca/runa-cmd/tree/main/packages/mcp#readme",
43
+ "bugs": {
44
+ "url": "https://github.com/jrodrigopuca/runa-cmd/issues"
45
+ },
46
+ "files": [
47
+ "dist",
48
+ "README.md"
49
+ ],
50
+ "devDependencies": {
51
+ "@types/node": "^25.6.0",
52
+ "typescript": "^6.0.2",
53
+ "zod": "^4.3.6",
54
+ "@runa-cmd/core": "0.1.0"
55
+ },
56
+ "scripts": {
57
+ "build": "tsc",
58
+ "dev": "tsc --watch --preserveWatchOutput",
59
+ "typecheck": "tsc --noEmit",
60
+ "test": "vitest run",
61
+ "test:watch": "vitest",
62
+ "lint": "biome check .",
63
+ "lint:fix": "biome check --write .",
64
+ "clean": "rm -rf dist"
65
+ }
66
+ }