Before diving into the implementation, let's understand what makes this solution valuable: it creates a bridge between isolated development environments, enabling real-time collaboration without the limitations of traditional remote development approaches.
The MCP (Model Context Protocol) server architecture consists of several key components that work together to facilitate communication between multiple VSCode instances:
- A centralized MCP server that handles message routing and state synchronization
- Client connections from multiple workspaces or codespaces
- STDIO transport layer for reliable communication
- Extension modules for specific collaborative features
First, we'll create the server configuration file that defines our MCP server:
{
// Input variables for secure credential management
"inputs": [
{
"type": "promptString",
"id": "server-token",
"description": "Authentication Token for MCP Server",
"password": true
}
],
"servers": {
"CollabMCPServer": {
"type": "stdio",
"command": "node",
"args": ["./dist/server.js"],
"env": {
"SERVER_TOKEN": "${input:server-token}",
"SERVER_PORT": "3000",
"LOG_LEVEL": "info"
}
}
}
}
This configuration will be placed in the .vscode/mcp.json
file of your project[1]. It sets up a server named "CollabMCPServer" that uses STDIO for communication and runs via Node.js.
Now, let's implement the MCP server in JavaScript/TypeScript:
// server.ts
import * as net from 'net';
import { EventEmitter } from 'events';
import { MCPMessage, ClientConnection, CollaborationSession } from './types';
class MCPServer extends EventEmitter {
private clients: Map<string, ClientConnection> = new Map();
private sessions: Map<string, CollaborationSession> = new Map();
constructor() {
super();
this.setupStdioHandling();
console.log('MCP Server initialized with STDIO transport');
}
private setupStdioHandling() {
// Set up raw mode for STDIO to handle binary data
if (process.stdin.isTTY) {
process.stdin.setRawMode(true);
}
process.stdin.on('data', this.handleIncomingData.bind(this));
process.stdin.resume();
// Handle process termination
process.on('SIGTERM', this.shutdown.bind(this));
process.on('SIGINT', this.shutdown.bind(this));
}
private handleIncomingData(data: Buffer) {
try {
const message = JSON.parse(data.toString('utf8')) as MCPMessage;
this.routeMessage(message);
} catch (error) {
console.error('Error parsing incoming message:', error);
}
}
private routeMessage(message: MCPMessage) {
switch (message.type) {
case 'connection':
this.handleNewConnection(message);
break;
case 'terminal':
this.handleTerminalData(message);
break;
case 'extension':
this.handleExtensionSync(message);
break;
case 'editor':
this.handleEditorCollaboration(message);
break;
default:
console.warn('Unknown message type:', message.type);
}
}
private handleNewConnection(message: MCPMessage) {
const { clientId, workspaceId } = message.payload;
this.clients.set(clientId, {
id: clientId,
workspaceId,
connectedAt: new Date(),
isActive: true
});
// Send acknowledgment back
this.sendToClient(clientId, {
type: 'connection_ack',
payload: {
status: 'connected',
serverTime: new Date().toISOString(),
connectedClients: this.getActiveClientCount()
}
});
console.log(`Client ${clientId} from workspace ${workspaceId} connected`);
}
private handleTerminalData(message: MCPMessage) {
const { sessionId, data, sourceClientId } = message.payload;
// Broadcast terminal data to all clients in the session except the source
this.broadcastToSession(sessionId, message, sourceClientId);
}
private handleExtensionSync(message: MCPMessage) {
const { extensionId, state, sourceClientId } = message.payload;
// Broadcast extension state to all connected clients except the source
this.broadcastToAll(message, sourceClientId);
}
private handleEditorCollaboration(message: MCPMessage) {
const { documentUri, edit, cursorPosition, sourceClientId } = message.payload;
// Broadcast editor changes to all clients in the session except the source
this.broadcastToSession(message.payload.sessionId, message, sourceClientId);
}
private broadcastToSession(sessionId: string, message: MCPMessage, excludeClientId?: string) {
const session = this.sessions.get(sessionId);
if (!session) return;
for (const clientId of session.participants) {
if (clientId !== excludeClientId) {
this.sendToClient(clientId, message);
}
}
}
private broadcastToAll(message: MCPMessage, excludeClientId?: string) {
for (const [clientId, client] of this.clients.entries()) {
if (client.isActive && clientId !== excludeClientId) {
this.sendToClient(clientId, message);
}
}
}
private sendToClient(clientId: string, message: MCPMessage) {
const client = this.clients.get(clientId);
if (!client || !client.isActive) return;
const messageStr = JSON.stringify(message);
process.stdout.write(messageStr + '\n');
}
private getActiveClientCount(): number {
let count = 0;
for (const client of this.clients.values()) {
if (client.isActive) count++;
}
return count;
}
private shutdown() {
console.log('MCP Server shutting down...');
// Notify all clients about server shutdown
this.broadcastToAll({
type: 'server_shutdown',
payload: {
reason: 'Server process terminated',
time: new Date().toISOString()
}
});
process.exit(0);
}
}
// Start the server
const server = new MCPServer();
Now, let's implement the client component that will run in each VSCode workspace:
// client.ts
import * as vscode from 'vscode';
import { v4 as uuidv4 } from 'uuid';
import { MCPMessage, CollaborationSession } from './types';
export class MCPClient {
private clientId: string;
private workspaceId: string;
private serverProcess: any;
private isConnected: boolean = false;
private messageQueue: MCPMessage[] = [];
private activeSessions: Map<string, CollaborationSession> = new Map();
constructor(context: vscode.ExtensionContext) {
this.clientId = uuidv4();
this.workspaceId = vscode.workspace.name || 'default-workspace';
// Register event handlers
context.subscriptions.push(
vscode.window.onDidChangeActiveTerminal(this.handleTerminalChange.bind(this)),
vscode.window.onDidChangeTextEditorSelection(this.handleSelectionChange.bind(this)),
vscode.workspace.onDidChangeTextDocument(this.handleDocumentChange.bind(this))
);
}
public async connect(): Promise<boolean> {
try {
// Use VSCode's MCP server configuration
await vscode.commands.executeCommand('mcp.startServer', 'CollabMCPServer');
// Create connection message
const connectionMessage: MCPMessage = {
type: 'connection',
payload: {
clientId: this.clientId,
workspaceId: this.workspaceId,
capabilities: ['terminal', 'editor', 'extensions']
}
};
// Send connection request
this.sendMessage(connectionMessage);
this.isConnected = true;
vscode.window.showInformationMessage('Connected to MCP Collaboration Server');
return true;
} catch (error) {
vscode.window.showErrorMessage(`Failed to connect to MCP Server: ${error}`);
return false;
}
}
public disconnect(): void {
if (!this.isConnected) return;
const disconnectMessage: MCPMessage = {
type: 'disconnection',
payload: {
clientId: this.clientId,
reason: 'Client disconnected'
}
};
this.sendMessage(disconnectMessage);
this.isConnected = false;
vscode.window.showInformationMessage('Disconnected from MCP Collaboration Server');
}
public createCollaborationSession(): string {
const sessionId = uuidv4();
const session: CollaborationSession = {
id: sessionId,
createdBy: this.clientId,
createdAt: new Date(),
participants: [this.clientId],
activeDocument: null,
sharedTerminal: null
};
this.activeSessions.set(sessionId, session);
const sessionMessage: MCPMessage = {
type: 'session_create',
payload: {
sessionId,
createdBy: this.clientId,
workspaceId: this.workspaceId
}
};
this.sendMessage(sessionMessage);
return sessionId;
}
public joinSession(sessionId: string): boolean {
const joinMessage: MCPMessage = {
type: 'session_join',
payload: {
sessionId,
clientId: this.clientId,
workspaceId: this.workspaceId
}
};
this.sendMessage(joinMessage);
return true;
}
private handleTerminalChange(terminal: vscode.Terminal | undefined): void {
if (!terminal || !this.isConnected) return;
// Implementation for sharing terminal state
}
private handleSelectionChange(event: vscode.TextEditorSelectionChangeEvent): void {
if (!this.isConnected) return;
// Implementation for sharing cursor position
}
private handleDocumentChange(event: vscode.TextDocumentChangeEvent): void {
if (!this.isConnected) return;
// Implementation for sharing document changes
}
private sendMessage(message: MCPMessage): void {
if (!this.isConnected) {
this.messageQueue.push(message);
return;
}
// Use VSCode's commands to send message to the MCP server
vscode.commands.executeCommand('mcp.sendMessage', 'CollabMCPServer', JSON.stringify(message));
}
}
Let's create the extension entry point that initializes the MCP server and client:
// extension.ts
import * as vscode from 'vscode';
import { MCPClient } from './client';
export function activate(context: vscode.ExtensionContext) {
console.log('Activating Collaborative MCP Server Extension');
const client = new MCPClient(context);
// Register commands
context.subscriptions.push(
vscode.commands.registerCommand('collaborativeMcp.connect', async () => {
await client.connect();
}),
vscode.commands.registerCommand('collaborativeMcp.disconnect', () => {
client.disconnect();
}),
vscode.commands.registerCommand('collaborativeMcp.createSession', () => {
const sessionId = client.createCollaborationSession();
vscode.env.clipboard.writeText(sessionId);
vscode.window.showInformationMessage(`Collaboration session created. ID copied to clipboard: ${sessionId}`);
}),
vscode.commands.registerCommand('collaborativeMcp.joinSession', async () => {
const sessionId = await vscode.window.showInputBox({
prompt: 'Enter session ID to join',
placeHolder: 'Session ID'
});
if (sessionId) {
client.joinSession(sessionId);
}
})
);
}
export function deactivate() {
console.log('Deactivating Collaborative MCP Server Extension');
}
Let's define the types used throughout the implementation:
// types.ts
export interface MCPMessage {
type: string;
payload: any;
}
export interface ClientConnection {
id: string;
workspaceId: string;
connectedAt: Date;
isActive: boolean;
}
export interface CollaborationSession {
id: string;
createdBy: string;
createdAt: Date;
participants: string[];
activeDocument: string | null;
sharedTerminal: string | null;
}
Now, let's implement comprehensive unit tests for our MCP server:
// server.test.ts
import * as assert from 'assert';
import { MCPServer } from '../src/server';
import { MCPClient } from '../src/client';
import { MCPMessage } from '../src/types';
import * as sinon from 'sinon';
// Mock VSCode API
const vscode = {
window: {
showInformationMessage: sinon.stub(),
showErrorMessage: sinon.stub()
},
commands: {
executeCommand: sinon.stub()
},
workspace: {
name: 'test-workspace'
}
};
describe('MCP Server Tests', () => {
let server: any;
let client: any;
let stdoutSpy: sinon.SinonSpy;
beforeEach(() => {
// Redirect stdout to capture server output
stdoutSpy = sinon.spy(process.stdout, 'write');
// Create server instance
server = new MCPServer();
// Create a mock client context
const mockContext = {
subscriptions: []
};
// Create client instance
client = new MCPClient(mockContext as any);
// Reset stubs
vscode.commands.executeCommand.reset();
vscode.window.showInformationMessage.reset();
});
afterEach(() => {
// Restore stdout
stdoutSpy.restore();
});
it('should initialize the server correctly', () => {
assert.ok(server);
assert.strictEqual(server.clients.size, 0);
assert.strictEqual(server.sessions.size, 0);
});
it('should handle client connections', () => {
// Simulate a client connection message
const connectionMessage: MCPMessage = {
type: 'connection',
payload: {
clientId: 'test-client-1',
workspaceId: 'test-workspace'
}
};
// Send message to server
server.routeMessage(connectionMessage);
// Verify client was added
assert.strictEqual(server.clients.size, 1);
assert.ok(server.clients.has('test-client-1'));
assert.strictEqual(server.clients.get('test-client-1').workspaceId, 'test-workspace');
// Verify acknowledgment was sent
assert.ok(stdoutSpy.calledOnce);
const response = JSON.parse(stdoutSpy.firstCall.args[0]);
assert.strictEqual(response.type, 'connection_ack');
assert.strictEqual(response.payload.status, 'connected');
});
it('should create and join collaboration sessions', () => {
// Set up two test clients
const client1Id = 'test-client-1';
const client2Id = 'test-client-2';
const sessionId = 'test-session-1';
// Connect clients
server.routeMessage({
type: 'connection',
payload: { clientId: client1Id, workspaceId: 'workspace-1' }
});
server.routeMessage({
type: 'connection',
payload: { clientId: client2Id, workspaceId: 'workspace-2' }
});
// Create a session
server.routeMessage({
type: 'session_create',
payload: { sessionId, createdBy: client1Id, workspaceId: 'workspace-1' }
});
// Join the session
server.routeMessage({
type: 'session_join',
payload: { sessionId, clientId: client2Id, workspaceId: 'workspace-2' }
});
// Verify session was created
assert.ok(server.sessions.has(sessionId));
const session = server.sessions.get(sessionId);
assert.strictEqual(session.participants.length, 2);
assert.ok(session.participants.includes(client1Id));
assert.ok(session.participants.includes(client2Id));
});
it('should broadcast terminal data to session participants', () => {
// Set up test clients and session
const client1Id = 'test-client-1';
const client2Id = 'test-client-2';
const sessionId = 'test-session-1';
// Connect clients
server.routeMessage({
type: 'connection',
payload: { clientId: client1Id, workspaceId: 'workspace-1' }
});
server.routeMessage({
type: 'connection',
payload: { clientId: client2Id, workspaceId: 'workspace-2' }
});
// Create and join session
server.routeMessage({
type: 'session_create',
payload: { sessionId, createdBy: client1Id, workspaceId: 'workspace-1' }
});
server.routeMessage({
type: 'session_join',
payload: { sessionId, clientId: client2Id, workspaceId: 'workspace-2' }
});
// Reset spy to clear previous calls
stdoutSpy.resetHistory();
// Send terminal data
const terminalData = "echo 'Hello, World!'";
server.routeMessage({
type: 'terminal',
payload: {
sessionId,
data: terminalData,
sourceClientId: client1Id
}
});
// Verify data was broadcasted to other client
assert.ok(stdoutSpy.calledOnce);
const response = JSON.parse(stdoutSpy.firstCall.args[0]);
assert.strictEqual(response.type, 'terminal');
assert.strictEqual(response.payload.data, terminalData);
});
it('should handle graceful server shutdown', () => {
// Connect test clients
server.routeMessage({
type: 'connection',
payload: { clientId: 'test-client-1', workspaceId: 'workspace-1' }
});
server.routeMessage({
type: 'connection',
payload: { clientId: 'test-client-2', workspaceId: 'workspace-2' }
});
// Mock process.exit to prevent actual exit during test
const exitStub = sinon.stub(process, 'exit');
// Reset spy to clear previous calls
stdoutSpy.resetHistory();
// Trigger shutdown
server.shutdown();
// Verify shutdown messages were sent to clients
assert.strictEqual(stdoutSpy.callCount, 2);
const response1 = JSON.parse(stdoutSpy.firstCall.args[0]);
assert.strictEqual(response1.type, 'server_shutdown');
// Verify exit was called
assert.ok(exitStub.calledOnce);
assert.strictEqual(exitStub.firstCall.args[0], 0);
// Restore stub
exitStub.restore();
});
});
Finally, let's configure the extension's package.json:
{
"name": "vscode-collaborative-mcp",
"displayName": "Collaborative MCP Server",
"version": "1.0.0",
"description": "An MCP server for VSCode that enables collaborative development across workspaces",
"main": "./dist/extension.js",
"engines": {
"vscode": "^1.70.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onCommand:collaborativeMcp.connect",
"onCommand:collaborativeMcp.createSession",
"onCommand:collaborativeMcp.joinSession"
],
"contributes": {
"commands": [
{
"command": "collaborativeMcp.connect",
"title": "Connect to Collaborative MCP Server"
},
{
"command": "collaborativeMcp.disconnect",
"title": "Disconnect from Collaborative MCP Server"
},
{
"command": "collaborativeMcp.createSession",
"title": "Create a new collaboration session"
},
{
"command": "collaborativeMcp.joinSession",
"title": "Join an existing collaboration session"
}
]
},
"scripts": {
"vscode:prepublish": "npm run package",
"compile": "webpack",
"watch": "webpack --watch",
"package": "webpack --mode production --devtool hidden-source-map",
"compile-tests": "tsc -p . --outDir out",
"watch-tests": "tsc -p . -w --outDir out",
"pretest": "npm run compile-tests && npm run compile && npm run lint",
"lint": "eslint src --ext ts",
"test": "node ./out/test/runTest.js"
},
"dependencies": {
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/mocha": "^9.1.1",
"@types/node": "16.x",
"@types/sinon": "^10.0.13",
"@types/uuid": "^8.3.4",
"@types/vscode": "^1.70.0",
"@typescript-eslint/eslint-plugin": "^5.31.0",
"@typescript-eslint/parser": "^5.31.0",
"@vscode/test-electron": "^2.1.5",
"eslint": "^8.20.0",
"mocha": "^10.0.0",
"sinon": "^14.0.0",
"ts-loader": "^9.3.1",
"typescript": "^4.7.4",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
}
}
To utilize this MCP server across multiple workspaces, follow these steps:
- Install the extension in all participating VSCode instances
- In one workspace, run the "Connect to Collaborative MCP Server" command
- Create a new collaboration session using "Create a new collaboration session"
- Copy the session ID that appears
- In other workspaces, connect to the MCP server and join the session using "Join an existing collaboration session"
- Enter the session ID when prompted
Once connected, all workspaces will be able to share:
- Terminal sessions
- Editor changes
- Cursor positions
- Extension states
This implementation provides a comprehensive VSCode MCP server that enables collaborative development across multiple workspaces or codespaces using STDIO communication. The server supports sharing terminals, synchronizing extension states, and collaborative editing.
The solution includes a robust architecture with:
- A centralized MCP server for message routing
- Client connections from multiple workspaces
- STDIO-based transport for reliable communication
- Comprehensive unit tests to ensure functionality
By following this guide, you can create a powerful collaborative development environment that enhances team productivity and enables seamless remote work[2][4].
Sources [1] Use MCP servers in VS Code (Preview) https://code.visualstudio.com/docs/copilot/chat/mcp-servers [2] Working collaboratively in a codespace - GitHub Docs https://docs.github.com/en/codespaces/developing-in-a-codespace/working-collaboratively-in-a-codespace [3] A Detailed Guide To Playwright MCP Server - QA Touch https://www.qatouch.com/blog/playwright-mcp-server/ [4] Visual Studio Code Server https://code.visualstudio.com/docs/remote/vscode-server [5] How to set up a remote connexion using codespace - VS Code https://discourse.julialang.org/t/how-to-set-up-a-remote-connexion-using-codespace/44138 [6] Tutorial: Test a .NET class library using Visual Studio Code https://learn.microsoft.com/en-us/dotnet/core/tutorials/testing-library-with-visual-studio-code [7] Setup MCP Servers in VS Code in Seconds #vscode #githubcopilot ... https://www.youtube.com/watch?v=vhdRUJlgwD4 [8] VSCode MCP Server - Visual Studio Marketplace https://marketplace.visualstudio.com/items?itemName=SemanticWorkbenchTeam.mcp-server-vscode [9] Try out MCP servers in VS Code : r/ChatGPTCoding - Reddit https://www.reddit.com/r/ChatGPTCoding/comments/1jfr05y/try_out_mcp_servers_in_vs_code/ [10] What is a VS Code workspace? https://code.visualstudio.com/docs/editing/workspaces/workspaces [11] Cursor terminal access for auto-fixing unit tests https://forum.cursor.com/t/cursor-terminal-access-for-auto-fixing-unit-tests/69922 [12] VSCode Remote Development via SSH and Tunnel - FASRC DOCS https://docs.rc.fas.harvard.edu/kb/vscode-remote-development-via-ssh-or-tunnel/ [13] Get started with unit testing - Visual Studio (Windows) | Microsoft Learn https://learn.microsoft.com/en-us/visualstudio/test/getting-started-with-unit-testing?view=vs-2022 [14] Configuring VS Code Workspaces for Multiple ObjectScript ... https://www.youtube.com/watch?v=CGEqFea0hh8 [15] Remote Development FAQ - Visual Studio Code https://code.visualstudio.com/docs/remote/faq [16] Supporting Remote Development and GitHub Codespaces https://code.visualstudio.com/api/advanced-topics/remote-extensions [17] VS Code Remote Development https://code.visualstudio.com/docs/remote/remote-overview [18] Multi-root Workspaces - Visual Studio Code https://code.visualstudio.com/docs/editor/workspaces/multi-root-workspaces [19] punkpeye/awesome-mcp-servers - GitHub https://github.com/punkpeye/awesome-mcp-servers [20] Getting Started with Remote Development - YouTube https://www.youtube.com/watch?v=QA9jlp-o5vQ [21] Effortless Pair Programming with GitHub Codespaces and VSCode https://microsoft.github.io/code-with-engineering-playbook/agile-development/advanced-topics/collaboration/pair-programming-tools/ [22] Remote Development using SSH - Visual Studio Code https://code.visualstudio.com/docs/remote/ssh [23] What options are there for collaborating with Visual Studio Code? https://stackoverflow.com/questions/62441404/what-options-are-there-for-collaborating-with-visual-studio-code [24] MCP Testing - MCP Servers https://mcp.so/server/mcp-testing [25] Introducing Remote Development for Visual Studio Code : r/vscode https://www.reddit.com/r/vscode/comments/bjycxh/introducing_remote_development_for_visual_studio/ [26] How to setup Visual Studio Codespaces for collaborative use https://techcommunity.microsoft.com/blog/itopstalkblog/how-to-setup-visual-studio-codespaces-for-collaborative-use/1436412 [27] VikashLoomba/copilot-mcp: A powerful VSCode extension ... - GitHub https://github.com/VikashLoomba/copilot-mcp [28] MCP: in a codespace, filepaths are rewritten for windows. #244178 microsoft/vscode#244178 [29] Share a server or terminal in Visual Studio Code - Learn Microsoft https://learn.microsoft.com/en-us/visualstudio/liveshare/use/share-server-visual-studio-code [30] Visual studio unit tests and client server programs - Stack Overflow https://stackoverflow.com/questions/6190585/visual-studio-unit-tests-and-client-server-programs [31] VSCode MCP - MCP Servers https://mcp.so/server/vscode-mcp/block [32] GitHub Codespaces - Visual Studio Code https://code.visualstudio.com/docs/remote/codespaces [33] Creating Unit Tests using ChatGPT's Visual Studio Code Extension ... https://www.youtube.com/watch?v=LlUndDIBRlY [34] Visual Studio Code - Multiple Remote SSH connections in one ... https://stackoverflow.com/questions/58350537/visual-studio-code-multiple-remote-ssh-connections-in-one-workspace [35] Extending Copilot Chat with the Model Context Protocol (MCP) https://docs.github.com/en/copilot/customizing-copilot/extending-copilot-chat-with-mcp [36] Using fnm to launch MCP server in Visual Studio Code ... - GitHub Schniz/fnm#1406