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
2 changes: 1 addition & 1 deletion src/backend/services/mcp/mcp-server-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export class MCPServerManager {
}

let options: CreateClientOptions | undefined;
if (config.transport === "inMemory" && "inMemoryServerId" in config) {
if (config.transport === "inMemory" && config.inMemoryServerId) {
const transport = MCPServerManager.internalClientTransports.get(config.inMemoryServerId);
if (!transport) {
throw new Error(
Expand Down
49 changes: 8 additions & 41 deletions src/backend/services/mcp/types.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,8 @@
import type { IOType } from "node:child_process";
import type Stream from "node:stream";

interface MCPBaseConfig {
id: string;
name: string;
description?: string;
transport: "streamableHttp" | "stdio" | "inMemory";
builtin?: boolean; // True for internal/built-in servers
toolWhitelist?: string[];
enabled?: boolean;
}

interface MCPHttpServerConfig extends MCPBaseConfig {
transport: "streamableHttp";
url: string; // For HTTP-based transports
headers?: Record<string, string>;
version?: string;
timeoutMs?: number;
}

interface MCPStdioServerConfig extends MCPBaseConfig {
transport: "stdio";
command: string; // For stdio transport
args?: string[];
env?: Record<string, string>;
stderr?: IOType | Stream | number;
cwd?: string;
}

interface MCPInMemoryServerConfig extends MCPBaseConfig {
transport: "inMemory";
inMemoryServerId: string; // Identifier for the in-memory server implementation
}

export type MCPServerConfig = MCPHttpServerConfig | MCPStdioServerConfig | MCPInMemoryServerConfig;

export interface MCPToolSummary {
name: string;
description?: string;
}
export type {
MCPHttpServerConfig,
MCPInMemoryServerConfig,
MCPServerConfig,
MCPStdioServerConfig,
MCPToolSummary,
Transport,
} from "../../../shared/types/mcp";
42 changes: 42 additions & 0 deletions src/shared/types/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,45 @@ export interface MCPStep {
timestamp?: number;
autoApproveAt?: number;
}

export type Transport = "streamableHttp" | "stdio" | "inMemory";

export interface MCPBaseConfig {
id: string;
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MCPBaseConfig.id is typed as a required string, but the UI now creates new configs with id: "" (see McpServerFormWrapper) as a sentinel for “not yet persisted”. That means the shared type doesn’t reflect the real contract and can lead to accidental empty IDs leaking into places that expect a stable identifier. Consider making id optional/nullable for “draft” configs (or introducing a separate Draft/New config type), and keep a non-empty id for persisted configs only (validated server-side).

Suggested change
id: string;
/**
* Unique identifier for persisted configs.
* For draft / not-yet-persisted configs this may be omitted, and the server
* is responsible for assigning and validating a non-empty id.
*/
id?: string;

Copilot uses AI. Check for mistakes.
name: string;
description?: string;
transport: Transport;
builtin?: boolean; // True for internal/built-in servers
toolWhitelist?: string[];
enabled?: boolean;
}

export interface MCPHttpServerConfig extends MCPBaseConfig {
transport: "streamableHttp";
url: string; // For HTTP-based transports
headers?: Record<string, string>;
version?: string;
timeoutMs?: number;
}

export interface MCPStdioServerConfig extends MCPBaseConfig {
transport: "stdio";
command: string; // For stdio transport
args?: string[];
env?: Record<string, string>;
stderr?: "inherit" | "ignore" | "pipe";
cwd?: string;
}

export interface MCPInMemoryServerConfig extends MCPBaseConfig {
transport: "inMemory";
inMemoryServerId?: string; // Identifier for the in-memory server implementation
builtin?: true;
}

export type MCPServerConfig = MCPHttpServerConfig | MCPStdioServerConfig | MCPInMemoryServerConfig;

export interface MCPToolSummary {
name: string;
description?: string;
}
42 changes: 4 additions & 38 deletions src/ui/src/components/settings/mcp/McpServerForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import type { MCPServerConfig, Transport } from "@shared/types/mcp";
import { type UseFormReturn, useForm } from "react-hook-form";
import { z } from "zod";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "../../ui/accordion";
Expand All @@ -16,41 +17,7 @@ import { Input } from "../../ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../ui/select";
import { Textarea } from "../../ui/textarea";

export type Transport = "streamableHttp" | "stdio" | "inMemory";

type MCPBaseConfig = {
id: string | null;
name: string;
description?: string;
transport: Transport;
builtin?: boolean;
toolWhitelist?: string[];
enabled?: boolean;
};

type MCPHttpServerConfig = MCPBaseConfig & {
transport: "streamableHttp";
url: string;
headers?: Record<string, string>;
version?: string;
timeoutMs?: number;
};

type MCPStdioServerConfig = MCPBaseConfig & {
transport: "stdio";
command: string;
args?: string[];
env?: Record<string, string>;
cwd?: string;
stderr?: "inherit" | "ignore" | "pipe";
};

type MCPInternalServerConfig = MCPBaseConfig & {
transport: "inMemory";
builtin: true;
};

export type MCPServerConfig = MCPHttpServerConfig | MCPStdioServerConfig | MCPInternalServerConfig;
export type { MCPServerConfig, Transport };

export const mcpServerSchema = z
.object({
Expand Down Expand Up @@ -468,7 +435,7 @@ export function McpServerFormWrapper({
}

const config: MCPServerConfig = {
id: initialData?.id ?? null,
id: initialData?.id ?? "",
name: data.name.trim(),
transport: "streamableHttp",
url: data.url?.trim() ?? "",
Expand All @@ -477,7 +444,6 @@ export function McpServerFormWrapper({
version: data.version?.trim() || undefined,
timeoutMs: typeof data.timeoutMs === "number" ? data.timeoutMs : undefined,
};

await onSubmit(config);
return;
}
Expand Down Expand Up @@ -546,7 +512,7 @@ export function McpServerFormWrapper({
const command = sanitizeSegment(data.command ?? "");

const config: MCPServerConfig = {
id: initialData?.id ?? null,
id: initialData?.id ?? "",
name: data.name.trim(),
transport: "stdio",
command,
Expand Down
Loading