backlog-mcp 0.23.0 → 0.24.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
CHANGED
|
@@ -6,10 +6,10 @@ Minimal task backlog MCP server. Works with any LLM agent or CLI editor that sup
|
|
|
6
6
|
|
|
7
7
|
## Web Viewer
|
|
8
8
|
|
|
9
|
-
Start the server and open `http://localhost:3030` for a visual task browser.
|
|
9
|
+
Start the server and open `http://localhost:3030` (production) or `http://localhost:3040` (dev) for a visual task browser.
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
pnpm dev # Starts MCP server + web viewer with hot reload
|
|
12
|
+
pnpm dev # Starts MCP server + web viewer with hot reload (port 3040)
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Features:
|
|
@@ -21,12 +21,14 @@ Features:
|
|
|
21
21
|
|
|
22
22
|
## Task Schema
|
|
23
23
|
|
|
24
|
-
Tasks are stored as individual markdown files with YAML frontmatter:
|
|
24
|
+
Tasks and epics are stored as individual markdown files with YAML frontmatter:
|
|
25
25
|
|
|
26
26
|
```markdown
|
|
27
27
|
---
|
|
28
28
|
id: TASK-0001
|
|
29
29
|
title: Fix bug in authentication
|
|
30
|
+
type: task
|
|
31
|
+
epic_id: EPIC-0002
|
|
30
32
|
status: open
|
|
31
33
|
created_at: '2024-01-10T10:00:00Z'
|
|
32
34
|
updated_at: '2024-01-10T10:00:00Z'
|
|
@@ -34,6 +36,9 @@ blocked_reason: Waiting for API access
|
|
|
34
36
|
evidence:
|
|
35
37
|
- Fixed in CR-12345
|
|
36
38
|
- Validated in beta
|
|
39
|
+
references:
|
|
40
|
+
- url: https://github.com/org/repo/issues/123
|
|
41
|
+
title: Related GitHub issue
|
|
37
42
|
---
|
|
38
43
|
|
|
39
44
|
## Description
|
|
@@ -45,13 +50,19 @@ The authentication flow has an issue where...
|
|
|
45
50
|
This came from Slack thread: https://...
|
|
46
51
|
```
|
|
47
52
|
|
|
53
|
+
**ID format:** `TASK-0001` for tasks, `EPIC-0001` for epics
|
|
54
|
+
**Type values:** `task` (default), `epic`
|
|
48
55
|
**Status values:** `open`, `in_progress`, `blocked`, `done`, `cancelled`
|
|
49
56
|
|
|
50
57
|
## MCP Tools
|
|
51
58
|
|
|
59
|
+
### Tasks & Epics
|
|
60
|
+
|
|
52
61
|
```
|
|
53
62
|
backlog_list # List active tasks (open, in_progress, blocked)
|
|
54
63
|
backlog_list status=["done"] # Show completed tasks
|
|
64
|
+
backlog_list type="epic" # List only epics
|
|
65
|
+
backlog_list epic_id="EPIC-0002" # Tasks in specific epic
|
|
55
66
|
backlog_list counts=true # Get counts by status
|
|
56
67
|
backlog_list limit=10 # Limit results
|
|
57
68
|
|
|
@@ -59,7 +70,8 @@ backlog_get id="TASK-0001" # Get single task
|
|
|
59
70
|
backlog_get id=["TASK-0001","TASK-0002"] # Batch get multiple tasks
|
|
60
71
|
|
|
61
72
|
backlog_create title="Fix bug" # Create task
|
|
62
|
-
backlog_create title="Fix bug" description="Details..."
|
|
73
|
+
backlog_create title="Fix bug" description="Details..." epic_id="EPIC-0002"
|
|
74
|
+
backlog_create title="Q1 Goals" type="epic" # Create epic
|
|
63
75
|
|
|
64
76
|
backlog_update id="TASK-0001" status="done" # Mark done
|
|
65
77
|
backlog_update id="TASK-0001" status="blocked" blocked_reason="Waiting on API"
|
|
@@ -68,6 +80,18 @@ backlog_update id="TASK-0001" evidence=["Fixed in CR-12345"] # Add completion
|
|
|
68
80
|
backlog_delete id="TASK-0001" # Permanently delete
|
|
69
81
|
```
|
|
70
82
|
|
|
83
|
+
### Resources (MCP Resources Protocol)
|
|
84
|
+
|
|
85
|
+
Access tasks and resources via MCP resource URIs:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
mcp://backlog/tasks # All tasks (JSON)
|
|
89
|
+
mcp://backlog/tasks/TASK-0001 # Single task (JSON)
|
|
90
|
+
mcp://backlog/resources/TASK-0001/adr.md # Task-attached resource
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Create/modify resources via `resource://` write operations.
|
|
94
|
+
|
|
71
95
|
## Installation
|
|
72
96
|
|
|
73
97
|
Add to your MCP config (`.mcp.json` or your MCP client config):
|
|
@@ -92,6 +116,24 @@ pnpm install && pnpm build
|
|
|
92
116
|
pnpm start
|
|
93
117
|
```
|
|
94
118
|
|
|
119
|
+
## CLI Commands
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
backlog-mcp # Run as stdio MCP server (default)
|
|
123
|
+
backlog-mcp serve # Run HTTP server with viewer
|
|
124
|
+
backlog-mcp version # Show version
|
|
125
|
+
backlog-mcp status # Check if server is running
|
|
126
|
+
backlog-mcp stop # Stop the server
|
|
127
|
+
backlog-mcp --help # Show help
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Troubleshooting:**
|
|
131
|
+
```bash
|
|
132
|
+
npx backlog-mcp status # Check if server is running
|
|
133
|
+
npx backlog-mcp stop # Stop misbehaving server
|
|
134
|
+
npx backlog-mcp version # Check installed version
|
|
135
|
+
```
|
|
136
|
+
|
|
95
137
|
## Configuration
|
|
96
138
|
|
|
97
139
|
Environment variables (create `.env` file for local development):
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,9 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { paths } from "../utils/paths.mjs";
|
|
3
|
+
import { getServerVersion, isServerRunning, shutdownServer } from "./server-manager.mjs";
|
|
4
|
+
|
|
2
5
|
//#region src/cli/index.ts
|
|
3
6
|
const command = process.argv.slice(2)[0];
|
|
4
7
|
if (command === "serve") {
|
|
5
8
|
const { startHttpServer } = await import("../server/fastify-server.mjs");
|
|
6
9
|
await startHttpServer(parseInt(process.env.BACKLOG_VIEWER_PORT || "3030"));
|
|
10
|
+
} else if (command === "version") {
|
|
11
|
+
console.log(paths.getVersion());
|
|
12
|
+
process.exit(0);
|
|
13
|
+
} else if (command === "status") {
|
|
14
|
+
const port = parseInt(process.env.BACKLOG_VIEWER_PORT || "3030");
|
|
15
|
+
if (!await isServerRunning(port)) {
|
|
16
|
+
console.log("Server is not running");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const version = await getServerVersion(port);
|
|
20
|
+
console.log(`Server is running on port ${port}`);
|
|
21
|
+
console.log(`Version: ${version || "unknown"}`);
|
|
22
|
+
console.log(`Viewer: http://localhost:${port}/`);
|
|
23
|
+
console.log(`MCP endpoint: http://localhost:${port}/mcp`);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
} else if (command === "stop") {
|
|
26
|
+
const port = parseInt(process.env.BACKLOG_VIEWER_PORT || "3030");
|
|
27
|
+
if (!await isServerRunning(port)) {
|
|
28
|
+
console.log("Server is not running");
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
console.log(`Stopping server on port ${port}...`);
|
|
32
|
+
await shutdownServer(port);
|
|
33
|
+
console.log("Server stopped");
|
|
34
|
+
process.exit(0);
|
|
7
35
|
} else if (command === "--help" || command === "-h") {
|
|
8
36
|
console.log(`
|
|
9
37
|
backlog-mcp - Task management MCP server
|
|
@@ -11,6 +39,9 @@ backlog-mcp - Task management MCP server
|
|
|
11
39
|
Usage:
|
|
12
40
|
backlog-mcp Run as stdio MCP server (auto-bridges to HTTP server)
|
|
13
41
|
backlog-mcp serve Run as HTTP MCP server with viewer
|
|
42
|
+
backlog-mcp version Show version
|
|
43
|
+
backlog-mcp status Check if server is running
|
|
44
|
+
backlog-mcp stop Stop the server
|
|
14
45
|
backlog-mcp --help Show this help
|
|
15
46
|
|
|
16
47
|
Environment variables:
|
package/dist/cli/index.mjs.map
CHANGED
|
@@ -1 +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":"
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { paths } from '@/utils/paths.js';\nimport { isServerRunning, getServerVersion, shutdownServer } from './server-manager.js';\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 === 'version') {\n // Show version\n console.log(paths.getVersion());\n process.exit(0);\n} else if (command === 'status') {\n // Check server status\n const port = parseInt(process.env.BACKLOG_VIEWER_PORT || '3030');\n const running = await isServerRunning(port);\n \n if (!running) {\n console.log('Server is not running');\n process.exit(1);\n }\n \n const version = await getServerVersion(port);\n console.log(`Server is running on port ${port}`);\n console.log(`Version: ${version || 'unknown'}`);\n console.log(`Viewer: http://localhost:${port}/`);\n console.log(`MCP endpoint: http://localhost:${port}/mcp`);\n process.exit(0);\n} else if (command === 'stop') {\n // Stop server\n const port = parseInt(process.env.BACKLOG_VIEWER_PORT || '3030');\n const running = await isServerRunning(port);\n \n if (!running) {\n console.log('Server is not running');\n process.exit(0);\n }\n \n console.log(`Stopping server on port ${port}...`);\n await shutdownServer(port);\n console.log('Server stopped');\n process.exit(0);\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 version Show version\n backlog-mcp status Check if server is running\n backlog-mcp stop Stop the server\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":";;;;;AAMA,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,WAAW;AAEhC,SAAQ,IAAI,MAAM,YAAY,CAAC;AAC/B,SAAQ,KAAK,EAAE;WACN,YAAY,UAAU;CAE/B,MAAM,OAAO,SAAS,QAAQ,IAAI,uBAAuB,OAAO;AAGhE,KAAI,CAFY,MAAM,gBAAgB,KAAK,EAE7B;AACZ,UAAQ,IAAI,wBAAwB;AACpC,UAAQ,KAAK,EAAE;;CAGjB,MAAM,UAAU,MAAM,iBAAiB,KAAK;AAC5C,SAAQ,IAAI,6BAA6B,OAAO;AAChD,SAAQ,IAAI,YAAY,WAAW,YAAY;AAC/C,SAAQ,IAAI,4BAA4B,KAAK,GAAG;AAChD,SAAQ,IAAI,kCAAkC,KAAK,MAAM;AACzD,SAAQ,KAAK,EAAE;WACN,YAAY,QAAQ;CAE7B,MAAM,OAAO,SAAS,QAAQ,IAAI,uBAAuB,OAAO;AAGhE,KAAI,CAFY,MAAM,gBAAgB,KAAK,EAE7B;AACZ,UAAQ,IAAI,wBAAwB;AACpC,UAAQ,KAAK,EAAE;;AAGjB,SAAQ,IAAI,2BAA2B,KAAK,KAAK;AACjD,OAAM,eAAe,KAAK;AAC1B,SAAQ,IAAI,iBAAiB;AAC7B,SAAQ,KAAK,EAAE;WACN,YAAY,YAAY,YAAY,MAAM;AACnD,SAAQ,IAAI;;;;;;;;;;;;;;;;;;;IAmBV;AACF,SAAQ,KAAK,EAAE;MAGf,OAAM,OAAO"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
//#region src/cli/server-manager.d.ts
|
|
2
|
+
declare function isServerRunning(port: number): Promise<boolean>;
|
|
3
|
+
declare function getServerVersion(port: number): Promise<string | null>;
|
|
4
|
+
declare function shutdownServer(port: number): Promise<void>;
|
|
2
5
|
declare function ensureServer(port: number): Promise<void>;
|
|
3
6
|
//#endregion
|
|
4
|
-
export { ensureServer };
|
|
7
|
+
export { ensureServer, getServerVersion, isServerRunning, shutdownServer };
|
|
5
8
|
//# sourceMappingURL=server-manager.d.mts.map
|
|
@@ -40,7 +40,7 @@ async function getServerVersion(port) {
|
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
async function spawnServer(port) {
|
|
43
|
-
const serverPath = join(paths.distRoot, "server", "fastify-server.
|
|
43
|
+
const serverPath = join(paths.distRoot, "server", "fastify-server.mjs");
|
|
44
44
|
spawn(process.execPath, [serverPath], {
|
|
45
45
|
detached: true,
|
|
46
46
|
stdio: "ignore",
|
|
@@ -89,5 +89,5 @@ async function ensureServer(port) {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
//#endregion
|
|
92
|
-
export { ensureServer };
|
|
92
|
+
export { ensureServer, getServerVersion, isServerRunning, shutdownServer };
|
|
93
93
|
//# sourceMappingURL=server-manager.mjs.map
|
|
@@ -1 +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.
|
|
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.mjs');\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\nexport { isServerRunning, getServerVersion, shutdownServer };\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,qBAAqB;AAMvE,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"}
|