export type MCPInput = {
type: string;
id: string;
description?: string;
password?: boolean;
};
export type CommandConfig = {
command: string; // e.g., "npx"
args?: string[];
env?: Record<string, string>;
};
// URLSearchParams will handle encoding; pass raw JSON strings
function toJson(v: unknown): string {
return JSON.stringify(v);
}
function makeBadge(color: string, label: string) {
return `https://img.shields.io/badge/${encodeURIComponent(label)}-NPM-${color}?style=flat-square&logo=visualstudiocode&logoColor=white`;
}
function buildInstallLink(base: string, name: string, inputs: MCPInput[], config: CommandConfig, insiders = false) {
const parts: string[] = [];
parts.push(`name=${encodeURIComponent(name)}`);
if (inputs && inputs.length) {
parts.push(`inputs=${encodeURIComponent(JSON.stringify(inputs))}`);
}
parts.push(`config=${encodeURIComponent(JSON.stringify(config))}`);
if (insiders) parts.push(`quality=insiders`);
return `${base}?${parts.join('&')}`;
}
export function generateButtonsMarkdown(name: string, inputs: MCPInput[], config: CommandConfig): string {
const base = "https://insiders.vscode.dev/redirect/mcp/install";
const stableBadge = makeBadge("0098FF", "VS_Code");
const insidersBadge = makeBadge("24bfa5", "VS_Code_Insiders");
const stableLink = buildInstallLink(base, name, inputs, config, false);
const insidersLink = buildInstallLink(base, name, inputs, config, true);
const stable = `[](${stableLink})`;
const insiders = `[](${insidersLink})`;
return `${stable}\n${insiders}`;
}
export function fromMcpConfigObject(name: string, configObj: any): { inputs: MCPInput[]; config: CommandConfig } {
// Accepts a JSON like the example and converts env placeholders ${input:...} to VS Code expected rendering untouched.
const inputs = Array.isArray(configObj.inputs) ? (configObj.inputs as MCPInput[]) : [];
// For NPX-based servers, we expect the consumer to specify the npx invocation
// Example: { command: 'npx', args: ['-y', '@scope/server@latest', '--flag'], env: { KEY: '${input:id}' } }
// If not given, default to npx with no args
const config: CommandConfig = configObj.config ?? { command: 'npx', args: [] };
return { inputs, config };
}
// ==========================
// VS Code Install Buttons (Awesome Copilot style)
// ==========================
type CopilotInstallKind = 'chat-instructions' | 'chat-prompt' | 'chat-mode';
function makeInstallBadge(color: string, label: string) {
// e.g., VS_Code-Install-0098FF
return `https://img.shields.io/badge/${encodeURIComponent(label)}-Install-${color}?style=flat-square&logo=visualstudiocode&logoColor=white`;
}
function buildCopilotInstallLink(kind: CopilotInstallKind, rawUrl: string, insiders = false) {
const scheme = insiders ? 'vscode-insiders' : 'vscode';
const host = insiders ? 'https://insiders.vscode.dev/redirect' : 'https://vscode.dev/redirect';
// Encode the prefix and the raw URL separately so inner % are not double-encoded
const prefix = encodeURIComponent(`${scheme}:${kind}/install?url=`);
const raw = encodeURIComponent(rawUrl);
return `${host}?url=${prefix}${raw}`;
}
export function generateCopilotInstallButtons(kind: CopilotInstallKind, rawUrl: string): string {
const stableBadge = makeInstallBadge('0098FF', 'VS_Code');
const insidersBadge = makeInstallBadge('24bfa5', 'VS_Code_Insiders');
const stableLink = buildCopilotInstallLink(kind, rawUrl, false);
const insidersLink = buildCopilotInstallLink(kind, rawUrl, true);
const stable = `[](${stableLink})`;
const insiders = `[](${insidersLink})`;
return `${stable}\n${insiders}`;
}
export function buildRawGithubUrl(owner: string, repo: string, branch: string, path: string): string {
return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`;
}
export function generateCopilotInstallButtonsFromGithub(kind: CopilotInstallKind, owner: string, repo: string, path: string, branch = 'main'): string {
const raw = buildRawGithubUrl(owner, repo, branch, path);
return generateCopilotInstallButtons(kind, raw);
}