#!/usr/bin/env node
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { generateButtonsMarkdown, fromMcpConfigObject, generateCopilotInstallButtons, generateCopilotInstallButtonsFromGithub } from "./lib/buttons.js";
type Kind = 'chat-instructions' | 'chat-prompt' | 'chat-mode';
function printCliUsage() {
console.error(`Usage:
mcp-vsc-button-gen --kind <chat-instructions|chat-prompt|chat-mode> --raw <url>
mcp-vsc-button-gen --kind <chat-instructions|chat-prompt|chat-mode> --owner <owner> --repo <repo> --path <path> [--branch <branch>]
Examples:
mcp-vsc-button-gen --kind chat-instructions --raw https://raw.githubusercontent.com/github/awesome-copilot/main/instructions/markdown.instructions.md
mcp-vsc-button-gen --kind chat-prompt --owner github --repo awesome-copilot --path prompts/readme-blueprint-generator.prompt.md
`);
}
function getArg(flag: string, argv: string[]): string | undefined {
const idx = argv.indexOf(flag);
if (idx !== -1 && idx + 1 < argv.length) return argv[idx + 1];
return undefined;
}
function hasFlag(flag: string, argv: string[]): boolean {
return argv.includes(flag);
}
async function maybeHandleCli(argv: string[]): Promise<boolean> {
if (hasFlag('--help', argv) || hasFlag('-h', argv)) {
printCliUsage();
process.exit(0);
}
const kind = getArg('--kind', argv) as Kind | undefined;
const raw = getArg('--raw', argv);
const owner = getArg('--owner', argv);
const repo = getArg('--repo', argv);
const path = getArg('--path', argv);
const branch = getArg('--branch', argv) ?? 'main';
const usingRaw = !!raw;
const usingGh = !!(owner && repo && path);
if (!kind || (!usingRaw && !usingGh) || (usingRaw && usingGh)) {
// Not a valid CLI invocation; if some flags present, show usage and exit 1.
if (kind || usingRaw || usingGh) {
printCliUsage();
process.exit(1);
}
return false; // fall through to server mode
}
try {
const md = usingRaw
? generateCopilotInstallButtons(kind, raw!)
: generateCopilotInstallButtonsFromGithub(kind, owner!, repo!, path!, branch);
console.log(md);
process.exit(0);
} catch (err) {
console.error((err as Error).message || String(err));
process.exit(1);
}
return true;
}
// Types
const InputSchema = z.object({
type: z.string(), // "promptString" supported for now
id: z.string(),
description: z.string().optional(),
password: z.boolean().optional(),
});
const ConfigSchema = z.object({
command: z.string().default("npx"),
args: z.array(z.string()).default([]),
env: z.record(z.string()).optional(),
});
const MakeButtonsParams = z.object({
name: z.string().min(1, "name is required"),
inputs: z.array(InputSchema).default([]),
config: ConfigSchema.default({ command: "npx", args: [] }),
});
function encode(value: unknown): string {
return encodeURIComponent(JSON.stringify(value));
}
// moved generation helpers to lib/buttons
async function main() {
// CLI mode: if args match, print buttons and exit early.
const handled = await maybeHandleCli(process.argv.slice(2));
if (handled) return;
const transport = new StdioServerTransport();
const server = new McpServer({
name: "mcp-vsc-button-gen",
version: "0.1.0",
});
server.registerTool(
"make_install_buttons",
{
title: "Generate install buttons",
description: "Generate VS Code Stable and Insiders MCP install button markdown for an NPX-based server.",
inputSchema: {
name: z.string().describe("Server display name (e.g., 'supabase')."),
inputs: z.array(z.object({
type: z.string(),
id: z.string(),
description: z.string().optional(),
password: z.boolean().optional(),
})).default([]),
config: z.object({
command: z.string().default("npx"),
args: z.array(z.string()).default([]),
env: z.record(z.string()).optional(),
}).default({ command: "npx", args: [] }),
}
},
async ({ name, inputs, config }) => {
const markdown = generateButtonsMarkdown(name, inputs, config);
return { content: [{ type: "text", text: markdown }] };
}
);
server.registerTool(
"from_mcp_config",
{
title: "Buttons from MCP config",
description: "Generate install buttons from a raw MCP JSON-like config object and a server name.",
inputSchema: {
name: z.string().describe("Server display name."),
mcp: z.any(),
}
},
async ({ name, mcp }) => {
const { inputs, config } = fromMcpConfigObject(name, mcp ?? {});
const markdown = generateButtonsMarkdown(name, inputs, config);
return { content: [{ type: "text", text: markdown }] };
}
);
// Awesome Copilot style buttons for Instructions/Prompts/Chat Modes
server.registerTool(
"copilot_buttons_from_raw",
{
title: "Copilot install buttons (raw URL)",
description: "Generate VS Code install buttons for a raw URL to chat instructions, prompts, or chat modes.",
inputSchema: {
kind: z.enum(["chat-instructions", "chat-prompt", "chat-mode"]).describe("Install kind"),
url: z.string().url().describe("Raw GitHub URL or any public raw URL to the file."),
}
},
async ({ kind, url }) => {
const markdown = generateCopilotInstallButtons(kind, url);
return { content: [{ type: "text", text: markdown }] };
}
);
server.registerTool(
"copilot_buttons_from_github",
{
title: "Copilot install buttons (GitHub)",
description: "Generate VS Code install buttons for a GitHub file using owner/repo/path and optional branch.",
inputSchema: {
kind: z.enum(["chat-instructions", "chat-prompt", "chat-mode"]).describe("Install kind"),
owner: z.string(),
repo: z.string(),
path: z.string().describe("Path within the repo"),
branch: z.string().default("main"),
}
},
async ({ kind, owner, repo, path, branch }) => {
const markdown = generateCopilotInstallButtonsFromGithub(kind, owner, repo, path, branch ?? 'main');
return { content: [{ type: "text", text: markdown }] };
}
);
await server.connect(transport);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});