@arvoretech/runtime-lens-mcp 1.0.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/.vscodeignore +21 -0
- package/README.md +136 -0
- package/agent/index.ts +263 -0
- package/agent/tsconfig.json +17 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/log-collector.d.ts +73 -0
- package/dist/log-collector.d.ts.map +1 -0
- package/dist/log-collector.js +349 -0
- package/dist/log-collector.js.map +1 -0
- package/dist/process-inspector.d.ts +44 -0
- package/dist/process-inspector.d.ts.map +1 -0
- package/dist/process-inspector.js +190 -0
- package/dist/process-inspector.js.map +1 -0
- package/dist/runtime-interceptor.d.ts +18 -0
- package/dist/runtime-interceptor.d.ts.map +1 -0
- package/dist/runtime-interceptor.js +133 -0
- package/dist/runtime-interceptor.js.map +1 -0
- package/dist/server.d.ts +26 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +301 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +280 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +102 -0
- package/dist/types.js.map +1 -0
- package/eslint.config.js +41 -0
- package/extension/decorator.ts +144 -0
- package/extension/extension.ts +98 -0
- package/extension/runtime-bridge.ts +206 -0
- package/extension/tsconfig.json +17 -0
- package/package.json +134 -0
- package/src/index.ts +18 -0
- package/src/log-collector.ts +441 -0
- package/src/process-inspector.ts +235 -0
- package/src/runtime-interceptor.ts +152 -0
- package/src/server.ts +387 -0
- package/src/types.ts +128 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import * as vscode from "vscode";
|
|
2
|
+
import { request } from "node:http";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import type { Socket } from "node:net";
|
|
5
|
+
import { InlineDecorator } from "./decorator.js";
|
|
6
|
+
|
|
7
|
+
interface AgentMessage {
|
|
8
|
+
type: "log" | "error" | "warn" | "info" | "debug" | "result";
|
|
9
|
+
file: string;
|
|
10
|
+
line: number;
|
|
11
|
+
column: number;
|
|
12
|
+
values: string[];
|
|
13
|
+
timestamp: number;
|
|
14
|
+
expression?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class RuntimeBridge {
|
|
18
|
+
private socket: Socket | null = null;
|
|
19
|
+
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
20
|
+
private readonly decorator: InlineDecorator;
|
|
21
|
+
private readonly port: number;
|
|
22
|
+
private connected = false;
|
|
23
|
+
private readonly outputChannel: vscode.OutputChannel;
|
|
24
|
+
private dataBuffer: Buffer = Buffer.alloc(0);
|
|
25
|
+
|
|
26
|
+
constructor(decorator: InlineDecorator, outputChannel: vscode.OutputChannel) {
|
|
27
|
+
this.decorator = decorator;
|
|
28
|
+
this.port = vscode.workspace.getConfiguration("runtimeLens").get("port", 9500);
|
|
29
|
+
this.outputChannel = outputChannel;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
connect(): void {
|
|
33
|
+
if (this.socket) return;
|
|
34
|
+
|
|
35
|
+
const key = randomBytes(16).toString("base64");
|
|
36
|
+
|
|
37
|
+
const req = request({
|
|
38
|
+
hostname: "localhost",
|
|
39
|
+
port: this.port,
|
|
40
|
+
path: "/",
|
|
41
|
+
method: "GET",
|
|
42
|
+
headers: {
|
|
43
|
+
Upgrade: "websocket",
|
|
44
|
+
Connection: "Upgrade",
|
|
45
|
+
"Sec-WebSocket-Key": key,
|
|
46
|
+
"Sec-WebSocket-Version": "13",
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
req.on("upgrade", (_res, socket) => {
|
|
51
|
+
this.socket = socket;
|
|
52
|
+
this.connected = true;
|
|
53
|
+
this.dataBuffer = Buffer.alloc(0);
|
|
54
|
+
this.outputChannel.appendLine(`[runtime-lens] Connected to agent on port ${this.port}`);
|
|
55
|
+
vscode.window.setStatusBarMessage("$(eye) Runtime Lens: Connected", 3000);
|
|
56
|
+
|
|
57
|
+
socket.on("data", (data: Buffer) => {
|
|
58
|
+
this.dataBuffer = Buffer.concat([this.dataBuffer, data]);
|
|
59
|
+
this.processFrames();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
socket.on("close", () => {
|
|
63
|
+
this.connected = false;
|
|
64
|
+
this.socket = null;
|
|
65
|
+
this.scheduleReconnect();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
socket.on("error", () => {
|
|
69
|
+
this.connected = false;
|
|
70
|
+
this.socket = null;
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
req.on("error", () => {
|
|
75
|
+
this.scheduleReconnect();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
req.end();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
disconnect(): void {
|
|
82
|
+
if (this.reconnectTimer) {
|
|
83
|
+
clearTimeout(this.reconnectTimer);
|
|
84
|
+
this.reconnectTimer = null;
|
|
85
|
+
}
|
|
86
|
+
if (this.socket) {
|
|
87
|
+
this.socket.end();
|
|
88
|
+
this.socket = null;
|
|
89
|
+
}
|
|
90
|
+
this.connected = false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
isConnected(): boolean {
|
|
94
|
+
return this.connected;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
evaluate(expression: string, file: string, line: number): void {
|
|
98
|
+
if (!this.socket || !this.connected) return;
|
|
99
|
+
const payload = JSON.stringify({ type: "eval", expression, file, line });
|
|
100
|
+
this.sendFrame(payload);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private processFrames(): void {
|
|
104
|
+
while (this.dataBuffer.length >= 2) {
|
|
105
|
+
const firstByte = this.dataBuffer[0];
|
|
106
|
+
const secondByte = this.dataBuffer[1];
|
|
107
|
+
const isFin = (firstByte & 0x80) !== 0;
|
|
108
|
+
const opcode = firstByte & 0x0f;
|
|
109
|
+
|
|
110
|
+
if (opcode === 0x08) {
|
|
111
|
+
this.socket?.end();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let payloadLen = secondByte & 0x7f;
|
|
116
|
+
let headerLen = 2;
|
|
117
|
+
|
|
118
|
+
if (payloadLen === 126) {
|
|
119
|
+
if (this.dataBuffer.length < 4) return;
|
|
120
|
+
payloadLen = this.dataBuffer.readUInt16BE(2);
|
|
121
|
+
headerLen = 4;
|
|
122
|
+
} else if (payloadLen === 127) {
|
|
123
|
+
if (this.dataBuffer.length < 10) return;
|
|
124
|
+
payloadLen = Number(this.dataBuffer.readBigUInt64BE(2));
|
|
125
|
+
headerLen = 10;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const totalLen = headerLen + payloadLen;
|
|
129
|
+
if (this.dataBuffer.length < totalLen) return;
|
|
130
|
+
|
|
131
|
+
const payload = this.dataBuffer.subarray(headerLen, totalLen);
|
|
132
|
+
this.dataBuffer = this.dataBuffer.subarray(totalLen);
|
|
133
|
+
|
|
134
|
+
if (isFin && (opcode === 0x01 || opcode === 0x02)) {
|
|
135
|
+
const text = payload.toString("utf-8");
|
|
136
|
+
try {
|
|
137
|
+
const msg: AgentMessage = JSON.parse(text);
|
|
138
|
+
this.handleMessage(msg);
|
|
139
|
+
} catch {
|
|
140
|
+
// invalid JSON
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private sendFrame(text: string): void {
|
|
147
|
+
if (!this.socket) return;
|
|
148
|
+
const data = Buffer.from(text, "utf-8");
|
|
149
|
+
const len = data.length;
|
|
150
|
+
let header: Buffer;
|
|
151
|
+
|
|
152
|
+
if (len < 126) {
|
|
153
|
+
header = Buffer.alloc(2);
|
|
154
|
+
header[0] = 0x81;
|
|
155
|
+
header[1] = len;
|
|
156
|
+
} else if (len < 65536) {
|
|
157
|
+
header = Buffer.alloc(4);
|
|
158
|
+
header[0] = 0x81;
|
|
159
|
+
header[1] = 126;
|
|
160
|
+
header.writeUInt16BE(len, 2);
|
|
161
|
+
} else {
|
|
162
|
+
header = Buffer.alloc(10);
|
|
163
|
+
header[0] = 0x81;
|
|
164
|
+
header[1] = 127;
|
|
165
|
+
header.writeBigUInt64BE(BigInt(len), 2);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
this.socket.write(Buffer.concat([header, data]));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private handleMessage(msg: AgentMessage): void {
|
|
172
|
+
const workspaceFolders = vscode.workspace.workspaceFolders;
|
|
173
|
+
if (!workspaceFolders) return;
|
|
174
|
+
|
|
175
|
+
let resolvedFile = msg.file;
|
|
176
|
+
for (const folder of workspaceFolders) {
|
|
177
|
+
if (msg.file.startsWith(folder.uri.fsPath)) {
|
|
178
|
+
resolvedFile = msg.file;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const text = msg.values.join(", ");
|
|
184
|
+
|
|
185
|
+
this.decorator.addValue({
|
|
186
|
+
file: resolvedFile,
|
|
187
|
+
line: msg.line,
|
|
188
|
+
column: msg.column,
|
|
189
|
+
text,
|
|
190
|
+
type: msg.type,
|
|
191
|
+
timestamp: msg.timestamp,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
this.outputChannel.appendLine(
|
|
195
|
+
`[${msg.type}] ${resolvedFile}:${msg.line} → ${text}`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private scheduleReconnect(): void {
|
|
200
|
+
if (this.reconnectTimer) return;
|
|
201
|
+
this.reconnectTimer = setTimeout(() => {
|
|
202
|
+
this.reconnectTimer = null;
|
|
203
|
+
this.connect();
|
|
204
|
+
}, 3000);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"moduleResolution": "Node",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"declaration": false,
|
|
11
|
+
"sourceMap": true,
|
|
12
|
+
"outDir": "../dist/extension",
|
|
13
|
+
"rootDir": "."
|
|
14
|
+
},
|
|
15
|
+
"include": ["./**/*.ts"],
|
|
16
|
+
"exclude": ["node_modules"]
|
|
17
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@arvoretech/runtime-lens-mcp",
|
|
3
|
+
"displayName": "Runtime Lens",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Runtime Lens - Runtime inspection with inline values for React, NestJS and Next.js",
|
|
6
|
+
"publisher": "arvore",
|
|
7
|
+
"main": "dist/extension/extension.js",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"runtime-lens-mcp": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"vscode": "^1.85.0"
|
|
16
|
+
},
|
|
17
|
+
"categories": [
|
|
18
|
+
"Debuggers",
|
|
19
|
+
"Other"
|
|
20
|
+
],
|
|
21
|
+
"activationEvents": [
|
|
22
|
+
"onStartupFinished"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"build:ext": "esbuild extension/extension.ts --bundle --outfile=dist/extension/extension.js --external:vscode --format=cjs --platform=node --sourcemap",
|
|
27
|
+
"build:agent": "esbuild agent/index.ts --bundle --outfile=dist/agent/index.js --format=cjs --platform=node --sourcemap",
|
|
28
|
+
"build:all": "pnpm build && pnpm build:ext && pnpm build:agent",
|
|
29
|
+
"dev": "tsx src/index.ts",
|
|
30
|
+
"start": "node dist/index.js",
|
|
31
|
+
"package": "pnpm build:all && vsce package --no-dependencies",
|
|
32
|
+
"prepublishOnly": "node -e \"const p=require('./package.json');p.name='@arvoretech/runtime-lens-mcp';require('fs').writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n')\"",
|
|
33
|
+
"postpublish": "node -e \"const p=require('./package.json');p.name='runtime-lens';require('fs').writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n')\"",
|
|
34
|
+
"test": "vitest",
|
|
35
|
+
"test:cov": "vitest --coverage",
|
|
36
|
+
"lint": "eslint src/**/*.ts",
|
|
37
|
+
"lint:fix": "eslint src/**/*.ts --fix"
|
|
38
|
+
},
|
|
39
|
+
"contributes": {
|
|
40
|
+
"commands": [
|
|
41
|
+
{
|
|
42
|
+
"command": "runtimeLens.start",
|
|
43
|
+
"title": "Runtime Lens: Start",
|
|
44
|
+
"icon": "$(zap)"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"command": "runtimeLens.stop",
|
|
48
|
+
"title": "Runtime Lens: Stop"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"command": "runtimeLens.toggle",
|
|
52
|
+
"title": "Runtime Lens: Toggle"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"command": "runtimeLens.clear",
|
|
56
|
+
"title": "Runtime Lens: Clear Inline Values"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"command": "runtimeLens.connect",
|
|
60
|
+
"title": "Runtime Lens: Connect to Running App"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"command": "runtimeLens.showOutput",
|
|
64
|
+
"title": "Runtime Lens: Show Output"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"command": "runtimeLens.injectEnv",
|
|
68
|
+
"title": "Runtime Lens: Inject Environment into Terminal"
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"configuration": {
|
|
72
|
+
"title": "Runtime Lens",
|
|
73
|
+
"properties": {
|
|
74
|
+
"runtimeLens.port": {
|
|
75
|
+
"type": "number",
|
|
76
|
+
"default": 9500,
|
|
77
|
+
"description": "WebSocket port for the runtime agent"
|
|
78
|
+
},
|
|
79
|
+
"runtimeLens.autoStart": {
|
|
80
|
+
"type": "boolean",
|
|
81
|
+
"default": false,
|
|
82
|
+
"description": "Automatically start Runtime Lens when the workspace opens"
|
|
83
|
+
},
|
|
84
|
+
"runtimeLens.maxInlineLength": {
|
|
85
|
+
"type": "number",
|
|
86
|
+
"default": 80,
|
|
87
|
+
"description": "Maximum length of inline value text before truncation"
|
|
88
|
+
},
|
|
89
|
+
"runtimeLens.showTimestamp": {
|
|
90
|
+
"type": "boolean",
|
|
91
|
+
"default": false,
|
|
92
|
+
"description": "Show timestamp in inline values"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"menus": {
|
|
97
|
+
"editor/title": [
|
|
98
|
+
{
|
|
99
|
+
"command": "runtimeLens.toggle",
|
|
100
|
+
"group": "navigation",
|
|
101
|
+
"when": "resourceLangId == typescript || resourceLangId == javascript || resourceLangId == typescriptreact || resourceLangId == javascriptreact"
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"keywords": [
|
|
107
|
+
"mcp",
|
|
108
|
+
"console-ninja",
|
|
109
|
+
"runtime-lens",
|
|
110
|
+
"debugging",
|
|
111
|
+
"logging",
|
|
112
|
+
"inline-values",
|
|
113
|
+
"react",
|
|
114
|
+
"nestjs",
|
|
115
|
+
"nextjs",
|
|
116
|
+
"runtime-inspection",
|
|
117
|
+
"arvore"
|
|
118
|
+
],
|
|
119
|
+
"author": "Arvore",
|
|
120
|
+
"license": "MIT",
|
|
121
|
+
"repository": {
|
|
122
|
+
"type": "git",
|
|
123
|
+
"url": "https://github.com/arvoreeducacao/arvore-mcp-servers.git",
|
|
124
|
+
"directory": "packages/runtime-lens"
|
|
125
|
+
},
|
|
126
|
+
"dependencies": {
|
|
127
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
128
|
+
"zod": "^3.22.4"
|
|
129
|
+
},
|
|
130
|
+
"devDependencies": {
|
|
131
|
+
"@types/vscode": "^1.85.0",
|
|
132
|
+
"esbuild": "^0.21.0"
|
|
133
|
+
}
|
|
134
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { RuntimeLensMCPServer } from "./server.js";
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const server = RuntimeLensMCPServer.fromEnvironment();
|
|
7
|
+
server.setupGracefulShutdown();
|
|
8
|
+
await server.start();
|
|
9
|
+
} catch (error) {
|
|
10
|
+
console.error("Failed to start Runtime Lens MCP Server:", error);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { RuntimeLensMCPServer } from "./server.js";
|
|
15
|
+
export { LogCollector } from "./log-collector.js";
|
|
16
|
+
export { ProcessInspector } from "./process-inspector.js";
|
|
17
|
+
export { RuntimeInterceptor } from "./runtime-interceptor.js";
|
|
18
|
+
export * from "./types.js";
|