@orbit360.lk/bugtracker-mcp 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Orbit Pvt Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # BugTracker MCP server
2
+
3
+ Gives an AI coding agent direct access to captured bug reports **and to your
4
+ local server logs** — without your application exposing anything.
5
+
6
+ This runs on your own machine. It is not deployed, not imported by the
7
+ application under test, and not reachable from the network.
8
+
9
+ ```
10
+ Chrome extension ──push──▶ bridge (127.0.0.1) ──▶ local store
11
+
12
+ your app's log files ────────read───────────────────▶│
13
+
14
+ MCP ◀── Claude Code / Copilot / Cursor
15
+ ```
16
+
17
+ ---
18
+
19
+ ## Why this instead of an endpoint in your app
20
+
21
+ The alternative is a route in your application that returns exceptions over
22
+ HTTP. That works, but it means shipping code, a token, and a CORS allowlist into
23
+ a project you may not want to touch.
24
+
25
+ This server reads `storage/logs/laravel.log` — or any log you point it at —
26
+ straight off the disk. No route, no token in your app, no CORS, nothing to
27
+ review, nothing to accidentally leave enabled in production.
28
+
29
+ ---
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ cd mcp-server
35
+ npm install
36
+ npm run build
37
+ ```
38
+
39
+ ## Configure
40
+
41
+ Everything comes from the environment. Nothing is hardcoded, so the same binary
42
+ serves whichever project you are working on.
43
+
44
+ | Variable | Required | Purpose |
45
+ |---|---|---|
46
+ | `BUGTRACKER_BRIDGE_TOKEN` | **yes** | Shared secret. Generate it in the extension under **Integration → Local agent bridge**. |
47
+ | `BUGTRACKER_LOG_PATHS` | for `find_logs` | Comma-separated files or directories the agent may read. An allowlist — nothing outside it is searchable. |
48
+ | `BUGTRACKER_BRIDGE_PORT` | no | Loopback port. Default `4319`. |
49
+ | `BUGTRACKER_DATA_DIR` | no | Where mirrored records are kept. Default `~/.bugtracker`. |
50
+
51
+ The server refuses to start without a token rather than opening an
52
+ unauthenticated port.
53
+
54
+ ## Register it with your agent
55
+
56
+ **Claude Code** — `.mcp.json` in your project, or `~/.claude.json`:
57
+
58
+ ```json
59
+ {
60
+ "mcpServers": {
61
+ "bugtracker": {
62
+ "command": "node",
63
+ "args": ["/absolute/path/to/mcp-server/dist/index.js"],
64
+ "env": {
65
+ "BUGTRACKER_BRIDGE_TOKEN": "paste-the-token-here",
66
+ "BUGTRACKER_LOG_PATHS": "/path/to/your-app/storage/logs"
67
+ }
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ **VS Code / Copilot** — the same block in `.vscode/mcp.json` under `servers`.
74
+
75
+ **Cursor** — the same block in `~/.cursor/mcp.json`.
76
+
77
+ Then in the extension: **Integration → Local agent bridge**, enable it, paste the
78
+ same token, and press **Test connection**.
79
+
80
+ ---
81
+
82
+ ## Tools
83
+
84
+ | Tool | What it does |
85
+ |---|---|
86
+ | `list_projects` | Applications with captures, their record counts and modules. Start here. |
87
+ | `list_bugs` | Records, newest first. Filter by project, module, status, type or free text. |
88
+ | `get_bug` | The full AI-ready report for one record. |
89
+ | `get_evidence` | Raw diagnostics — console, exceptions, every network request with bodies. |
90
+ | `get_module_backlog` | Every open item in a module, ordered by priority, in one document. |
91
+ | `find_logs` | Searches your local logs for a literal string. |
92
+ | `find_logs_for_bug` | Takes a record id, pulls the correlation ids the browser saw, searches the logs for each. |
93
+ | `mark_fixed` | Queues a status change the extension applies on its next sync. |
94
+
95
+ `find_logs_for_bug` is the one that replaces the backend endpoint: it goes from a
96
+ browser symptom to the server stack trace in a single call.
97
+
98
+ ### Try it
99
+
100
+ > List the open bugs in the Orders module, then find the server logs for the
101
+ > highest-priority one and tell me the root cause.
102
+
103
+ ---
104
+
105
+ ## What gets mirrored
106
+
107
+ Record metadata, the rendered Markdown report, and the raw diagnostics.
108
+
109
+ **Screenshots are not pushed** — they stay in the browser. Records are mirrored
110
+ on save, edit and delete; use **Push all records** in the extension to backfill
111
+ after enabling the bridge.
112
+
113
+ ---
114
+
115
+ ## Security
116
+
117
+ - The bridge binds `127.0.0.1` explicitly. It is not reachable from your network.
118
+ - Every endpoint except `/health` requires the bearer token, compared in constant time.
119
+ - `find_logs` is confined to `BUGTRACKER_LOG_PATHS`. A path outside it is rejected,
120
+ including via `..` — the check resolves before comparing.
121
+ - Files over 256 MB are skipped so a rotated log cannot hang the agent.
122
+ - The token lives in your agent's config and the extension's settings. Treat that
123
+ config file the way you would an `.env`.
124
+
125
+ Your captured records include URLs, request and response bodies, and console
126
+ output from the applications you test. Redaction runs in the extension before
127
+ anything is stored, but review **Settings → Redaction** before pointing an agent
128
+ at production captures.
129
+
130
+ ---
131
+
132
+ ## Verify the install
133
+
134
+ ```bash
135
+ node scripts/smoke.mjs
136
+ ```
137
+
138
+ Starts the server on a scratch port, pushes a record through the bridge,
139
+ exercises every tool over stdio, and checks that a path outside the allowlist is
140
+ refused. All checks should pass.
package/dist/bridge.js ADDED
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Loopback ingest endpoint.
3
+ *
4
+ * The extension cannot be queried — it has no server — so it pushes instead.
5
+ * This binds to 127.0.0.1 only and requires a bearer token, so it is reachable
6
+ * from the browser on this machine and from nothing else. Nothing here is ever
7
+ * exposed to the application under test.
8
+ */
9
+ import { createServer } from 'node:http';
10
+ import { timingSafeEqual } from 'node:crypto';
11
+ /** A capture with diagnostics is large; anything past this is malformed. */
12
+ const MAX_BODY_BYTES = 32 * 1024 * 1024;
13
+ function authorised(request, token) {
14
+ const header = request.headers.authorization ?? '';
15
+ const supplied = header.startsWith('Bearer ') ? header.slice(7) : '';
16
+ if (supplied.length !== token.length)
17
+ return false;
18
+ return timingSafeEqual(Buffer.from(supplied), Buffer.from(token));
19
+ }
20
+ function readBody(request) {
21
+ return new Promise((resolve, reject) => {
22
+ let size = 0;
23
+ const chunks = [];
24
+ request.on('data', (chunk) => {
25
+ size += chunk.length;
26
+ if (size > MAX_BODY_BYTES) {
27
+ reject(new Error('Payload too large'));
28
+ request.destroy();
29
+ return;
30
+ }
31
+ chunks.push(chunk);
32
+ });
33
+ request.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
34
+ request.on('error', reject);
35
+ });
36
+ }
37
+ function json(response, status, body) {
38
+ const payload = JSON.stringify(body);
39
+ response.writeHead(status, {
40
+ 'content-type': 'application/json',
41
+ 'content-length': Buffer.byteLength(payload),
42
+ });
43
+ response.end(payload);
44
+ }
45
+ export function startBridge(options) {
46
+ const { port, token, store } = options;
47
+ const log = options.onLog ?? (() => { });
48
+ const server = createServer((request, response) => {
49
+ /*
50
+ * The caller is an extension service worker, so every request is
51
+ * cross-origin and preflighted. Reflecting the origin is safe here because
52
+ * the token is the actual gate and the socket only listens on loopback.
53
+ */
54
+ const origin = request.headers.origin;
55
+ if (origin) {
56
+ response.setHeader('access-control-allow-origin', origin);
57
+ response.setHeader('access-control-allow-headers', 'authorization, content-type');
58
+ response.setHeader('access-control-allow-methods', 'GET, POST, OPTIONS');
59
+ response.setHeader('vary', 'origin');
60
+ }
61
+ if (request.method === 'OPTIONS') {
62
+ response.writeHead(204);
63
+ response.end();
64
+ return;
65
+ }
66
+ const url = new URL(request.url ?? '/', 'http://127.0.0.1');
67
+ if (url.pathname === '/health' && request.method === 'GET') {
68
+ json(response, 200, { ok: true, records: store.size });
69
+ return;
70
+ }
71
+ if (!authorised(request, token)) {
72
+ json(response, 401, { error: 'Bad or missing bearer token.' });
73
+ return;
74
+ }
75
+ void handle(request, response, url, store, log).catch((error) => {
76
+ json(response, 500, { error: error instanceof Error ? error.message : 'Bridge error' });
77
+ });
78
+ });
79
+ return new Promise((resolve, reject) => {
80
+ server.once('error', reject);
81
+ // Explicitly loopback: the default would listen on every interface.
82
+ server.listen(port, '127.0.0.1', () => {
83
+ log(`bridge listening on http://127.0.0.1:${port}`);
84
+ resolve(() => new Promise((done) => {
85
+ server.close(() => done());
86
+ }));
87
+ });
88
+ });
89
+ }
90
+ async function handle(request, response, url, store, log) {
91
+ if (url.pathname === '/records' && request.method === 'POST') {
92
+ const body = JSON.parse(await readBody(request));
93
+ const records = Array.isArray(body.records) ? body.records.filter((r) => r?.id) : [];
94
+ const total = await store.upsert(records);
95
+ log(`received ${records.length} record(s), ${total} held`);
96
+ json(response, 200, { ok: true, received: records.length, total });
97
+ return;
98
+ }
99
+ if (url.pathname === '/records/delete' && request.method === 'POST') {
100
+ const body = JSON.parse(await readBody(request));
101
+ const removed = await store.remove(Array.isArray(body.ids) ? body.ids : []);
102
+ json(response, 200, { ok: true, removed });
103
+ return;
104
+ }
105
+ // The extension polls this to learn about changes an agent made.
106
+ if (url.pathname === '/pending' && request.method === 'GET') {
107
+ json(response, 200, { operations: await store.drainPending() });
108
+ return;
109
+ }
110
+ json(response, 404, { error: 'Unknown endpoint' });
111
+ }
112
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,YAAY,EAA6C,MAAM,WAAW,CAAC;AACpF,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9C,4EAA4E;AAC5E,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAExC,SAAS,UAAU,CAAC,OAAwB,EAAE,KAAa;IACzD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;IACnD,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACrE,IAAI,QAAQ,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACnD,OAAO,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,QAAQ,CAAC,OAAwB;IACxC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACnC,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC;YACrB,IAAI,IAAI,GAAG,cAAc,EAAE,CAAC;gBAC1B,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC;gBACvC,OAAO,CAAC,OAAO,EAAE,CAAC;gBAClB,OAAO;YACT,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACzE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,IAAI,CAAC,QAAwB,EAAE,MAAc,EAAE,IAAa;IACnE,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACrC,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE;QACzB,cAAc,EAAE,kBAAkB;QAClC,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC;KAC7C,CAAC,CAAC;IACH,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACxB,CAAC;AAUD,MAAM,UAAU,WAAW,CAAC,OAAsB;IAChD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IACvC,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAExC,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;QAChD;;;;WAIG;QACH,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;QACtC,IAAI,MAAM,EAAE,CAAC;YACX,QAAQ,CAAC,SAAS,CAAC,6BAA6B,EAAE,MAAM,CAAC,CAAC;YAC1D,QAAQ,CAAC,SAAS,CAAC,8BAA8B,EAAE,6BAA6B,CAAC,CAAC;YAClF,QAAQ,CAAC,SAAS,CAAC,8BAA8B,EAAE,oBAAoB,CAAC,CAAC;YACzE,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACvC,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YACjC,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACxB,QAAQ,CAAC,GAAG,EAAE,CAAC;YACf,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;QAE5D,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YAC3D,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACvD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;YAChC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC,CAAC;YAC/D,OAAO;QACT,CAAC;QAED,KAAK,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACvE,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC;QAC1F,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC7B,oEAAoE;QACpE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE;YACpC,GAAG,CAAC,wCAAwC,IAAI,EAAE,CAAC,CAAC;YACpD,OAAO,CACL,GAAG,EAAE,CACH,IAAI,OAAO,CAAO,CAAC,IAAI,EAAE,EAAE;gBACzB,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;YAC7B,CAAC,CAAC,CACL,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,MAAM,CACnB,OAAwB,EACxB,QAAwB,EACxB,GAAQ,EACR,KAAY,EACZ,GAA8B;IAE9B,IAAI,GAAG,CAAC,QAAQ,KAAK,UAAU,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAmC,CAAC;QACnF,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACrF,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC1C,GAAG,CAAC,YAAY,OAAO,CAAC,MAAM,eAAe,KAAK,OAAO,CAAC,CAAC;QAC3D,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QACnE,OAAO;IACT,CAAC;IAED,IAAI,GAAG,CAAC,QAAQ,KAAK,iBAAiB,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QACpE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAuB,CAAC;QACvE,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC5E,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QAC3C,OAAO;IACT,CAAC;IAED,iEAAiE;IACjE,IAAI,GAAG,CAAC,QAAQ,KAAK,UAAU,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;QAC5D,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QAChE,OAAO;IACT,CAAC;IAED,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;AACrD,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Configuration, entirely from the environment.
3
+ *
4
+ * Nothing here is hardcoded into a project: the token, the port and the log
5
+ * paths all come from the shell that launched the server, so the same binary
6
+ * serves whichever application the developer happens to be working on.
7
+ */
8
+ import { homedir } from 'node:os';
9
+ import { join } from 'node:path';
10
+ function list(value) {
11
+ return (value ?? '')
12
+ .split(/[,;]/)
13
+ .map((entry) => entry.trim())
14
+ .filter(Boolean);
15
+ }
16
+ export function loadConfig() {
17
+ const token = process.env.BUGTRACKER_BRIDGE_TOKEN?.trim() ?? '';
18
+ return {
19
+ port: Number.parseInt(process.env.BUGTRACKER_BRIDGE_PORT ?? '4319', 10) || 4319,
20
+ token,
21
+ dataDir: process.env.BUGTRACKER_DATA_DIR?.trim() || join(homedir(), '.bugtracker'),
22
+ logPaths: list(process.env.BUGTRACKER_LOG_PATHS),
23
+ projectRoots: list(process.env.BUGTRACKER_PROJECT_ROOTS),
24
+ };
25
+ }
26
+ /** Fails fast with instructions rather than starting an unauthenticated bridge. */
27
+ export function requireToken(config) {
28
+ if (config.token)
29
+ return;
30
+ throw new Error('BUGTRACKER_BRIDGE_TOKEN is not set.\n\n' +
31
+ 'Generate one in the extension under Settings → AI agent bridge, then start\n' +
32
+ 'this server with it in the environment. Without a token the bridge would\n' +
33
+ 'accept records from anything else running on this machine.');
34
+ }
35
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAqBjC,SAAS,IAAI,CAAC,KAAyB;IACrC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;SACjB,KAAK,CAAC,MAAM,CAAC;SACb,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;SAC5B,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAEhE,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,MAAM,EAAE,EAAE,CAAC,IAAI,IAAI;QAC/E,KAAK;QACL,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC;QAClF,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;QAChD,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;KACzD,CAAC;AACJ,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,IAAI,MAAM,CAAC,KAAK;QAAE,OAAO;IACzB,MAAM,IAAI,KAAK,CACb,yCAAyC;QACvC,8EAA8E;QAC9E,4EAA4E;QAC5E,4DAA4D,CAC/D,CAAC;AACJ,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP server for the AI Bug & Feature Tracker.
4
+ *
5
+ * Runs on the developer's own machine. It is not deployed, not imported by the
6
+ * application under test, and not reachable from the network — which is the
7
+ * whole point: an agent can be handed real bug reports and real server logs
8
+ * without the application exposing an endpoint to anybody.
9
+ *
10
+ * Two halves:
11
+ * - a loopback bridge the browser extension pushes captures into
12
+ * - this MCP surface, which the agent queries over stdio
13
+ */
14
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
15
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
16
+ import { z } from 'zod';
17
+ import { loadConfig, requireToken } from './config.js';
18
+ import { Store } from './store.js';
19
+ import { startBridge } from './bridge.js';
20
+ import { findInLogs } from './logs.js';
21
+ const config = loadConfig();
22
+ requireToken(config);
23
+ const store = new Store(config.dataDir);
24
+ /** MCP speaks over stdout, so every diagnostic has to go to stderr. */
25
+ const log = (message) => process.stderr.write(`[bugtracker-mcp] ${message}\n`);
26
+ function text(body) {
27
+ return { content: [{ type: 'text', text: body }] };
28
+ }
29
+ function summarise(record) {
30
+ const severity = record.severity ? `/${record.severity}` : '';
31
+ return [
32
+ `- **${record.title}**`,
33
+ ` id: \`${record.id}\` · ${record.type} · ${record.priority}${severity} · ${record.status}`,
34
+ ` project: ${record.projectName ?? 'Unassigned'} · module: ${record.module || '—'}`,
35
+ ` ${record.url}`,
36
+ ].join('\n');
37
+ }
38
+ const server = new McpServer({
39
+ name: 'bugtracker',
40
+ version: '0.1.0',
41
+ });
42
+ /* ------------------------------------------------------------- discovery */
43
+ server.registerTool('list_projects', {
44
+ title: 'List projects',
45
+ description: 'Applications that have captured bug reports, with how many records and which modules each contains. Start here to find out what is available.',
46
+ inputSchema: {},
47
+ }, () => {
48
+ const projects = store.projects();
49
+ if (!projects.length) {
50
+ return text('No records yet. The browser extension pushes them here as they are captured — ' +
51
+ 'check that the bridge is enabled in its settings.');
52
+ }
53
+ return text(projects
54
+ .map((p) => `- **${p.name}** (\`${p.id}\`) — ${p.records} record(s)\n modules: ${p.modules.join(', ') || '—'}`)
55
+ .join('\n'));
56
+ });
57
+ server.registerTool('list_bugs', {
58
+ title: 'List bugs and feature requests',
59
+ description: 'Captured records, newest first. Filter by project, module, status or type. Returns summaries — call get_bug for the full report.',
60
+ inputSchema: {
61
+ project: z.string().optional().describe('Project id or name'),
62
+ module: z.string().optional().describe('Module name, e.g. "Orders"'),
63
+ status: z.enum(['new', 'in_progress', 'done', 'archived']).optional(),
64
+ type: z.enum(['bug', 'feature']).optional(),
65
+ search: z.string().optional().describe('Free text over title, module, URL and tags'),
66
+ limit: z.number().int().min(1).max(100).optional().default(25),
67
+ },
68
+ }, ({ project, module, status, type, search, limit }) => {
69
+ const found = store.list({
70
+ ...(project ? { project } : {}),
71
+ ...(module ? { module } : {}),
72
+ ...(status ? { status } : {}),
73
+ ...(type ? { type } : {}),
74
+ ...(search ? { search } : {}),
75
+ });
76
+ if (!found.length)
77
+ return text('No records matched those filters.');
78
+ const shown = found.slice(0, limit);
79
+ const more = found.length > shown.length ? `\n\n…and ${found.length - shown.length} more.` : '';
80
+ return text(`${found.length} record(s):\n\n${shown.map(summarise).join('\n\n')}${more}`);
81
+ });
82
+ /* ----------------------------------------------------------------- detail */
83
+ server.registerTool('get_bug', {
84
+ title: 'Get the full report for one record',
85
+ description: 'The complete AI-ready report: task brief, tester narrative, environment, server exceptions, console, failed requests, and the application source files implicated by the stack traces.',
86
+ inputSchema: { id: z.string().describe('Record id from list_bugs') },
87
+ }, ({ id }) => {
88
+ const record = store.get(id);
89
+ if (!record)
90
+ return text(`No record with id \`${id}\`. Call list_bugs to see what exists.`);
91
+ return text(record.markdown);
92
+ });
93
+ server.registerTool('get_evidence', {
94
+ title: 'Get raw diagnostics for one record',
95
+ description: 'The unformatted capture: console entries, JS exceptions, server-side exceptions, every network request with bodies, and the navigation trail. Use when the report has been summarised too far.',
96
+ inputSchema: { id: z.string() },
97
+ }, ({ id }) => {
98
+ const record = store.get(id);
99
+ if (!record)
100
+ return text(`No record with id \`${id}\`.`);
101
+ if (!record.evidence)
102
+ return text('No raw diagnostics were pushed for this record.');
103
+ return text('```json\n' + JSON.stringify(record.evidence, null, 2) + '\n```');
104
+ });
105
+ server.registerTool('get_module_backlog', {
106
+ title: 'Get every open item for one module',
107
+ description: 'All bugs and improvements for a module in one document, ordered by priority. Use this to clear a module in a single pass rather than fixing one issue at a time.',
108
+ inputSchema: {
109
+ module: z.string().describe('Module name'),
110
+ project: z.string().optional(),
111
+ includeDone: z.boolean().optional().default(false),
112
+ },
113
+ }, ({ module, project, includeDone }) => {
114
+ const all = store
115
+ .list({ module, ...(project ? { project } : {}) })
116
+ .filter((r) => includeDone || (r.status !== 'done' && r.status !== 'archived'));
117
+ if (!all.length)
118
+ return text(`Nothing open in module "${module}".`);
119
+ const rank = { critical: 0, high: 1, medium: 2, low: 3 };
120
+ const ordered = [...all].sort((a, b) => (rank[a.priority] ?? 9) - (rank[b.priority] ?? 9) ||
121
+ (a.type === b.type ? 0 : a.type === 'bug' ? -1 : 1));
122
+ const table = [
123
+ '| id | type | priority | title |',
124
+ '| --- | --- | --- | --- |',
125
+ ...ordered.map((r) => `| \`${r.id}\` | ${r.type} | ${r.priority} | ${r.title.replace(/\|/g, '\\|')} |`),
126
+ ];
127
+ return text([
128
+ `# Module backlog: ${module}`,
129
+ '',
130
+ `${ordered.length} open item(s). Read the whole list before changing anything —`,
131
+ 'items in one module routinely share a root cause.',
132
+ '',
133
+ ...table,
134
+ '',
135
+ '---',
136
+ '',
137
+ ...ordered.map((r) => `${r.markdown}\n\n---\n`),
138
+ ].join('\n'));
139
+ });
140
+ /* -------------------------------------------------------------- local logs */
141
+ server.registerTool('find_logs', {
142
+ title: 'Search local server logs',
143
+ description: 'Searches log files on this machine for a literal string — normally the correlation id from a failed request in a bug report. This reads the developer’s own disk; the application under test exposes nothing.',
144
+ inputSchema: {
145
+ query: z
146
+ .string()
147
+ .describe('Literal string to find, e.g. the request/trace id from a report'),
148
+ paths: z
149
+ .array(z.string())
150
+ .optional()
151
+ .describe('Restrict to specific files inside the configured log paths'),
152
+ },
153
+ }, async ({ query, paths }) => {
154
+ const result = await findInLogs(query, config.logPaths, paths ?? []);
155
+ if (!result.matches.length) {
156
+ const why = result.skipped.length
157
+ ? `\n\nSkipped:\n${result.skipped.map((s) => `- ${s.file}: ${s.reason}`).join('\n')}`
158
+ : '';
159
+ return text(`No log lines contain "${query}".${why}\n\n` +
160
+ (config.logPaths.length
161
+ ? `Searched: ${result.filesSearched.join(', ') || 'nothing readable'}`
162
+ : 'Set BUGTRACKER_LOG_PATHS to the log files this server may read.'));
163
+ }
164
+ const body = result.matches
165
+ .map((m) => `${m.file}:${m.line}\n${m.text}`)
166
+ .join('\n\n');
167
+ return text(`${result.matches.length} matching line(s)${result.truncated ? ' (truncated)' : ''}:\n\n` +
168
+ '```\n' +
169
+ body +
170
+ '\n```');
171
+ });
172
+ server.registerTool('find_logs_for_bug', {
173
+ title: 'Find the server logs behind a bug',
174
+ description: 'Takes a record id, pulls the correlation ids the browser captured on its failed requests, and searches the local logs for each. The fastest path from a browser symptom to a server stack trace.',
175
+ inputSchema: { id: z.string() },
176
+ }, async ({ id }) => {
177
+ const record = store.get(id);
178
+ if (!record)
179
+ return text(`No record with id \`${id}\`.`);
180
+ const ids = record.requestIds ?? [];
181
+ if (!ids.length) {
182
+ return text('This capture carries no correlation id, so there is nothing to search on.\n\n' +
183
+ 'The extension reads X-Request-Id, traceparent, CF-Ray, X-Amzn-Trace-Id, ' +
184
+ 'X-Vercel-Id and similar. If none of those reaches the browser, the server ' +
185
+ 'is not emitting one.');
186
+ }
187
+ const sections = [];
188
+ for (const requestId of ids.slice(0, 5)) {
189
+ const result = await findInLogs(requestId, config.logPaths);
190
+ sections.push(result.matches.length
191
+ ? `## \`${requestId}\`\n\n\`\`\`\n${result.matches
192
+ .map((m) => `${m.file}:${m.line}\n${m.text}`)
193
+ .join('\n\n')}\n\`\`\``
194
+ : `## \`${requestId}\`\n\nNo matching log lines.`);
195
+ }
196
+ return text(sections.join('\n\n'));
197
+ });
198
+ /* ------------------------------------------------------------- write back */
199
+ server.registerTool('mark_fixed', {
200
+ title: 'Mark a record as fixed',
201
+ description: 'Queues a status change for the extension to apply next time it syncs. Use once a fix is committed, so the tester sees it and does not refile it.',
202
+ inputSchema: {
203
+ id: z.string(),
204
+ note: z.string().optional().describe('What you changed, for the tester'),
205
+ },
206
+ }, async ({ id, note }) => {
207
+ const record = store.get(id);
208
+ if (!record)
209
+ return text(`No record with id \`${id}\`.`);
210
+ await store.queue({ recordId: id, op: 'set-status', value: 'done', ...(note ? { note } : {}) });
211
+ return text(`Queued "${record.title}" as done. The extension applies this the next time it syncs.`);
212
+ });
213
+ /* ------------------------------------------------------------------- boot */
214
+ async function main() {
215
+ await store.load();
216
+ log(`loaded ${store.size} record(s) from ${config.dataDir}`);
217
+ await startBridge({ port: config.port, token: config.token, store, onLog: log });
218
+ if (!config.logPaths.length) {
219
+ log('BUGTRACKER_LOG_PATHS is not set — find_logs will return nothing.');
220
+ }
221
+ await server.connect(new StdioServerTransport());
222
+ log('MCP server ready on stdio');
223
+ }
224
+ main().catch((error) => {
225
+ process.stderr.write(`[bugtracker-mcp] failed to start: ${error instanceof Error ? error.message : String(error)}\n`);
226
+ process.exit(1);
227
+ });
228
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/logs.js ADDED
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Local log search.
3
+ *
4
+ * This is the reason the MCP server is worth running: it removes the need for
5
+ * the application to expose anything at all. Instead of asking the app for its
6
+ * exception over HTTP, the agent asks this process, which reads the log file
7
+ * off the developer's own disk.
8
+ *
9
+ * Access is restricted to paths the developer listed in the environment. An
10
+ * agent that asks for a request id must not be able to walk out of the project
11
+ * and read whatever it likes.
12
+ */
13
+ import { createReadStream } from 'node:fs';
14
+ import { stat, readdir } from 'node:fs/promises';
15
+ import { createInterface } from 'node:readline';
16
+ import { isAbsolute, join, resolve, sep } from 'node:path';
17
+ /** Reading a multi-gigabyte rotated log would hang the agent. */
18
+ const MAX_FILE_BYTES = 256 * 1024 * 1024;
19
+ const MAX_MATCHES = 200;
20
+ const MAX_LINE_CHARS = 4000;
21
+ /** True when `candidate` is inside one of the allowed roots. */
22
+ function isAllowed(candidate, roots) {
23
+ const target = resolve(candidate);
24
+ return roots.some((root) => {
25
+ const allowed = resolve(root);
26
+ return target === allowed || target.startsWith(allowed + sep);
27
+ });
28
+ }
29
+ /** Expands configured paths into concrete files, one level deep for directories. */
30
+ async function collectFiles(paths) {
31
+ const files = [];
32
+ const skipped = [];
33
+ for (const path of paths) {
34
+ try {
35
+ const info = await stat(path);
36
+ if (info.isDirectory()) {
37
+ for (const entry of await readdir(path, { withFileTypes: true })) {
38
+ if (!entry.isFile())
39
+ continue;
40
+ if (!/\.(log|txt|out)$/i.test(entry.name))
41
+ continue;
42
+ files.push(join(path, entry.name));
43
+ }
44
+ continue;
45
+ }
46
+ if (info.size > MAX_FILE_BYTES) {
47
+ skipped.push({ file: path, reason: `larger than ${MAX_FILE_BYTES / 1024 / 1024} MB` });
48
+ continue;
49
+ }
50
+ files.push(path);
51
+ }
52
+ catch (error) {
53
+ skipped.push({ file: path, reason: error instanceof Error ? error.message : 'unreadable' });
54
+ }
55
+ }
56
+ return { files, skipped };
57
+ }
58
+ async function searchFile(file, needle, matches) {
59
+ const stream = createReadStream(file, { encoding: 'utf8' });
60
+ const lines = createInterface({ input: stream, crlfDelay: Infinity });
61
+ let lineNumber = 0;
62
+ try {
63
+ for await (const line of lines) {
64
+ lineNumber++;
65
+ if (!line.includes(needle))
66
+ continue;
67
+ matches.push({
68
+ file,
69
+ line: lineNumber,
70
+ text: line.length > MAX_LINE_CHARS ? `${line.slice(0, MAX_LINE_CHARS)}…` : line,
71
+ });
72
+ if (matches.length >= MAX_MATCHES)
73
+ break;
74
+ }
75
+ }
76
+ finally {
77
+ lines.close();
78
+ stream.destroy();
79
+ }
80
+ }
81
+ /**
82
+ * Searches the configured logs for a literal string — normally a request id.
83
+ *
84
+ * @param extraPaths Caller-supplied paths, honoured only if they sit inside the
85
+ * configured allowlist.
86
+ */
87
+ export async function findInLogs(needle, configuredPaths, extraPaths = []) {
88
+ if (!needle.trim()) {
89
+ return { matches: [], filesSearched: [], skipped: [], truncated: false };
90
+ }
91
+ if (configuredPaths.length === 0) {
92
+ return {
93
+ matches: [],
94
+ filesSearched: [],
95
+ skipped: [{ file: '(none configured)', reason: 'BUGTRACKER_LOG_PATHS is not set' }],
96
+ truncated: false,
97
+ };
98
+ }
99
+ const requested = extraPaths.length ? extraPaths : configuredPaths;
100
+ const allowed = [];
101
+ const skipped = [];
102
+ for (const path of requested) {
103
+ const absolute = isAbsolute(path) ? path : resolve(path);
104
+ if (isAllowed(absolute, configuredPaths))
105
+ allowed.push(absolute);
106
+ else
107
+ skipped.push({ file: path, reason: 'outside BUGTRACKER_LOG_PATHS' });
108
+ }
109
+ const { files, skipped: unreadable } = await collectFiles(allowed);
110
+ const matches = [];
111
+ const searched = [];
112
+ for (const file of files) {
113
+ if (matches.length >= MAX_MATCHES)
114
+ break;
115
+ try {
116
+ await searchFile(file, needle, matches);
117
+ searched.push(file);
118
+ }
119
+ catch (error) {
120
+ unreadable.push({ file, reason: error instanceof Error ? error.message : 'read failed' });
121
+ }
122
+ }
123
+ return {
124
+ matches,
125
+ filesSearched: searched,
126
+ skipped: [...skipped, ...unreadable],
127
+ truncated: matches.length >= MAX_MATCHES,
128
+ };
129
+ }
130
+ //# sourceMappingURL=logs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.js","sourceRoot":"","sources":["../src/logs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAE3D,iEAAiE;AACjE,MAAM,cAAc,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AACzC,MAAM,WAAW,GAAG,GAAG,CAAC;AACxB,MAAM,cAAc,GAAG,IAAI,CAAC;AAe5B,gEAAgE;AAChE,SAAS,SAAS,CAAC,SAAiB,EAAE,KAAe;IACnD,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAClC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QACzB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC9B,OAAO,MAAM,KAAK,OAAO,IAAI,MAAM,CAAC,UAAU,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,oFAAoF;AACpF,KAAK,UAAU,YAAY,CAAC,KAAe;IACzC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,OAAO,GAA+B,EAAE,CAAC;IAE/C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;YAE9B,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;gBACvB,KAAK,MAAM,KAAK,IAAI,MAAM,OAAO,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;oBACjE,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;wBAAE,SAAS;oBAC9B,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;wBAAE,SAAS;oBACpD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;gBACrC,CAAC;gBACD,SAAS;YACX,CAAC;YAED,IAAI,IAAI,CAAC,IAAI,GAAG,cAAc,EAAE,CAAC;gBAC/B,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,cAAc,GAAG,IAAI,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC;gBACvF,SAAS;YACX,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC;QAC9F,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAC5B,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,IAAY,EAAE,MAAc,EAAE,OAAmB;IACzE,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;IAEtE,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,CAAC;QACH,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAC/B,UAAU,EAAE,CAAC;YACb,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAAE,SAAS;YACrC,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI;gBACJ,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,IAAI,CAAC,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI;aAChF,CAAC,CAAC;YACH,IAAI,OAAO,CAAC,MAAM,IAAI,WAAW;gBAAE,MAAM;QAC3C,CAAC;IACH,CAAC;YAAS,CAAC;QACT,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAc,EACd,eAAyB,EACzB,aAAuB,EAAE;IAEzB,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QACnB,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAC3E,CAAC;IACD,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO;YACL,OAAO,EAAE,EAAE;YACX,aAAa,EAAE,EAAE;YACjB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,iCAAiC,EAAE,CAAC;YACnF,SAAS,EAAE,KAAK;SACjB,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,eAAe,CAAC;IACnE,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAA+B,EAAE,CAAC;IAE/C,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACzD,IAAI,SAAS,CAAC,QAAQ,EAAE,eAAe,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;;YAC5D,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,8BAA8B,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;IACnE,MAAM,OAAO,GAAe,EAAE,CAAC;IAC/B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,OAAO,CAAC,MAAM,IAAI,WAAW;YAAE,MAAM;QACzC,IAAI,CAAC;YACH,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;YACxC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC;QAC5F,CAAC;IACH,CAAC;IAED,OAAO;QACL,OAAO;QACP,aAAa,EAAE,QAAQ;QACvB,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,GAAG,UAAU,CAAC;QACpC,SAAS,EAAE,OAAO,CAAC,MAAM,IAAI,WAAW;KACzC,CAAC;AACJ,CAAC"}
package/dist/store.js ADDED
@@ -0,0 +1,128 @@
1
+ /**
2
+ * The local mirror of what the extension has captured.
3
+ *
4
+ * A JSON file rather than a database on purpose: the working set is a few
5
+ * hundred records, the whole thing has to survive being inspected by hand, and
6
+ * a native SQLite build is a poor trade for an install every developer on the
7
+ * team has to do.
8
+ */
9
+ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ const EMPTY = { version: 1, records: [], pending: [] };
12
+ export class Store {
13
+ dataDir;
14
+ data = EMPTY;
15
+ file;
16
+ /** Serialises writes so two concurrent pushes cannot interleave. */
17
+ writing = Promise.resolve();
18
+ constructor(dataDir) {
19
+ this.dataDir = dataDir;
20
+ this.file = join(dataDir, 'records.json');
21
+ }
22
+ async load() {
23
+ try {
24
+ const raw = await readFile(this.file, 'utf8');
25
+ const parsed = JSON.parse(raw);
26
+ this.data = {
27
+ version: 1,
28
+ records: Array.isArray(parsed.records) ? parsed.records : [],
29
+ pending: Array.isArray(parsed.pending) ? parsed.pending : [],
30
+ };
31
+ }
32
+ catch {
33
+ // Missing or corrupt: start clean rather than refusing to run.
34
+ this.data = { ...EMPTY, records: [], pending: [] };
35
+ }
36
+ }
37
+ persist() {
38
+ // Write to a temp file and rename, so a crash mid-write cannot truncate it.
39
+ this.writing = this.writing.then(async () => {
40
+ await mkdir(this.dataDir, { recursive: true });
41
+ const temp = `${this.file}.tmp`;
42
+ await writeFile(temp, JSON.stringify(this.data, null, 2), 'utf8');
43
+ await rename(temp, this.file);
44
+ });
45
+ return this.writing;
46
+ }
47
+ async upsert(records) {
48
+ for (const record of records) {
49
+ const index = this.data.records.findIndex((r) => r.id === record.id);
50
+ if (index >= 0)
51
+ this.data.records[index] = record;
52
+ else
53
+ this.data.records.push(record);
54
+ }
55
+ await this.persist();
56
+ return this.data.records.length;
57
+ }
58
+ async remove(ids) {
59
+ const before = this.data.records.length;
60
+ const doomed = new Set(ids);
61
+ this.data.records = this.data.records.filter((r) => !doomed.has(r.id));
62
+ this.data.pending = this.data.pending.filter((p) => !doomed.has(p.recordId));
63
+ await this.persist();
64
+ return before - this.data.records.length;
65
+ }
66
+ list(filter) {
67
+ const needle = filter.search?.trim().toLowerCase();
68
+ return this.data.records
69
+ .filter((r) => {
70
+ if (filter.project && !matchesProject(r, filter.project))
71
+ return false;
72
+ if (filter.module && r.module.toLowerCase() !== filter.module.toLowerCase())
73
+ return false;
74
+ if (filter.status && r.status !== filter.status)
75
+ return false;
76
+ if (filter.type && r.type !== filter.type)
77
+ return false;
78
+ if (needle) {
79
+ const hay = `${r.title} ${r.module} ${r.url} ${r.tags.join(' ')}`.toLowerCase();
80
+ if (!hay.includes(needle))
81
+ return false;
82
+ }
83
+ return true;
84
+ })
85
+ .sort((a, b) => b.createdAt - a.createdAt);
86
+ }
87
+ get(id) {
88
+ return this.data.records.find((r) => r.id === id);
89
+ }
90
+ projects() {
91
+ const byProject = new Map();
92
+ for (const record of this.data.records) {
93
+ const key = record.projectId ?? 'unassigned';
94
+ const entry = byProject.get(key) ?? {
95
+ name: record.projectName ?? 'Unassigned',
96
+ records: 0,
97
+ modules: new Set(),
98
+ };
99
+ entry.records++;
100
+ if (record.module)
101
+ entry.modules.add(record.module);
102
+ byProject.set(key, entry);
103
+ }
104
+ return [...byProject.entries()]
105
+ .map(([id, v]) => ({ id, name: v.name, records: v.records, modules: [...v.modules].sort() }))
106
+ .sort((a, b) => b.records - a.records);
107
+ }
108
+ async queue(op) {
109
+ this.data.pending.push({ ...op, id: `${Date.now()}-${this.data.pending.length}`, queuedAt: Date.now() });
110
+ await this.persist();
111
+ }
112
+ /** Returns queued operations and clears them; the extension calls this once. */
113
+ async drainPending() {
114
+ const pending = this.data.pending;
115
+ this.data.pending = [];
116
+ await this.persist();
117
+ return pending;
118
+ }
119
+ get size() {
120
+ return this.data.records.length;
121
+ }
122
+ }
123
+ function matchesProject(record, query) {
124
+ const q = query.toLowerCase();
125
+ return (record.projectId?.toLowerCase() === q ||
126
+ (record.projectName ?? 'unassigned').toLowerCase() === q);
127
+ }
128
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@orbit360.lk/bugtracker-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Local MCP server exposing captured bug reports and local server logs to an AI coding agent.",
5
+ "author": "Orbit Pvt Ltd <orbit.to.us@gmail.com> (https://orbit360.lk/)",
6
+ "license": "MIT",
7
+ "homepage": "https://orbit360.lk/",
8
+ "type": "module",
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "bin": {
13
+ "bugtracker-mcp": "dist/index.js"
14
+ },
15
+ "main": "dist/index.js",
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "dev": "tsc --watch",
24
+ "start": "node dist/index.js",
25
+ "typecheck": "tsc --noEmit",
26
+ "smoke": "node scripts/smoke.mjs",
27
+ "prepublishOnly": "npm run build && npm run smoke"
28
+ },
29
+ "keywords": [
30
+ "mcp",
31
+ "model-context-protocol",
32
+ "bug-tracker",
33
+ "qa",
34
+ "ai-agent"
35
+ ],
36
+ "engines": {
37
+ "node": ">=20"
38
+ },
39
+ "dependencies": {
40
+ "@modelcontextprotocol/sdk": "^1.20.0",
41
+ "zod": "^3.25.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^26.1.2",
45
+ "typescript": "^5.9.3"
46
+ }
47
+ }