backlog-mcp 0.20.0 → 0.23.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/README.md +15 -0
- package/dist/cli/bridge.d.mts +1 -0
- package/dist/cli/bridge.mjs +30 -0
- package/dist/cli/bridge.mjs.map +1 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/{cli.js → cli/index.mjs} +13 -22
- package/dist/cli/index.mjs.map +1 -0
- package/dist/cli/server-manager.d.mts +5 -0
- package/dist/cli/server-manager.mjs +93 -0
- package/dist/cli/server-manager.mjs.map +1 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +5 -0
- package/dist/middleware/auth.d.mts +7 -0
- package/dist/middleware/auth.mjs +11 -0
- package/dist/middleware/auth.mjs.map +1 -0
- package/dist/resources/index-resources.d.mts +7 -0
- package/dist/resources/index-resources.mjs +16 -0
- package/dist/resources/index-resources.mjs.map +1 -0
- package/dist/resources/index.d.mts +3 -0
- package/dist/resources/index.mjs +3 -0
- package/dist/resources/operations.d.mts +7 -0
- package/dist/resources/operations.mjs +26 -0
- package/dist/resources/operations.mjs.map +1 -0
- package/dist/resources/resource-file.d.mts +7 -0
- package/dist/resources/resource-file.mjs +20 -0
- package/dist/resources/resource-file.mjs.map +1 -0
- package/dist/resources/resource-reader.d.mts +10 -0
- package/dist/resources/resource-reader.mjs +34 -0
- package/dist/resources/resource-reader.mjs.map +1 -0
- package/dist/resources/task-attached.d.mts +7 -0
- package/dist/resources/task-attached.mjs +17 -0
- package/dist/resources/task-attached.mjs.map +1 -0
- package/dist/resources/task-by-id.d.mts +7 -0
- package/dist/resources/task-by-id.mjs +23 -0
- package/dist/resources/task-by-id.mjs.map +1 -0
- package/dist/resources/tasks.d.mts +7 -0
- package/dist/resources/tasks.mjs +20 -0
- package/dist/resources/tasks.mjs.map +1 -0
- package/dist/resources/types.d.mts +33 -0
- package/dist/resources/types.mjs +1 -0
- package/dist/resources/uri.d.mts +11 -0
- package/dist/resources/uri.mjs +22 -0
- package/dist/resources/uri.mjs.map +1 -0
- package/dist/resources/write.d.mts +11 -0
- package/dist/resources/write.mjs +63 -0
- package/dist/resources/write.mjs.map +1 -0
- package/dist/server/fastify-server.d.mts +5 -0
- package/dist/server/fastify-server.mjs +50 -0
- package/dist/server/fastify-server.mjs.map +1 -0
- package/dist/server/mcp-handler.d.mts +7 -0
- package/dist/server/mcp-handler.mjs +32 -0
- package/dist/server/mcp-handler.mjs.map +1 -0
- package/dist/server/viewer-routes.d.mts +7 -0
- package/dist/server/viewer-routes.mjs +120 -0
- package/dist/server/viewer-routes.mjs.map +1 -0
- package/dist/storage/backlog.d.mts +37 -0
- package/dist/storage/backlog.mjs +127 -0
- package/dist/storage/backlog.mjs.map +1 -0
- package/dist/storage/schema.d.mts +38 -0
- package/dist/storage/schema.mjs +44 -0
- package/dist/storage/schema.mjs.map +1 -0
- package/dist/tools/backlog-create.d.mts +7 -0
- package/dist/tools/backlog-create.mjs +38 -0
- package/dist/tools/backlog-create.mjs.map +1 -0
- package/dist/tools/backlog-delete.d.mts +7 -0
- package/dist/tools/backlog-delete.mjs +20 -0
- package/dist/tools/backlog-delete.mjs.map +1 -0
- package/dist/tools/backlog-get.d.mts +7 -0
- package/dist/tools/backlog-get.mjs +27 -0
- package/dist/tools/backlog-get.mjs.map +1 -0
- package/dist/tools/backlog-list.d.mts +7 -0
- package/dist/tools/backlog-list.mjs +39 -0
- package/dist/tools/backlog-list.mjs.map +1 -0
- package/dist/tools/backlog-update.d.mts +7 -0
- package/dist/tools/backlog-update.mjs +42 -0
- package/dist/tools/backlog-update.mjs.map +1 -0
- package/dist/tools/index.d.mts +7 -0
- package/dist/tools/index.mjs +18 -0
- package/dist/tools/index.mjs.map +1 -0
- package/dist/utils/paths.d.mts +72 -0
- package/dist/utils/paths.mjs +117 -0
- package/dist/utils/paths.mjs.map +1 -0
- package/dist/utils/uri-resolver.d.mts +8 -0
- package/dist/utils/uri-resolver.mjs +45 -0
- package/dist/utils/uri-resolver.mjs.map +1 -0
- package/dist/viewer/chevron-CBYYYF2L.svg +3 -0
- package/dist/viewer/copy-3LO2VTYZ.svg +4 -0
- package/dist/viewer/epic-OFTO5ZU3.svg +4 -0
- package/dist/viewer/external-link-5YHGZUVF.svg +3 -0
- package/dist/viewer/file-IS5HWDMR.svg +3 -0
- package/dist/viewer/index.html +46 -0
- package/dist/viewer/lightning-bolt-KZ4Y6ALR.svg +3 -0
- package/dist/viewer/logo.svg +14 -0
- package/dist/viewer/main.css +1728 -0
- package/dist/viewer/main.js +46 -12
- package/dist/viewer/pin-CTBSQJY3.svg +3 -0
- package/dist/viewer/task-QBLM24U4.svg +3 -0
- package/package.json +18 -15
- package/viewer/components/md-block.ts +305 -0
- package/viewer/components/resource-viewer.ts +227 -0
- package/viewer/components/svg-icon.ts +72 -0
- package/viewer/components/task-badge.ts +26 -0
- package/viewer/components/task-detail.ts +125 -0
- package/viewer/components/task-filter-bar.ts +70 -0
- package/viewer/components/task-item.ts +81 -0
- package/viewer/components/task-list.ts +143 -0
- package/viewer/icons/chevron.svg +3 -0
- package/viewer/icons/copy.svg +4 -0
- package/viewer/icons/epic.svg +4 -0
- package/viewer/icons/external-link.svg +3 -0
- package/viewer/icons/file.svg +3 -0
- package/viewer/icons/gradient-icons-showcase.html +280 -0
- package/viewer/icons/gradient-icons.ts +68 -0
- package/viewer/icons/hexagon.svg +3 -0
- package/viewer/icons/index.ts +12 -0
- package/viewer/icons/infinity.svg +3 -0
- package/viewer/icons/lightning-bolt-animated.svg +3 -0
- package/viewer/icons/lightning-bolt.svg +3 -0
- package/viewer/icons/pin.svg +3 -0
- package/viewer/icons/portal.svg +5 -0
- package/viewer/icons/prism.svg +3 -0
- package/viewer/icons/ring.svg +3 -0
- package/viewer/icons/sparkle.svg +3 -0
- package/viewer/icons/star.svg +3 -0
- package/viewer/icons/task.svg +3 -0
- package/viewer/icons/wave.svg +3 -0
- package/viewer/index.html +1 -2
- package/viewer/main.ts +85 -0
- package/viewer/styles.css +11 -1
- package/viewer/svg.d.ts +4 -0
- package/viewer/tsconfig.json +16 -0
- package/viewer/utils/api.ts +38 -0
- package/viewer/utils/layout.ts +17 -0
- package/viewer/utils/resize.ts +76 -0
- package/viewer/utils/split-pane.ts +208 -0
- package/viewer/utils/url-state.ts +55 -0
- package/dist/backlog.d.ts +0 -33
- package/dist/backlog.js +0 -144
- package/dist/backlog.js.map +0 -1
- package/dist/cli/bridge.d.ts +0 -2
- package/dist/cli/bridge.js +0 -170
- package/dist/cli/bridge.js.map +0 -1
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js.map +0 -1
- package/dist/http-server.d.ts +0 -2
- package/dist/http-server.js +0 -380
- package/dist/http-server.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -7
- package/dist/index.js.map +0 -1
- package/dist/resource-reader.d.ts +0 -6
- package/dist/resource-reader.js +0 -33
- package/dist/resource-reader.js.map +0 -1
- package/dist/resources/index.d.ts +0 -3
- package/dist/resources/index.js +0 -3
- package/dist/resources/index.js.map +0 -1
- package/dist/resources/operations.d.ts +0 -2
- package/dist/resources/operations.js +0 -35
- package/dist/resources/operations.js.map +0 -1
- package/dist/resources/types.d.ts +0 -29
- package/dist/resources/types.js +0 -3
- package/dist/resources/types.js.map +0 -1
- package/dist/resources/uri.d.ts +0 -7
- package/dist/resources/uri.js +0 -26
- package/dist/resources/uri.js.map +0 -1
- package/dist/resources/write.d.ts +0 -6
- package/dist/resources/write.js +0 -76
- package/dist/resources/write.js.map +0 -1
- package/dist/schema.d.ts +0 -34
- package/dist/schema.js +0 -46
- package/dist/schema.js.map +0 -1
- package/dist/uri-resolver.d.ts +0 -4
- package/dist/uri-resolver.js +0 -70
- package/dist/uri-resolver.js.map +0 -1
package/README.md
CHANGED
|
@@ -92,6 +92,21 @@ pnpm install && pnpm build
|
|
|
92
92
|
pnpm start
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
+
## Configuration
|
|
96
|
+
|
|
97
|
+
Environment variables (create `.env` file for local development):
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Copy example config
|
|
101
|
+
cp .env.example .env
|
|
102
|
+
|
|
103
|
+
# Edit with your values
|
|
104
|
+
BACKLOG_DATA_DIR=/Users/yourname/.backlog # Where to store tasks
|
|
105
|
+
BACKLOG_VIEWER_PORT=3030 # HTTP server port
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**For agents/LLMs:** Read the actual `.env` file to understand the configured values, not the defaults in code.
|
|
109
|
+
|
|
95
110
|
## Storage
|
|
96
111
|
|
|
97
112
|
- Default: `data/tasks/` and `data/archive/` (local to project)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { paths } from "../utils/paths.mjs";
|
|
3
|
+
import { ensureServer } from "./server-manager.mjs";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
|
|
7
|
+
//#region src/cli/bridge.ts
|
|
8
|
+
async function runBridge(port) {
|
|
9
|
+
await ensureServer(port);
|
|
10
|
+
const serverUrl = `http://localhost:${port}/mcp`;
|
|
11
|
+
const mcpRemotePath = paths.getBinPath("mcp-remote");
|
|
12
|
+
if (!existsSync(mcpRemotePath)) {
|
|
13
|
+
console.error("mcp-remote not found. Please run: pnpm install");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
spawn(mcpRemotePath, [
|
|
17
|
+
serverUrl,
|
|
18
|
+
"--allow-http",
|
|
19
|
+
"--transport",
|
|
20
|
+
"http-only"
|
|
21
|
+
], { stdio: "inherit" }).on("exit", (code) => process.exit(code || 0));
|
|
22
|
+
}
|
|
23
|
+
runBridge(parseInt(process.env.BACKLOG_VIEWER_PORT || "3030")).catch((error) => {
|
|
24
|
+
console.error("Bridge error:", error);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { };
|
|
30
|
+
//# sourceMappingURL=bridge.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.mjs","names":[],"sources":["../../src/cli/bridge.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { ensureServer } from './server-manager.js';\nimport { paths } from '@/utils/paths.js';\n\nasync function runBridge(port: number): Promise<void> {\n await ensureServer(port);\n \n const serverUrl = `http://localhost:${port}/mcp`;\n const mcpRemotePath = paths.getBinPath('mcp-remote');\n \n if (!existsSync(mcpRemotePath)) {\n console.error('mcp-remote not found. Please run: pnpm install');\n process.exit(1);\n }\n \n const bridge = spawn(mcpRemotePath, [serverUrl, '--allow-http', '--transport', 'http-only'], {\n stdio: 'inherit'\n });\n \n bridge.on('exit', (code) => process.exit(code || 0));\n}\n\nconst port = parseInt(process.env.BACKLOG_VIEWER_PORT || '3030');\nrunBridge(port).catch((error) => {\n console.error('Bridge error:', error);\n process.exit(1);\n});\n"],"mappings":";;;;;;;AAOA,eAAe,UAAU,MAA6B;AACpD,OAAM,aAAa,KAAK;CAExB,MAAM,YAAY,oBAAoB,KAAK;CAC3C,MAAM,gBAAgB,MAAM,WAAW,aAAa;AAEpD,KAAI,CAAC,WAAW,cAAc,EAAE;AAC9B,UAAQ,MAAM,iDAAiD;AAC/D,UAAQ,KAAK,EAAE;;AAOjB,CAJe,MAAM,eAAe;EAAC;EAAW;EAAgB;EAAe;EAAY,EAAE,EAC3F,OAAO,WACR,CAAC,CAEK,GAAG,SAAS,SAAS,QAAQ,KAAK,QAAQ,EAAE,CAAC;;AAItD,UADa,SAAS,QAAQ,IAAI,uBAAuB,OAAO,CACjD,CAAC,OAAO,UAAU;AAC/B,SAAQ,MAAM,iBAAiB,MAAM;AACrC,SAAQ,KAAK,EAAE;EACf"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// HTTP server mode
|
|
10
|
-
const { startHttpServer } = await import('./http-server.js');
|
|
11
|
-
const port = parseInt(process.env.BACKLOG_VIEWER_PORT || '3030');
|
|
12
|
-
await startHttpServer(port);
|
|
13
|
-
}
|
|
14
|
-
else if (command === '--help' || command === '-h') {
|
|
15
|
-
console.log(`
|
|
2
|
+
//#region src/cli/index.ts
|
|
3
|
+
const command = process.argv.slice(2)[0];
|
|
4
|
+
if (command === "serve") {
|
|
5
|
+
const { startHttpServer } = await import("../server/fastify-server.mjs");
|
|
6
|
+
await startHttpServer(parseInt(process.env.BACKLOG_VIEWER_PORT || "3030"));
|
|
7
|
+
} else if (command === "--help" || command === "-h") {
|
|
8
|
+
console.log(`
|
|
16
9
|
backlog-mcp - Task management MCP server
|
|
17
10
|
|
|
18
11
|
Usage:
|
|
@@ -29,11 +22,9 @@ How it works:
|
|
|
29
22
|
- HTTP server persists across sessions (shared by multiple clients)
|
|
30
23
|
- Automatic version upgrades on server restart
|
|
31
24
|
`);
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
export {};
|
|
39
|
-
//# sourceMappingURL=cli.js.map
|
|
25
|
+
process.exit(0);
|
|
26
|
+
} else await import("./bridge.mjs");
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { };
|
|
30
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nconst args = process.argv.slice(2);\nconst command = args[0];\n\nif (command === 'serve') {\n // HTTP server mode\n const { startHttpServer } = await import('../server/fastify-server.js');\n const port = parseInt(process.env.BACKLOG_VIEWER_PORT || '3030');\n await startHttpServer(port);\n} else if (command === '--help' || command === '-h') {\n console.log(`\nbacklog-mcp - Task management MCP server\n\nUsage:\n backlog-mcp Run as stdio MCP server (auto-bridges to HTTP server)\n backlog-mcp serve Run as HTTP MCP server with viewer\n backlog-mcp --help Show this help\n\nEnvironment variables:\n BACKLOG_DATA_DIR Data directory path (default: ./data)\n BACKLOG_VIEWER_PORT HTTP server port (default: 3030)\n\nHow it works:\n - Default mode auto-spawns HTTP server and bridges stdio to it\n - HTTP server persists across sessions (shared by multiple clients)\n - Automatic version upgrades on server restart\n `);\n process.exit(0);\n} else {\n // Default: bridge mode (auto-spawn HTTP server)\n await import('./bridge.js');\n}\n"],"mappings":";;AAGA,MAAM,UADO,QAAQ,KAAK,MAAM,EAAE,CACb;AAErB,IAAI,YAAY,SAAS;CAEvB,MAAM,EAAE,oBAAoB,MAAM,OAAO;AAEzC,OAAM,gBADO,SAAS,QAAQ,IAAI,uBAAuB,OAAO,CACrC;WAClB,YAAY,YAAY,YAAY,MAAM;AACnD,SAAQ,IAAI;;;;;;;;;;;;;;;;IAgBV;AACF,SAAQ,KAAK,EAAE;MAGf,OAAM,OAAO"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { paths } from "../utils/paths.mjs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { request } from "node:http";
|
|
5
|
+
|
|
6
|
+
//#region src/cli/server-manager.ts
|
|
7
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
|
+
async function isServerRunning(port) {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
const req = request({
|
|
11
|
+
host: "localhost",
|
|
12
|
+
port,
|
|
13
|
+
path: "/version",
|
|
14
|
+
method: "GET"
|
|
15
|
+
}, (res) => {
|
|
16
|
+
resolve(res.statusCode === 200);
|
|
17
|
+
});
|
|
18
|
+
req.on("error", () => resolve(false));
|
|
19
|
+
req.end();
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async function getServerVersion(port) {
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
const req = request({
|
|
25
|
+
host: "localhost",
|
|
26
|
+
port,
|
|
27
|
+
path: "/version",
|
|
28
|
+
method: "GET"
|
|
29
|
+
}, (res) => {
|
|
30
|
+
if (res.statusCode !== 200) {
|
|
31
|
+
resolve(null);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
let data = "";
|
|
35
|
+
res.on("data", (chunk) => data += chunk);
|
|
36
|
+
res.on("end", () => resolve(data.trim()));
|
|
37
|
+
});
|
|
38
|
+
req.on("error", () => resolve(null));
|
|
39
|
+
req.end();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async function spawnServer(port) {
|
|
43
|
+
const serverPath = join(paths.distRoot, "server", "fastify-server.js");
|
|
44
|
+
spawn(process.execPath, [serverPath], {
|
|
45
|
+
detached: true,
|
|
46
|
+
stdio: "ignore",
|
|
47
|
+
env: {
|
|
48
|
+
...process.env,
|
|
49
|
+
BACKLOG_VIEWER_PORT: String(port)
|
|
50
|
+
}
|
|
51
|
+
}).unref();
|
|
52
|
+
}
|
|
53
|
+
async function shutdownServer(port) {
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
const req = request({
|
|
56
|
+
host: "localhost",
|
|
57
|
+
port,
|
|
58
|
+
path: "/shutdown",
|
|
59
|
+
method: "POST"
|
|
60
|
+
}, () => {
|
|
61
|
+
resolve();
|
|
62
|
+
});
|
|
63
|
+
req.on("error", () => resolve());
|
|
64
|
+
req.end();
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async function waitForServer(port, timeout) {
|
|
68
|
+
const start = Date.now();
|
|
69
|
+
let delay = 100;
|
|
70
|
+
while (Date.now() - start < timeout) {
|
|
71
|
+
if (await isServerRunning(port)) return;
|
|
72
|
+
await sleep(delay);
|
|
73
|
+
delay = Math.min(delay * 1.5, 1e3);
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`Server failed to start within ${timeout}ms`);
|
|
76
|
+
}
|
|
77
|
+
async function ensureServer(port) {
|
|
78
|
+
if (!await isServerRunning(port)) {
|
|
79
|
+
await spawnServer(port);
|
|
80
|
+
await waitForServer(port, 1e4);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (await getServerVersion(port) !== paths.getVersion()) {
|
|
84
|
+
await shutdownServer(port);
|
|
85
|
+
await sleep(1e3);
|
|
86
|
+
await spawnServer(port);
|
|
87
|
+
await waitForServer(port, 1e4);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
//#endregion
|
|
92
|
+
export { ensureServer };
|
|
93
|
+
//# sourceMappingURL=server-manager.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-manager.mjs","names":[],"sources":["../../src/cli/server-manager.ts"],"sourcesContent":["import { request } from 'node:http';\nimport { spawn } from 'node:child_process';\nimport { join } from 'node:path';\nimport { paths } from '@/utils/paths.js';\n\nconst sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));\n\nasync function isServerRunning(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const req = request({ host: 'localhost', port, path: '/version', method: 'GET' }, (res) => {\n resolve(res.statusCode === 200);\n });\n req.on('error', () => resolve(false));\n req.end();\n });\n}\n\nasync function getServerVersion(port: number): Promise<string | null> {\n return new Promise((resolve) => {\n const req = request({ host: 'localhost', port, path: '/version', method: 'GET' }, (res) => {\n if (res.statusCode !== 200) {\n resolve(null);\n return;\n }\n let data = '';\n res.on('data', chunk => data += chunk);\n res.on('end', () => resolve(data.trim()));\n });\n req.on('error', () => resolve(null));\n req.end();\n });\n}\n\nasync function spawnServer(port: number): Promise<void> {\n const serverPath = join(paths.distRoot, 'server', 'fastify-server.js');\n const child = spawn(process.execPath, [serverPath], {\n detached: true,\n stdio: 'ignore',\n env: { ...process.env, BACKLOG_VIEWER_PORT: String(port) }\n });\n child.unref();\n}\n\nasync function shutdownServer(port: number): Promise<void> {\n return new Promise((resolve) => {\n const req = request({ host: 'localhost', port, path: '/shutdown', method: 'POST' }, () => {\n resolve();\n });\n req.on('error', () => resolve());\n req.end();\n });\n}\n\nasync function waitForServer(port: number, timeout: number): Promise<void> {\n const start = Date.now();\n let delay = 100;\n \n while (Date.now() - start < timeout) {\n if (await isServerRunning(port)) return;\n await sleep(delay);\n delay = Math.min(delay * 1.5, 1000);\n }\n \n throw new Error(`Server failed to start within ${timeout}ms`);\n}\n\nexport async function ensureServer(port: number): Promise<void> {\n const running = await isServerRunning(port);\n \n if (!running) {\n await spawnServer(port);\n await waitForServer(port, 10000);\n return;\n }\n \n const serverVersion = await getServerVersion(port);\n if (serverVersion !== paths.getVersion()) {\n await shutdownServer(port);\n await sleep(1000);\n await spawnServer(port);\n await waitForServer(port, 10000);\n }\n}\n"],"mappings":";;;;;;AAKA,MAAM,SAAS,OAAe,IAAI,SAAQ,YAAW,WAAW,SAAS,GAAG,CAAC;AAE7E,eAAe,gBAAgB,MAAgC;AAC7D,QAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,MAAM,QAAQ;GAAE,MAAM;GAAa;GAAM,MAAM;GAAY,QAAQ;GAAO,GAAG,QAAQ;AACzF,WAAQ,IAAI,eAAe,IAAI;IAC/B;AACF,MAAI,GAAG,eAAe,QAAQ,MAAM,CAAC;AACrC,MAAI,KAAK;GACT;;AAGJ,eAAe,iBAAiB,MAAsC;AACpE,QAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,MAAM,QAAQ;GAAE,MAAM;GAAa;GAAM,MAAM;GAAY,QAAQ;GAAO,GAAG,QAAQ;AACzF,OAAI,IAAI,eAAe,KAAK;AAC1B,YAAQ,KAAK;AACb;;GAEF,IAAI,OAAO;AACX,OAAI,GAAG,SAAQ,UAAS,QAAQ,MAAM;AACtC,OAAI,GAAG,aAAa,QAAQ,KAAK,MAAM,CAAC,CAAC;IACzC;AACF,MAAI,GAAG,eAAe,QAAQ,KAAK,CAAC;AACpC,MAAI,KAAK;GACT;;AAGJ,eAAe,YAAY,MAA6B;CACtD,MAAM,aAAa,KAAK,MAAM,UAAU,UAAU,oBAAoB;AAMtE,CALc,MAAM,QAAQ,UAAU,CAAC,WAAW,EAAE;EAClD,UAAU;EACV,OAAO;EACP,KAAK;GAAE,GAAG,QAAQ;GAAK,qBAAqB,OAAO,KAAK;GAAE;EAC3D,CAAC,CACI,OAAO;;AAGf,eAAe,eAAe,MAA6B;AACzD,QAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,MAAM,QAAQ;GAAE,MAAM;GAAa;GAAM,MAAM;GAAa,QAAQ;GAAQ,QAAQ;AACxF,YAAS;IACT;AACF,MAAI,GAAG,eAAe,SAAS,CAAC;AAChC,MAAI,KAAK;GACT;;AAGJ,eAAe,cAAc,MAAc,SAAgC;CACzE,MAAM,QAAQ,KAAK,KAAK;CACxB,IAAI,QAAQ;AAEZ,QAAO,KAAK,KAAK,GAAG,QAAQ,SAAS;AACnC,MAAI,MAAM,gBAAgB,KAAK,CAAE;AACjC,QAAM,MAAM,MAAM;AAClB,UAAQ,KAAK,IAAI,QAAQ,KAAK,IAAK;;AAGrC,OAAM,IAAI,MAAM,iCAAiC,QAAQ,IAAI;;AAG/D,eAAsB,aAAa,MAA6B;AAG9D,KAAI,CAFY,MAAM,gBAAgB,KAAK,EAE7B;AACZ,QAAM,YAAY,KAAK;AACvB,QAAM,cAAc,MAAM,IAAM;AAChC;;AAIF,KADsB,MAAM,iBAAiB,KAAK,KAC5B,MAAM,YAAY,EAAE;AACxC,QAAM,eAAe,KAAK;AAC1B,QAAM,MAAM,IAAK;AACjB,QAAM,YAAY,KAAK;AACvB,QAAM,cAAc,MAAM,IAAM"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { CreateTaskInput, STATUSES, Status, Task, createTask, formatTaskId, isValidTaskId, nextTaskId, parseTaskId } from "./storage/schema.mjs";
|
|
2
|
+
import { storage } from "./storage/backlog.mjs";
|
|
3
|
+
import { startHttpServer } from "./server/fastify-server.mjs";
|
|
4
|
+
export { type CreateTaskInput, STATUSES, type Status, type Task, createTask, formatTaskId, isValidTaskId, nextTaskId, parseTaskId, startHttpServer, storage };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { STATUSES, createTask, formatTaskId, isValidTaskId, nextTaskId, parseTaskId } from "./storage/schema.mjs";
|
|
2
|
+
import { storage } from "./storage/backlog.mjs";
|
|
3
|
+
import { startHttpServer } from "./server/fastify-server.mjs";
|
|
4
|
+
|
|
5
|
+
export { STATUSES, createTask, formatTaskId, isValidTaskId, nextTaskId, parseTaskId, startHttpServer, storage };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FastifyReply, FastifyRequest } from "fastify";
|
|
2
|
+
|
|
3
|
+
//#region src/middleware/auth.d.ts
|
|
4
|
+
declare function authMiddleware(request: FastifyRequest, reply: FastifyReply): Promise<undefined>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { authMiddleware };
|
|
7
|
+
//# sourceMappingURL=auth.d.mts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/middleware/auth.ts
|
|
2
|
+
async function authMiddleware(request, reply) {
|
|
3
|
+
if (request.url === "/health" || request.url === "/version") return;
|
|
4
|
+
if (request.url.startsWith("/mcp") && process.env.API_KEY) {
|
|
5
|
+
if (request.headers.authorization !== `Bearer ${process.env.API_KEY}`) return reply.code(401).send({ error: "Unauthorized" });
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
//#endregion
|
|
10
|
+
export { authMiddleware };
|
|
11
|
+
//# sourceMappingURL=auth.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.mjs","names":[],"sources":["../../src/middleware/auth.ts"],"sourcesContent":["import type { FastifyRequest, FastifyReply } from 'fastify';\n\nexport async function authMiddleware(\n request: FastifyRequest,\n reply: FastifyReply\n) {\n // Skip auth for health check and version\n if (request.url === '/health' || request.url === '/version') {\n return;\n }\n\n // API key auth for MCP endpoint (optional, for cloud deployment)\n if (request.url.startsWith('/mcp') && process.env.API_KEY) {\n const apiKey = request.headers.authorization;\n if (apiKey !== `Bearer ${process.env.API_KEY}`) {\n return reply.code(401).send({ error: 'Unauthorized' });\n }\n }\n}\n"],"mappings":";AAEA,eAAsB,eACpB,SACA,OACA;AAEA,KAAI,QAAQ,QAAQ,aAAa,QAAQ,QAAQ,WAC/C;AAIF,KAAI,QAAQ,IAAI,WAAW,OAAO,IAAI,QAAQ,IAAI,SAEhD;MADe,QAAQ,QAAQ,kBAChB,UAAU,QAAQ,IAAI,UACnC,QAAO,MAAM,KAAK,IAAI,CAAC,KAAK,EAAE,OAAO,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
|
|
3
|
+
//#region src/resources/index-resources.d.ts
|
|
4
|
+
declare function registerResources(server: McpServer): void;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { registerResources };
|
|
7
|
+
//# sourceMappingURL=index-resources.d.mts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { registerTasksResource } from "./tasks.mjs";
|
|
2
|
+
import { registerTaskByIdResource } from "./task-by-id.mjs";
|
|
3
|
+
import { registerTaskAttachedResource } from "./task-attached.mjs";
|
|
4
|
+
import { registerResourceFileResource } from "./resource-file.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/resources/index-resources.ts
|
|
7
|
+
function registerResources(server) {
|
|
8
|
+
registerTasksResource(server);
|
|
9
|
+
registerTaskByIdResource(server);
|
|
10
|
+
registerTaskAttachedResource(server);
|
|
11
|
+
registerResourceFileResource(server);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
export { registerResources };
|
|
16
|
+
//# sourceMappingURL=index-resources.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-resources.mjs","names":[],"sources":["../../src/resources/index-resources.ts"],"sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { registerTasksResource } from './tasks.js';\nimport { registerTaskByIdResource } from './task-by-id.js';\nimport { registerTaskAttachedResource } from './task-attached.js';\nimport { registerResourceFileResource } from './resource-file.js';\n\nexport function registerResources(server: McpServer) {\n registerTasksResource(server);\n registerTaskByIdResource(server);\n registerTaskAttachedResource(server);\n registerResourceFileResource(server);\n}\n"],"mappings":";;;;;;AAMA,SAAgB,kBAAkB,QAAmB;AACnD,uBAAsB,OAAO;AAC7B,0BAAyB,OAAO;AAChC,8BAA6B,OAAO;AACpC,8BAA6B,OAAO"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/resources/operations.ts
|
|
2
|
+
function applyOperation(content, operation) {
|
|
3
|
+
switch (operation.type) {
|
|
4
|
+
case "str_replace": {
|
|
5
|
+
const { old_str, new_str } = operation;
|
|
6
|
+
if (!content.includes(old_str)) throw new Error(`str_replace failed: old_str not found in content`);
|
|
7
|
+
return content.replace(old_str, new_str);
|
|
8
|
+
}
|
|
9
|
+
case "append": return content + operation.content;
|
|
10
|
+
case "prepend": return operation.content + content;
|
|
11
|
+
case "insert": {
|
|
12
|
+
const lines = content.split("\n");
|
|
13
|
+
if (operation.line < 0 || operation.line > lines.length) throw new Error(`insert failed: line ${operation.line} out of range (0-${lines.length})`);
|
|
14
|
+
lines.splice(operation.line, 0, operation.content);
|
|
15
|
+
return lines.join("\n");
|
|
16
|
+
}
|
|
17
|
+
case "delete":
|
|
18
|
+
if (!content.includes(operation.content)) throw new Error(`delete failed: content not found`);
|
|
19
|
+
return content.replace(operation.content, "");
|
|
20
|
+
default: throw new Error(`Unknown operation type: ${operation.type}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
export { applyOperation };
|
|
26
|
+
//# sourceMappingURL=operations.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.mjs","names":[],"sources":["../../src/resources/operations.ts"],"sourcesContent":["// Apply operations to text content\n\nimport type { Operation } from './types.js';\n\nexport function applyOperation(content: string, operation: Operation): string {\n switch (operation.type) {\n case 'str_replace': {\n const { old_str, new_str } = operation;\n if (!content.includes(old_str)) {\n throw new Error(`str_replace failed: old_str not found in content`);\n }\n return content.replace(old_str, new_str);\n }\n\n case 'append': {\n return content + operation.content;\n }\n\n case 'prepend': {\n return operation.content + content;\n }\n\n case 'insert': {\n const lines = content.split('\\n');\n if (operation.line < 0 || operation.line > lines.length) {\n throw new Error(`insert failed: line ${operation.line} out of range (0-${lines.length})`);\n }\n lines.splice(operation.line, 0, operation.content);\n return lines.join('\\n');\n }\n\n case 'delete': {\n if (!content.includes(operation.content)) {\n throw new Error(`delete failed: content not found`);\n }\n return content.replace(operation.content, '');\n }\n\n default:\n throw new Error(`Unknown operation type: ${(operation as any).type}`);\n }\n}\n"],"mappings":";AAIA,SAAgB,eAAe,SAAiB,WAA8B;AAC5E,SAAQ,UAAU,MAAlB;EACE,KAAK,eAAe;GAClB,MAAM,EAAE,SAAS,YAAY;AAC7B,OAAI,CAAC,QAAQ,SAAS,QAAQ,CAC5B,OAAM,IAAI,MAAM,mDAAmD;AAErE,UAAO,QAAQ,QAAQ,SAAS,QAAQ;;EAG1C,KAAK,SACH,QAAO,UAAU,UAAU;EAG7B,KAAK,UACH,QAAO,UAAU,UAAU;EAG7B,KAAK,UAAU;GACb,MAAM,QAAQ,QAAQ,MAAM,KAAK;AACjC,OAAI,UAAU,OAAO,KAAK,UAAU,OAAO,MAAM,OAC/C,OAAM,IAAI,MAAM,uBAAuB,UAAU,KAAK,mBAAmB,MAAM,OAAO,GAAG;AAE3F,SAAM,OAAO,UAAU,MAAM,GAAG,UAAU,QAAQ;AAClD,UAAO,MAAM,KAAK,KAAK;;EAGzB,KAAK;AACH,OAAI,CAAC,QAAQ,SAAS,UAAU,QAAQ,CACtC,OAAM,IAAI,MAAM,mCAAmC;AAErD,UAAO,QAAQ,QAAQ,UAAU,SAAS,GAAG;EAG/C,QACE,OAAM,IAAI,MAAM,2BAA4B,UAAkB,OAAO"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
|
|
3
|
+
//#region src/resources/resource-file.d.ts
|
|
4
|
+
declare function registerResourceFileResource(server: McpServer): void;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { registerResourceFileResource };
|
|
7
|
+
//# sourceMappingURL=resource-file.d.mts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { readMcpResource } from "./resource-reader.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/resources/resource-file.ts
|
|
4
|
+
function registerResourceFileResource(server) {
|
|
5
|
+
server.registerResource("Resource File", "mcp://backlog/resources/{path}", {
|
|
6
|
+
description: "Read a resource file",
|
|
7
|
+
mimeType: "text/plain"
|
|
8
|
+
}, async (uri) => {
|
|
9
|
+
const resource = await readMcpResource(uri.toString());
|
|
10
|
+
return { contents: [{
|
|
11
|
+
uri: uri.toString(),
|
|
12
|
+
mimeType: resource.mimeType,
|
|
13
|
+
text: resource.content
|
|
14
|
+
}] };
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { registerResourceFileResource };
|
|
20
|
+
//# sourceMappingURL=resource-file.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-file.mjs","names":[],"sources":["../../src/resources/resource-file.ts"],"sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { readMcpResource } from './resource-reader.js';\n\nexport function registerResourceFileResource(server: McpServer) {\n server.registerResource(\n 'Resource File',\n 'mcp://backlog/resources/{path}',\n { description: 'Read a resource file', mimeType: 'text/plain' },\n async (uri: URL) => {\n const resource = await readMcpResource(uri.toString());\n return { contents: [{ uri: uri.toString(), mimeType: resource.mimeType, text: resource.content }] };\n }\n );\n}\n"],"mappings":";;;AAGA,SAAgB,6BAA6B,QAAmB;AAC9D,QAAO,iBACL,iBACA,kCACA;EAAE,aAAa;EAAwB,UAAU;EAAc,EAC/D,OAAO,QAAa;EAClB,MAAM,WAAW,MAAM,gBAAgB,IAAI,UAAU,CAAC;AACtD,SAAO,EAAE,UAAU,CAAC;GAAE,KAAK,IAAI,UAAU;GAAE,UAAU,SAAS;GAAU,MAAM,SAAS;GAAS,CAAC,EAAE;GAEtG"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/resources/resource-reader.d.ts
|
|
2
|
+
interface ResourceContent {
|
|
3
|
+
content: string;
|
|
4
|
+
frontmatter?: Record<string, any>;
|
|
5
|
+
mimeType: string;
|
|
6
|
+
}
|
|
7
|
+
declare function readMcpResource(uri: string): ResourceContent;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { ResourceContent, readMcpResource };
|
|
10
|
+
//# sourceMappingURL=resource-reader.d.mts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { resolveMcpUri } from "../utils/uri-resolver.mjs";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import matter from "gray-matter";
|
|
4
|
+
|
|
5
|
+
//#region src/resources/resource-reader.ts
|
|
6
|
+
function readMcpResource(uri) {
|
|
7
|
+
const filePath = resolveMcpUri(uri);
|
|
8
|
+
if (!existsSync(filePath)) throw new Error(`Resource not found: ${uri}`);
|
|
9
|
+
const content = readFileSync(filePath, "utf-8");
|
|
10
|
+
const ext = filePath.split(".").pop()?.toLowerCase() || "txt";
|
|
11
|
+
const mimeType = {
|
|
12
|
+
md: "text/markdown",
|
|
13
|
+
json: "application/json",
|
|
14
|
+
ts: "text/typescript",
|
|
15
|
+
js: "application/javascript",
|
|
16
|
+
txt: "text/plain"
|
|
17
|
+
}[ext] || "text/plain";
|
|
18
|
+
if (ext === "md") {
|
|
19
|
+
const parsed = matter(content);
|
|
20
|
+
return {
|
|
21
|
+
content: parsed.content,
|
|
22
|
+
frontmatter: parsed.data,
|
|
23
|
+
mimeType
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
content,
|
|
28
|
+
mimeType
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
export { readMcpResource };
|
|
34
|
+
//# sourceMappingURL=resource-reader.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-reader.mjs","names":[],"sources":["../../src/resources/resource-reader.ts"],"sourcesContent":["import { readFileSync, existsSync } from 'node:fs';\nimport matter from 'gray-matter';\nimport { resolveMcpUri } from '../utils/uri-resolver.js';\n\nexport interface ResourceContent {\n content: string;\n frontmatter?: Record<string, any>;\n mimeType: string;\n}\n\nexport function readMcpResource(uri: string): ResourceContent {\n const filePath = resolveMcpUri(uri);\n \n if (!existsSync(filePath)) {\n throw new Error(`Resource not found: ${uri}`);\n }\n \n const content = readFileSync(filePath, 'utf-8');\n const ext = filePath.split('.').pop()?.toLowerCase() || 'txt';\n \n const mimeMap: Record<string, string> = {\n md: 'text/markdown',\n json: 'application/json',\n ts: 'text/typescript',\n js: 'application/javascript',\n txt: 'text/plain',\n };\n \n const mimeType = mimeMap[ext] || 'text/plain';\n \n // Parse frontmatter for markdown files\n if (ext === 'md') {\n const parsed = matter(content);\n return {\n content: parsed.content,\n frontmatter: parsed.data,\n mimeType,\n };\n }\n \n return {\n content,\n mimeType,\n };\n}\n"],"mappings":";;;;;AAUA,SAAgB,gBAAgB,KAA8B;CAC5D,MAAM,WAAW,cAAc,IAAI;AAEnC,KAAI,CAAC,WAAW,SAAS,CACvB,OAAM,IAAI,MAAM,uBAAuB,MAAM;CAG/C,MAAM,UAAU,aAAa,UAAU,QAAQ;CAC/C,MAAM,MAAM,SAAS,MAAM,IAAI,CAAC,KAAK,EAAE,aAAa,IAAI;CAUxD,MAAM,WARkC;EACtC,IAAI;EACJ,MAAM;EACN,IAAI;EACJ,IAAI;EACJ,KAAK;EACN,CAEwB,QAAQ;AAGjC,KAAI,QAAQ,MAAM;EAChB,MAAM,SAAS,OAAO,QAAQ;AAC9B,SAAO;GACL,SAAS,OAAO;GAChB,aAAa,OAAO;GACpB;GACD;;AAGH,QAAO;EACL;EACA;EACD"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
|
|
3
|
+
//#region src/resources/task-attached.d.ts
|
|
4
|
+
declare function registerTaskAttachedResource(server: McpServer): void;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { registerTaskAttachedResource };
|
|
7
|
+
//# sourceMappingURL=task-attached.d.mts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { readMcpResource } from "./resource-reader.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/resources/task-attached.ts
|
|
4
|
+
function registerTaskAttachedResource(server) {
|
|
5
|
+
server.registerResource("Task-Attached Resource", "mcp://backlog/resources/{taskId}/{filename}", { description: "Task-attached resources (ADRs, design docs, etc.)" }, async (uri) => {
|
|
6
|
+
const { content, mimeType } = await readMcpResource(uri.toString());
|
|
7
|
+
return { contents: [{
|
|
8
|
+
uri: uri.toString(),
|
|
9
|
+
mimeType,
|
|
10
|
+
text: content
|
|
11
|
+
}] };
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { registerTaskAttachedResource };
|
|
17
|
+
//# sourceMappingURL=task-attached.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-attached.mjs","names":[],"sources":["../../src/resources/task-attached.ts"],"sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { readMcpResource } from './resource-reader.js';\n\nexport function registerTaskAttachedResource(server: McpServer) {\n server.registerResource(\n 'Task-Attached Resource',\n 'mcp://backlog/resources/{taskId}/{filename}',\n { description: 'Task-attached resources (ADRs, design docs, etc.)' },\n async (uri: URL) => {\n const { content, mimeType } = await readMcpResource(uri.toString());\n return { contents: [{ uri: uri.toString(), mimeType, text: content }] };\n }\n );\n}\n"],"mappings":";;;AAGA,SAAgB,6BAA6B,QAAmB;AAC9D,QAAO,iBACL,0BACA,+CACA,EAAE,aAAa,qDAAqD,EACpE,OAAO,QAAa;EAClB,MAAM,EAAE,SAAS,aAAa,MAAM,gBAAgB,IAAI,UAAU,CAAC;AACnE,SAAO,EAAE,UAAU,CAAC;GAAE,KAAK,IAAI,UAAU;GAAE;GAAU,MAAM;GAAS,CAAC,EAAE;GAE1E"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
|
|
3
|
+
//#region src/resources/task-by-id.d.ts
|
|
4
|
+
declare function registerTaskByIdResource(server: McpServer): void;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { registerTaskByIdResource };
|
|
7
|
+
//# sourceMappingURL=task-by-id.d.mts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { storage } from "../storage/backlog.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/resources/task-by-id.ts
|
|
4
|
+
function registerTaskByIdResource(server) {
|
|
5
|
+
server.registerResource("Task by ID", "mcp://backlog/tasks/{taskId}/file", {
|
|
6
|
+
description: "Get a specific task",
|
|
7
|
+
mimeType: "text/markdown"
|
|
8
|
+
}, async (uri) => {
|
|
9
|
+
const match = uri.toString().match(/mcp:\/\/backlog\/tasks\/([^/]+)\/file/);
|
|
10
|
+
if (!match || !match[1]) throw new Error("Invalid URI");
|
|
11
|
+
const markdown = storage.getMarkdown(match[1]);
|
|
12
|
+
if (!markdown) throw new Error("Task not found");
|
|
13
|
+
return { contents: [{
|
|
14
|
+
uri: uri.toString(),
|
|
15
|
+
mimeType: "text/markdown",
|
|
16
|
+
text: markdown
|
|
17
|
+
}] };
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
export { registerTaskByIdResource };
|
|
23
|
+
//# sourceMappingURL=task-by-id.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-by-id.mjs","names":[],"sources":["../../src/resources/task-by-id.ts"],"sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { storage } from '../storage/backlog.js';\n\nexport function registerTaskByIdResource(server: McpServer) {\n server.registerResource(\n 'Task by ID',\n 'mcp://backlog/tasks/{taskId}/file',\n { description: 'Get a specific task', mimeType: 'text/markdown' },\n async (uri: URL) => {\n const match = uri.toString().match(/mcp:\\/\\/backlog\\/tasks\\/([^/]+)\\/file/);\n if (!match || !match[1]) throw new Error('Invalid URI');\n const markdown = storage.getMarkdown(match[1]);\n if (!markdown) throw new Error('Task not found');\n return { contents: [{ uri: uri.toString(), mimeType: 'text/markdown', text: markdown }] };\n }\n );\n}\n"],"mappings":";;;AAGA,SAAgB,yBAAyB,QAAmB;AAC1D,QAAO,iBACL,cACA,qCACA;EAAE,aAAa;EAAuB,UAAU;EAAiB,EACjE,OAAO,QAAa;EAClB,MAAM,QAAQ,IAAI,UAAU,CAAC,MAAM,wCAAwC;AAC3E,MAAI,CAAC,SAAS,CAAC,MAAM,GAAI,OAAM,IAAI,MAAM,cAAc;EACvD,MAAM,WAAW,QAAQ,YAAY,MAAM,GAAG;AAC9C,MAAI,CAAC,SAAU,OAAM,IAAI,MAAM,iBAAiB;AAChD,SAAO,EAAE,UAAU,CAAC;GAAE,KAAK,IAAI,UAAU;GAAE,UAAU;GAAiB,MAAM;GAAU,CAAC,EAAE;GAE5F"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { storage } from "../storage/backlog.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/resources/tasks.ts
|
|
4
|
+
function registerTasksResource(server) {
|
|
5
|
+
server.registerResource("All Tasks", "mcp://backlog/tasks", {
|
|
6
|
+
description: "List of all tasks",
|
|
7
|
+
mimeType: "application/json"
|
|
8
|
+
}, async () => {
|
|
9
|
+
const tasks = storage.list({});
|
|
10
|
+
return { contents: [{
|
|
11
|
+
uri: "mcp://backlog/tasks",
|
|
12
|
+
mimeType: "application/json",
|
|
13
|
+
text: JSON.stringify(tasks, null, 2)
|
|
14
|
+
}] };
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { registerTasksResource };
|
|
20
|
+
//# sourceMappingURL=tasks.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasks.mjs","names":[],"sources":["../../src/resources/tasks.ts"],"sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { storage } from '../storage/backlog.js';\n\nexport function registerTasksResource(server: McpServer) {\n server.registerResource(\n 'All Tasks',\n 'mcp://backlog/tasks',\n { description: 'List of all tasks', mimeType: 'application/json' },\n async () => {\n const tasks = storage.list({});\n return { contents: [{ uri: 'mcp://backlog/tasks', mimeType: 'application/json', text: JSON.stringify(tasks, null, 2) }] };\n }\n );\n}\n"],"mappings":";;;AAGA,SAAgB,sBAAsB,QAAmB;AACvD,QAAO,iBACL,aACA,uBACA;EAAE,aAAa;EAAqB,UAAU;EAAoB,EAClE,YAAY;EACV,MAAM,QAAQ,QAAQ,KAAK,EAAE,CAAC;AAC9B,SAAO,EAAE,UAAU,CAAC;GAAE,KAAK;GAAuB,UAAU;GAAoB,MAAM,KAAK,UAAU,OAAO,MAAM,EAAE;GAAE,CAAC,EAAE;GAE5H"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//#region src/resources/types.d.ts
|
|
2
|
+
type OperationType = 'str_replace' | 'append' | 'prepend' | 'insert' | 'delete';
|
|
3
|
+
interface StrReplaceOperation {
|
|
4
|
+
type: 'str_replace';
|
|
5
|
+
old_str: string;
|
|
6
|
+
new_str: string;
|
|
7
|
+
}
|
|
8
|
+
interface AppendOperation {
|
|
9
|
+
type: 'append';
|
|
10
|
+
content: string;
|
|
11
|
+
}
|
|
12
|
+
interface PrependOperation {
|
|
13
|
+
type: 'prepend';
|
|
14
|
+
content: string;
|
|
15
|
+
}
|
|
16
|
+
interface InsertOperation {
|
|
17
|
+
type: 'insert';
|
|
18
|
+
line: number;
|
|
19
|
+
content: string;
|
|
20
|
+
}
|
|
21
|
+
interface DeleteOperation {
|
|
22
|
+
type: 'delete';
|
|
23
|
+
content: string;
|
|
24
|
+
}
|
|
25
|
+
type Operation = StrReplaceOperation | AppendOperation | PrependOperation | InsertOperation | DeleteOperation;
|
|
26
|
+
interface WriteResourceResult {
|
|
27
|
+
success: boolean;
|
|
28
|
+
message: string;
|
|
29
|
+
error?: string;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { AppendOperation, DeleteOperation, InsertOperation, Operation, OperationType, PrependOperation, StrReplaceOperation, WriteResourceResult };
|
|
33
|
+
//# sourceMappingURL=types.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/resources/uri.d.ts
|
|
2
|
+
interface ParsedURI {
|
|
3
|
+
server: string;
|
|
4
|
+
resource: string;
|
|
5
|
+
taskId?: string;
|
|
6
|
+
field?: string;
|
|
7
|
+
}
|
|
8
|
+
declare function parseURI(uri: string): ParsedURI | null;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { ParsedURI, parseURI };
|
|
11
|
+
//# sourceMappingURL=uri.d.mts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/resources/uri.ts
|
|
2
|
+
function parseURI(uri) {
|
|
3
|
+
const match = uri.match(/^mcp:\/\/([^\/]+)\/(.+)$/);
|
|
4
|
+
if (!match) return null;
|
|
5
|
+
const [, server, resource] = match;
|
|
6
|
+
if (!server || !resource) return null;
|
|
7
|
+
const taskMatch = resource.match(/^tasks\/([^\/]+)(?:\/(description|file))?$/);
|
|
8
|
+
if (taskMatch) return {
|
|
9
|
+
server,
|
|
10
|
+
resource,
|
|
11
|
+
taskId: taskMatch[1],
|
|
12
|
+
field: taskMatch[2] || "file"
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
server,
|
|
16
|
+
resource
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
export { parseURI };
|
|
22
|
+
//# sourceMappingURL=uri.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uri.mjs","names":[],"sources":["../../src/resources/uri.ts"],"sourcesContent":["// URI parser for mcp:// scheme\n\nexport interface ParsedURI {\n server: string;\n resource: string;\n taskId?: string;\n field?: string;\n}\n\nexport function parseURI(uri: string): ParsedURI | null {\n // Expected format: mcp://backlog/{resource}\n const match = uri.match(/^mcp:\\/\\/([^\\/]+)\\/(.+)$/);\n if (!match) return null;\n\n const [, server, resource] = match;\n \n if (!server || !resource) return null;\n \n // Check if it's a task field edit: tasks/{id}/description or tasks/{id}/file\n const taskMatch = resource.match(/^tasks\\/([^\\/]+)(?:\\/(description|file))?$/);\n if (taskMatch) {\n return {\n server,\n resource,\n taskId: taskMatch[1],\n field: taskMatch[2] || 'file',\n };\n }\n\n // General resource (artifacts, resources, etc.)\n return {\n server,\n resource,\n };\n}\n"],"mappings":";AASA,SAAgB,SAAS,KAA+B;CAEtD,MAAM,QAAQ,IAAI,MAAM,2BAA2B;AACnD,KAAI,CAAC,MAAO,QAAO;CAEnB,MAAM,GAAG,QAAQ,YAAY;AAE7B,KAAI,CAAC,UAAU,CAAC,SAAU,QAAO;CAGjC,MAAM,YAAY,SAAS,MAAM,6CAA6C;AAC9E,KAAI,UACF,QAAO;EACL;EACA;EACA,QAAQ,UAAU;EAClB,OAAO,UAAU,MAAM;EACxB;AAIH,QAAO;EACL;EACA;EACD"}
|