Skip to content

Commit f5b655c

Browse files
authored
fix: cli commands for mcp configs (#125)
<img width="681" height="389" alt="image" src="https://github.com/user-attachments/assets/f3eccd27-d619-4566-999e-8e60e588f65f" />
1 parent 91e793b commit f5b655c

File tree

12 files changed

+936
-0
lines changed

12 files changed

+936
-0
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,16 @@ rli gateway-config update <id> # Update a gateway configuration
164164
rli gateway-config delete <id> # Delete a gateway configuration
165165
```
166166

167+
### Mcp-config Commands (alias: `mcpc`)
168+
169+
```bash
170+
rli mcp-config list # List MCP configurations
171+
rli mcp-config create # Create a new MCP configuration
172+
rli mcp-config get <id> # Get MCP configuration details
173+
rli mcp-config update <id> # Update an MCP configuration
174+
rli mcp-config delete <id> # Delete an MCP configuration
175+
```
176+
167177
### Mcp Commands
168178

169179
```bash

src/commands/devbox/create.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface CreateOptions {
2525
networkPolicy?: string;
2626
tunnel?: string;
2727
gateways?: string[];
28+
mcp?: string[];
2829
output?: string;
2930
}
3031

@@ -112,6 +113,29 @@ function parseGateways(
112113
return result;
113114
}
114115

116+
function parseMcpSpecs(
117+
specs: string[],
118+
): Array<{ mcp_config: string; secret: string }> {
119+
return specs.map((spec) => {
120+
const commaIndex = spec.indexOf(",");
121+
if (commaIndex === -1) {
122+
throw new Error(
123+
`Invalid MCP spec format: ${spec}. Expected mcp_config_id_or_name,secret_id_or_name`,
124+
);
125+
}
126+
const mcpConfig = spec.substring(0, commaIndex);
127+
const secret = spec.substring(commaIndex + 1);
128+
129+
if (!mcpConfig || !secret) {
130+
throw new Error(
131+
`Invalid MCP spec format: ${spec}. Expected mcp_config_id_or_name,secret_id_or_name`,
132+
);
133+
}
134+
135+
return { mcp_config: mcpConfig, secret };
136+
});
137+
}
138+
115139
export async function createDevbox(options: CreateOptions = {}) {
116140
try {
117141
const client = getClient();
@@ -233,6 +257,11 @@ export async function createDevbox(options: CreateOptions = {}) {
233257
createRequest.gateways = parseGateways(options.gateways);
234258
}
235259

260+
// Handle MCP configs
261+
if (options.mcp && options.mcp.length > 0) {
262+
createRequest.mcp = parseMcpSpecs(options.mcp);
263+
}
264+
236265
if (Object.keys(launchParameters).length > 0) {
237266
createRequest.launch_parameters = launchParameters;
238267
}

src/commands/mcp-config/create.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Create MCP config command
3+
*/
4+
5+
import { getClient } from "../../utils/client.js";
6+
import { output, outputError } from "../../utils/output.js";
7+
import { validateMcpConfig } from "../../utils/mcpConfigValidation.js";
8+
9+
interface CreateOptions {
10+
name: string;
11+
endpoint: string;
12+
allowedTools: string;
13+
description?: string;
14+
output?: string;
15+
}
16+
17+
export async function createMcpConfig(options: CreateOptions) {
18+
try {
19+
const client = getClient();
20+
21+
const validation = validateMcpConfig(
22+
{
23+
name: options.name,
24+
endpoint: options.endpoint,
25+
allowedTools: options.allowedTools,
26+
},
27+
{ requireName: true, requireEndpoint: true, requireAllowedTools: true },
28+
);
29+
30+
if (!validation.valid) {
31+
outputError(validation.errors.join("\n"));
32+
return;
33+
}
34+
35+
const { sanitized } = validation;
36+
37+
const config = await client.mcpConfigs.create({
38+
name: sanitized!.name!,
39+
endpoint: sanitized!.endpoint!,
40+
allowed_tools: sanitized!.allowedTools!,
41+
description: options.description?.trim() || undefined,
42+
});
43+
44+
if (!options.output || options.output === "text") {
45+
console.log(config.id);
46+
} else {
47+
output(config, { format: options.output, defaultFormat: "json" });
48+
}
49+
} catch (error) {
50+
outputError("Failed to create MCP config", error);
51+
}
52+
}

src/commands/mcp-config/delete.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Delete MCP config command
3+
*/
4+
5+
import { getClient } from "../../utils/client.js";
6+
import { output, outputError } from "../../utils/output.js";
7+
8+
interface DeleteOptions {
9+
output?: string;
10+
}
11+
12+
export async function deleteMcpConfig(id: string, options: DeleteOptions = {}) {
13+
try {
14+
const client = getClient();
15+
16+
await client.mcpConfigs.delete(id);
17+
18+
if (!options.output || options.output === "text") {
19+
console.log(id);
20+
} else {
21+
output(
22+
{ id, status: "deleted" },
23+
{ format: options.output, defaultFormat: "json" },
24+
);
25+
}
26+
} catch (error) {
27+
outputError("Failed to delete MCP config", error);
28+
}
29+
}

src/commands/mcp-config/get.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Get MCP config command - supports lookup by ID or name
3+
*/
4+
5+
import { getMcpConfigByIdOrName } from "../../services/mcpConfigService.js";
6+
import { output, outputError } from "../../utils/output.js";
7+
8+
interface GetOptions {
9+
id: string;
10+
output?: string;
11+
}
12+
13+
export async function getMcpConfig(options: GetOptions) {
14+
try {
15+
const config = await getMcpConfigByIdOrName(options.id);
16+
17+
if (!config) {
18+
outputError(`MCP config not found: ${options.id}`);
19+
return;
20+
}
21+
22+
output(config, { format: options.output, defaultFormat: "json" });
23+
} catch (error) {
24+
outputError("Failed to get MCP config", error);
25+
}
26+
}

src/commands/mcp-config/update.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* Update MCP config command
3+
*/
4+
5+
import { getClient } from "../../utils/client.js";
6+
import { output, outputError } from "../../utils/output.js";
7+
import { validateMcpConfig } from "../../utils/mcpConfigValidation.js";
8+
9+
interface UpdateOptions {
10+
id: string;
11+
name?: string;
12+
endpoint?: string;
13+
allowedTools?: string;
14+
description?: string;
15+
output?: string;
16+
}
17+
18+
export async function updateMcpConfig(options: UpdateOptions) {
19+
try {
20+
const client = getClient();
21+
22+
const validation = validateMcpConfig(
23+
{
24+
name: options.name,
25+
endpoint: options.endpoint,
26+
allowedTools: options.allowedTools,
27+
},
28+
{
29+
requireName: false,
30+
requireEndpoint: false,
31+
requireAllowedTools: false,
32+
},
33+
);
34+
35+
if (!validation.valid) {
36+
outputError(validation.errors.join("\n"));
37+
return;
38+
}
39+
40+
const { sanitized } = validation;
41+
42+
const updateParams: Record<string, unknown> = {};
43+
44+
if (sanitized!.name) {
45+
updateParams.name = sanitized!.name;
46+
}
47+
if (sanitized!.endpoint) {
48+
updateParams.endpoint = sanitized!.endpoint;
49+
}
50+
if (sanitized!.allowedTools) {
51+
updateParams.allowed_tools = sanitized!.allowedTools;
52+
}
53+
if (options.description !== undefined) {
54+
updateParams.description = options.description.trim() || undefined;
55+
}
56+
57+
if (Object.keys(updateParams).length === 0) {
58+
outputError(
59+
"No update options provided. Use --name, --endpoint, --allowed-tools, or --description",
60+
);
61+
return;
62+
}
63+
64+
const config = await client.mcpConfigs.update(
65+
options.id,
66+
updateParams as Parameters<typeof client.mcpConfigs.update>[1],
67+
);
68+
69+
if (!options.output || options.output === "text") {
70+
console.log(config.id);
71+
} else {
72+
output(config, { format: options.output, defaultFormat: "json" });
73+
}
74+
} catch (error) {
75+
outputError("Failed to update MCP config", error);
76+
}
77+
}

src/utils/commands.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ export function createProgram(): Command {
6969
"--gateways <gateways...>",
7070
"Gateway configurations (format: ENV_PREFIX=gateway_id_or_name,secret_id_or_name)",
7171
)
72+
.option(
73+
"--mcp <specs...>",
74+
"MCP configurations (format: mcp_config_id_or_name,secret_id_or_name)",
75+
)
7276
.option(
7377
"-o, --output [format]",
7478
"Output format: text|json|yaml (default: text)",
@@ -902,6 +906,92 @@ export function createProgram(): Command {
902906
await deleteGatewayConfig(id, options);
903907
});
904908

909+
// MCP config commands
910+
const mcpConfig = program
911+
.command("mcp-config")
912+
.description("Manage MCP configurations")
913+
.alias("mcpc");
914+
915+
mcpConfig
916+
.command("list")
917+
.description("List MCP configurations")
918+
.option("--name <name>", "Filter by name")
919+
.option("--limit <n>", "Max results", "20")
920+
.option(
921+
"-o, --output [format]",
922+
"Output format: text|json|yaml (default: json)",
923+
)
924+
.action(async (options) => {
925+
const { listMcpConfigs } = await import("../commands/mcp-config/list.js");
926+
await listMcpConfigs(options);
927+
});
928+
929+
mcpConfig
930+
.command("create")
931+
.description("Create a new MCP configuration")
932+
.requiredOption("--name <name>", "MCP config name (required)")
933+
.requiredOption("--endpoint <url>", "Target endpoint URL (required)")
934+
.requiredOption(
935+
"--allowed-tools <tools>",
936+
"Allowed tool patterns, comma-separated (required, e.g. '*' or 'github.search_*,github.get_*')",
937+
)
938+
.option("--description <description>", "Description")
939+
.option(
940+
"-o, --output [format]",
941+
"Output format: text|json|yaml (default: text)",
942+
)
943+
.action(async (options) => {
944+
const { createMcpConfig } =
945+
await import("../commands/mcp-config/create.js");
946+
await createMcpConfig(options);
947+
});
948+
949+
mcpConfig
950+
.command("get <id>")
951+
.description("Get MCP configuration details")
952+
.option(
953+
"-o, --output [format]",
954+
"Output format: text|json|yaml (default: json)",
955+
)
956+
.action(async (id, options) => {
957+
const { getMcpConfig } = await import("../commands/mcp-config/get.js");
958+
await getMcpConfig({ id, ...options });
959+
});
960+
961+
mcpConfig
962+
.command("update <id>")
963+
.description("Update an MCP configuration")
964+
.option("--name <name>", "New name")
965+
.option("--endpoint <url>", "New endpoint URL")
966+
.option(
967+
"--allowed-tools <tools>",
968+
"New allowed tool patterns, comma-separated",
969+
)
970+
.option("--description <description>", "New description")
971+
.option(
972+
"-o, --output [format]",
973+
"Output format: text|json|yaml (default: text)",
974+
)
975+
.action(async (id, options) => {
976+
const { updateMcpConfig } =
977+
await import("../commands/mcp-config/update.js");
978+
await updateMcpConfig({ id, ...options });
979+
});
980+
981+
mcpConfig
982+
.command("delete <id>")
983+
.description("Delete an MCP configuration")
984+
.alias("rm")
985+
.option(
986+
"-o, --output [format]",
987+
"Output format: text|json|yaml (default: text)",
988+
)
989+
.action(async (id, options) => {
990+
const { deleteMcpConfig } =
991+
await import("../commands/mcp-config/delete.js");
992+
await deleteMcpConfig(id, options);
993+
});
994+
905995
// MCP server commands
906996
const mcp = program
907997
.command("mcp")

0 commit comments

Comments
 (0)