@i-scope/mcp-server 0.4.2

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/LICENSE +21 -0
  3. package/README.md +373 -0
  4. package/dist/src/abi-check.d.ts +19 -0
  5. package/dist/src/abi-check.js +66 -0
  6. package/dist/src/bridge-driver.d.ts +90 -0
  7. package/dist/src/bridge-driver.js +290 -0
  8. package/dist/src/dap-client.d.ts +80 -0
  9. package/dist/src/dap-client.js +296 -0
  10. package/dist/src/dap-driver.d.ts +162 -0
  11. package/dist/src/dap-driver.js +703 -0
  12. package/dist/src/index.d.ts +3 -0
  13. package/dist/src/index.js +175 -0
  14. package/dist/src/state.d.ts +86 -0
  15. package/dist/src/state.js +15 -0
  16. package/dist/src/tools/abi-check.d.ts +3 -0
  17. package/dist/src/tools/abi-check.js +64 -0
  18. package/dist/src/tools/breakpoints.d.ts +3 -0
  19. package/dist/src/tools/breakpoints.js +56 -0
  20. package/dist/src/tools/execution.d.ts +3 -0
  21. package/dist/src/tools/execution.js +75 -0
  22. package/dist/src/tools/helpers.d.ts +27 -0
  23. package/dist/src/tools/helpers.js +134 -0
  24. package/dist/src/tools/inspection.d.ts +3 -0
  25. package/dist/src/tools/inspection.js +141 -0
  26. package/dist/src/tools/lifecycle.d.ts +3 -0
  27. package/dist/src/tools/lifecycle.js +103 -0
  28. package/dist/src/tools/preflight.d.ts +3 -0
  29. package/dist/src/tools/preflight.js +95 -0
  30. package/dist/src/tools/registry.d.ts +15 -0
  31. package/dist/src/tools/registry.js +19 -0
  32. package/dist/src/tools/snapshot.d.ts +3 -0
  33. package/dist/src/tools/snapshot.js +117 -0
  34. package/dist/src/tools/source-maps.d.ts +3 -0
  35. package/dist/src/tools/source-maps.js +232 -0
  36. package/dist/src/tools/sync.d.ts +3 -0
  37. package/dist/src/tools/sync.js +80 -0
  38. package/dist/src/tools/ui-modal.d.ts +3 -0
  39. package/dist/src/tools/ui-modal.js +182 -0
  40. package/package.json +73 -0
  41. package/src/abi-check.ts +97 -0
  42. package/src/bridge-driver.ts +328 -0
  43. package/src/dap-client.ts +336 -0
  44. package/src/dap-driver.ts +810 -0
  45. package/src/index.ts +155 -0
  46. package/src/state.ts +115 -0
  47. package/src/tools/abi-check.ts +66 -0
  48. package/src/tools/breakpoints.ts +59 -0
  49. package/src/tools/execution.ts +105 -0
  50. package/src/tools/helpers.ts +142 -0
  51. package/src/tools/inspection.ts +173 -0
  52. package/src/tools/lifecycle.ts +129 -0
  53. package/src/tools/preflight.ts +95 -0
  54. package/src/tools/registry.ts +34 -0
  55. package/src/tools/snapshot.ts +132 -0
  56. package/src/tools/source-maps.ts +222 -0
  57. package/src/tools/sync.ts +90 -0
  58. package/src/tools/ui-modal.ts +201 -0
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ // @i-scope/mcp-server entry point.
4
+ //
5
+ // Wires:
6
+ // StdioServerTransport (stdin/stdout pipes to the MCP client, e.g.
7
+ // Cursor / Claude Desktop) → McpServer → tool registry → DapDriver
8
+ // (spawns the `server.js` shipped inside `@i-scope/dap-adapter` as
9
+ // the DAP child process; no path arithmetic required).
10
+ //
11
+ // Tool groups (each in its own file under ./tools):
12
+ // - lifecycle: debug_launch / debug_disconnect / debug_current_state
13
+ // - breakpoints: debug_set_breakpoints
14
+ // - execution: debug_continue / debug_step_over|into|out
15
+ // - sync: debug_wait_for_paused / debug_read_output
16
+ // - inspection: debug_stack_trace / debug_scopes / debug_variables / debug_evaluate
17
+ // - snapshot: debug_snapshot (composite — equivalent to inspect.cjs)
18
+ // - source-maps: debug_resolve_source (no debug session needed)
19
+ //
20
+ // Cursor wiring (`.cursor/mcp.json`):
21
+ // {
22
+ // "mcpServers": {
23
+ // "iscope-debugger": {
24
+ // "command": "npx",
25
+ // "args": ["-y", "@i-scope/mcp-server"]
26
+ // }
27
+ // }
28
+ // }
29
+ // No ENV variables required — everything is resolved through the
30
+ // installed npm packages (`@i-scope/dap-adapter` →
31
+ // `@i-scope/iscope-bridge-client` → `@i-scope/iscope-bridge`).
32
+ // For monorepo development use the dist path directly:
33
+ // `command: "node", args: ["E:/.../packages/mcp-server/dist/src/index.js"]`.
34
+ //
35
+ // Stdout discipline:
36
+ // StdioServerTransport reads the JSON-RPC wire from stdin and
37
+ // writes it to stdout. ANY accidental console.log poisons the
38
+ // stream. We force all of our diagnostics through stderr and we
39
+ // monkey-patch console.log → console.error in this file so even a
40
+ // stray dependency log lands on the safe channel.
41
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
42
+ if (k2 === undefined) k2 = k;
43
+ var desc = Object.getOwnPropertyDescriptor(m, k);
44
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
45
+ desc = { enumerable: true, get: function() { return m[k]; } };
46
+ }
47
+ Object.defineProperty(o, k2, desc);
48
+ }) : (function(o, m, k, k2) {
49
+ if (k2 === undefined) k2 = k;
50
+ o[k2] = m[k];
51
+ }));
52
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
53
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
54
+ }) : function(o, v) {
55
+ o["default"] = v;
56
+ });
57
+ var __importStar = (this && this.__importStar) || (function () {
58
+ var ownKeys = function(o) {
59
+ ownKeys = Object.getOwnPropertyNames || function (o) {
60
+ var ar = [];
61
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
62
+ return ar;
63
+ };
64
+ return ownKeys(o);
65
+ };
66
+ return function (mod) {
67
+ if (mod && mod.__esModule) return mod;
68
+ var result = {};
69
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
70
+ __setModuleDefault(result, mod);
71
+ return result;
72
+ };
73
+ })();
74
+ Object.defineProperty(exports, "__esModule", { value: true });
75
+ const os = __importStar(require("node:os"));
76
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
77
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
78
+ const bridge_driver_js_1 = require("./bridge-driver.js");
79
+ const dap_driver_js_1 = require("./dap-driver.js");
80
+ const breakpoints_js_1 = require("./tools/breakpoints.js");
81
+ const execution_js_1 = require("./tools/execution.js");
82
+ const helpers_js_1 = require("./tools/helpers.js");
83
+ const inspection_js_1 = require("./tools/inspection.js");
84
+ const lifecycle_js_1 = require("./tools/lifecycle.js");
85
+ const abi_check_js_1 = require("./tools/abi-check.js");
86
+ const preflight_js_1 = require("./tools/preflight.js");
87
+ const snapshot_js_1 = require("./tools/snapshot.js");
88
+ const source_maps_js_1 = require("./tools/source-maps.js");
89
+ const sync_js_1 = require("./tools/sync.js");
90
+ const ui_modal_js_1 = require("./tools/ui-modal.js");
91
+ function silenceStdout() {
92
+ // Reroute anything that wants to write to stdout (rogue
93
+ // dependency console.log, etc.) onto stderr. The MCP transport
94
+ // owns stdout.
95
+ /* eslint-disable no-console */
96
+ const origLog = console.log.bind(console);
97
+ void origLog;
98
+ console.log = ((...args) => console.error('[stdout-redirect]', ...args));
99
+ console.info = ((...args) => console.error('[stdout-redirect]', ...args));
100
+ /* eslint-enable no-console */
101
+ }
102
+ async function main() {
103
+ silenceStdout();
104
+ // `ISCOPE_EXTENSION_PATH` is optional now — it only acts as a
105
+ // `bundledRoot` override forwarded to the spawned DAP server.
106
+ // The default deployment installs `@i-scope/dap-adapter` and
107
+ // `@i-scope/iscope-bridge` from npm and needs no env at all.
108
+ const extensionPath = process.env['ISCOPE_EXTENSION_PATH'] || undefined;
109
+ process.stderr.write(`[@i-scope/mcp-server v${helpers_js_1.SERVER_INFO.version}] starting on ${os.platform()} ${os.arch()}` +
110
+ (extensionPath ? `; ISCOPE_EXTENSION_PATH=${extensionPath}` : '') +
111
+ `\n`);
112
+ const dap = new dap_driver_js_1.DapDriver({
113
+ extensionPath,
114
+ log: (line) => process.stderr.write(line + '\n'),
115
+ });
116
+ const bridge = new bridge_driver_js_1.BridgeDriver({
117
+ bundledRoot: extensionPath,
118
+ log: (line) => process.stderr.write(line + '\n'),
119
+ });
120
+ const drivers = { dap, bridge };
121
+ const server = new mcp_js_1.McpServer(helpers_js_1.SERVER_INFO, {
122
+ instructions: helpers_js_1.SERVER_INSTRUCTIONS,
123
+ capabilities: {
124
+ tools: { listChanged: false },
125
+ logging: {},
126
+ },
127
+ });
128
+ (0, lifecycle_js_1.registerLifecycleTools)(server, drivers);
129
+ (0, breakpoints_js_1.registerBreakpointTools)(server, drivers);
130
+ (0, execution_js_1.registerExecutionTools)(server, drivers);
131
+ (0, sync_js_1.registerSyncTools)(server, drivers);
132
+ (0, inspection_js_1.registerInspectionTools)(server, drivers);
133
+ (0, snapshot_js_1.registerSnapshotTools)(server, drivers);
134
+ (0, source_maps_js_1.registerSourceMapTools)(server, drivers);
135
+ (0, ui_modal_js_1.registerUIModalTools)(server, drivers);
136
+ (0, preflight_js_1.registerPreflightTools)(server, drivers);
137
+ (0, abi_check_js_1.registerAbiCheckTools)(server, drivers);
138
+ const transport = new stdio_js_1.StdioServerTransport();
139
+ // Graceful cleanup so we don't orphan Oscilloscope on ^C / IDE
140
+ // restart. The MCP transport closes naturally when stdin EOFs,
141
+ // but we still need to tear down the DAP child explicitly.
142
+ let shuttingDown = false;
143
+ const shutdown = (signal) => {
144
+ if (shuttingDown)
145
+ return;
146
+ shuttingDown = true;
147
+ process.stderr.write(`[@i-scope/mcp-server] ${signal} received, shutting down\n`);
148
+ // Fire-and-forget: best-effort disconnect, then exit. Don't
149
+ // await — node's signal handler should be quick.
150
+ Promise.allSettled([
151
+ dap.disconnect(),
152
+ bridge.disconnect(),
153
+ ]).then((results) => {
154
+ for (const r of results) {
155
+ if (r.status === 'rejected')
156
+ process.stderr.write(`[@i-scope/mcp-server] disconnect error: ${r.reason}\n`);
157
+ }
158
+ process.exit(0);
159
+ });
160
+ };
161
+ process.on('SIGINT', () => shutdown('SIGINT'));
162
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
163
+ transport.onclose = () => {
164
+ // Client disconnected (Cursor restarted, user removed the
165
+ // server from .cursor/mcp.json, ...). Same cleanup path.
166
+ shutdown('transport-close');
167
+ };
168
+ await server.connect(transport);
169
+ process.stderr.write('[@i-scope/mcp-server] ready; awaiting MCP requests on stdio\n');
170
+ }
171
+ main().catch((err) => {
172
+ process.stderr.write(`[@i-scope/mcp-server] fatal: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
173
+ process.exit(1);
174
+ });
175
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,86 @@
1
+ /** High-level lifecycle of a single MCP debug session. */
2
+ export type SessionState = 'idle' | 'launching' | 'running' | 'paused' | 'terminated';
3
+ /** DAP `StoppedEvent.reason` values we care about — narrowed to the
4
+ * subset the iScope adapter actually emits. The MCP layer keeps the
5
+ * raw DAP string verbatim so AI doesn't lose nuance. */
6
+ export type StoppedReason = 'breakpoint' | 'step' | 'exception' | 'entry' | 'pause' | 'goto' | string;
7
+ /** Origin of the `source` path returned in a frame.
8
+ *
9
+ * AI uses this to detect "source map gap" without parsing
10
+ * configuration. `unmapped-fallback` is the loudest signal — it means
11
+ * the user launched against `.ts` but for THIS specific line the
12
+ * bundler did not emit a mapping, so we surface the generated `.ajs`
13
+ * position instead of pretending we know the original. */
14
+ export type SourceOrigin = 'mapped' | 'generated' | 'unmapped-fallback';
15
+ /** One stack frame, post-translation. Mirrors DAP `StackFrame` plus
16
+ * the source-map provenance fields. */
17
+ export interface FrameInfo {
18
+ /** DAP frame id. Stable across `stack_trace` calls within ONE
19
+ * paused state; invalidated on resume/step/terminate. */
20
+ id: number;
21
+ /** Frame name from the engine (`probe`, `Host script block`, ...). */
22
+ name: string;
23
+ /** Source path the user would recognise — `.ts` if we mapped,
24
+ * `.ajs` otherwise. */
25
+ source: string;
26
+ /** Provenance of `source` (see {@link SourceOrigin}). */
27
+ sourceOrigin: SourceOrigin;
28
+ /** 1-based start line of the active statement in `source`. */
29
+ line: number;
30
+ /** 1-based start column in `source`. */
31
+ column: number;
32
+ /** 1-based end line for statement highlight; may equal `line`. */
33
+ endLine?: number;
34
+ /** 1-based end column (exclusive of trailing `;`). */
35
+ endColumn?: number;
36
+ /** Always populated when an `.ajs` exists, even for mapped
37
+ * frames — useful when the AI wants to cross-reference with raw
38
+ * engine offsets / engine logs. */
39
+ generatedSource?: string;
40
+ /** 1-based line in the generated `.ajs` regardless of mapping. */
41
+ generatedLine?: number;
42
+ /** 1-based column in the generated `.ajs`. */
43
+ generatedColumn?: number;
44
+ }
45
+ /** Snapshot of "where execution is right now" produced after every
46
+ * state transition into `paused`. */
47
+ export interface StoppedInfo {
48
+ reason: StoppedReason;
49
+ /** Innermost-frame error description when reason==='exception'. */
50
+ description?: string;
51
+ /** Full call stack, innermost first. */
52
+ frames: FrameInfo[];
53
+ }
54
+ /** One buffered `OutputEvent`. Cursor pagination uses `id` (monotonic
55
+ * insertion order). */
56
+ export interface OutputEntry {
57
+ /** Monotonic, gapless integer. Starts at 1. AI passes the highest
58
+ * seen `id` back as `cursor` to read the tail. */
59
+ id: number;
60
+ /** DAP category: `stdout` / `stderr` / `console` / `important` ... */
61
+ category: string;
62
+ /** Raw text from the engine / adapter. May or may not end in `\n`
63
+ * — we do not normalise. */
64
+ text: string;
65
+ /** Unix epoch ms when this MCP server received the event. */
66
+ timestamp: number;
67
+ }
68
+ /** Result returned by execution-control / sync tools after waiting on
69
+ * the next state transition. Either "we paused" (with details) or
70
+ * "the script ended" (`terminated: true`) — never both. */
71
+ export type ExecutionTransition = {
72
+ state: 'paused';
73
+ stopped: StoppedInfo;
74
+ } | {
75
+ state: 'terminated';
76
+ exitInfo?: TerminationInfo;
77
+ } | {
78
+ state: 'timeout';
79
+ waitedMs: number;
80
+ };
81
+ export interface TerminationInfo {
82
+ /** Best-effort reason from the adapter / helper. Empty when the
83
+ * script ended cleanly via `DBGEID_STOP`. */
84
+ reason?: string;
85
+ }
86
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ // Shared types for the MCP server.
3
+ //
4
+ // Why these live in a dedicated module:
5
+ // - DapDriver, every tool group, and (Phase 2/3) BridgeDriver all
6
+ // need to talk about the same notions: "paused state", "frame",
7
+ // "output entry". Without a single source of truth the field names
8
+ // drift between tools and the AI gets a confusing tool-result
9
+ // surface.
10
+ // - Tool input/output Zod schemas live next to each tool's
11
+ // registration; THESE types are the runtime data exchanged between
12
+ // the driver layer and the tool layer (post-validation). The Zod
13
+ // side is presentation; this side is implementation.
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1,3 @@
1
+ import type { ToolGroupRegistrar } from './registry.js';
2
+ export declare const registerAbiCheckTools: ToolGroupRegistrar;
3
+ //# sourceMappingURL=abi-check.d.ts.map
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ // system_abi_check — wire ABI probe via `protocol/version` (no COM).
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.registerAbiCheckTools = void 0;
5
+ const zod_1 = require("zod");
6
+ const helpers_js_1 = require("./helpers.js");
7
+ const policySchema = zod_1.z.enum(['strict', 'warn', 'ignore']).optional();
8
+ const warningShape = zod_1.z.object({
9
+ clientAbi: zod_1.z.string(),
10
+ serverAbi: zod_1.z.string(),
11
+ reason: zod_1.z.string(),
12
+ });
13
+ const registerAbiCheckTools = (server, { bridge }) => {
14
+ server.registerTool('system_abi_check', {
15
+ title: 'Check iScopeBridge wire ABI',
16
+ description: 'Probe the bundled iScopeBridge.exe JSON-RPC wire ABI via `protocol/version` ' +
17
+ '(no Oscilloscope, no COM, no debug session). Returns client vs server semver, ' +
18
+ 'compatibility kind (ok / warn / fatal), and actionable hints when the helper ' +
19
+ 'binary is older or newer than @i-scope/iscope-bridge-client expects. Call before ' +
20
+ 'debug_launch when upgrading packages or after copying a custom helper binary.',
21
+ inputSchema: {
22
+ policy: policySchema.describe('Semver policy: strict (default) — fatal on MAJOR mismatch; warn — emit warnings only; ignore — skip checks.'),
23
+ },
24
+ outputSchema: {
25
+ ok: zod_1.z.boolean(),
26
+ clientAbi: zod_1.z.string(),
27
+ serverAbi: zod_1.z.string(),
28
+ server: zod_1.z.string(),
29
+ buildTime: zod_1.z.string().optional(),
30
+ compatKind: zod_1.z.enum(['ok', 'warn', 'fatal']),
31
+ compatReason: zod_1.z.string(),
32
+ policy: policySchema,
33
+ warnings: zod_1.z.array(warningShape),
34
+ userHints: zod_1.z.array(zod_1.z.string()),
35
+ },
36
+ annotations: {
37
+ title: 'Check iScopeBridge wire ABI',
38
+ readOnlyHint: true,
39
+ idempotentHint: true,
40
+ },
41
+ }, async (args) => {
42
+ try {
43
+ const result = await bridge.abiCheck(args.policy);
44
+ return (0, helpers_js_1.ok)(result);
45
+ }
46
+ catch (e) {
47
+ return (0, helpers_js_1.fail)(`system_abi_check failed: ${errMsg(e)}`);
48
+ }
49
+ });
50
+ };
51
+ exports.registerAbiCheckTools = registerAbiCheckTools;
52
+ function errMsg(e) {
53
+ if (e instanceof Error)
54
+ return e.message;
55
+ if (typeof e === 'string')
56
+ return e;
57
+ try {
58
+ return JSON.stringify(e);
59
+ }
60
+ catch {
61
+ return String(e);
62
+ }
63
+ }
64
+ //# sourceMappingURL=abi-check.js.map
@@ -0,0 +1,3 @@
1
+ import type { ToolGroupRegistrar } from './registry.js';
2
+ export declare const registerBreakpointTools: ToolGroupRegistrar;
3
+ //# sourceMappingURL=breakpoints.d.ts.map
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ // debug_set_breakpoints.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.registerBreakpointTools = void 0;
5
+ const zod_1 = require("zod");
6
+ const helpers_js_1 = require("./helpers.js");
7
+ const setBreakpointsInput = {
8
+ source: zod_1.z.string().describe('Absolute or workspace-relative source path. `.ts` is fine — the adapter will map lines to the generated `.ajs` automatically. ⚠ REPLACE semantics: the list of lines you pass becomes THE complete breakpoint set for THIS source file. To clear all breakpoints in this file pass lines: []. Existing breakpoints in OTHER source files are NOT touched.'),
9
+ lines: zod_1.z.array(zod_1.z.number().int().positive()).describe('1-based line numbers in `source`. Empty array clears all breakpoints in `source`. Lines outside executable code (comments, blank lines) are silently dropped by the engine — they will come back `verified: false`.'),
10
+ };
11
+ const registerBreakpointTools = (server, { dap }) => {
12
+ server.registerTool('debug_set_breakpoints', {
13
+ title: 'Set breakpoints',
14
+ description: 'Replace breakpoints for ONE source file. Returns one `{line, verified}` per requested line in the same order. May be called before debug_launch (the adapter buffers them until configurationDone) or while paused (mid-run set is forwarded as individual SetResetBrkPnt RPCs). Calling while the script is running is fine but the engine cannot install BPs until the next pause — verified flags reflect best effort.',
15
+ inputSchema: setBreakpointsInput,
16
+ outputSchema: {
17
+ breakpoints: zod_1.z.array(zod_1.z.object({
18
+ line: zod_1.z.number().optional(),
19
+ verified: zod_1.z.boolean(),
20
+ message: zod_1.z.string().optional(),
21
+ })),
22
+ },
23
+ annotations: {
24
+ title: 'Set breakpoints',
25
+ idempotentHint: true,
26
+ destructiveHint: false,
27
+ },
28
+ }, async (args) => {
29
+ try {
30
+ const body = await dap.setBreakpoints(args.source, args.lines);
31
+ const bps = (body?.breakpoints ?? []).map((bp) => ({
32
+ line: bp.line,
33
+ verified: bp.verified === true,
34
+ message: bp.message,
35
+ }));
36
+ return (0, helpers_js_1.ok)({ breakpoints: bps });
37
+ }
38
+ catch (e) {
39
+ return (0, helpers_js_1.fail)(`debug_set_breakpoints failed: ${errMsg(e)}`);
40
+ }
41
+ });
42
+ };
43
+ exports.registerBreakpointTools = registerBreakpointTools;
44
+ function errMsg(e) {
45
+ if (e instanceof Error)
46
+ return e.message;
47
+ if (typeof e === 'string')
48
+ return e;
49
+ try {
50
+ return JSON.stringify(e);
51
+ }
52
+ catch {
53
+ return String(e);
54
+ }
55
+ }
56
+ //# sourceMappingURL=breakpoints.js.map
@@ -0,0 +1,3 @@
1
+ import type { ToolGroupRegistrar } from './registry.js';
2
+ export declare const registerExecutionTools: ToolGroupRegistrar;
3
+ //# sourceMappingURL=execution.d.ts.map
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ // debug_continue / debug_step_over / debug_step_into / debug_step_out.
3
+ //
4
+ // All four tools share the same shape: send a DAP request, wait for
5
+ // the next state transition (stopped / terminated / timeout), return
6
+ // it as the response. The AI doesn't need to call a separate
7
+ // `debug_wait_for_paused` afterwards.
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.registerExecutionTools = void 0;
10
+ const zod_1 = require("zod");
11
+ const helpers_js_1 = require("./helpers.js");
12
+ const stepInput = {
13
+ timeoutMs: zod_1.z.number().int().positive().optional().describe('Max ms to wait for the resulting paused/terminated event. Defaults to 30 000. If the script keeps running past this timeout, returns {state:"timeout"}; you can then call debug_wait_for_paused to keep waiting.'),
14
+ };
15
+ const transitionShape = zod_1.z.object({
16
+ state: zod_1.z.enum(['paused', 'terminated', 'timeout']),
17
+ stopped: zod_1.z.unknown().optional(),
18
+ exitInfo: zod_1.z.unknown().optional(),
19
+ waitedMs: zod_1.z.number().optional(),
20
+ });
21
+ function makeResume(dap, label, dapCall) {
22
+ return async (args) => {
23
+ try {
24
+ const t = await dap.resumeAndWait(dapCall, args.timeoutMs);
25
+ return (0, helpers_js_1.ok)({ transition: (0, helpers_js_1.shapeTransition)(t) });
26
+ }
27
+ catch (e) {
28
+ return (0, helpers_js_1.fail)(`${label} failed: ${errMsg(e)}`);
29
+ }
30
+ };
31
+ }
32
+ const registerExecutionTools = (server, { dap }) => {
33
+ server.registerTool('debug_continue', {
34
+ title: 'Continue',
35
+ description: 'Resume the script. Returns the next paused state (breakpoint / step done / exception) or `terminated` if the script finishes. The response contains the new stack — no need to call debug_stack_trace separately when stopped on a breakpoint.',
36
+ inputSchema: stepInput,
37
+ outputSchema: { transition: transitionShape },
38
+ annotations: { title: 'Continue', idempotentHint: false },
39
+ }, makeResume(dap, 'debug_continue', (dc) => dc.request('continue', { threadId: 1 })));
40
+ server.registerTool('debug_step_over', {
41
+ title: 'Step over',
42
+ description: 'Step over: execute the current statement and pause on the next one in the SAME frame (do not descend into function calls).',
43
+ inputSchema: stepInput,
44
+ outputSchema: { transition: transitionShape },
45
+ annotations: { title: 'Step over', idempotentHint: false },
46
+ }, makeResume(dap, 'debug_step_over', (dc) => dc.request('next', { threadId: 1 })));
47
+ server.registerTool('debug_step_into', {
48
+ title: 'Step into',
49
+ description: 'Step into: execute the current statement and, if it calls a function, pause on the first statement of that function.',
50
+ inputSchema: stepInput,
51
+ outputSchema: { transition: transitionShape },
52
+ annotations: { title: 'Step into', idempotentHint: false },
53
+ }, makeResume(dap, 'debug_step_into', (dc) => dc.request('stepIn', { threadId: 1 })));
54
+ server.registerTool('debug_step_out', {
55
+ title: 'Step out',
56
+ description: 'Step out: resume until the current frame returns, then pause in the caller.',
57
+ inputSchema: stepInput,
58
+ outputSchema: { transition: transitionShape },
59
+ annotations: { title: 'Step out', idempotentHint: false },
60
+ }, makeResume(dap, 'debug_step_out', (dc) => dc.request('stepOut', { threadId: 1 })));
61
+ };
62
+ exports.registerExecutionTools = registerExecutionTools;
63
+ function errMsg(e) {
64
+ if (e instanceof Error)
65
+ return e.message;
66
+ if (typeof e === 'string')
67
+ return e;
68
+ try {
69
+ return JSON.stringify(e);
70
+ }
71
+ catch {
72
+ return String(e);
73
+ }
74
+ }
75
+ //# sourceMappingURL=execution.js.map
@@ -0,0 +1,27 @@
1
+ import type { CallToolResult, Implementation } from '@modelcontextprotocol/sdk/types.js';
2
+ import type { ExecutionTransition, FrameInfo, OutputEntry, StoppedInfo } from '../state.js';
3
+ /** Wrap a structured payload as a CallToolResult with a JSON-text
4
+ * mirror. `structuredContent` is validated by the SDK against the
5
+ * tool's `outputSchema` (when one is declared). */
6
+ export declare function ok<T extends Record<string, unknown>>(payload: T): CallToolResult;
7
+ /** Build the standard error result. We deliberately use the SDK's
8
+ * `isError` flag rather than throwing, so the AI gets a clear tool-
9
+ * level error response instead of a transport failure. */
10
+ export declare function fail(message: string, details?: Record<string, unknown>): CallToolResult;
11
+ /** Drop `undefined` values so JSON.stringify produces a clean
12
+ * output. The MCP SDK's strict JSON-Schema validator may reject
13
+ * payloads where an optional field is explicitly `undefined`
14
+ * (becomes `null` after JSON.parse). */
15
+ export declare function pruneUndefined<T extends Record<string, unknown>>(o: T): T;
16
+ export declare function shapeFrame(f: FrameInfo): Record<string, unknown>;
17
+ export declare function shapeStopped(s: StoppedInfo): Record<string, unknown>;
18
+ export declare function shapeTransition(t: ExecutionTransition): Record<string, unknown>;
19
+ export declare function shapeOutputEntry(e: OutputEntry): Record<string, unknown>;
20
+ /** Centralised server identity. Bumped together with package.json. */
21
+ export declare const SERVER_INFO: Implementation;
22
+ /** Server instructions surfaced to the LLM by clients that respect
23
+ * the MCP spec (Claude Desktop / Cursor do). Keep concise — the AI
24
+ * reads tool descriptions on each call already; this is for cross-
25
+ * tool workflow rules that don't fit any single tool's description. */
26
+ export declare const SERVER_INSTRUCTIONS: string;
27
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ // Helpers shared across tool groups.
3
+ //
4
+ // Two concerns live here:
5
+ // 1. Pack a CallToolResult that has BOTH structuredContent (the
6
+ // JSON the AI cares about) AND a `content` text mirror (so MCP
7
+ // clients that ignore structuredContent still get something
8
+ // readable). The MCP SDK validates structuredContent against
9
+ // `outputSchema` when one is declared.
10
+ // 2. Translate driver-level types (ExecutionTransition, FrameInfo,
11
+ // ...) into AI-friendly objects. The driver types are wire-
12
+ // level; we expose only the fields the AI needs.
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.SERVER_INSTRUCTIONS = exports.SERVER_INFO = void 0;
15
+ exports.ok = ok;
16
+ exports.fail = fail;
17
+ exports.pruneUndefined = pruneUndefined;
18
+ exports.shapeFrame = shapeFrame;
19
+ exports.shapeStopped = shapeStopped;
20
+ exports.shapeTransition = shapeTransition;
21
+ exports.shapeOutputEntry = shapeOutputEntry;
22
+ /** Wrap a structured payload as a CallToolResult with a JSON-text
23
+ * mirror. `structuredContent` is validated by the SDK against the
24
+ * tool's `outputSchema` (when one is declared). */
25
+ function ok(payload) {
26
+ const text = {
27
+ type: 'text',
28
+ text: JSON.stringify(payload),
29
+ };
30
+ return {
31
+ content: [text],
32
+ structuredContent: payload,
33
+ };
34
+ }
35
+ /** Build the standard error result. We deliberately use the SDK's
36
+ * `isError` flag rather than throwing, so the AI gets a clear tool-
37
+ * level error response instead of a transport failure. */
38
+ function fail(message, details) {
39
+ const text = {
40
+ type: 'text',
41
+ text: details
42
+ ? `${message}\n${JSON.stringify(details, null, 2)}`
43
+ : message,
44
+ };
45
+ return {
46
+ content: [text],
47
+ isError: true,
48
+ structuredContent: { error: message, ...(details ?? {}) },
49
+ };
50
+ }
51
+ /** Drop `undefined` values so JSON.stringify produces a clean
52
+ * output. The MCP SDK's strict JSON-Schema validator may reject
53
+ * payloads where an optional field is explicitly `undefined`
54
+ * (becomes `null` after JSON.parse). */
55
+ function pruneUndefined(o) {
56
+ for (const k of Object.keys(o)) {
57
+ if (o[k] === undefined)
58
+ delete o[k];
59
+ }
60
+ return o;
61
+ }
62
+ function shapeFrame(f) {
63
+ return pruneUndefined({
64
+ id: f.id,
65
+ name: f.name,
66
+ source: f.source,
67
+ sourceOrigin: f.sourceOrigin,
68
+ line: f.line,
69
+ column: f.column,
70
+ endLine: f.endLine,
71
+ endColumn: f.endColumn,
72
+ generatedSource: f.generatedSource,
73
+ generatedLine: f.generatedLine,
74
+ generatedColumn: f.generatedColumn,
75
+ });
76
+ }
77
+ function shapeStopped(s) {
78
+ return pruneUndefined({
79
+ reason: s.reason,
80
+ description: s.description,
81
+ frames: s.frames.map(shapeFrame),
82
+ });
83
+ }
84
+ function shapeTransition(t) {
85
+ if (t.state === 'paused')
86
+ return { state: 'paused', stopped: shapeStopped(t.stopped) };
87
+ if (t.state === 'terminated')
88
+ return pruneUndefined({
89
+ state: 'terminated',
90
+ exitInfo: t.exitInfo ? pruneUndefined({ reason: t.exitInfo.reason }) : undefined,
91
+ });
92
+ return { state: 'timeout', waitedMs: t.waitedMs };
93
+ }
94
+ function shapeOutputEntry(e) {
95
+ return {
96
+ id: e.id,
97
+ category: e.category,
98
+ text: e.text,
99
+ timestamp: e.timestamp,
100
+ };
101
+ }
102
+ /** Centralised server identity. Bumped together with package.json. */
103
+ exports.SERVER_INFO = {
104
+ name: '@i-scope/mcp-server',
105
+ version: '0.4.2',
106
+ title: 'iScope Debugger',
107
+ };
108
+ /** Server instructions surfaced to the LLM by clients that respect
109
+ * the MCP spec (Claude Desktop / Cursor do). Keep concise — the AI
110
+ * reads tool descriptions on each call already; this is for cross-
111
+ * tool workflow rules that don't fit any single tool's description. */
112
+ exports.SERVER_INSTRUCTIONS = [
113
+ 'iScope MCP server: drives a `.ajs` (or transpiled-from-`.ts`) debug session in Oscilloscope.exe via the iScope DAP adapter.',
114
+ '',
115
+ 'Workflow rules:',
116
+ '- Call `debug_launch` before any other debug_* tool. State must be "paused" before stack_trace / scopes / variables / evaluate.',
117
+ '- Execution tools (`debug_continue`, `debug_step_*`) return the NEXT paused state (or terminated/timeout) — no separate `debug_wait_for_paused` call needed in the common case.',
118
+ '- `debug_set_breakpoints` REPLACES the breakpoint set for the given source. Passing `lines: []` clears them. Pass the COMPLETE list each time.',
119
+ '- Always finish with `debug_disconnect`. Leaking a session orphans Oscilloscope when this server exits.',
120
+ '- Source maps: pass `.ts` paths to `program` and `source` whenever a TS sibling exists — frames come back in `.ts` with `sourceOrigin: "mapped"`. `sourceOrigin: "unmapped-fallback"` signals that the .ajs.map has a gap; treat returned positions as raw `.ajs` coordinates.',
121
+ '- Use `debug_resolve_source` to translate `.ts ↔ .ajs` positions without launching a session (e.g. when inspecting engine error logs).',
122
+ '',
123
+ 'UI modal control (`ui_modal_*`):',
124
+ '- Use `ui_modal_list` to see if Oscilloscope is currently showing any child dialog (Configure() input form, "Save changes?" prompt, "Unable to..." error).',
125
+ '- A `debug_continue`/`debug_step_*` that silently times out is often the AI signal that a script-level Configure() modal is blocking the engine — list, fill, click OK, and execution will resume.',
126
+ '- These tools work WITHOUT an active debug session (Oscilloscope only needs to be running) and are SAFE to mix with an active DAP session.',
127
+ '',
128
+ 'System diagnostics:',
129
+ '- Use `system_preflight_check` proactively when a launch silently fails / breakpoints are ignored / Oscilloscope reports "Unable to create debuger object!". It probes the registry for the Windows Script Debugger (Machine Debug Manager) and the Oscilloscope COM CLSID — the two prerequisites of the iScope debug stack — and returns a structured verdict with user-actionable hints + download URL when something is missing. Read-only / idempotent / does not require Oscilloscope to be running.',
130
+ '- Use `system_abi_check` when mixing npm package versions or after swapping iScopeBridge.exe — it calls `protocol/version` (no COM) and compares wire ABI semver to @i-scope/iscope-bridge-client. Run before debug_launch if you see ProtocolVersionMismatchError or suspect a stale helper binary.',
131
+ '',
132
+ 'Pause is NOT supported (Oscilloscope COM API has no Pause()). Use breakpoints + step instead.',
133
+ ].join('\n');
134
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1,3 @@
1
+ import type { ToolGroupRegistrar } from './registry.js';
2
+ export declare const registerInspectionTools: ToolGroupRegistrar;
3
+ //# sourceMappingURL=inspection.d.ts.map