Skip to content

Commit b31c5e7

Browse files
committed
feat: Enhance installation process with Claude support and improved input handling
- Added new types and interfaces for installation commands in rpcTypes.ts. - Refactored RegistryServerCard component to support installation in both VS Code and Claude Code. - Introduced toggle groups for selecting installation mode and target. - Implemented utility functions for building installation commands and handling inputs in registryInstall.ts. - Improved error handling and user feedback during installation processes. - Updated UI to reflect changes in installation options and statuses.
1 parent cfaee16 commit b31c5e7

File tree

6 files changed

+898
-278
lines changed

6 files changed

+898
-278
lines changed

WHATS_NEW.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# What's New in Copilot MCP!
22

3+
## Version 0.0.85 – Claude Code, Bug Fixes, and Reliability Improvements 🛠
4+
5+
- Unified install controls let you choose between VS Code and Claude Code before running a single install button.
6+
- Claude Code support: Install MCP servers directly into Claude Code (if installed) using stdio or HTTP transports.
7+
8+
*Sponsored by [Cloud MCP](https://cloudmcp.run?utm_source=copilot-mcp&utm_medium=vscode&utm_campaign=whats-new-0.0.84) - Try out Cloud MCP's [Router Mode](https://cloudmcp.run/blog/cloud-mcp-router?utm_referrer=copilot-mcp) that significantly improves tool calling success by AI agents!*
9+
310
## Version 0.0.84 – Remote Registry Installs Are Back 🎯
411
*(October 8 2025)*
512

@@ -12,10 +19,6 @@ What’s new
1219
- Remote MCP server results will show an "Install Remote" button that will set up VSCode with the server.
1320

1421

15-
*Sponsored by [Cloud MCP](https://cloudmcp.run?utm_source=copilot-mcp&utm_medium=vscode&utm_campaign=whats-new-0.0.84) - Try out Cloud MCP's [Router Mode](https://cloudmcp.run/blog/cloud-mcp-router?utm_referrer=copilot-mcp) that significantly improves tool calling success by AI agents!*
16-
17-
18-
1922
## Version 0.0.80 – Official MCP Registry Search + Direct Installs 🎉
2023
*(September 12 2025)*
2124

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
"url": "https://github.com/vikashloomba/copilot-mcp",
1111
"type": "git"
1212
},
13-
"displayName": "Copilot MCP ",
13+
"displayName": "Copilot MCP",
1414
"description": "Search, manage, and install open-source MCP servers",
15-
"version": "0.0.84",
15+
"version": "0.0.85",
1616
"icon": "logo.png",
1717
"engines": {
1818
"vscode": "^1.101.0"

src/panels/ExtensionPanel.ts

Lines changed: 190 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,28 @@ import {
1919
import { TelemetryEvents } from "../telemetry/types";
2020
import { Messenger } from "vscode-messenger";
2121
import {
22-
aiAssistedSetupType,
23-
deleteServerType,
24-
getMcpConfigType,
25-
searchServersType,
26-
sendFeedbackType,
27-
updateMcpConfigType,
28-
updateServerEnvVarType,
29-
cloudMCPInterestType,
30-
previewReadmeType,
31-
installFromConfigType,
32-
registrySearchType,
22+
aiAssistedSetupType,
23+
deleteServerType,
24+
getMcpConfigType,
25+
searchServersType,
26+
sendFeedbackType,
27+
updateMcpConfigType,
28+
updateServerEnvVarType,
29+
cloudMCPInterestType,
30+
previewReadmeType,
31+
installFromConfigType,
32+
installClaudeFromConfigType,
33+
registrySearchType,
34+
} from "../shared/types/rpcTypes";
35+
import type {
36+
InstallCommandPayload,
37+
InstallInput,
38+
InstallTransport,
39+
ClaudeInstallRequest,
3340
} from "../shared/types/rpcTypes";
3441
import axios from "axios";
42+
import { outputLogger } from "../utilities/outputLogger";
43+
import { resolve } from "dns";
3544

3645
// Helper function to read servers from .vscode/mcp.json
3746
async function getServersFromMcpJsonFile(
@@ -51,7 +60,7 @@ async function getServersFromMcpJsonFile(
5160

5261
// Consolidates servers from global settings, workspace settings, and .vscode/mcp.json files
5362
async function getAllServers(): Promise<Record<string, any>> {
54-
const config = vscode.workspace.getConfiguration("mcp");
63+
const config = vscode.workspace.getConfiguration("mcp");
5564

5665
// 1. Get servers from global settings
5766
const globalServers = config.inspect<Record<string, any>>("servers")?.globalValue || {};
@@ -74,7 +83,136 @@ async function getAllServers(): Promise<Record<string, any>> {
7483
mergedServers = { ...mergedServers, ...workspaceSettingsServers };
7584
mergedServers = { ...mergedServers, ...mcpJsonFileServers };
7685

77-
return mergedServers;
86+
return mergedServers;
87+
}
88+
89+
const INPUT_PLACEHOLDER_REGEX = /\\?\${input:([^}]+)}/g;
90+
91+
function replaceInputPlaceholders(value: string, replacements: Map<string, string>): string {
92+
return value.replace(INPUT_PLACEHOLDER_REGEX, (_, rawId) => {
93+
const key = String(rawId ?? "").trim();
94+
if (!key) {
95+
return "";
96+
}
97+
return replacements.get(key) ?? "";
98+
});
99+
}
100+
101+
function applyInputsToPayload(
102+
payload: InstallCommandPayload,
103+
replacements: Map<string, string>
104+
): InstallCommandPayload {
105+
const args = payload.args?.map((arg) => replaceInputPlaceholders(arg, replacements));
106+
const envEntries = payload.env ? Object.entries(payload.env) : [];
107+
const env = envEntries.length
108+
? envEntries.reduce<Record<string, string>>((acc, [key, value]) => {
109+
acc[key] = replaceInputPlaceholders(value, replacements);
110+
return acc;
111+
}, {})
112+
: undefined;
113+
const headers = payload.headers?.map((header) => ({
114+
name: header.name,
115+
value: header.value !== undefined ? replaceInputPlaceholders(header.value, replacements) : header.value,
116+
}));
117+
118+
return {
119+
...payload,
120+
args,
121+
env,
122+
headers,
123+
inputs: undefined,
124+
};
125+
}
126+
127+
async function collectInstallInputs(inputs?: InstallInput[]): Promise<{
128+
values: Map<string, string>;
129+
canceled: boolean;
130+
}> {
131+
const values = new Map<string, string>();
132+
if (!inputs || inputs.length === 0) {
133+
return { values, canceled: false };
134+
}
135+
136+
for (const input of inputs) {
137+
if (values.has(input.id)) {
138+
continue;
139+
}
140+
const response = await vscode.window.showInputBox({
141+
prompt: input.description,
142+
password: input.password ?? false,
143+
ignoreFocusOut: true,
144+
});
145+
if (response === undefined) {
146+
return { values, canceled: true };
147+
}
148+
values.set(input.id, response);
149+
}
150+
151+
return { values, canceled: false };
152+
}
153+
154+
function headersArrayToRecord(headers?: Array<{ name: string; value: string }>) {
155+
if (!headers) {
156+
return undefined;
157+
}
158+
const record: Record<string, string> = {};
159+
for (const header of headers) {
160+
if (!header?.name) {
161+
continue;
162+
}
163+
record[header.name] = header.value ?? "";
164+
}
165+
return Object.keys(record).length > 0 ? record : undefined;
166+
}
167+
168+
function buildClaudeConfigObject(payload: InstallCommandPayload, transport: InstallTransport) {
169+
const config: Record<string, unknown> = { type: transport };
170+
if (transport === "stdio") {
171+
if (payload.command) {
172+
config.command = payload.command;
173+
}
174+
if (payload.args && payload.args.length > 0) {
175+
config.args = payload.args;
176+
}
177+
if (payload.env && Object.keys(payload.env).length > 0) {
178+
config.env = payload.env;
179+
}
180+
} else {
181+
if (payload.url) {
182+
config.url = payload.url;
183+
}
184+
const headerRecord = headersArrayToRecord(payload.headers);
185+
if (headerRecord) {
186+
config.headers = headerRecord;
187+
}
188+
}
189+
return config;
190+
}
191+
192+
async function performVscodeInstall(payload: InstallCommandPayload) {
193+
const response = await openMcpInstallUri(payload);
194+
return !!(response && (response as any).success);
195+
}
196+
197+
async function runClaudeCliTask(
198+
name: string,
199+
transport: InstallTransport,
200+
config: Record<string, unknown>,
201+
): Promise<void> {
202+
const claudeBinary = "claude";
203+
const configJson = JSON.stringify(config);
204+
outputLogger.info("Config", config);
205+
206+
const shellExecution = vscode.window.createTerminal({
207+
name: `Claude MCP Install (${name})`,
208+
});
209+
210+
shellExecution.show();
211+
shellExecution.sendText(`${claudeBinary} mcp add-json ${name} '${configJson}'`);
212+
213+
void vscode.window.showInformationMessage(
214+
"Claude CLI install started. See Terminal Output for progress.",
215+
);
78216
}
79217

80218

@@ -184,15 +322,42 @@ export class CopilotMcpViewProvider implements vscode.WebviewViewProvider {
184322
});
185323

186324
// Direct install path from structured config (Official Registry results)
187-
messenger.onRequest(installFromConfigType, async (payload) => {
188-
try {
189-
const cmdResponse = await openMcpInstallUri(payload as any);
190-
return !!(cmdResponse && (cmdResponse as any).success);
191-
} catch (error) {
192-
console.error("Error during direct install: ", error);
193-
return false;
194-
}
195-
});
325+
messenger.onRequest(installFromConfigType, async (payload) => {
326+
try {
327+
logWebviewInstallAttempt(payload.name);
328+
return await performVscodeInstall(payload);
329+
} catch (error) {
330+
console.error("Error during direct install: ", error);
331+
logError(error as Error, "registry-install", { target: "vscode" });
332+
return false;
333+
}
334+
});
335+
336+
messenger.onRequest(installClaudeFromConfigType, async (payload: ClaudeInstallRequest) => {
337+
logWebviewInstallAttempt(payload.name);
338+
const { values, canceled } = await collectInstallInputs(payload.inputs);
339+
if (canceled) {
340+
void vscode.window.showInformationMessage("Claude installation canceled.");
341+
return;
342+
}
343+
344+
const substitutedPayload = applyInputsToPayload(payload, values);
345+
346+
try {
347+
const config = buildClaudeConfigObject(substitutedPayload, payload.transport);
348+
await runClaudeCliTask(payload.name, payload.transport, config);
349+
} catch (error) {
350+
logError(error as Error, "claude-cli-install", {
351+
transport: payload.transport,
352+
mode: payload.mode,
353+
});
354+
void vscode.window.showErrorMessage(
355+
error instanceof Error
356+
? error.message
357+
: "Claude CLI failed to start. Check the terminal output.",
358+
);
359+
}
360+
});
196361

197362
// Official Registry search proxied via extension (avoids webview CORS)
198363
messenger.onRequest(registrySearchType, async (payload) => {
@@ -202,7 +367,9 @@ export class CopilotMcpViewProvider implements vscode.WebviewViewProvider {
202367
limit: payload.limit ?? 10,
203368
search: payload.search,
204369
};
205-
if (payload.cursor) params.cursor = payload.cursor;
370+
if (payload.cursor) {
371+
params.cursor = payload.cursor;
372+
}
206373
const res = await axios.get('https://registry.modelcontextprotocol.io/v0/servers', { params });
207374
const data = res?.data ?? {};
208375
return {

src/shared/types/rpcTypes.ts

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,34 @@
11
import {
2-
type RequestType,
3-
type NotificationType,
2+
type RequestType,
3+
type NotificationType,
44
} from "vscode-messenger-common";
55

6+
export type InstallInput = {
7+
type: "promptString";
8+
id: string;
9+
description?: string;
10+
password?: boolean;
11+
};
12+
13+
export interface InstallCommandPayload {
14+
name: string;
15+
command?: string;
16+
args?: string[];
17+
env?: Record<string, string>;
18+
url?: string;
19+
headers?: Array<{ name: string; value: string }>;
20+
inputs?: InstallInput[];
21+
}
22+
23+
export type InstallTransport = "stdio" | "http" | "sse";
24+
25+
export type InstallMode = "package" | "remote";
26+
27+
export interface ClaudeInstallRequest extends InstallCommandPayload {
28+
transport: InstallTransport;
29+
mode: InstallMode;
30+
}
31+
632
export const searchServersType: RequestType<
733
{ query: string;
834
page?: number;
@@ -56,15 +82,13 @@ export const previewReadmeType: NotificationType<{
5682
}> = { method: "previewReadme" };
5783

5884
// Direct installation from a structured config (used by Official Registry results)
59-
export const installFromConfigType: RequestType<{
60-
name: string;
61-
command?: string;
62-
args?: string[];
63-
env?: Record<string, string>;
64-
inputs?: Array<{ type: 'promptString'; id: string; description?: string; password?: boolean }>;
65-
url?: string; // for remote installs
66-
headers?: Array<{ name: string; value: string }>; // for remote installs with headers
67-
}, boolean> = { method: "installFromConfig" };
85+
export const installFromConfigType: RequestType<InstallCommandPayload, boolean> = {
86+
method: "installFromConfig",
87+
};
88+
89+
export const installClaudeFromConfigType: RequestType<ClaudeInstallRequest, void> = {
90+
method: "installClaudeFromConfig",
91+
};
6892

6993
// Official Registry search (proxied via extension to avoid CORS)
7094
export const registrySearchType: RequestType<

0 commit comments

Comments
 (0)