add extension sample for mcp server publishing (#1156)

This commit is contained in:
Connor Peet
2025-03-13 23:49:53 -07:00
committed by GitHub
parent 46f526c39c
commit 6a364308ef
8 changed files with 461 additions and 0 deletions

6
mcp-extension-sample/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
out
node_modules
.vscode-test/
*.vsix
*.d.ts

View File

@ -0,0 +1,21 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "npm: watch"
}
]
}

20
mcp-extension-sample/.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,20 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@ -0,0 +1,13 @@
# MCP Extension sample
This sample demonstrates usage of the MCP connection API. This API is currently still proposed.
## Running the Sample
- Run `npm install` in terminal to install dependencies
- A `postinstall` script would download latest version of `vscode.proposed.<proposalName>.d.ts`
- Run the `Run Extension` target in the Debug View. This will:
- Start a task `npm: watch` to compile the code
- Run the extension in a new VS Code window
- In the new window, run the command `Add Gist Source` command with a gist containing MCP servers ([example](https://gist.github.com/connor4312/3939ae7f6e55b2e391b5d585df27465c))
- You can now run these MCP servers in chat.

245
mcp-extension-sample/package-lock.json generated Normal file
View File

@ -0,0 +1,245 @@
{
"name": "mcp-extension-sample",
"version": "0.0.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "mcp-extension-sample",
"version": "0.0.1",
"hasInstallScript": true,
"license": "MIT",
"devDependencies": {
"@types/node": "^20",
"@vscode/dts": "^0.4.1",
"typescript": "^5.8.2"
},
"engines": {
"vscode": "^1.99.0"
}
},
"node_modules/@types/node": {
"version": "20.16.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz",
"integrity": "sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==",
"dev": true,
"dependencies": {
"undici-types": "~6.19.2"
}
},
"node_modules/@vscode/dts": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@vscode/dts/-/dts-0.4.1.tgz",
"integrity": "sha512-o8cI5Vqt6S6Y5mCI7yCkSQdiLQaLG5DMUpciJV3zReZwE+dA5KERxSVX8H3cPEhyKw21XwKGmIrg6YmN6M5uZA==",
"dev": true,
"dependencies": {
"https-proxy-agent": "^7.0.0",
"minimist": "^1.2.8",
"prompts": "^2.4.2"
},
"bin": {
"dts": "index.js"
}
},
"node_modules/agent-base": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
"integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
"dev": true,
"dependencies": {
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.0.tgz",
"integrity": "sha512-0euwPCRyAPSgGdzD1IVN9nJYHtBhJwb6XPfbpQcYbPCwrBidX6GzxmchnaF4sfF/jPb74Ojx5g4yTg3sixlyPw==",
"dev": true,
"dependencies": {
"agent-base": "^7.0.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/kleur": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
"integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
"dev": true,
"dependencies": {
"kleur": "^3.0.3",
"sisteransi": "^1.0.5"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
"dev": true
},
"node_modules/typescript": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true
}
},
"dependencies": {
"@types/node": {
"version": "20.16.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz",
"integrity": "sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==",
"dev": true,
"requires": {
"undici-types": "~6.19.2"
}
},
"@vscode/dts": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@vscode/dts/-/dts-0.4.1.tgz",
"integrity": "sha512-o8cI5Vqt6S6Y5mCI7yCkSQdiLQaLG5DMUpciJV3zReZwE+dA5KERxSVX8H3cPEhyKw21XwKGmIrg6YmN6M5uZA==",
"dev": true,
"requires": {
"https-proxy-agent": "^7.0.0",
"minimist": "^1.2.8",
"prompts": "^2.4.2"
}
},
"agent-base": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
"integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
"dev": true,
"requires": {
"debug": "^4.3.4"
}
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
},
"https-proxy-agent": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.0.tgz",
"integrity": "sha512-0euwPCRyAPSgGdzD1IVN9nJYHtBhJwb6XPfbpQcYbPCwrBidX6GzxmchnaF4sfF/jPb74Ojx5g4yTg3sixlyPw==",
"dev": true,
"requires": {
"agent-base": "^7.0.2",
"debug": "4"
}
},
"kleur": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
"dev": true
},
"minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
"integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
"dev": true,
"requires": {
"kleur": "^3.0.3",
"sisteransi": "^1.0.5"
}
},
"sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
"dev": true
},
"typescript": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"dev": true
},
"undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true
}
}
}

View File

@ -0,0 +1,51 @@
{
"enabledApiProposals": [
"mcpConfigurationProvider"
],
"name": "mcp-extension-sample",
"displayName": "mcp-extension-sample",
"description": "Sample showing how to use Proposed API",
"version": "0.0.1",
"publisher": "vscode-samples",
"private": true,
"license": "MIT",
"repository": "https://github.com/Microsoft/vscode-extension-samples",
"engines": {
"vscode": "^1.99.0"
},
"categories": [
"Other"
],
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "mcp-extension-sample.addGist",
"title": "MCP Extension Sample: Add Gist Source"
},
{
"command": "mcp-extension-sample.removeGist",
"title": "MCP Extension Sample: Remove Gist Source"
}
],
"modelContextServerCollections": [
{
"id": "exampleGist",
"label": "Github Gists"
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"download-api": "dts dev",
"postdownload-api": "dts main",
"postinstall": "npm run download-api"
},
"devDependencies": {
"@types/node": "^20",
"@vscode/dts": "^0.4.1",
"typescript": "^5.8.2"
}
}

View File

@ -0,0 +1,93 @@
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
let gists: string[] = context.globalState.get('gists', []);
const didChangeEmitter = new vscode.EventEmitter<void>();
/**
* You can use proposed API here. `vscode.` should start auto complete
* Proposed API as defined in vscode.proposed.<proposalName>.d.ts.
*/
context.subscriptions.push(vscode.commands.registerCommand('mcp-extension-sample.addGist', async () => {
const gist = await vscode.window.showInputBox({ prompt: 'Enter Gist URL' });
if (gist) {
gists.push(gist);
context.globalState.update('gists', gists);
vscode.window.showInformationMessage(`Gist added: ${gist}`);
didChangeEmitter.fire();
}
}));
context.subscriptions.push(vscode.commands.registerCommand('mcp-extension-sample.removeGist', async () => {
const gist = await vscode.window.showQuickPick(gists, { placeHolder: 'Select Gist to remove' });
if (gist) {
gists = gists.filter(g => g !== gist);
context.globalState.update('gists', gists);
vscode.window.showInformationMessage(`Gist removed: ${gist}`);
didChangeEmitter.fire();
}
}));
context.subscriptions.push(vscode.lm.registerMcpConfigurationProvider('exampleGist', {
onDidChange: didChangeEmitter.event,
provideMcpServerDefinitions: async () => {
let output: vscode.McpServerDefinition[] = [];
await Promise.all(gists.map(g => fetchGistContents(g).then(content => {
const s = JSON.parse(content);
if (!Array.isArray(s)) {
throw new Error(`Gist content is not an MCP server array: ${g}`);
}
output.push(...s);
})));
return output;
}
}));
}
async function fetchGistContents(gistUrl: string): Promise<string> {
// Parse the gist URL to get the ID
const gistId = extractGistId(gistUrl);
if (!gistId) {
throw new Error(`Invalid Gist URL: ${gistUrl}`);
}
// Fetch the raw gist content
try {
const response = await fetch(`https://api.github.com/gists/${gistId}`);
if (!response.ok) {
throw new Error(`Failed to fetch gist: ${response.status} ${response.statusText}`);
}
const gistData: any = await response.json();
// Get the first file content from the gist
const files = gistData.files;
const firstFile = Object.keys(files)[0];
if (files[firstFile].truncated) {
// If content is truncated, fetch the raw URL
const rawResponse = await fetch(files[firstFile].raw_url);
if (!rawResponse.ok) {
throw new Error(`Failed to fetch raw content: ${rawResponse.status}`);
}
return await rawResponse.text();
} else {
return files[firstFile].content;
}
} catch (error) {
console.error('Error fetching gist:', error);
throw error;
}
// Helper function to extract gist ID from URL
function extractGistId(url: string): string | null {
// Handle URLs like https://gist.github.com/user/gistId or just the ID
const match = url.match(/gist\.github\.com\/(?:[^/]+\/)?([a-zA-Z0-9]+)/) || url.match(/^([a-zA-Z0-9]+)$/);
return match ? match[1] : null;
}
}

View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2020",
"lib": ["es2020"],
"outDir": "out",
"sourceMap": true,
"strict": true,
"rootDir": "src"
},
"exclude": ["node_modules", ".vscode-test"]
}