UNPKG

jlink-mcp

Version:

MCP server for SEGGER J-Link debug probes — LLM-driven embedded debugging with RTT, GDB server, and Trice/Pigweed support

235 lines 8.37 kB
"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.RTTClient = void 0; const net = __importStar(require("net")); const events_1 = require("events"); const logger_1 = require("../utils/logger"); /** Strip ANSI escape sequences from text */ function stripAnsi(text) { return text.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").replace(/\[0m/g, ""); } /** Check if a line is SEGGER RTT header boilerplate */ function isRttHeader(line) { return (line.startsWith("SEGGER J-Link") || line.startsWith("Process: JLink") || line.trim() === ""); } /** Parse a Zephyr-style log line: [HH:MM:SS.mmm,uuu] <level> module: message */ function parseZephyrLog(line) { const match = line.match(/^\[(\d{2}:\d{2}:\d{2}\.\d{3},?\d{0,3})\]\s*<(\w+)>\s*(\w[\w._-]*):\s*(.*)$/); if (match) { return { deviceTime: match[1], level: match[2], module: match[3], message: match[4], raw: line, }; } return { deviceTime: null, level: null, module: null, message: line, raw: line }; } /** * Connects to RTT via telnet (when JLinkGDBServer is running). * JLinkGDBServer exposes RTT channel 0 on a configurable telnet port (default 19021). * * Automatically strips ANSI escape codes and SEGGER banners. * Parses Zephyr-style log lines into structured fields. */ class RTTClient extends events_1.EventEmitter { socket = null; host; port; messages = []; /** Flat buffer of all parsed log lines for searching */ allLines = []; maxMessages = 5000; maxLines = 20000; reconnectTimer = null; connected = false; lineBuffer = ""; constructor(host = "localhost", port = 19021) { super(); this.host = host; this.port = port; } /** Connect to RTT telnet port */ connect() { return new Promise((resolve, reject) => { if (this.connected) { resolve(); return; } this.socket = new net.Socket(); this.socket.on("connect", () => { (0, logger_1.log)(`RTT Client connected to ${this.host}:${this.port}`); this.connected = true; this.emit("connected"); resolve(); }); this.socket.on("data", (data) => { const raw = data.toString(); // Clean and split into lines, handling partial lines const cleaned = stripAnsi(raw); this.lineBuffer += cleaned; const parts = this.lineBuffer.split("\n"); // Last part might be incomplete this.lineBuffer = parts.pop() || ""; const parsedLines = []; for (const part of parts) { const trimmed = part.trim(); if (!trimmed || isRttHeader(trimmed)) continue; parsedLines.push(parseZephyrLog(trimmed)); } if (parsedLines.length > 0) { const msg = { channel: 0, timestamp: new Date(), rawData: raw, lines: parsedLines, }; this.messages.push(msg); if (this.messages.length > this.maxMessages) { this.messages.shift(); } for (const line of parsedLines) { this.allLines.push(line); } while (this.allLines.length > this.maxLines) { this.allLines.shift(); } this.emit("data", msg); } }); this.socket.on("close", () => { (0, logger_1.log)("RTT Client disconnected"); this.connected = false; this.emit("disconnected"); }); this.socket.on("error", (err) => { (0, logger_1.logError)("RTT Client error", err); this.connected = false; reject(err); }); this.socket.connect(this.port, this.host); }); } /** Disconnect */ disconnect() { if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; } if (this.socket) { this.socket.destroy(); this.socket = null; } this.connected = false; this.lineBuffer = ""; } /** Send data to RTT down-channel (host → device) */ send(data) { if (!this.socket || !this.connected) return false; this.socket.write(data); return true; } /** Get recent log lines as formatted text */ getLines(count = 50) { const lines = this.allLines.slice(-count); return lines.map((l) => { if (l.deviceTime && l.level && l.module) { return `[${l.deviceTime}] <${l.level}> ${l.module}: ${l.message}`; } return l.message; }); } /** Search/filter log lines */ search(opts) { let results = [...this.allLines]; if (opts.level) { const lvl = opts.level.toLowerCase(); results = results.filter((l) => l.level?.toLowerCase() === lvl); } if (opts.module) { const mod = opts.module.toLowerCase(); results = results.filter((l) => l.module?.toLowerCase().includes(mod)); } if (opts.pattern) { try { const re = new RegExp(opts.pattern, "i"); results = results.filter((l) => re.test(l.message) || re.test(l.raw)); } catch { // fall back to simple string match const pat = opts.pattern.toLowerCase(); results = results.filter((l) => l.message.toLowerCase().includes(pat) || l.raw.toLowerCase().includes(pat)); } } if (opts.count) { results = results.slice(-opts.count); } return results; } /** Get messages (for backward compat / resource access) */ getMessages(count) { if (count) return this.messages.slice(-count); return [...this.messages]; } /** Clear all buffers */ clearBuffer() { this.messages = []; this.allLines = []; this.lineBuffer = ""; } /** Check connection status */ isConnected() { return this.connected; } /** Get buffer stats */ getStats() { return { connected: this.connected, lineCount: this.allLines.length, messageCount: this.messages.length, host: this.host, port: this.port, }; } } exports.RTTClient = RTTClient; //# sourceMappingURL=rtt-client.js.map