vscode-terminal-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/dist/extension.js +6436 -0
- package/dist/mcp-entry.js +203 -0
- package/icon.png +0 -0
- package/icon.svg +65 -0
- package/package.json +124 -0
- package/research/01-mcp-sdk-patterns.md +54 -0
- package/research/02-vscode-terminal-api.md +36 -0
- package/research/03-vscode-mcp-integration.md +43 -0
- package/research/04-desktop-commander-referencia.md +45 -0
- package/server.json +13 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
|
|
25
|
+
// src/mcp-entry.ts
|
|
26
|
+
var net = __toESM(require("net"));
|
|
27
|
+
var path = __toESM(require("path"));
|
|
28
|
+
var os = __toESM(require("os"));
|
|
29
|
+
var SOCKET_PATH = path.join(os.tmpdir(), "vscode-terminal-mcp.sock");
|
|
30
|
+
var RECONNECT_DELAY_MS = 1e3;
|
|
31
|
+
var MAX_RECONNECT_ATTEMPTS = 30;
|
|
32
|
+
var StdioToIpcBridge = class {
|
|
33
|
+
socket = null;
|
|
34
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
35
|
+
socketBuffer = "";
|
|
36
|
+
stdinBuffer = "";
|
|
37
|
+
connected = false;
|
|
38
|
+
reconnectAttempts = 0;
|
|
39
|
+
async start() {
|
|
40
|
+
await this.connectToExtension();
|
|
41
|
+
this.listenStdin();
|
|
42
|
+
}
|
|
43
|
+
async connectToExtension() {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const attempt = () => {
|
|
46
|
+
this.socket = net.createConnection(SOCKET_PATH, () => {
|
|
47
|
+
this.connected = true;
|
|
48
|
+
this.reconnectAttempts = 0;
|
|
49
|
+
this.setupSocketListeners();
|
|
50
|
+
resolve();
|
|
51
|
+
});
|
|
52
|
+
this.socket.on("error", (err) => {
|
|
53
|
+
this.connected = false;
|
|
54
|
+
this.reconnectAttempts++;
|
|
55
|
+
if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
56
|
+
const errorMsg = `Failed to connect to extension host after ${MAX_RECONNECT_ATTEMPTS} attempts: ${err.message}`;
|
|
57
|
+
process.stderr.write(errorMsg + "\n");
|
|
58
|
+
reject(new Error(errorMsg));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
setTimeout(attempt, RECONNECT_DELAY_MS);
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
attempt();
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
setupSocketListeners() {
|
|
68
|
+
if (!this.socket) return;
|
|
69
|
+
this.socket.on("data", (data) => {
|
|
70
|
+
this.socketBuffer += data.toString();
|
|
71
|
+
let newlineIndex;
|
|
72
|
+
while ((newlineIndex = this.socketBuffer.indexOf("\n")) !== -1) {
|
|
73
|
+
const messageStr = this.socketBuffer.slice(0, newlineIndex);
|
|
74
|
+
this.socketBuffer = this.socketBuffer.slice(newlineIndex + 1);
|
|
75
|
+
if (!messageStr.trim()) continue;
|
|
76
|
+
try {
|
|
77
|
+
const ipcResponse = JSON.parse(messageStr);
|
|
78
|
+
this.handleIpcResponse(ipcResponse);
|
|
79
|
+
} catch {
|
|
80
|
+
process.stderr.write(
|
|
81
|
+
`Failed to parse IPC response: ${messageStr}
|
|
82
|
+
`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
this.socket.on("close", () => {
|
|
88
|
+
this.connected = false;
|
|
89
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
90
|
+
pending.reject(new Error("IPC connection closed"));
|
|
91
|
+
this.pendingRequests.delete(id);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
this.socket.on("error", (err) => {
|
|
95
|
+
process.stderr.write(`IPC socket error: ${err.message}
|
|
96
|
+
`);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
handleIpcResponse(ipcResponse) {
|
|
100
|
+
const pending = this.pendingRequests.get(ipcResponse.id);
|
|
101
|
+
if (!pending) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
this.pendingRequests.delete(ipcResponse.id);
|
|
105
|
+
if (ipcResponse.error) {
|
|
106
|
+
const errorResponse = {
|
|
107
|
+
jsonrpc: "2.0",
|
|
108
|
+
id: ipcResponse.id,
|
|
109
|
+
error: ipcResponse.error
|
|
110
|
+
};
|
|
111
|
+
this.writeStdout(errorResponse);
|
|
112
|
+
} else {
|
|
113
|
+
const successResponse = {
|
|
114
|
+
jsonrpc: "2.0",
|
|
115
|
+
id: ipcResponse.id,
|
|
116
|
+
result: ipcResponse.result
|
|
117
|
+
};
|
|
118
|
+
this.writeStdout(successResponse);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
listenStdin() {
|
|
122
|
+
process.stdin.setEncoding("utf8");
|
|
123
|
+
process.stdin.on("data", (chunk) => {
|
|
124
|
+
this.stdinBuffer += chunk;
|
|
125
|
+
let newlineIndex;
|
|
126
|
+
while ((newlineIndex = this.stdinBuffer.indexOf("\n")) !== -1) {
|
|
127
|
+
const messageStr = this.stdinBuffer.slice(0, newlineIndex);
|
|
128
|
+
this.stdinBuffer = this.stdinBuffer.slice(newlineIndex + 1);
|
|
129
|
+
if (!messageStr.trim()) continue;
|
|
130
|
+
try {
|
|
131
|
+
const jsonRpc = JSON.parse(messageStr);
|
|
132
|
+
this.handleJsonRpcRequest(jsonRpc);
|
|
133
|
+
} catch {
|
|
134
|
+
const errorResponse = {
|
|
135
|
+
jsonrpc: "2.0",
|
|
136
|
+
id: void 0,
|
|
137
|
+
error: { code: -32700, message: "Parse error" }
|
|
138
|
+
};
|
|
139
|
+
this.writeStdout(errorResponse);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
process.stdin.on("end", () => {
|
|
144
|
+
this.shutdown();
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
handleJsonRpcRequest(message) {
|
|
148
|
+
if (!this.connected || !this.socket) {
|
|
149
|
+
if (message.id !== void 0) {
|
|
150
|
+
const errorResponse = {
|
|
151
|
+
jsonrpc: "2.0",
|
|
152
|
+
id: message.id,
|
|
153
|
+
error: {
|
|
154
|
+
code: -32603,
|
|
155
|
+
message: "Extension host not connected"
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
this.writeStdout(errorResponse);
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const ipcRequest = {
|
|
163
|
+
id: String(message.id ?? `notif-${Date.now()}`),
|
|
164
|
+
method: message.method,
|
|
165
|
+
params: message.params
|
|
166
|
+
};
|
|
167
|
+
if (message.id !== void 0) {
|
|
168
|
+
this.pendingRequests.set(ipcRequest.id, {
|
|
169
|
+
resolve: () => {
|
|
170
|
+
},
|
|
171
|
+
reject: (err) => {
|
|
172
|
+
const errorResponse = {
|
|
173
|
+
jsonrpc: "2.0",
|
|
174
|
+
id: message.id,
|
|
175
|
+
error: {
|
|
176
|
+
code: -32603,
|
|
177
|
+
message: err instanceof Error ? err.message : String(err)
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
this.writeStdout(errorResponse);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
this.socket.write(JSON.stringify(ipcRequest) + "\n");
|
|
185
|
+
}
|
|
186
|
+
writeStdout(message) {
|
|
187
|
+
process.stdout.write(JSON.stringify(message) + "\n");
|
|
188
|
+
}
|
|
189
|
+
shutdown() {
|
|
190
|
+
if (this.socket) {
|
|
191
|
+
this.socket.destroy();
|
|
192
|
+
this.socket = null;
|
|
193
|
+
}
|
|
194
|
+
process.exit(0);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
var bridge = new StdioToIpcBridge();
|
|
198
|
+
bridge.start().catch((err) => {
|
|
199
|
+
process.stderr.write(`Fatal: ${err.message}
|
|
200
|
+
`);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
});
|
|
203
|
+
//# sourceMappingURL=mcp-entry.js.map
|
package/icon.png
ADDED
|
Binary file
|
package/icon.svg
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" style="stop-color:#1e1e1e"/>
|
|
5
|
+
<stop offset="100%" style="stop-color:#252526"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="accent" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
8
|
+
<stop offset="0%" style="stop-color:#0098ff"/>
|
|
9
|
+
<stop offset="100%" style="stop-color:#007acc"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
</defs>
|
|
12
|
+
|
|
13
|
+
<!-- Background rounded square -->
|
|
14
|
+
<rect x="8" y="8" width="240" height="240" rx="28" ry="28" fill="url(#bg)"/>
|
|
15
|
+
|
|
16
|
+
<!-- VSCode-style bottom panel area -->
|
|
17
|
+
<rect x="24" y="56" width="208" height="176" rx="8" ry="8" fill="#1e1e1e" stroke="#3c3c3c" stroke-width="1.5"/>
|
|
18
|
+
|
|
19
|
+
<!-- Tab bar -->
|
|
20
|
+
<rect x="24" y="56" width="208" height="32" rx="8" ry="0" fill="#2d2d2d"/>
|
|
21
|
+
<rect x="24" y="76" width="208" height="12" fill="#2d2d2d"/>
|
|
22
|
+
|
|
23
|
+
<!-- Active tab -->
|
|
24
|
+
<rect x="24" y="56" width="96" height="32" rx="8" ry="0" fill="#1e1e1e"/>
|
|
25
|
+
<rect x="24" y="76" width="96" height="12" fill="#1e1e1e"/>
|
|
26
|
+
<!-- Active tab bottom border (VSCode blue accent) -->
|
|
27
|
+
<rect x="24" y="86" width="96" height="2" fill="#007acc"/>
|
|
28
|
+
|
|
29
|
+
<!-- Tab icon: terminal symbol -->
|
|
30
|
+
<polyline points="36,66 44,72 36,78" fill="none" stroke="#cccccc" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
31
|
+
<text x="50" y="77" font-family="Segoe UI, Arial, sans-serif" font-size="11" fill="#cccccc" font-weight="500">TERMINAL</text>
|
|
32
|
+
|
|
33
|
+
<!-- Inactive tab -->
|
|
34
|
+
<text x="134" y="77" font-family="Segoe UI, Arial, sans-serif" font-size="11" fill="#6e6e6e">OUTPUT</text>
|
|
35
|
+
|
|
36
|
+
<!-- Terminal content area -->
|
|
37
|
+
<!-- Prompt line 1 -->
|
|
38
|
+
<text x="36" y="116" font-family="Consolas, monospace" font-size="13" fill="#6a9955">~</text>
|
|
39
|
+
<text x="48" y="116" font-family="Consolas, monospace" font-size="13" fill="#cccccc">$</text>
|
|
40
|
+
<text x="62" y="116" font-family="Consolas, monospace" font-size="13" fill="#ce9178">npm run build</text>
|
|
41
|
+
|
|
42
|
+
<!-- Output lines -->
|
|
43
|
+
<text x="36" y="138" font-family="Consolas, monospace" font-size="11" fill="#608b4e">✓</text>
|
|
44
|
+
<text x="50" y="138" font-family="Consolas, monospace" font-size="11" fill="#858585">compiled successfully</text>
|
|
45
|
+
|
|
46
|
+
<text x="36" y="156" font-family="Consolas, monospace" font-size="11" fill="#608b4e">✓</text>
|
|
47
|
+
<text x="50" y="156" font-family="Consolas, monospace" font-size="11" fill="#858585">tests passed</text>
|
|
48
|
+
|
|
49
|
+
<!-- Current prompt line -->
|
|
50
|
+
<text x="36" y="182" font-family="Consolas, monospace" font-size="13" fill="#6a9955">~</text>
|
|
51
|
+
<text x="48" y="182" font-family="Consolas, monospace" font-size="13" fill="#cccccc">$</text>
|
|
52
|
+
|
|
53
|
+
<!-- Blinking cursor -->
|
|
54
|
+
<rect x="62" y="170" width="8" height="16" rx="1" fill="#007acc">
|
|
55
|
+
<animate attributeName="opacity" values="1;0.3;1" dur="1s" repeatCount="indefinite"/>
|
|
56
|
+
</rect>
|
|
57
|
+
|
|
58
|
+
<!-- MCP connection dot top-right -->
|
|
59
|
+
<circle cx="216" cy="68" r="4" fill="#28c840"/>
|
|
60
|
+
|
|
61
|
+
<!-- Bottom status bar -->
|
|
62
|
+
<rect x="24" y="220" width="208" height="12" rx="0" ry="0" fill="#007acc"/>
|
|
63
|
+
<text x="32" y="229" font-family="Segoe UI, Arial, sans-serif" font-size="8" fill="#ffffff">MCP: 1 session</text>
|
|
64
|
+
<text x="184" y="229" font-family="Segoe UI, Arial, sans-serif" font-size="8" fill="#ffffff">bash</text>
|
|
65
|
+
</svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vscode-terminal-mcp",
|
|
3
|
+
"displayName": "Terminal MCP Server",
|
|
4
|
+
"description": "MCP server that provides visible terminal sessions in VSCode for Claude Code and subagents",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"publisher": "sirlordt",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "sirlordt",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/sirlordt/vscode-terminal-mcp.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/sirlordt/vscode-terminal-mcp#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/sirlordt/vscode-terminal-mcp/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"terminal",
|
|
20
|
+
"vscode",
|
|
21
|
+
"claude",
|
|
22
|
+
"claude-code",
|
|
23
|
+
"model-context-protocol",
|
|
24
|
+
"ai-tools"
|
|
25
|
+
],
|
|
26
|
+
"mcpName": "io.github.sirlordt/vscode-terminal-mcp",
|
|
27
|
+
"icon": "icon.png",
|
|
28
|
+
"engines": {
|
|
29
|
+
"vscode": "^1.99.0"
|
|
30
|
+
},
|
|
31
|
+
"categories": [
|
|
32
|
+
"Other"
|
|
33
|
+
],
|
|
34
|
+
"activationEvents": [
|
|
35
|
+
"onStartupFinished"
|
|
36
|
+
],
|
|
37
|
+
"main": "./dist/extension.js",
|
|
38
|
+
"contributes": {
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"vscode-terminal-mcp": {
|
|
41
|
+
"type": "stdio",
|
|
42
|
+
"command": "node",
|
|
43
|
+
"args": [
|
|
44
|
+
"${extensionPath}/dist/mcp-entry.js"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"configuration": {
|
|
49
|
+
"title": "Terminal MCP",
|
|
50
|
+
"properties": {
|
|
51
|
+
"terminalMcp.blockedCommands": {
|
|
52
|
+
"type": "array",
|
|
53
|
+
"items": {
|
|
54
|
+
"type": "string"
|
|
55
|
+
},
|
|
56
|
+
"default": [
|
|
57
|
+
"rm -rf /",
|
|
58
|
+
"mkfs",
|
|
59
|
+
"dd if=",
|
|
60
|
+
":(){ :|:& };:"
|
|
61
|
+
],
|
|
62
|
+
"description": "Commands that are never allowed to execute"
|
|
63
|
+
},
|
|
64
|
+
"terminalMcp.allowedDirectories": {
|
|
65
|
+
"type": "array",
|
|
66
|
+
"items": {
|
|
67
|
+
"type": "string"
|
|
68
|
+
},
|
|
69
|
+
"default": [],
|
|
70
|
+
"description": "Allowed working directories (empty = unrestricted)"
|
|
71
|
+
},
|
|
72
|
+
"terminalMcp.defaultTimeoutMs": {
|
|
73
|
+
"type": "number",
|
|
74
|
+
"default": 30000,
|
|
75
|
+
"description": "Default timeout for command execution in milliseconds"
|
|
76
|
+
},
|
|
77
|
+
"terminalMcp.maxConcurrentSessions": {
|
|
78
|
+
"type": "number",
|
|
79
|
+
"default": 10,
|
|
80
|
+
"description": "Maximum number of concurrent terminal sessions"
|
|
81
|
+
},
|
|
82
|
+
"terminalMcp.maxOutputLines": {
|
|
83
|
+
"type": "number",
|
|
84
|
+
"default": 10000,
|
|
85
|
+
"description": "Maximum number of output lines to buffer per session"
|
|
86
|
+
},
|
|
87
|
+
"terminalMcp.idleTimeoutMs": {
|
|
88
|
+
"type": "number",
|
|
89
|
+
"default": 300000,
|
|
90
|
+
"description": "Auto-close idle sessions after this many milliseconds (0 = disabled)"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"scripts": {
|
|
96
|
+
"vscode:prepublish": "npm run build",
|
|
97
|
+
"build": "node esbuild.config.mjs",
|
|
98
|
+
"watch": "node esbuild.config.mjs --watch",
|
|
99
|
+
"lint": "eslint src/ --ext .ts",
|
|
100
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
101
|
+
"test": "vitest run",
|
|
102
|
+
"test:watch": "vitest",
|
|
103
|
+
"package": "vsce package"
|
|
104
|
+
},
|
|
105
|
+
"dependencies": {
|
|
106
|
+
"@modelcontextprotocol/sdk": "^1.9.0",
|
|
107
|
+
"strip-ansi": "^7.1.0",
|
|
108
|
+
"uuid": "^9.0.0",
|
|
109
|
+
"zod": "^3.24.0",
|
|
110
|
+
"zod-to-json-schema": "^3.23.0"
|
|
111
|
+
},
|
|
112
|
+
"devDependencies": {
|
|
113
|
+
"@types/node": "^20.12.0",
|
|
114
|
+
"@types/uuid": "^9.0.0",
|
|
115
|
+
"@types/vscode": "^1.99.0",
|
|
116
|
+
"@vscode/test-electron": "^2.4.0",
|
|
117
|
+
"@vscode/vsce": "^2.26.0",
|
|
118
|
+
"esbuild": "^0.21.0",
|
|
119
|
+
"eslint": "^8.57.0",
|
|
120
|
+
"prettier": "^3.2.0",
|
|
121
|
+
"typescript": "^5.4.0",
|
|
122
|
+
"vitest": "^1.6.0"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# MCP SDK - Patterns and Architecture
|
|
2
|
+
|
|
3
|
+
## Base Protocol
|
|
4
|
+
- JSON-RPC 2.0 as message format
|
|
5
|
+
- 3-component architecture: **Server** (exposes capabilities), **Client** (consumes), **Host** (manages communication - Claude Desktop, VSCode)
|
|
6
|
+
|
|
7
|
+
## Package @modelcontextprotocol/sdk
|
|
8
|
+
- Core: `@modelcontextprotocol/server` and `@modelcontextprotocol/client`
|
|
9
|
+
- Transports: `@modelcontextprotocol/node` (HTTP), Express, Hono
|
|
10
|
+
- Requires `zod` as peer dependency for schema validation
|
|
11
|
+
- Setup with ES Modules (`"type": "module"`)
|
|
12
|
+
|
|
13
|
+
## 3 Protocol Primitives
|
|
14
|
+
1. **Tools** - Executable functions the LLM can invoke with structured parameters
|
|
15
|
+
2. **Resources** - Data sources (databases, APIs, filesystem)
|
|
16
|
+
3. **Prompts** - Predefined templates for interactions
|
|
17
|
+
|
|
18
|
+
## Request Structure (JSON-RPC 2.0)
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"jsonrpc": "2.0",
|
|
22
|
+
"id": "<unique-id>",
|
|
23
|
+
"method": "tools/call",
|
|
24
|
+
"params": {
|
|
25
|
+
"name": "<tool-name>",
|
|
26
|
+
"arguments": {}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Tool Definition
|
|
32
|
+
Each tool requires:
|
|
33
|
+
- `name` - Unique identifier
|
|
34
|
+
- `title` - Human-readable name
|
|
35
|
+
- `description` - Functionality explanation
|
|
36
|
+
- `inputSchema` - JSON Schema (defined with Zod)
|
|
37
|
+
- Handler function - Executes logic and returns results
|
|
38
|
+
|
|
39
|
+
## Supported Transports
|
|
40
|
+
|
|
41
|
+
### stdio (Standard I/O)
|
|
42
|
+
- Ideal for local servers
|
|
43
|
+
- Communication via process stdin/stdout
|
|
44
|
+
- Auto-discovery in VSCode extensions
|
|
45
|
+
|
|
46
|
+
### SSE (Server-Sent Events over HTTP)
|
|
47
|
+
- POST for client->server
|
|
48
|
+
- Supports streaming
|
|
49
|
+
- Standard HTTP authentication (bearer tokens, API keys, OAuth)
|
|
50
|
+
|
|
51
|
+
## Connection Lifecycle
|
|
52
|
+
1. **Initialization** - Capability exchange and version negotiation
|
|
53
|
+
2. **Operation** - Normal tool execution and resource access
|
|
54
|
+
3. **Termination** - Orderly connection shutdown
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# VSCode Terminal API - Capabilities and Limitations
|
|
2
|
+
|
|
3
|
+
## Terminal Creation
|
|
4
|
+
```typescript
|
|
5
|
+
const terminal = vscode.window.createTerminal('MyTerminal');
|
|
6
|
+
terminal.sendText("command", addNewLine);
|
|
7
|
+
terminal.show();
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Pseudoterminal Interface (Custom PTY)
|
|
11
|
+
```typescript
|
|
12
|
+
interface Pseudoterminal {
|
|
13
|
+
open(): void;
|
|
14
|
+
close(): void;
|
|
15
|
+
handleInput(data: string): void;
|
|
16
|
+
onDidWrite: Event<string>; // Writes data to terminal UI
|
|
17
|
+
setDimensions(cols, rows): void;
|
|
18
|
+
onDidClose: Event<number | void>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
vscode.window.createTerminal({ pty: myPseudoterminal });
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Critical Limitation
|
|
25
|
+
The standard Terminal API **does NOT provide direct output reading** from integrated terminals.
|
|
26
|
+
`sendText()` sends text to the shell, but capturing output requires workarounds.
|
|
27
|
+
|
|
28
|
+
## Shell Integration API (VSCode 1.93+)
|
|
29
|
+
- `onDidStartTerminalShellExecution` - Event fired when a command starts
|
|
30
|
+
- `execution.read()` - AsyncIterable<string> with output chunks
|
|
31
|
+
- `execution.exitCode` - Thenable<number | undefined>
|
|
32
|
+
- Requires active shell integration (automatic in bash/zsh/PowerShell)
|
|
33
|
+
|
|
34
|
+
## Recommended Strategy
|
|
35
|
+
1. **Primary**: Shell Integration API (when available)
|
|
36
|
+
2. **Fallback**: Polling-based output stabilization detection
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# VSCode Extension as MCP Server
|
|
2
|
+
|
|
3
|
+
## Native Support (VSCode 1.99+)
|
|
4
|
+
- Extensions can serve as MCP servers
|
|
5
|
+
- Auto-discovery by Claude Code / Copilot without additional configuration
|
|
6
|
+
- Communication via **stdio** by default
|
|
7
|
+
|
|
8
|
+
## Declaration in package.json
|
|
9
|
+
```jsonc
|
|
10
|
+
{
|
|
11
|
+
"contributes": {
|
|
12
|
+
"mcpServers": {
|
|
13
|
+
"server-name": {
|
|
14
|
+
"type": "stdio",
|
|
15
|
+
"command": "node",
|
|
16
|
+
"args": ["${extensionPath}/dist/mcp-entry.js"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Key Architectural Problem
|
|
24
|
+
`contributes.mcpServers` launches a **child process** separate from the extension host.
|
|
25
|
+
The VSCode Terminal API (`vscode.window.createTerminal`, Shell Integration) is only
|
|
26
|
+
available **within the extension host process**.
|
|
27
|
+
|
|
28
|
+
## Solution: IPC Bridge
|
|
29
|
+
```
|
|
30
|
+
[Claude Code] --stdio--> [mcp-entry.js (shim)] --IPC--> [Extension Host (Terminal API)]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- `mcp-entry.js`: Thin stdio-to-IPC bridge. Reads JSON-RPC from stdin, forwards via Unix socket.
|
|
34
|
+
- `extension.ts`: In activate(), creates IPC server (Unix domain socket), instantiates MCP Server with handlers, processes forwarded requests.
|
|
35
|
+
|
|
36
|
+
## Workspace Configuration
|
|
37
|
+
`.vscode/mcp.json` or `settings.json` file for per-team configuration.
|
|
38
|
+
|
|
39
|
+
## Existing MCP Servers in the Environment
|
|
40
|
+
1. **desktop-commander** (v0.2.38) - Command execution, files
|
|
41
|
+
2. **playwright-mcp** - Browser automation
|
|
42
|
+
3. **context7** - Contextual documentation
|
|
43
|
+
4. **chrome-devtools-mcp** (v0.20.2) - DevTools
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Desktop Commander MCP - Implementation Reference
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
Desktop Commander is the most relevant MCP as a reference. It implements command
|
|
5
|
+
execution with output capture, session management, and security.
|
|
6
|
+
|
|
7
|
+
## Exposed Tools
|
|
8
|
+
- **Terminal**: `execute_command`, `read_output`, `force_terminate`, `list_sessions`, `list_processes`
|
|
9
|
+
- **Filesystem**: `read_file`, `write_file`, `move_file`, `search`
|
|
10
|
+
- **Code editing**: `edit_block` (surgical text replacement)
|
|
11
|
+
|
|
12
|
+
## Security Patterns
|
|
13
|
+
- Two execution modes:
|
|
14
|
+
1. **Safe mode** - Executable allowlist without shell interpretation
|
|
15
|
+
2. **Legacy mode** - Command string allowlist/blocklist
|
|
16
|
+
- Command validation before execution
|
|
17
|
+
- Timeout control
|
|
18
|
+
- Working directory restrictions
|
|
19
|
+
- Stdin support with timeout protection
|
|
20
|
+
|
|
21
|
+
## Configuration
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"blockedCommands": ["rm -rf /", "mkfs", "dd if="],
|
|
25
|
+
"defaultShell": "/bin/bash",
|
|
26
|
+
"allowedDirectories": ["/home/user/projects"],
|
|
27
|
+
"fileReadLineLimit": 1000,
|
|
28
|
+
"fileWriteLineLimit": 1000
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Differences with Our Solution
|
|
33
|
+
| Aspect | Desktop Commander | Our MCP |
|
|
34
|
+
|--------|------------------|---------|
|
|
35
|
+
| Visibility | No visible terminal | Visible terminal in VSCode |
|
|
36
|
+
| Integration | Independent process | Embedded in VSCode extension |
|
|
37
|
+
| Output | Captured text only | Text + visual terminal |
|
|
38
|
+
| Subagents | Not designed for this | Native support with agentId |
|
|
39
|
+
| Context | Loses visual context | User sees the execution |
|
|
40
|
+
|
|
41
|
+
## Lessons Applied
|
|
42
|
+
1. Circular buffer for output with pagination
|
|
43
|
+
2. Dangerous command blocklist by default
|
|
44
|
+
3. Configurable timeout with partial return
|
|
45
|
+
4. Session management with Map<id, session>
|
package/server.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
|
|
3
|
+
"name": "io.github.sirlordt/vscode-terminal-mcp",
|
|
4
|
+
"title": "Terminal MCP Server",
|
|
5
|
+
"description": "MCP server that executes commands in visible VSCode terminal tabs with full output capture, session reuse, long-running process support, and subagent isolation.",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"packages": [
|
|
8
|
+
{
|
|
9
|
+
"registry": "npm",
|
|
10
|
+
"name": "vscode-terminal-mcp"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|