Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,16 @@ rli gateway-config update <id> # Update a gateway configuration
rli gateway-config delete <id> # Delete a gateway configuration
```

### Mcp-config Commands (alias: `mcpc`)

```bash
rli mcp-config list # List MCP configurations
rli mcp-config create # Create a new MCP configuration
rli mcp-config get <id> # Get MCP configuration details
rli mcp-config update <id> # Update an MCP configuration
rli mcp-config delete <id> # Delete an MCP configuration
```

### Mcp Commands

```bash
Expand Down
29 changes: 29 additions & 0 deletions src/commands/devbox/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface CreateOptions {
networkPolicy?: string;
tunnel?: string;
gateways?: string[];
mcp?: string[];
output?: string;
}

Expand Down Expand Up @@ -112,6 +113,29 @@ function parseGateways(
return result;
}

function parseMcpSpecs(
specs: string[],
): Array<{ mcp_config: string; secret: string }> {
return specs.map((spec) => {
const commaIndex = spec.indexOf(",");
if (commaIndex === -1) {
throw new Error(
`Invalid MCP spec format: ${spec}. Expected mcp_config_id_or_name,secret_id_or_name`,
);
}
const mcpConfig = spec.substring(0, commaIndex);
const secret = spec.substring(commaIndex + 1);

if (!mcpConfig || !secret) {
throw new Error(
`Invalid MCP spec format: ${spec}. Expected mcp_config_id_or_name,secret_id_or_name`,
);
}

return { mcp_config: mcpConfig, secret };
});
}

export async function createDevbox(options: CreateOptions = {}) {
try {
const client = getClient();
Expand Down Expand Up @@ -233,6 +257,11 @@ export async function createDevbox(options: CreateOptions = {}) {
createRequest.gateways = parseGateways(options.gateways);
}

// Handle MCP configs
if (options.mcp && options.mcp.length > 0) {
createRequest.mcp = parseMcpSpecs(options.mcp);
}

if (Object.keys(launchParameters).length > 0) {
createRequest.launch_parameters = launchParameters;
}
Expand Down
36 changes: 20 additions & 16 deletions src/commands/gateway-config/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -210,13 +210,13 @@ const ListGatewayConfigsUI = ({
},
{
key: "edit",
label: "Edit AI Gateway Config",
label: "Edit Agent Gateway Config",
color: colors.warning,
icon: figures.pointer,
},
{
key: "delete",
label: "Delete AI Gateway Config",
label: "Delete Agent Gateway Config",
color: colors.error,
icon: figures.cross,
},
Expand Down Expand Up @@ -320,7 +320,7 @@ const ListGatewayConfigsUI = ({
case "delete":
await client.gatewayConfigs.delete(config.id);
setOperationResult(
`AI gateway config "${config.name}" deleted successfully`,
`Agent gateway config "${config.name}" deleted successfully`,
);
break;
}
Expand Down Expand Up @@ -500,11 +500,11 @@ const ListGatewayConfigsUI = ({
if (showDeleteConfirm && selectedConfig) {
return (
<ConfirmationPrompt
title="Delete AI Gateway Config"
title="Delete Agent Gateway Config"
message={`Are you sure you want to delete "${selectedConfig.name}"?`}
details="This action cannot be undone. Any devboxes using this AI gateway config will no longer have access to it."
details="This action cannot be undone. Any devboxes using this Agent gateway config will no longer have access to it."
breadcrumbItems={[
{ label: "AI Gateway Configs" },
{ label: "Agent Gateway Configs" },
{ label: selectedConfig.name || selectedConfig.id },
{ label: "Delete", active: true },
]}
Expand All @@ -530,7 +530,7 @@ const ListGatewayConfigsUI = ({
<>
<Breadcrumb
items={[
{ label: "AI Gateway Configs" },
{ label: "Agent Gateway Configs" },
{
label: selectedConfig?.name || selectedConfig?.id || "Config",
},
Expand All @@ -553,13 +553,13 @@ const ListGatewayConfigsUI = ({
operations.find((o) => o.key === executingOperation)?.label ||
"Operation";
const messages: Record<string, string> = {
delete: "Deleting AI gateway config...",
delete: "Deleting Agent gateway config...",
};
return (
<>
<Breadcrumb
items={[
{ label: "AI Gateway Configs" },
{ label: "Agent Gateway Configs" },
{ label: selectedConfig.name || selectedConfig.id },
{ label: operationLabel, active: true },
]}
Expand Down Expand Up @@ -608,8 +608,10 @@ const ListGatewayConfigsUI = ({
if (loading && configs.length === 0) {
return (
<>
<Breadcrumb items={[{ label: "AI Gateway Configs", active: true }]} />
<SpinnerComponent message="Loading AI gateway configs..." />
<Breadcrumb
items={[{ label: "Agent Gateway Configs", active: true }]}
/>
<SpinnerComponent message="Loading Agent gateway configs..." />
</>
);
}
Expand All @@ -618,9 +620,11 @@ const ListGatewayConfigsUI = ({
if (error) {
return (
<>
<Breadcrumb items={[{ label: "AI Gateway Configs", active: true }]} />
<Breadcrumb
items={[{ label: "Agent Gateway Configs", active: true }]}
/>
<ErrorMessage
message="Failed to list AI gateway configs"
message="Failed to list Agent gateway configs"
error={error}
/>
</>
Expand All @@ -630,7 +634,7 @@ const ListGatewayConfigsUI = ({
// Main list view
return (
<>
<Breadcrumb items={[{ label: "AI Gateway Configs", active: true }]} />
<Breadcrumb items={[{ label: "Agent Gateway Configs", active: true }]} />

{/* Search bar */}
<SearchBar
Expand All @@ -640,7 +644,7 @@ const ListGatewayConfigsUI = ({
resultCount={totalCount}
onSearchChange={search.setSearchQuery}
onSearchSubmit={search.submitSearch}
placeholder="Search AI gateway configs..."
placeholder="Search Agent gateway configs..."
/>

{/* Table - hide when popup is shown */}
Expand All @@ -653,7 +657,7 @@ const ListGatewayConfigsUI = ({
columns={columns}
emptyState={
<Text color={colors.textDim}>
{figures.info} No AI gateway configs found. Press [c] to create
{figures.info} No Agent gateway configs found. Press [c] to create
one.
</Text>
}
Expand Down
52 changes: 52 additions & 0 deletions src/commands/mcp-config/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Create MCP config command
*/

import { getClient } from "../../utils/client.js";
import { output, outputError } from "../../utils/output.js";
import { validateMcpConfig } from "../../utils/mcpConfigValidation.js";

interface CreateOptions {
name: string;
endpoint: string;
allowedTools: string;
description?: string;
output?: string;
}

export async function createMcpConfig(options: CreateOptions) {
try {
const client = getClient();

const validation = validateMcpConfig(
{
name: options.name,
endpoint: options.endpoint,
allowedTools: options.allowedTools,
},
{ requireName: true, requireEndpoint: true, requireAllowedTools: true },
);

if (!validation.valid) {
outputError(validation.errors.join("\n"));
return;
}

const { sanitized } = validation;

const config = await client.mcpConfigs.create({
name: sanitized!.name!,
endpoint: sanitized!.endpoint!,
allowed_tools: sanitized!.allowedTools!,
description: options.description?.trim() || undefined,
});

if (!options.output || options.output === "text") {
console.log(config.id);
} else {
output(config, { format: options.output, defaultFormat: "json" });
}
} catch (error) {
outputError("Failed to create MCP config", error);
}
}
29 changes: 29 additions & 0 deletions src/commands/mcp-config/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Delete MCP config command
*/

import { getClient } from "../../utils/client.js";
import { output, outputError } from "../../utils/output.js";

interface DeleteOptions {
output?: string;
}

export async function deleteMcpConfig(id: string, options: DeleteOptions = {}) {
try {
const client = getClient();

await client.mcpConfigs.delete(id);

if (!options.output || options.output === "text") {
console.log(id);
} else {
output(
{ id, status: "deleted" },
{ format: options.output, defaultFormat: "json" },
);
}
} catch (error) {
outputError("Failed to delete MCP config", error);
}
}
26 changes: 26 additions & 0 deletions src/commands/mcp-config/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Get MCP config command - supports lookup by ID or name
*/

import { getMcpConfigByIdOrName } from "../../services/mcpConfigService.js";
import { output, outputError } from "../../utils/output.js";

interface GetOptions {
id: string;
output?: string;
}

export async function getMcpConfig(options: GetOptions) {
try {
const config = await getMcpConfigByIdOrName(options.id);

if (!config) {
outputError(`MCP config not found: ${options.id}`);
return;
}

output(config, { format: options.output, defaultFormat: "json" });
} catch (error) {
outputError("Failed to get MCP config", error);
}
}
77 changes: 77 additions & 0 deletions src/commands/mcp-config/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Update MCP config command
*/

import { getClient } from "../../utils/client.js";
import { output, outputError } from "../../utils/output.js";
import { validateMcpConfig } from "../../utils/mcpConfigValidation.js";

interface UpdateOptions {
id: string;
name?: string;
endpoint?: string;
allowedTools?: string;
description?: string;
output?: string;
}

export async function updateMcpConfig(options: UpdateOptions) {
try {
const client = getClient();

const validation = validateMcpConfig(
{
name: options.name,
endpoint: options.endpoint,
allowedTools: options.allowedTools,
},
{
requireName: false,
requireEndpoint: false,
requireAllowedTools: false,
},
);

if (!validation.valid) {
outputError(validation.errors.join("\n"));
return;
}

const { sanitized } = validation;

const updateParams: Record<string, unknown> = {};

if (sanitized!.name) {
updateParams.name = sanitized!.name;
}
if (sanitized!.endpoint) {
updateParams.endpoint = sanitized!.endpoint;
}
if (sanitized!.allowedTools) {
updateParams.allowed_tools = sanitized!.allowedTools;
}
if (options.description !== undefined) {
updateParams.description = options.description.trim() || undefined;
}

if (Object.keys(updateParams).length === 0) {
outputError(
"No update options provided. Use --name, --endpoint, --allowed-tools, or --description",
);
return;
}

const config = await client.mcpConfigs.update(
options.id,
updateParams as Parameters<typeof client.mcpConfigs.update>[1],
);

if (!options.output || options.output === "text") {
console.log(config.id);
} else {
output(config, { format: options.output, defaultFormat: "json" });
}
} catch (error) {
outputError("Failed to update MCP config", error);
}
}
4 changes: 2 additions & 2 deletions src/commands/network-policy/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface CreateOptions {
description?: string;
allowAll?: boolean;
allowDevboxToDevbox?: boolean;
allowAiGateway?: boolean;
allowAgentGateway?: boolean;
allowMcpGateway?: boolean;
allowedHostnames?: string[];
output?: string;
Expand All @@ -25,7 +25,7 @@ export async function createNetworkPolicy(options: CreateOptions) {
description: options.description,
allow_all: options.allowAll ?? false,
allow_devbox_to_devbox: options.allowDevboxToDevbox ?? false,
allow_ai_gateway: options.allowAiGateway ?? false,
allow_ai_gateway: options.allowAgentGateway ?? false,
allow_mcp_gateway: options.allowMcpGateway ?? false,
allowed_hostnames: options.allowedHostnames ?? [],
});
Expand Down
Loading
Loading