@lightpath-dev/mcp 0.0.1 → 0.1.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/dist/slices.js ADDED
@@ -0,0 +1,216 @@
1
+ import { fitText, MAX_CHARS } from './budget.js';
2
+ import { functionName, numberRanges, phaseTitle } from './summary.js';
3
+ const MAX_LINE = 200;
4
+ /** Room kept for the page line at the end of each page. */
5
+ const PAGE_LINE_ROOM = 300;
6
+ const fnKey = (f) => `${f.path}|${f.name}|${f.startLine}`;
7
+ /**
8
+ * `get_trace` with `file` or `function` (design section 6.3, D-117): the executed lines of each
9
+ * outermost function that ran, with numbers, merged over every phase. A run of not-run lines folds
10
+ * into one marker, an untaken block inside a line that ran is named after the line, lines with no
11
+ * coverage (blank, comments, types) are left out. Paged under 16,000 characters.
12
+ */
13
+ export async function sliceTrace(trace, query, read) {
14
+ const fns = trace.functions ?? [];
15
+ let path;
16
+ if (query.file !== undefined) {
17
+ const found = pick(trace.files.map((f) => f.path), query.file);
18
+ if (typeof found !== 'string')
19
+ return listMessage(trace, 'file', query.file, found);
20
+ path = found;
21
+ }
22
+ let units;
23
+ let label;
24
+ if (query.function !== undefined) {
25
+ const name = query.function;
26
+ const pool = fns.filter((f) => (path ? f.path === path : true));
27
+ let named = pool.filter((f) => functionName(f) === name);
28
+ if (named.length === 0)
29
+ named = pool.filter((f) => functionName(f).toLowerCase() === name.toLowerCase());
30
+ const groups = new Map();
31
+ for (const f of named)
32
+ groups.set(fnKey(f), [...(groups.get(fnKey(f)) ?? []), f]);
33
+ if (groups.size !== 1) {
34
+ const all = [...new Set(pool.map((f) => `${functionName(f)} (${f.path}:${f.startLine})`))];
35
+ const near = all.filter((s) => s.toLowerCase().includes(name.toLowerCase()));
36
+ return listMessage(trace, 'function', name, groups.size === 0
37
+ ? near.length
38
+ ? near
39
+ : all
40
+ : [...groups.values()].map((g) => `${functionName(g[0])} (${g[0]?.path}:${g[0]?.startLine})`), groups.size > 1);
41
+ }
42
+ const group = [...groups.values()][0];
43
+ const first = group[0];
44
+ path = first.path;
45
+ const endLine = Math.max(...group.map((f) => f.endLine));
46
+ units = [
47
+ {
48
+ path,
49
+ startLine: first.startLine,
50
+ endLine,
51
+ fns: fns.filter((f) => f.path === path && f.startLine >= first.startLine && f.endLine <= endLine),
52
+ },
53
+ ];
54
+ label = `function "${name}"`;
55
+ }
56
+ else if (path) {
57
+ units = outermost(path, fns, trace);
58
+ label = `file "${path}"`;
59
+ }
60
+ else {
61
+ return 'Pass a file or a function to see its executed lines.';
62
+ }
63
+ const file = trace.files.find((f) => f.path === path);
64
+ const lit = new Set((file?.lines ?? []).map((l) => l.line));
65
+ const source = await read(path);
66
+ if (!source) {
67
+ return fitText(`${path}: can't read this file from the workspace, so only line numbers are shown.\nlines that ran: ${numberRanges([...lit])}`);
68
+ }
69
+ const src = source.text.split(/\r?\n/);
70
+ const phaseNames = [
71
+ ...new Set(fns
72
+ .filter((f) => f.path === path)
73
+ .map((f) => trace.phases?.[f.phase])
74
+ .filter((p) => p !== undefined)
75
+ .map((p) => phaseTitle(trace, p).replace(/ \[.*\]$/, ''))),
76
+ ];
77
+ const head = [];
78
+ if (source.mtimeMs > trace.startedAt) {
79
+ head.push(`warning: ${path} changed after this trace; line numbers may not match. Rerun the action for a fresh trace.`);
80
+ }
81
+ head.push(`${path} (trace ${trace.id}${phaseNames.length ? `, phase ${phaseNames.join(', ')}` : ''})`);
82
+ // Each unit as its own block of lines.
83
+ const blocks = units.map((u) => unitLines(u, src, lit));
84
+ const pages = paginate(blocks, MAX_CHARS - PAGE_LINE_ROOM - head.join('\n').length - 1);
85
+ const n = pages.length;
86
+ const page = Math.min(Math.max(1, query.page ?? 1), Math.max(1, n));
87
+ if ((query.page ?? 1) > n && n > 0)
88
+ return `${path} has ${n} page${n === 1 ? '' : 's'}; there is no page ${query.page}.`;
89
+ const body = pages[page - 1] ?? [];
90
+ const out = [...head, ...body];
91
+ if (n > 1) {
92
+ out.push(page < n
93
+ ? `page ${page} of ${n}. Call get_trace with traceId "${trace.id}", ${label}, page ${page + 1} for the next.`
94
+ : `page ${page} of ${n}.`);
95
+ }
96
+ return fitText(out.join('\n'), MAX_CHARS, trace.id);
97
+ }
98
+ /** A path from `paths` that `query` names: exactly, or as the only path containing it. */
99
+ function pick(paths, query) {
100
+ if (paths.includes(query))
101
+ return query;
102
+ const q = query.replace(/\\/g, '/').toLowerCase();
103
+ const hits = paths.filter((p) => p.toLowerCase().includes(q));
104
+ return hits.length === 1 ? hits[0] : hits;
105
+ }
106
+ function listMessage(trace, what, query, options, ambiguous = options.length > 1) {
107
+ const shown = options.slice(0, 30);
108
+ const more = options.length > shown.length ? `\n +${options.length - shown.length} more` : '';
109
+ const head = ambiguous
110
+ ? `"${query}" matches ${options.length} ${what}s in trace ${trace.id}. Call get_trace again with one of them:`
111
+ : `No ${what} "${query}" in trace ${trace.id}. Its ${what}s:`;
112
+ return fitText(`${head}\n${shown.map((o) => ` ${o}`).join('\n')}${more}`);
113
+ }
114
+ /** The outermost functions of one file, merged over phases, or one unit for a file without any. */
115
+ function outermost(path, fns, trace) {
116
+ const mine = fns.filter((f) => f.path === path);
117
+ if (mine.length === 0) {
118
+ const lines = trace.files.find((f) => f.path === path)?.lines.map((l) => l.line) ?? [];
119
+ return lines.length
120
+ ? [{ path, startLine: Math.min(...lines), endLine: Math.max(...lines), fns: [] }]
121
+ : [];
122
+ }
123
+ const spans = [...new Map(mine.map((f) => [`${f.startLine}-${f.endLine}`, f])).values()].sort((a, b) => a.startLine - b.startLine || b.endLine - a.endLine);
124
+ const units = [];
125
+ for (const f of spans) {
126
+ const last = units.at(-1);
127
+ if (last && f.startLine >= last.startLine && f.endLine <= last.endLine)
128
+ continue; // nested
129
+ if (last && f.startLine <= last.endLine)
130
+ last.endLine = Math.max(last.endLine, f.endLine);
131
+ else
132
+ units.push({ path, startLine: f.startLine, endLine: f.endLine, fns: [] });
133
+ }
134
+ for (const u of units)
135
+ u.fns = mine.filter((f) => f.startLine >= u.startLine && f.endLine <= u.endLine);
136
+ return units;
137
+ }
138
+ /** The printed lines of one unit. */
139
+ function unitLines(u, src, lit) {
140
+ const notRun = new Set(u.fns.flatMap((f) => f.notRun ?? []));
141
+ // A block is untaken only if every phase that ran its function left it untaken.
142
+ const spansByFn = new Map();
143
+ for (const f of u.fns) {
144
+ const list = spansByFn.get(fnKey(f)) ?? [];
145
+ list.push(new Map((f.notRunSpans ?? []).map((s) => [`${s.line}:${s.startColumn}`, s])));
146
+ spansByFn.set(fnKey(f), list);
147
+ }
148
+ const spans = new Map();
149
+ for (const list of spansByFn.values()) {
150
+ const [firstSet, ...others] = list;
151
+ for (const [key, s] of firstSet ?? []) {
152
+ if (!others.every((m) => m.has(key)))
153
+ continue;
154
+ const line = src[s.line - 1] ?? '';
155
+ const tail = /^[\w$'"`.\])]+/.exec(line.slice(s.endColumn - 1))?.[0].length ?? 1;
156
+ const text = line
157
+ .slice(s.startColumn, s.endColumn - 1 + tail)
158
+ .trim()
159
+ .slice(0, 40) || s.text;
160
+ if (text)
161
+ spans.set(s.line, [...(spans.get(s.line) ?? []), text]);
162
+ }
163
+ }
164
+ const out = [];
165
+ let gap = [];
166
+ const flush = () => {
167
+ if (gap.length) {
168
+ out.push(` // not run: ${gap.length === 1 ? `line ${gap[0]}` : `lines ${gap[0]}-${gap.at(-1)}`}`);
169
+ }
170
+ gap = [];
171
+ };
172
+ for (let l = u.startLine; l <= u.endLine; l++) {
173
+ if (lit.has(l)) {
174
+ flush();
175
+ let text = src[l - 1] ?? '';
176
+ if (text.length > MAX_LINE)
177
+ text = `${text.slice(0, MAX_LINE - 1)}…`;
178
+ const marks = spans.get(l);
179
+ out.push(`${String(l).padStart(4)} ${text}${marks?.length ? ` // not run: ${[...new Set(marks)].join(', ')}` : ''}`);
180
+ }
181
+ else if (notRun.has(l))
182
+ gap.push(l);
183
+ }
184
+ flush();
185
+ return out;
186
+ }
187
+ /** Pages under `room` characters: break between units, and inside a unit only when it alone is too long. */
188
+ function paginate(blocks, room) {
189
+ const pages = [];
190
+ let page = [];
191
+ let used = 0;
192
+ const push = (line) => {
193
+ if (used + line.length + 1 > room && page.length) {
194
+ pages.push(page);
195
+ page = [];
196
+ used = 0;
197
+ }
198
+ page.push(line);
199
+ used += line.length + 1;
200
+ };
201
+ for (const [i, block] of blocks.entries()) {
202
+ const lines = i > 0 ? [' ...', ...block] : block;
203
+ const blockSize = lines.reduce((n, l) => n + l.length + 1, 0);
204
+ if (page.length && used + blockSize > room && blockSize <= room) {
205
+ pages.push(page);
206
+ page = [];
207
+ used = 0;
208
+ }
209
+ for (const l of lines)
210
+ push(l);
211
+ }
212
+ if (page.length)
213
+ pages.push(page);
214
+ return pages;
215
+ }
216
+ //# sourceMappingURL=slices.js.map
@@ -0,0 +1,30 @@
1
+ import type { FunctionHit, Trace, TracePhase, TraceProblem, TraceRequest } from '@lightpath-dev/protocol';
2
+ /** Sorted line numbers as ranges: [1,2,3,7] → "1-3, 7". */
3
+ export declare function numberRanges(lines: readonly number[]): string;
4
+ export declare const requestLine: (r: TraceRequest) => string;
5
+ /** The title of one phase: `before POST /api/orders [browser]`, `POST /api/orders [node, 3 ms]`. */
6
+ export declare function phaseTitle(trace: Trace, phase: TracePhase): string;
7
+ export declare const functionName: (f: Pick<FunctionHit, 'name'>) => string;
8
+ /** A problem message as one line of at most `max` characters, with where it happened. */
9
+ export declare function problemText(p: TraceProblem, max?: number): string;
10
+ /** Errors (thrown, console.error, uncaught) in the order they happened, dropped ones counted. */
11
+ export declare function errorsOf(trace: Trace): {
12
+ list: TraceProblem[];
13
+ total: number;
14
+ };
15
+ /**
16
+ * Failed requests (D-121): status 400 and up from the browser or the backend, and network
17
+ * errors, once each by method, path and status (or failure).
18
+ */
19
+ export declare function failedRequestsOf(trace: Trace): string[];
20
+ /** The summary's problem lines (design section 8): counts and the first message, when any. */
21
+ export declare function problemLines(trace: Trace): string[];
22
+ /** `checkout 21-33 ×1 not run: 29`. */
23
+ export declare function functionLine(f: FunctionHit): string;
24
+ /**
25
+ * The default trace text (design section 6.1, D-116): header lines, then one block per phase with
26
+ * its files and their functions in line order, cut to 16,000 characters by `fit()`.
27
+ * `extraHeader` goes after the first line (run_action's action, page and warning lines).
28
+ */
29
+ export declare function summarizeTrace(trace: Trace, extraHeader?: readonly string[]): string;
30
+ //# sourceMappingURL=summary.d.ts.map
@@ -0,0 +1,185 @@
1
+ import { fit } from './budget.js';
2
+ const MAX_REQUEST_LINES = 20;
3
+ /** Sorted line numbers as ranges: [1,2,3,7] → "1-3, 7". */
4
+ export function numberRanges(lines) {
5
+ const sorted = [...new Set(lines)].sort((a, b) => a - b);
6
+ const parts = [];
7
+ let start;
8
+ let prev;
9
+ for (const l of sorted) {
10
+ if (start === undefined)
11
+ start = l;
12
+ else if (prev !== undefined && l !== prev + 1) {
13
+ parts.push(start === prev ? `${start}` : `${start}-${prev}`);
14
+ start = l;
15
+ }
16
+ prev = l;
17
+ }
18
+ if (start !== undefined && prev !== undefined)
19
+ parts.push(start === prev ? `${start}` : `${start}-${prev}`);
20
+ return parts.join(', ');
21
+ }
22
+ export const requestLine = (r) => `${r.method} ${r.path}${r.status ? ` → ${r.status}` : ''} (${r.collector}, ${Math.round(r.durationMs)} ms${r.overlapped ? ', overlapped with another request' : ''})`;
23
+ /** The title of one phase: `before POST /api/orders [browser]`, `POST /api/orders [node, 3 ms]`. */
24
+ export function phaseTitle(trace, phase) {
25
+ const r = phase.request !== undefined ? trace.requests[phase.request] : undefined;
26
+ switch (phase.kind) {
27
+ case 'before-request':
28
+ return r ? `before ${r.method} ${r.path} [browser]` : 'before a request [browser]';
29
+ case 'backend':
30
+ return r
31
+ ? `${r.method} ${r.path}${r.status ? ` → ${r.status}` : ''} [${r.collector}, ${Math.round(r.durationMs)} ms]`
32
+ : 'a request [backend]';
33
+ case 'after-response':
34
+ return 'after the response [browser]';
35
+ case 'browser':
36
+ return 'in the browser [browser]';
37
+ }
38
+ }
39
+ export const functionName = (f) => f.name || '(anonymous)';
40
+ const ERROR_KINDS = new Set(['exception', 'console-error', 'uncaught']);
41
+ /** A problem message as one line of at most `max` characters, with where it happened. */
42
+ export function problemText(p, max = 200) {
43
+ const first = p.message.split('\n')[0]?.trim() ?? '';
44
+ const where = p.path ? ` at ${p.path}${p.line ? `:${p.line}` : ''}` : '';
45
+ const text = `${first}${where}`;
46
+ return text.length > max ? `${text.slice(0, max - 1)}…` : text;
47
+ }
48
+ /** Errors (thrown, console.error, uncaught) in the order they happened, dropped ones counted. */
49
+ export function errorsOf(trace) {
50
+ const list = (trace.problems ?? []).filter((p) => ERROR_KINDS.has(p.kind)).sort((a, b) => a.at - b.at);
51
+ return { list, total: list.length + (trace.problemsDropped ?? 0) };
52
+ }
53
+ /**
54
+ * Failed requests (D-121): status 400 and up from the browser or the backend, and network
55
+ * errors, once each by method, path and status (or failure).
56
+ */
57
+ export function failedRequestsOf(trace) {
58
+ const seen = new Map();
59
+ for (const p of [...(trace.problems ?? [])].sort((a, b) => a.at - b.at)) {
60
+ if (p.kind !== 'request-failed' || !p.request)
61
+ continue;
62
+ const r = p.request;
63
+ const key = `${r.method} ${r.path} ${r.status ?? r.failure ?? ''}`;
64
+ if (!seen.has(key))
65
+ seen.set(key, r.status ? `${r.method} ${r.path} → ${r.status}` : `${r.method} ${r.path} (${r.failure ?? 'failed'})`);
66
+ }
67
+ for (const r of trace.requests) {
68
+ if (r.status === undefined || r.status < 400)
69
+ continue;
70
+ const key = `${r.method} ${r.path} ${r.status}`;
71
+ if (!seen.has(key))
72
+ seen.set(key, `${r.method} ${r.path} → ${r.status}`);
73
+ }
74
+ return [...seen.values()];
75
+ }
76
+ /** The summary's problem lines (design section 8): counts and the first message, when any. */
77
+ export function problemLines(trace) {
78
+ const out = [];
79
+ const errors = errorsOf(trace);
80
+ const first = errors.list[0];
81
+ if (errors.total > 0)
82
+ out.push(`errors: ${errors.total}${first ? ` (first: ${problemText(first)})` : ''}`);
83
+ const failed = failedRequestsOf(trace);
84
+ if (failed.length > 0)
85
+ out.push(`failed requests: ${failed.length} (first: ${failed[0]})`);
86
+ return out;
87
+ }
88
+ /** `checkout 21-33 ×1 not run: 29`. */
89
+ export function functionLine(f) {
90
+ const range = f.startLine === f.endLine ? `${f.startLine}` : `${f.startLine}-${f.endLine}`;
91
+ const calls = f.count > 0 ? `×${f.count}` : 'resumed';
92
+ const notRun = f.notRun?.length ? ` not run: ${numberRanges(f.notRun)}` : '';
93
+ return `${functionName(f)} ${range} ${calls}${notRun}`;
94
+ }
95
+ const litRanges = (f) => numberRanges(f.lines.map((l) => l.line));
96
+ /**
97
+ * The default trace text (design section 6.1, D-116): header lines, then one block per phase with
98
+ * its files and their functions in line order, cut to 16,000 characters by `fit()`.
99
+ * `extraHeader` goes after the first line (run_action's action, page and warning lines).
100
+ */
101
+ export function summarizeTrace(trace, extraHeader = []) {
102
+ const header = [`Trace ${trace.id}: ${trace.label}`, ...extraHeader];
103
+ for (const r of trace.requests.slice(0, MAX_REQUEST_LINES))
104
+ header.push(`request: ${requestLine(r)}`);
105
+ if (trace.requests.length > MAX_REQUEST_LINES)
106
+ header.push(`+${trace.requests.length - MAX_REQUEST_LINES} more requests`);
107
+ header.push(...problemLines(trace));
108
+ if (trace.files.length === 0)
109
+ header.push('no workspace code ran');
110
+ header.push('');
111
+ const fileOf = new Map(trace.files.map((f) => [f.path, f]));
112
+ const phases = trace.phases ?? [];
113
+ const sections = phases.map((p) => ({ title: phaseTitle(trace, p), files: [] }));
114
+ const byPhase = new Map();
115
+ for (const fn of trace.functions ?? []) {
116
+ let files = byPhase.get(fn.phase);
117
+ if (!files) {
118
+ files = new Map();
119
+ byPhase.set(fn.phase, files);
120
+ }
121
+ files.set(fn.path, [...(files.get(fn.path) ?? []), fn]);
122
+ }
123
+ const placed = new Set();
124
+ const order = (paths) => [...paths].sort((a, b) => {
125
+ const fa = fileOf.get(a);
126
+ const fb = fileOf.get(b);
127
+ const bg = Number(Boolean(fa?.background)) - Number(Boolean(fb?.background));
128
+ return bg || (fa?.firstSeenAt ?? 0) - (fb?.firstSeenAt ?? 0) || a.localeCompare(b);
129
+ });
130
+ for (const [i, files] of byPhase) {
131
+ const section = sections[i];
132
+ if (!section)
133
+ continue;
134
+ for (const path of order(files.keys())) {
135
+ const fns = (files.get(path) ?? []).sort((a, b) => a.startLine - b.startLine || b.endLine - a.endLine);
136
+ const bg = fileOf.get(path)?.background ? ' (background)' : '';
137
+ section.files.push({
138
+ path,
139
+ lines: [` ${path}${bg}`, ...fns.map((f) => ` ${functionLine(f)}`)],
140
+ functions: fns.length,
141
+ });
142
+ placed.add(path);
143
+ }
144
+ }
145
+ // Files with no function list (Python lines, or code outside any function): by line range, in
146
+ // the phase their collector belongs to, else in a last section.
147
+ const rest = order(trace.files.filter((f) => !placed.has(f.path)).map((f) => f.path));
148
+ let other;
149
+ for (const path of rest) {
150
+ const f = fileOf.get(path);
151
+ const i = phases.findIndex((p) => f.collector === 'browser'
152
+ ? p.kind === 'browser' || p.kind === 'after-response'
153
+ : p.kind === 'backend' &&
154
+ p.request !== undefined &&
155
+ trace.requests[p.request]?.collector === f.collector);
156
+ const block = {
157
+ path,
158
+ lines: [` ${path} [${f.collector}] lines ${litRanges(f)}${f.background ? ' (background)' : ''}`],
159
+ functions: 0,
160
+ };
161
+ const section = i >= 0 ? sections[i] : undefined;
162
+ if (section)
163
+ section.files.push(block);
164
+ else {
165
+ if (!other) {
166
+ other = { title: phases.length ? 'other files' : 'files', files: [] };
167
+ sections.push(other);
168
+ }
169
+ other.files.push(block);
170
+ }
171
+ }
172
+ const hidden = trace.dropped.filter((d) => d.count > 0);
173
+ const footer = hidden.length ? [`hidden: ${hidden.map((d) => `${d.count} ${d.reason}`).join(', ')}`] : [];
174
+ return fit({
175
+ traceId: trace.id,
176
+ header,
177
+ sections,
178
+ footer,
179
+ rank: (path) => {
180
+ const f = fileOf.get(path);
181
+ return { background: Boolean(f?.background), hits: f ? f.lines.reduce((n, l) => n + l.count, 0) : 0 };
182
+ },
183
+ });
184
+ }
185
+ //# sourceMappingURL=summary.js.map
@@ -0,0 +1,10 @@
1
+ /** The server's `instructions`: what Lightpath records, when to call it, the fix loop, the cap. */
2
+ export declare const INSTRUCTIONS = "Lightpath records which source lines actually ran, in the browser and on the Node server, for each action in the user's running app. It uses real runtime coverage, not static analysis. Use it first when the user reports a bug they see in the app: reproduce it with run_action before searching files, one step per call (the page, sign-in and data stay between calls). Also use it when asked which code handles a button, page or request, or whether a fix changed what runs. run_action clicks or presses a key in the app and returns that action's trace in one call; run_request does the same for one HTTP request, with no browser; find_code_for_action returns the latest trace for a label without acting. Every result is capped at about 4,000 tokens: follow its 'not shown' line to drill down with get_trace. To check a fix, call run_action before and after it, then diff_traces with both trace ids.";
3
+ export declare const DESCRIPTIONS: {
4
+ readonly find_code_for_action: 'Find the files, functions and lines that ran, frontend and backend, the last time an action was done in the app. Use it before searching files when asked which code handles a button or feature.';
5
+ readonly get_trace: "Show the executed code of one recorded action, one file or function at a time, with lines that did not run marked, paged. Use it after a 'not shown' line, or to read which branches ran.";
6
+ readonly list_traces: 'List recent recorded actions, newest first, with trace ids and file counts. Use it to find ids for get_trace or diff_traces.';
7
+ readonly diff_traces: 'Compare two traces of the same action, before and after a code change, at function level: functions and branches that ran in only one, and changed call counts. Use it to verify a fix.';
8
+ readonly run_request: "Send one HTTP request to the user's running app on localhost and return the backend files, functions and lines it ran, with its status. No browser needed: use it for APIs and backend-only apps, or to rerun a request after a fix.";
9
+ };
10
+ //# sourceMappingURL=texts.d.ts.map
package/dist/texts.js ADDED
@@ -0,0 +1,13 @@
1
+ // The texts of docs/design/agent-loop.md section 3 (D-119), with build 5's run_request clause and
2
+ // shorter descriptions so six tools fit in tools/list (D-129). Tests check the limits:
3
+ // instructions under 1,000 characters, each description under 300, tools/list at most 4,000.
4
+ /** The server's `instructions`: what Lightpath records, when to call it, the fix loop, the cap. */
5
+ export const INSTRUCTIONS = "Lightpath records which source lines actually ran, in the browser and on the Node server, for each action in the user's running app. It uses real runtime coverage, not static analysis. Use it first when the user reports a bug they see in the app: reproduce it with run_action before searching files, one step per call (the page, sign-in and data stay between calls). Also use it when asked which code handles a button, page or request, or whether a fix changed what runs. run_action clicks or presses a key in the app and returns that action's trace in one call; run_request does the same for one HTTP request, with no browser; find_code_for_action returns the latest trace for a label without acting. Every result is capped at about 4,000 tokens: follow its 'not shown' line to drill down with get_trace. To check a fix, call run_action before and after it, then diff_traces with both trace ids.";
6
+ export const DESCRIPTIONS = {
7
+ find_code_for_action: 'Find the files, functions and lines that ran, frontend and backend, the last time an action was done in the app. Use it before searching files when asked which code handles a button or feature.',
8
+ get_trace: "Show the executed code of one recorded action, one file or function at a time, with lines that did not run marked, paged. Use it after a 'not shown' line, or to read which branches ran.",
9
+ list_traces: 'List recent recorded actions, newest first, with trace ids and file counts. Use it to find ids for get_trace or diff_traces.',
10
+ diff_traces: 'Compare two traces of the same action, before and after a code change, at function level: functions and branches that ran in only one, and changed call counts. Use it to verify a fix.',
11
+ run_request: "Send one HTTP request to the user's running app on localhost and return the backend files, functions and lines it ran, with its status. No browser needed: use it for APIs and backend-only apps, or to rerun a request after a fix.",
12
+ };
13
+ //# sourceMappingURL=texts.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightpath-dev/mcp",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "MCP server that lets coding agents ask which code ran for an action in your app",
5
5
  "keywords": [
6
6
  "mcp",
@@ -36,10 +36,13 @@
36
36
  "access": "public"
37
37
  },
38
38
  "dependencies": {
39
- "@lightpath-dev/protocol": "0.0.1",
39
+ "@lightpath-dev/protocol": "0.1.0",
40
40
  "@modelcontextprotocol/sdk": "1.32.2",
41
41
  "zod": "4.6.7"
42
42
  },
43
+ "devDependencies": {
44
+ "@lightpath-dev/daemon": "0.1.0"
45
+ },
43
46
  "scripts": {
44
47
  "test": "vitest run",
45
48
  "typecheck": "tsc -p test"