jlink-mcp
Version:
MCP server for SEGGER J-Link debug probes — LLM-driven embedded debugging with RTT, GDB server, and Trice/Pigweed support
207 lines • 7.74 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.TelnetProxy = void 0;
const net = __importStar(require("net"));
const events_1 = require("events");
const logger_1 = require("../utils/logger");
/**
* A TCP proxy server that sits between the RTT telnet port and external consumers
* (like Trice or Pigweed detokenizer). It tees the data so both the MCP server
* and external tools can consume the RTT stream simultaneously.
*
* Architecture:
* JLinkGDBServer:19021 (RTT) --> TelnetProxy:19400 --> multiple clients
* --> internal buffer (for MCP)
*/
class TelnetProxy extends events_1.EventEmitter {
server = null;
sourceSocket = null;
clients = new Set();
listenPort;
sourceHost;
sourcePort;
running = false;
dataBuffer = [];
maxBufferLines = 2000;
constructor(listenPort = 19400, sourceHost = "localhost", sourcePort = 19021) {
super();
this.listenPort = listenPort;
this.sourceHost = sourceHost;
this.sourcePort = sourcePort;
}
/** Start the proxy: connect to source and listen for clients */
async start() {
if (this.running) {
return { success: true, message: `Telnet proxy already running on port ${this.listenPort}` };
}
try {
// Connect to RTT source
await this.connectToSource();
// Start listening server
await this.startServer();
this.running = true;
return {
success: true,
message: `Telnet proxy listening on port ${this.listenPort}, connected to ${this.sourceHost}:${this.sourcePort}`,
};
}
catch (err) {
(0, logger_1.logError)("Failed to start telnet proxy", err);
return {
success: false,
message: `Failed to start telnet proxy: ${err instanceof Error ? err.message : String(err)}`,
};
}
}
connectToSource() {
return new Promise((resolve, reject) => {
this.sourceSocket = new net.Socket();
this.sourceSocket.on("connect", () => {
(0, logger_1.log)(`Telnet proxy connected to source ${this.sourceHost}:${this.sourcePort}`);
resolve();
});
this.sourceSocket.on("data", (data) => {
const text = data.toString();
// Buffer for MCP access
const lines = text.split("\n");
for (const line of lines) {
if (line.trim()) {
this.dataBuffer.push(line);
if (this.dataBuffer.length > this.maxBufferLines) {
this.dataBuffer.shift();
}
}
}
// Forward to all connected clients
for (const client of this.clients) {
try {
client.write(data);
}
catch {
this.clients.delete(client);
}
}
this.emit("data", text);
});
this.sourceSocket.on("close", () => {
(0, logger_1.log)("Telnet proxy: source connection closed");
this.emit("sourceDisconnected");
});
this.sourceSocket.on("error", (err) => {
(0, logger_1.logError)("Telnet proxy source error", err);
reject(err);
});
this.sourceSocket.connect(this.sourcePort, this.sourceHost);
});
}
startServer() {
return new Promise((resolve, reject) => {
this.server = net.createServer((client) => {
(0, logger_1.log)(`Telnet proxy: client connected from ${client.remoteAddress}:${client.remotePort}`);
this.clients.add(client);
client.on("close", () => {
this.clients.delete(client);
(0, logger_1.log)("Telnet proxy: client disconnected");
});
client.on("error", () => {
this.clients.delete(client);
});
// Forward client data to source (for down-channel)
client.on("data", (data) => {
if (this.sourceSocket && !this.sourceSocket.destroyed) {
this.sourceSocket.write(data);
}
});
});
this.server.on("error", (err) => {
(0, logger_1.logError)("Telnet proxy server error", err);
reject(err);
});
this.server.listen(this.listenPort, "127.0.0.1", () => {
(0, logger_1.log)(`Telnet proxy server listening on port ${this.listenPort}`);
resolve();
});
});
}
/** Stop the proxy */
stop() {
for (const client of this.clients) {
client.destroy();
}
this.clients.clear();
if (this.sourceSocket) {
this.sourceSocket.destroy();
this.sourceSocket = null;
}
if (this.server) {
this.server.close();
this.server = null;
}
this.running = false;
this.dataBuffer = [];
(0, logger_1.log)("Telnet proxy stopped");
}
/** Get buffered data */
getBuffer(lines) {
if (lines)
return this.dataBuffer.slice(-lines);
return [...this.dataBuffer];
}
/** Clear buffer */
clearBuffer() {
this.dataBuffer = [];
}
/** Write data to source (device) */
writeToSource(data) {
if (this.sourceSocket && !this.sourceSocket.destroyed) {
this.sourceSocket.write(data);
return true;
}
return false;
}
/** Get proxy status */
getStatus() {
return {
running: this.running,
listenPort: this.listenPort,
sourceConnected: !!this.sourceSocket && !this.sourceSocket.destroyed,
clientCount: this.clients.size,
bufferedLines: this.dataBuffer.length,
};
}
}
exports.TelnetProxy = TelnetProxy;
//# sourceMappingURL=telnet-proxy.js.map