@i-scope/mcp-server
v0.4.5
Published
Model Context Protocol server for iScope / Oscilloscope.exe debug & control. Lets AI agents drive a .ajs debug session — breakpoints, step, variables, evaluate — through standard MCP tools layered on top of the iScope DAP adapter.
Maintainers
Readme
@i-scope/mcp-server
Part of the
@i-scope/debuggerSDK — one of seven leaf packages aggregated by the@i-scope/debuggermeta-umbrella. Most users should install the meta (npm install @i-scope/debugger), which transitively pulls this package in together with the rest of the Debugger SDK (DAP adapter, native helper, source-map / VLST / COM leaves). Install@i-scope/mcp-serverdirectly only when you want the MCP server surface without the rest of the SDK on your top-levelpackage.json(e.g. an MCP-only Claude Code config vianpx -y @i-scope/mcp-server). Cursor / VS Code end-users should install the iScope .ajs Debugger extension from Marketplace and run iScope: Enable MCP instead of editingmcp.jsonby hand.
Model Context Protocol (MCP) server that lets AI agents drive a
Oscilloscope.exe debug session for .ajs scripts — set breakpoints,
step, read variables and call stack, evaluate watch expressions — all
through standard MCP tools layered on top of the iScope DAP adapter.
Status: 22 MCP tools shipped: 16
debug_*, 4ui_modal_*(ui_modal_list,ui_modal_click,ui_modal_fill,ui_modal_dismiss), 2system_*(system_preflight_check,system_abi_check).
Quick start
Prerequisites (Windows):
Oscilloscope.exev5 installed (default install path is supported out-of-the-box; for portable installs see theoscilloscopePathlaunch argument).
Install the MCP server. Two flavours:
Cursor / VS Code (recommended) — install iScope .ajs Debugger (
cursor --install-extension i-scope.iscope-debuggeror the same withcode). Command Palette → iScope: Enable MCP → confirm. That mergesnpx -y @i-scope/mcp-serverinto workspace.cursor/mcp.json(Cursor) or.vscode/mcp.json(VS Code) and leaves other servers alone. Then Developer: Reload Window in this editor (not Oscilloscope).Other MCP clients / no extension — no repo clone:
npm install -g @i-scope/mcp-server # ...or use npx in the client's mcp.json (see step 3).This pulls in
@i-scope/dap-adapter,@i-scope/iscope-bridge-client, and the native helper@i-scope/iscope-bridge(which bundlesiScopeBridge.exe) as transitive dependencies. NoISCOPE_*environment variable required.Monorepo developer — full local build:
npm install npm run build:packages npm --workspace=@i-scope/iscope-bridge run build:msbuildConfigure your MCP client only if you skipped iScope: Enable MCP.
Cursor (
.cursor/mcp.json):{ "mcpServers": { "iscope-debug": { "command": "npx", "args": ["-y", "@i-scope/mcp-server"] } } }VS Code (
.vscode/mcp.json):{ "servers": { "iscope-debug": { "type": "stdio", "command": "npx", "args": ["-y", "@i-scope/mcp-server"] } } }For local development from the monorepo:
{ "mcpServers": { "iscope-debug": { "command": "node", "args": ["${workspaceFolder}/packages/mcp-server/dist/src/index.js"] } } }ISCOPE_EXTENSION_PATHis optional now — set it only when you want to override the helper-binary lookup with a custom bundled root.Reload this editor (Command Palette →
Developer: Reload Windowin Cursor or VS Code — not Oscilloscope) and ask your AI agent:"Run
dist/Analyzer.ajsand pause onsrc/index.ts:42, show me the value ofsignalat that point."The agent will use
debug_launch+debug_set_breakpoints+debug_continue+debug_variablesto inspect your script.
Architecture
AI Agent (Cursor / Claude Code)
↕ MCP JSON-RPC over stdio
@i-scope/mcp-server (this package)
↕ DAP over stdio (custom DapClient → spawned server.js)
@i-scope/dap-adapter (AjsDebugSession + stdio server entry)
↕ JSON-RPC over stdio
@i-scope/iscope-bridge-client → @i-scope/iscope-bridge (iScopeBridge.exe)
↕ COM (DISPID Invoke)
Oscilloscope.exe (JScript engine + ScopeAppDbgCtrl)Each MCP tool delegates to the existing DAP adapter — no debug logic is
duplicated. The session state machine
(idle → launching → running ⇄ paused → terminated) lives in
src/dap-driver.ts. Tool groups live under
src/tools/.
Tool reference
22 tools (16 debug + 4 UI modal + 2 system diagnostics).
Every tool is invokable via the standard MCP tools/call request;
consult the tool descriptions inside the running server (tools/list)
for the canonical schemas.
Lifecycle
| Tool | Purpose |
|-----------------------|-------------------------------------------------------------------------|
| debug_launch | Spawn the DAP adapter, start program under Oscilloscope. |
| debug_disconnect | Tear down the session; close Oscilloscope (if we launched it). |
| debug_current_state | Lock-free snapshot of session state + last-known stop info. |
Breakpoints
| Tool | Purpose |
|-------------------------|----------------------------------------------------------|
| debug_set_breakpoints | Replace the breakpoint set for one source file. Pass lines:[] to clear. |
Execution control
| Tool | Purpose |
|---------------------|-------------------------------------------------------------------------|
| debug_continue | Resume; returns the next paused/terminated transition. |
| debug_step_over | Step over (same frame). |
| debug_step_into | Step into the called function. |
| debug_step_out | Step out of the current frame. |
Sync
| Tool | Purpose |
|-------------------------|----------------------------------------------------------------------|
| debug_wait_for_paused | Block until the next pause / termination (or timeout). |
| debug_read_output | Cursor-paginated read of buffered Host.ReportOut + adapter logs. |
Inspection
| Tool | Purpose |
|---------------------|-------------------------------------------------------------------------|
| debug_stack_trace | Get the current call stack with source-map provenance per frame. |
| debug_scopes | List scopes (Locals, …) for a given frame. |
| debug_variables | Enumerate variables under a scope or compound variablesReference. |
| debug_evaluate | Evaluate a JScript expression (watch / hover / repl). |
Composite
| Tool | Purpose |
|------------------|--------------------------------------------------------------------------|
| debug_snapshot | Stack + scopes + locals in one round-trip, atomically under one mutex. |
Source maps (no debug session required)
| Tool | Purpose |
|------------------------|--------------------------------------------------------------------|
| debug_resolve_source | Translate .ts ↔ .ajs positions for any on-disk pair. |
UI modal control (interactive AI agent)
Surface Oscilloscope's child dialogs as a programmatically driveable
tree. Use these tools when a debug_continue / debug_step_*
unexpectedly times out — the script is most likely blocked on a
Host.Configure() form, or Oscilloscope itself is reporting a
diagnostic prompt that needs dismissal.
| Tool | Purpose |
|--------------------|----------------------------------------------------------------------------------------------------------|
| ui_modal_list | Enumerate every visible top-level dialog owned by Oscilloscope, with child controls and a signature classification (configure / diagnostic / unknown). |
| ui_modal_click | Programmatic button click (WM_COMMAND + BN_CLICKED). Standard ids: 1=IDOK, 2=IDCANCEL, 6=IDYES, 7=IDNO. |
| ui_modal_fill | Set the text of a child Edit (WM_SETTEXT) or ComboBox (CB_FINDSTRINGEXACT + CB_SETCURSEL, WM_SETTEXT fallback). Returns previousText for diffing. |
| ui_modal_dismiss | Explicit close — mode='close' → PostMessage(WM_CLOSE) (the X button); mode='cancel' / 'ok' → SendMessage(WM_COMMAND, IDCANCEL/IDOK). |
Safety contract enforced in C++ for every UI tool:
GetWindowThreadProcessId(hwnd) == oscPid— we never touch a window owned by a different process.- Target HWND must not be the Oscilloscope main window — we only act on its owned dialogs.
- All synchronous calls use
SendMessageTimeout(SMTO_ABORTIFHUNG, 500 ms)— a hung target STA cannot stall the helper.
AI usage pattern — Configure() form
// 1. debug_continue times out — script is likely blocked on a modal.
const list = await callTool('ui_modal_list', {});
const m = list.modals.find(m => m.signature === 'configure');
if (m) {
// 2. Find the input + the OK button.
const edit = m.controls.find(c => c.className === 'Edit');
const ok = m.controls.find(c => c.controlId === 1 /* IDOK */);
// 3. Fill and submit.
await callTool('ui_modal_fill', { hwnd: m.hwnd, controlId: edit.controlId, text: '42' });
await callTool('ui_modal_click', { hwnd: m.hwnd, controlId: ok.controlId });
// 4. Resume — engine should now proceed past the blocked Configure() call.
}AI usage pattern — diagnostic dismissal
const list = await callTool('ui_modal_list', {});
const diag = list.modals.find(m => m.signature === 'diagnostic');
if (diag) {
// Read diag.title, take action, then dismiss with Enter/Esc.
await callTool('ui_modal_dismiss', { hwnd: diag.hwnd, mode: 'ok' });
}These tools work without an active debug session (Oscilloscope only
needs to be running) and are safe to mix with an active DAP session —
COM lets multiple clients attach to Oscilloscope. ui_modal_* is the
path for interactive control.
System diagnostics
Verify host machine prerequisites before (or after a failure during) any debug session. Read-only / idempotent — safe to call without Oscilloscope running.
| Tool | Purpose |
|--------------------------|---------------------------------------------------------------------------------------------------------|
| system_preflight_check | Verify that Windows Script Debugger (Machine Debug Manager, pdm.dll) is installed and that Oscilloscope's COM classes are registered. Returns a structured verdict (ok / script-debugger-not-registered / script-debugger-file-missing), per-check details, and human-readable hints + a download URL. Read-only / idempotent; safe to call before any debug session and without Oscilloscope running. AI agents should call this proactively when a launch fails or a user reports "BP ignored". |
| system_abi_check | Probe iScopeBridge.exe wire ABI via protocol/version (no COM). Compares helper abi semver to @i-scope/iscope-bridge-client. Optional policy (strict default). Call when mixing npm versions or after swapping a custom helper binary — before debug_launch if you see ProtocolVersionMismatchError. |
Wire contract: preflight response shape is exported from
@i-scope/com-protocol-types as PreflightCheckResponse.
AI usage pattern — preflight before launch
const r = await callTool('system_preflight_check', {});
if (!r.ok) {
// Surface r.userHints + r.downloadUrl to the user.
// Distinguish: r.verdict === 'script-debugger-not-registered' → install MDM
// r.verdict === 'script-debugger-file-missing' → repair install
// r.oscilloscope.comRegistered === false → Oscilloscope itself is not installed
}Frame source-map provenance
Every frame in debug_stack_trace / debug_snapshot carries a
sourceOrigin field:
mapped—sourceis.tsbecause the adapter resolved the bundler's source map.unmapped-fallback— launch was.tsbut the.ajs.maphad a gap for this line;sourceis the raw.ajsso you don't act on stale TS coordinates.generated— launch was directly against.ajs/.apn/.aps.
Tool namespaces
| Prefix | What it drives | Shipped |
|--------------|-----------------------------------------------------|---------|
| debug_* | Debug session: launch, breakpoints, step, stack, variables, evaluate, snapshot, source maps | 16 |
| ui_modal_* | Native modal dialogs owned by Oscilloscope | 4 |
| system_* | Host preflight and helper wire-ABI check | 2 |
Source maps
Pass .ts paths to program and source. When a source map sits next
to the generated .ajs, frames and breakpoints come back in .ts
space.
Testing
Three integration tests (drive the server through the official
@modelcontextprotocol/sdk client over stdio) plus one unit-test
suite for the pure driver internals:
# Unit tests — no Oscilloscope, no DAP child. Covers Mutex, output-
# buffer pagination, classifyFrame with reverse source-map lookup.
npm --workspace=@i-scope/mcp-server run test:unit
# Integration: pure source-map translation. No Oscilloscope.
npm --workspace=iscope-debugger run test:mcp-sourcemaps
# Integration: full launch → read output → disconnect cycle.
# Requires Oscilloscope.exe + a .mwf.
npm --workspace=iscope-debugger run test:mcp
# Integration: snapshot equivalence — asserts debug_snapshot returns
# the same payload as the headless inspect.cjs harness on the VLST
# fixture. Requires Oscilloscope + .mwf.
npm --workspace=iscope-debugger run test:mcp-snapshotRelated packages
This package is one of seven leaves of the
@i-scope/debugger SDK
meta-umbrella. The full family:
| Package | Role |
|---------|------|
| @i-scope/debugger | meta — one-install entry point for the whole SDK |
| @i-scope/mcp-server (you are here) | MCP server for AI agents (22 tools: 16 debug + 4 UI modal + 2 system) |
| @i-scope/dap-adapter | DAP server (AjsDebugSession) — embeddable + stdio |
| @i-scope/iscope-bridge-client | Node JSON-RPC client to the native helper |
| @i-scope/iscope-bridge | Win32 native helper binary (iScopeBridge.exe) |
| @i-scope/source-map-bridge | TS ↔ AJS source-map manager |
| @i-scope/vlst-parser | Parser for VLST locals/Watch blobs |
| @i-scope/com-protocol-types | TS-mirror of COM DISPID / event constants |
License
MIT. See LICENSE.
