@goke/mcp 0.1.1 → 0.1.2

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 CHANGED
@@ -95,6 +95,7 @@ const cli = goke("my-cli")
95
95
  cli
96
96
  .command("search", "Search pages")
97
97
  .option("--query <query>", z.string().describe("Search query"))
98
+ .required()
98
99
  .option("--limit [limit]", z.number().default(10).describe("Max results"))
99
100
  .action((options) => {
100
101
  return { results: findPages(options.query, options.limit) }
@@ -129,7 +130,7 @@ my-cli mcp
129
130
 
130
131
  When running as MCP, the server exposes `search` and `deploy` as tools. The `mcp` command itself is excluded. Options with Zod schemas (or any Standard Schema) become typed `inputSchema` properties in the MCP tool definition.
131
132
 
132
- **`inputSchema.required` is not the CLI `<value>` syntax.** `--query <query>` means the flag needs a value if it is present. The flag is still optional unless the schema rejects omit (`z.string()`, not `z.string().optional()`). Required **positionals** like `<env>` always go in `required`. Schema-required flags go in `required` too. Untyped flags and `wrapJsonSchema()` flags stay optional.
133
+ **`inputSchema.required` is not the CLI `<value>` syntax.** `--query <query>` means the flag needs a value if it is present. The flag itself is still optional unless you chain `.required()`. Required **positionals** like `<env>` always go in `required`. `.required()` flags go in `required` too.
133
134
 
134
135
  ### Installing the MCP server in clients
135
136
 
@@ -15,6 +15,7 @@ function createCli() {
15
15
  cli
16
16
  .command("say hi", "Say hello")
17
17
  .option("--name <name>", z.string().describe("Person to greet"))
18
+ .required()
18
19
  .option("--caps", z.boolean().default(false).describe("Uppercase output"))
19
20
  .action((options) => {
20
21
  const message = `Hello ${options.name}!`;
@@ -29,7 +29,9 @@ describe("createMcpAction", () => {
29
29
  cli
30
30
  .command("add", "Add numbers")
31
31
  .option("--a <a>", z.number().describe("First"))
32
+ .required()
32
33
  .option("--b <b>", z.number().describe("Second"))
34
+ .required()
33
35
  .action((options) => ({ sum: options.a + options.b }));
34
36
  const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
35
37
  cli.command("mcp", "Start MCP server").action(createMcpAction({
@@ -186,7 +188,7 @@ describe("createMcpAction", () => {
186
188
  expect(searchTool.description).toBe("Search for items");
187
189
  expect(searchTool.inputSchema.properties).toHaveProperty("query");
188
190
  expect(searchTool.inputSchema.properties).toHaveProperty("limit");
189
- expect(searchTool.inputSchema.required).toEqual(["query"]);
191
+ expect(searchTool.inputSchema.required).toBeUndefined();
190
192
  const deployTool = tools.tools.find((t) => t.name === "deploy");
191
193
  expect(deployTool.inputSchema.properties).toHaveProperty("env");
192
194
  expect(deployTool.inputSchema.properties).toHaveProperty("dryRun");
@@ -271,12 +273,14 @@ describe("createMcpAction", () => {
271
273
  await client.close();
272
274
  }
273
275
  });
274
- it("puts schema-required --flag <value> in inputSchema.required", async () => {
276
+ it("puts .required() --flag <value> in inputSchema.required", async () => {
275
277
  const cli = goke("checks");
276
278
  cli
277
279
  .command("checks create", "Create a check")
278
280
  .option("--url <url>", z.string().describe("URL to check"))
281
+ .required()
279
282
  .option("--name <name>", z.string().describe("Check name"))
283
+ .required()
280
284
  .option("--timeout [ms]", z.number().optional().describe("Timeout"))
281
285
  .action((options) => options);
282
286
  const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
@@ -90,6 +90,7 @@ function buildBaseCli() {
90
90
  cli
91
91
  .command("save <filename>", "Save content to a file in the tenant workspace")
92
92
  .option("--content <content>", z.string().describe("File content"))
93
+ .required()
93
94
  .action(async (filename, options, ctx) => {
94
95
  const full = path.posix.join(ctx.process.cwd, filename);
95
96
  await ctx.fs.writeFile(full, options.content);
@@ -1 +1 @@
1
- {"version":3,"file":"cli-to-mcp.d.ts","sourceRoot":"","sources":["../src/cli-to-mcp.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAS/E,OAAO,EAML,KAAK,IAAI,EAIV,MAAM,MAAM,CAAC;AAwFd,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,IAAI,CAAC;IACV,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD,gBAAgB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,CAAC;CACpD;AA4fD,MAAM,WAAW,sBAAsB;IACrC,mEAAmE;IACnE,GAAG,EAAE,IAAI,CAAC;IACV,iGAAiG;IACjG,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD,iCAAiC;IACjC,gBAAgB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,CAAC;IACnD,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,eAAe,CAAC,EAAE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACxD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAsClG;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAyCvE"}
1
+ {"version":3,"file":"cli-to-mcp.d.ts","sourceRoot":"","sources":["../src/cli-to-mcp.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAS/E,OAAO,EAKL,KAAK,IAAI,EAIV,MAAM,MAAM,CAAC;AAyFd,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,IAAI,CAAC;IACV,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD,gBAAgB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,CAAC;CACpD;AAufD,MAAM,WAAW,sBAAsB;IACrC,mEAAmE;IACnE,GAAG,EAAE,IAAI,CAAC;IACV,iGAAiG;IACjG,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD,iCAAiC;IACjC,gBAAgB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,CAAC;IACnD,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,eAAe,CAAC,EAAE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACxD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAsClG;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAyCvE"}
@@ -5,7 +5,7 @@
5
5
  * or a high-level McpServer by mounting tools/list + tools/call handlers.
6
6
  */
7
7
  import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
8
- import { coerceBySchema, extractJsonSchema, GokeProcessExit, schemaAcceptsOmittedValue, } from "goke";
8
+ import { coerceBySchema, extractJsonSchema, GokeProcessExit, } from "goke";
9
9
  const CLI_TO_MCP_STATE = Symbol.for("@goke/mcp/cli-to-mcp-state");
10
10
  function createTextCaptureStream() {
11
11
  const chunks = [];
@@ -360,14 +360,11 @@ function createBinding(cli, command, toolName) {
360
360
  }
361
361
  }
362
362
  // `--days <days>` means "value required if the flag is present". The flag
363
- // itself is required only when a non-optional schema rejects `undefined`.
363
+ // itself is required only when `.required()` set `flagRequired`.
364
364
  for (const option of options) {
365
365
  const normalized = normalizeOptionSchema(option);
366
366
  properties[option.name] = normalized.schema;
367
- if (option.required
368
- && option.default === undefined
369
- && option.schema
370
- && !schemaAcceptsOmittedValue(option.schema)) {
367
+ if (option.flagRequired && option.default === undefined) {
371
368
  requiredNames.push(option.name);
372
369
  }
373
370
  optionBindings.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goke/mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Dynamically generate CLI commands from MCP server tools",
6
6
  "repository": {
@@ -51,7 +51,7 @@
51
51
  "@types/node": "^22.19.7",
52
52
  "vitest": "^3.1.0",
53
53
  "zod": "^4.3.6",
54
- "goke": "^6.16.0"
54
+ "goke": "^6.17.0"
55
55
  },
56
56
  "scripts": {
57
57
  "clean": "rm -rf dist",
@@ -18,6 +18,7 @@ function createCli() {
18
18
  cli
19
19
  .command("say hi", "Say hello")
20
20
  .option("--name <name>", z.string().describe("Person to greet"))
21
+ .required()
21
22
  .option("--caps", z.boolean().default(false).describe("Uppercase output"))
22
23
  .action((options) => {
23
24
  const message = `Hello ${options.name}!`;
@@ -35,7 +35,9 @@ describe("createMcpAction", () => {
35
35
  cli
36
36
  .command("add", "Add numbers")
37
37
  .option("--a <a>", z.number().describe("First"))
38
+ .required()
38
39
  .option("--b <b>", z.number().describe("Second"))
40
+ .required()
39
41
  .action((options) => ({ sum: options.a + options.b }));
40
42
 
41
43
  const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
@@ -238,7 +240,7 @@ describe("createMcpAction", () => {
238
240
  expect(searchTool.description).toBe("Search for items");
239
241
  expect(searchTool.inputSchema.properties).toHaveProperty("query");
240
242
  expect(searchTool.inputSchema.properties).toHaveProperty("limit");
241
- expect(searchTool.inputSchema.required).toEqual(["query"]);
243
+ expect(searchTool.inputSchema.required).toBeUndefined();
242
244
 
243
245
  const deployTool = tools.tools.find((t) => t.name === "deploy")!;
244
246
  expect(deployTool.inputSchema.properties).toHaveProperty("env");
@@ -346,13 +348,15 @@ describe("createMcpAction", () => {
346
348
  }
347
349
  });
348
350
 
349
- it("puts schema-required --flag <value> in inputSchema.required", async () => {
351
+ it("puts .required() --flag <value> in inputSchema.required", async () => {
350
352
  const cli = goke("checks");
351
353
 
352
354
  cli
353
355
  .command("checks create", "Create a check")
354
356
  .option("--url <url>", z.string().describe("URL to check"))
357
+ .required()
355
358
  .option("--name <name>", z.string().describe("Check name"))
359
+ .required()
356
360
  .option("--timeout [ms]", z.number().optional().describe("Timeout"))
357
361
  .action((options) => options);
358
362
 
@@ -104,6 +104,7 @@ function buildBaseCli(): Goke {
104
104
  cli
105
105
  .command("save <filename>", "Save content to a file in the tenant workspace")
106
106
  .option("--content <content>", z.string().describe("File content"))
107
+ .required()
107
108
  .action(async (filename: string, options: { content: string }, ctx) => {
108
109
  const full = path.posix.join(ctx.process.cwd, filename);
109
110
  await ctx.fs.writeFile(full, options.content);
package/src/cli-to-mcp.ts CHANGED
@@ -20,7 +20,6 @@ import {
20
20
  coerceBySchema,
21
21
  extractJsonSchema,
22
22
  GokeProcessExit,
23
- schemaAcceptsOmittedValue,
24
23
  type Command,
25
24
  type Goke,
26
25
  type GokeExecutionContext,
@@ -41,6 +40,7 @@ interface OptionLike {
41
40
  description: string;
42
41
  default?: unknown;
43
42
  required?: boolean;
43
+ flagRequired?: boolean;
44
44
  isBoolean?: boolean;
45
45
  schema?: StandardJSONSchemaV1;
46
46
  }
@@ -505,17 +505,12 @@ function createBinding(cli: Goke, command: Command, toolName: string): CliToolBi
505
505
  }
506
506
 
507
507
  // `--days <days>` means "value required if the flag is present". The flag
508
- // itself is required only when a non-optional schema rejects `undefined`.
508
+ // itself is required only when `.required()` set `flagRequired`.
509
509
  for (const option of options) {
510
510
  const normalized = normalizeOptionSchema(option);
511
511
  properties[option.name] = normalized.schema;
512
512
 
513
- if (
514
- option.required
515
- && option.default === undefined
516
- && option.schema
517
- && !schemaAcceptsOmittedValue(option.schema)
518
- ) {
513
+ if (option.flagRequired && option.default === undefined) {
519
514
  requiredNames.push(option.name);
520
515
  }
521
516