backlog-mcp 0.6.7 → 0.8.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
@@ -1,6 +1,6 @@
1
1
  # backlog-mcp
2
2
 
3
- Minimal task backlog MCP server for Claude and AI agents.
3
+ Minimal task backlog MCP server. Works with any LLM agent or CLI editor that supports MCP integration (Claude, Kiro, Cursor, Codex, etc).
4
4
 
5
5
  > **Quick start**: Tell your LLM: `Add backlog-mcp to .mcp.json and use it to track tasks`
6
6
 
@@ -47,24 +47,30 @@ This came from Slack thread: https://...
47
47
 
48
48
  **Status values:** `open`, `in_progress`, `blocked`, `done`, `cancelled`
49
49
 
50
- ## MCP Tool
51
-
52
- Single unified tool with action parameter:
50
+ ## MCP Tools
53
51
 
54
52
  ```
55
- backlog action="list" # List all active tasks
56
- backlog action="list" summary=true # Get counts by status
57
- backlog action="list" status=["open"] # Filter by status
58
- backlog action="list" status=["done"] # Show completed tasks (last 10)
59
- backlog action="list" status=["done"] archived_limit=20 # Show last 20 completed
60
- backlog action="get" id="TASK-0001" # Get task details
61
- backlog action="create" title="Fix bug" # Create task
62
- backlog action="update" id="TASK-0001" set_status="done" # Update task
53
+ backlog_list # List active tasks (open, in_progress, blocked)
54
+ backlog_list status=["done"] # Show completed tasks
55
+ backlog_list counts=true # Get counts by status
56
+ backlog_list limit=10 # Limit results
57
+
58
+ backlog_get id="TASK-0001" # Get single task
59
+ backlog_get id=["TASK-0001","TASK-0002"] # Batch get multiple tasks
60
+
61
+ backlog_create title="Fix bug" # Create task
62
+ backlog_create title="Fix bug" description="Details..."
63
+
64
+ backlog_update id="TASK-0001" status="done" # Mark done
65
+ backlog_update id="TASK-0001" status="blocked" blocked_reason="Waiting on API"
66
+ backlog_update id="TASK-0001" evidence=["Fixed in CR-12345"] # Add completion proof
67
+
68
+ backlog_delete id="TASK-0001" # Permanently delete
63
69
  ```
64
70
 
65
71
  ## Installation
66
72
 
67
- Add to your MCP config (`.mcp.json` or Claude Desktop config):
73
+ Add to your MCP config (`.mcp.json` or your MCP client config):
68
74
 
69
75
  ```json
70
76
  {
@@ -0,0 +1,26 @@
1
+ import type { Task, Status } from './schema.js';
2
+ declare class BacklogStorage {
3
+ private dataDir;
4
+ private static instance;
5
+ static getInstance(): BacklogStorage;
6
+ init(dataDir: string): void;
7
+ private get tasksPath();
8
+ private get archivePath();
9
+ private ensureDir;
10
+ private taskFilePath;
11
+ private taskToMarkdown;
12
+ private markdownToTask;
13
+ getFilePath(id: string): string | null;
14
+ get(id: string): Task | undefined;
15
+ getMarkdown(id: string): string | null;
16
+ list(filter?: {
17
+ status?: Status[];
18
+ limit?: number;
19
+ }): Task[];
20
+ add(task: Task): void;
21
+ save(task: Task): void;
22
+ delete(id: string): boolean;
23
+ counts(): Record<Status, number>;
24
+ }
25
+ export declare const storage: BacklogStorage;
26
+ export {};
@@ -0,0 +1,167 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, unlinkSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import matter from 'gray-matter';
4
+ const TASKS_DIR = 'tasks';
5
+ const ARCHIVE_DIR = 'archive';
6
+ const TERMINAL_STATUSES = ['done', 'cancelled'];
7
+ class BacklogStorage {
8
+ dataDir = 'data';
9
+ static instance;
10
+ static getInstance() {
11
+ if (!BacklogStorage.instance) {
12
+ BacklogStorage.instance = new BacklogStorage();
13
+ }
14
+ return BacklogStorage.instance;
15
+ }
16
+ init(dataDir) {
17
+ this.dataDir = dataDir;
18
+ }
19
+ get tasksPath() {
20
+ return join(this.dataDir, TASKS_DIR);
21
+ }
22
+ get archivePath() {
23
+ return join(this.dataDir, ARCHIVE_DIR);
24
+ }
25
+ ensureDir(dir) {
26
+ if (!existsSync(dir)) {
27
+ mkdirSync(dir, { recursive: true });
28
+ }
29
+ }
30
+ taskFilePath(id, archived = false) {
31
+ return join(archived ? this.archivePath : this.tasksPath, `${id}.md`);
32
+ }
33
+ taskToMarkdown(task) {
34
+ const { description, ...frontmatter } = task;
35
+ return matter.stringify(description || '', frontmatter);
36
+ }
37
+ markdownToTask(content) {
38
+ const { data, content: description } = matter(content);
39
+ return { ...data, description: description.trim() };
40
+ }
41
+ getFilePath(id) {
42
+ const activePath = this.taskFilePath(id, false);
43
+ if (existsSync(activePath))
44
+ return activePath;
45
+ const archivePath = this.taskFilePath(id, true);
46
+ if (existsSync(archivePath))
47
+ return archivePath;
48
+ return null;
49
+ }
50
+ get(id) {
51
+ const activePath = this.taskFilePath(id, false);
52
+ if (existsSync(activePath)) {
53
+ return this.markdownToTask(readFileSync(activePath, 'utf-8'));
54
+ }
55
+ const archivePath = this.taskFilePath(id, true);
56
+ if (existsSync(archivePath)) {
57
+ return this.markdownToTask(readFileSync(archivePath, 'utf-8'));
58
+ }
59
+ return undefined;
60
+ }
61
+ getMarkdown(id) {
62
+ const activePath = this.taskFilePath(id, false);
63
+ if (existsSync(activePath)) {
64
+ return readFileSync(activePath, 'utf-8');
65
+ }
66
+ const archivePath = this.taskFilePath(id, true);
67
+ if (existsSync(archivePath)) {
68
+ return readFileSync(archivePath, 'utf-8');
69
+ }
70
+ return null;
71
+ }
72
+ list(filter) {
73
+ const tasks = [];
74
+ const statusFilter = filter?.status;
75
+ const limit = filter?.limit ?? 20;
76
+ // Load active tasks
77
+ if (existsSync(this.tasksPath)) {
78
+ for (const file of readdirSync(this.tasksPath).filter(f => f.endsWith('.md'))) {
79
+ const task = this.markdownToTask(readFileSync(join(this.tasksPath, file), 'utf-8'));
80
+ if (!statusFilter || statusFilter.includes(task.status)) {
81
+ tasks.push(task);
82
+ }
83
+ }
84
+ }
85
+ // Load archived tasks if needed
86
+ const needsArchived = !statusFilter || statusFilter.some(s => TERMINAL_STATUSES.includes(s));
87
+ if (needsArchived && existsSync(this.archivePath)) {
88
+ for (const file of readdirSync(this.archivePath).filter(f => f.endsWith('.md'))) {
89
+ const task = this.markdownToTask(readFileSync(join(this.archivePath, file), 'utf-8'));
90
+ if (!statusFilter || statusFilter.includes(task.status)) {
91
+ tasks.push(task);
92
+ }
93
+ }
94
+ }
95
+ // Sort: active first, then by date desc
96
+ const isTerminal = (s) => TERMINAL_STATUSES.includes(s);
97
+ return tasks
98
+ .sort((a, b) => {
99
+ const aTerminal = isTerminal(a.status) ? 1 : 0;
100
+ const bTerminal = isTerminal(b.status) ? 1 : 0;
101
+ if (aTerminal !== bTerminal)
102
+ return aTerminal - bTerminal;
103
+ return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime();
104
+ })
105
+ .slice(0, limit);
106
+ }
107
+ add(task) {
108
+ this.ensureDir(this.tasksPath);
109
+ const filePath = this.taskFilePath(task.id, false);
110
+ writeFileSync(filePath, this.taskToMarkdown(task));
111
+ }
112
+ save(task) {
113
+ const isTerminal = TERMINAL_STATUSES.includes(task.status);
114
+ const activePath = this.taskFilePath(task.id, false);
115
+ const archivePath = this.taskFilePath(task.id, true);
116
+ // Move to archive if terminal status
117
+ if (isTerminal) {
118
+ this.ensureDir(this.archivePath);
119
+ writeFileSync(archivePath, this.taskToMarkdown(task));
120
+ if (existsSync(activePath))
121
+ unlinkSync(activePath);
122
+ }
123
+ else {
124
+ this.ensureDir(this.tasksPath);
125
+ writeFileSync(activePath, this.taskToMarkdown(task));
126
+ if (existsSync(archivePath))
127
+ unlinkSync(archivePath);
128
+ }
129
+ }
130
+ delete(id) {
131
+ const activePath = this.taskFilePath(id, false);
132
+ if (existsSync(activePath)) {
133
+ unlinkSync(activePath);
134
+ return true;
135
+ }
136
+ const archivePath = this.taskFilePath(id, true);
137
+ if (existsSync(archivePath)) {
138
+ unlinkSync(archivePath);
139
+ return true;
140
+ }
141
+ return false;
142
+ }
143
+ counts() {
144
+ const counts = {
145
+ open: 0,
146
+ in_progress: 0,
147
+ blocked: 0,
148
+ done: 0,
149
+ cancelled: 0,
150
+ };
151
+ if (existsSync(this.tasksPath)) {
152
+ for (const file of readdirSync(this.tasksPath).filter(f => f.endsWith('.md'))) {
153
+ const task = this.markdownToTask(readFileSync(join(this.tasksPath, file), 'utf-8'));
154
+ counts[task.status]++;
155
+ }
156
+ }
157
+ if (existsSync(this.archivePath)) {
158
+ for (const file of readdirSync(this.archivePath).filter(f => f.endsWith('.md'))) {
159
+ const task = this.markdownToTask(readFileSync(join(this.archivePath, file), 'utf-8'));
160
+ counts[task.status]++;
161
+ }
162
+ }
163
+ return counts;
164
+ }
165
+ }
166
+ export const storage = BacklogStorage.getInstance();
167
+ //# sourceMappingURL=backlog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backlog.js","sourceRoot":"","sources":["../src/backlog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACtG,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,aAAa,CAAC;AAGjC,MAAM,SAAS,GAAG,OAAO,CAAC;AAC1B,MAAM,WAAW,GAAG,SAAS,CAAC;AAC9B,MAAM,iBAAiB,GAAa,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAE1D,MAAM,cAAc;IACV,OAAO,GAAW,MAAM,CAAC;IACzB,MAAM,CAAC,QAAQ,CAAiB;IAExC,MAAM,CAAC,WAAW;QAChB,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC;YAC7B,cAAc,CAAC,QAAQ,GAAG,IAAI,cAAc,EAAE,CAAC;QACjD,CAAC;QACD,OAAO,cAAc,CAAC,QAAQ,CAAC;IACjC,CAAC;IAED,IAAI,CAAC,OAAe;QAClB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,IAAY,SAAS;QACnB,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACvC,CAAC;IAED,IAAY,WAAW;QACrB,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACzC,CAAC;IAEO,SAAS,CAAC,GAAW;QAC3B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAEO,YAAY,CAAC,EAAU,EAAE,WAAoB,KAAK;QACxD,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IACxE,CAAC;IAEO,cAAc,CAAC,IAAU;QAC/B,MAAM,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,GAAG,IAAI,CAAC;QAC7C,OAAO,MAAM,CAAC,SAAS,CAAC,WAAW,IAAI,EAAE,EAAE,WAAW,CAAC,CAAC;IAC1D,CAAC;IAEO,cAAc,CAAC,OAAe;QACpC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;QACvD,OAAO,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,IAAI,EAAE,EAAU,CAAC;IAC9D,CAAC;IAED,WAAW,CAAC,EAAU;QACpB,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAChD,IAAI,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO,UAAU,CAAC;QAC9C,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAChD,IAAI,UAAU,CAAC,WAAW,CAAC;YAAE,OAAO,WAAW,CAAC;QAChD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAChD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;QAChE,CAAC;QACD,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAChD,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,WAAW,CAAC,EAAU;QACpB,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAChD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,OAAO,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC3C,CAAC;QACD,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAChD,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC5B,OAAO,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC,MAA8C;QACjD,MAAM,KAAK,GAAW,EAAE,CAAC;QACzB,MAAM,YAAY,GAAG,MAAM,EAAE,MAAM,CAAC;QACpC,MAAM,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC;QAElC,oBAAoB;QACpB,IAAI,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAC/B,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBAC9E,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;gBACpF,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;oBACxD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACnB,CAAC;YACH,CAAC;QACH,CAAC;QAED,gCAAgC;QAChC,MAAM,aAAa,GAAG,CAAC,YAAY,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7F,IAAI,aAAa,IAAI,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YAClD,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBAChF,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;gBACtF,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;oBACxD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACnB,CAAC;YACH,CAAC;QACH,CAAC;QAED,wCAAwC;QACxC,MAAM,UAAU,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAChE,OAAO,KAAK;aACT,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACb,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/C,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/C,IAAI,SAAS,KAAK,SAAS;gBAAE,OAAO,SAAS,GAAG,SAAS,CAAC;YAC1D,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC;QAC7E,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACrB,CAAC;IAED,GAAG,CAAC,IAAU;QACZ,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QACnD,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,IAAI,CAAC,IAAU;QACb,MAAM,UAAU,GAAG,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC3D,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QACrD,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAErD,qCAAqC;QACrC,IAAI,UAAU,EAAE,CAAC;YACf,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACjC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;YACtD,IAAI,UAAU,CAAC,UAAU,CAAC;gBAAE,UAAU,CAAC,UAAU,CAAC,CAAC;QACrD,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC/B,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;YACrD,IAAI,UAAU,CAAC,WAAW,CAAC;gBAAE,UAAU,CAAC,WAAW,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,MAAM,CAAC,EAAU;QACf,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAChD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,UAAU,CAAC,UAAU,CAAC,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAChD,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC5B,UAAU,CAAC,WAAW,CAAC,CAAC;YACxB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM;QACJ,MAAM,MAAM,GAA2B;YACrC,IAAI,EAAE,CAAC;YACP,WAAW,EAAE,CAAC;YACd,OAAO,EAAE,CAAC;YACV,IAAI,EAAE,CAAC;YACP,SAAS,EAAE,CAAC;SACb,CAAC;QAEF,IAAI,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAC/B,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBAC9E,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;gBACpF,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACxB,CAAC;QACH,CAAC;QAED,IAAI,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YACjC,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBAChF,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;gBACtF,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACxB,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,cAAc,CAAC,WAAW,EAAE,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { isValidTaskId, parseTaskId, formatTaskId, nextTaskId, STATUSES, type Status, type Task, type CreateTaskInput, createTask, } from './schema.js';
2
- export { type Backlog, type StorageOptions, loadBacklog, saveBacklog, getTask, listTasks, addTask, saveTask, deleteTask, taskExists, getTaskCounts, getTaskMarkdown, } from './storage.js';
2
+ export { storage } from './backlog.js';
3
3
  export { startViewer } from './viewer.js';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Schema
2
2
  export { isValidTaskId, parseTaskId, formatTaskId, nextTaskId, STATUSES, createTask, } from './schema.js';
3
3
  // Storage
4
- export { loadBacklog, saveBacklog, getTask, listTasks, addTask, saveTask, deleteTask, taskExists, getTaskCounts, getTaskMarkdown, } from './storage.js';
4
+ export { storage } from './backlog.js';
5
5
  // Viewer
6
6
  export { startViewer } from './viewer.js';
7
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,SAAS;AACT,OAAO,EACL,aAAa,EACb,WAAW,EACX,YAAY,EACZ,UAAU,EACV,QAAQ,EAIR,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,UAAU;AACV,OAAO,EAGL,WAAW,EACX,WAAW,EACX,OAAO,EACP,SAAS,EACT,OAAO,EACP,QAAQ,EACR,UAAU,EACV,UAAU,EACV,aAAa,EACb,eAAe,GAChB,MAAM,cAAc,CAAC;AAEtB,SAAS;AACT,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,SAAS;AACT,OAAO,EACL,aAAa,EACb,WAAW,EACX,YAAY,EACZ,UAAU,EACV,QAAQ,EAIR,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,UAAU;AACV,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,SAAS;AACT,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
package/dist/server.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ import 'dotenv/config';
package/dist/server.js CHANGED
@@ -1,106 +1,110 @@
1
1
  #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ import { readFileSync } from 'node:fs';
4
+ import { dirname, join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
2
6
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
7
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
8
  import { z } from 'zod';
5
9
  import { createTask, STATUSES } from './schema.js';
6
- import { countTasks } from './summary.js';
7
- import { loadBacklog, getTask, getTaskMarkdown, listTasks, addTask, saveTask, getTaskCounts } from './storage.js';
10
+ import { storage } from './backlog.js';
8
11
  import { startViewer } from './viewer.js';
9
- // ============================================================================
10
- // Server
11
- // ============================================================================
12
+ // Read version from package.json
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
15
+ // Init storage
16
+ const dataDir = process.env.BACKLOG_DATA_DIR ?? 'data';
17
+ storage.init(dataDir);
12
18
  const server = new McpServer({
13
19
  name: 'backlog-mcp',
14
- version: '0.1.0',
20
+ version: pkg.version,
15
21
  });
16
- const storageOptions = {
17
- dataDir: process.env.BACKLOG_DATA_DIR ?? 'data',
18
- };
19
22
  // ============================================================================
20
23
  // Tools
21
24
  // ============================================================================
22
- const ACTIONS = ['list', 'get', 'create', 'update'];
23
- server.registerTool('backlog', {
24
- description: 'Manage tasks. Actions: list, get, create, update',
25
+ server.registerTool('backlog_list', {
26
+ description: 'List tasks from backlog. Shows open/in_progress/blocked by default. Use status=["done"] to see completed tasks.',
27
+ inputSchema: {
28
+ status: z.array(z.enum(STATUSES)).optional().describe('Filter: open, in_progress, blocked, done, cancelled. Default: open, in_progress, blocked'),
29
+ counts: z.boolean().optional().describe('Return counts per status instead of task list'),
30
+ limit: z.number().optional().describe('Max tasks to return. Default: 20'),
31
+ },
32
+ }, async ({ status, counts, limit }) => {
33
+ const tasks = storage.list({ status, limit });
34
+ if (counts) {
35
+ return { content: [{ type: 'text', text: JSON.stringify(storage.counts(), null, 2) }] };
36
+ }
37
+ const list = tasks.map((t) => ({ id: t.id, title: t.title, status: t.status }));
38
+ return { content: [{ type: 'text', text: JSON.stringify(list, null, 2) }] };
39
+ });
40
+ server.registerTool('backlog_get', {
41
+ description: 'Get full task details by ID. Works for any task regardless of status.',
42
+ inputSchema: {
43
+ id: z.union([z.string(), z.array(z.string())]).describe('Task ID like TASK-0001, or array for batch fetch'),
44
+ },
45
+ }, async ({ id }) => {
46
+ const taskIds = Array.isArray(id) ? id : [id];
47
+ if (taskIds.length === 0) {
48
+ return { content: [{ type: 'text', text: 'Required: id' }], isError: true };
49
+ }
50
+ const results = taskIds.map((tid) => storage.getMarkdown(tid) || `Not found: ${tid}`);
51
+ return { content: [{ type: 'text', text: results.join('\n\n---\n\n') }] };
52
+ });
53
+ server.registerTool('backlog_create', {
54
+ description: 'Create a new task in the backlog.',
25
55
  inputSchema: {
26
- action: z.enum(ACTIONS).describe('Action to perform'),
27
- // list options
28
- status: z.array(z.enum(STATUSES)).optional().describe('Filter by status (list). Include "done" or "cancelled" to see archived tasks'),
29
- summary: z.boolean().optional().describe('Return counts instead of list (list)'),
30
- archived_limit: z.number().optional().describe('Max archived tasks when status includes done/cancelled (list). Default: 10'),
31
- // get/update options
32
- id: z.string().optional().describe('Task ID (get, update)'),
33
- // create/update options
34
- title: z.string().optional().describe('Task title (create, update)'),
35
- description: z.string().optional().describe('Task description (create, update)'),
36
- // update-only options
37
- set_status: z.enum(STATUSES).optional().describe('New status (update)'),
38
- blocked_reason: z.string().optional().describe('Reason for blocked status (update)'),
39
- evidence: z.array(z.string()).optional().describe('Evidence of completion (update)'),
56
+ title: z.string().describe('Task title'),
57
+ description: z.string().optional().describe('Task description in markdown'),
40
58
  },
41
- }, async ({ action, status, summary, archived_limit, id, title, description, set_status, blocked_reason, evidence }) => {
42
- switch (action) {
43
- case 'list': {
44
- const filter = status || archived_limit ? { status, archivedLimit: archived_limit } : undefined;
45
- const tasks = listTasks(filter, storageOptions);
46
- if (summary) {
47
- const counts = status ? countTasks(tasks) : getTaskCounts(storageOptions);
48
- return { content: [{ type: 'text', text: JSON.stringify(counts, null, 2) }] };
49
- }
50
- const list = tasks.map((t) => ({ id: t.id, title: t.title, status: t.status }));
51
- return { content: [{ type: 'text', text: JSON.stringify(list, null, 2) }] };
52
- }
53
- case 'get': {
54
- if (!id) {
55
- return { content: [{ type: 'text', text: 'Missing required: id' }], isError: true };
56
- }
57
- const markdown = getTaskMarkdown(id, storageOptions);
58
- if (!markdown) {
59
- return { content: [{ type: 'text', text: `Not found: ${id}` }], isError: true };
60
- }
61
- return { content: [{ type: 'text', text: markdown }] };
62
- }
63
- case 'create': {
64
- if (!title) {
65
- return { content: [{ type: 'text', text: 'Missing required: title' }], isError: true };
66
- }
67
- const backlog = loadBacklog(storageOptions);
68
- const task = createTask({ title, description }, backlog.tasks);
69
- addTask(task, storageOptions);
70
- return { content: [{ type: 'text', text: `Created ${task.id}` }] };
71
- }
72
- case 'update': {
73
- if (!id) {
74
- return { content: [{ type: 'text', text: 'Missing required: id' }], isError: true };
75
- }
76
- const task = getTask(id, storageOptions);
77
- if (!task) {
78
- return { content: [{ type: 'text', text: `Not found: ${id}` }], isError: true };
79
- }
80
- const updates = { title, description, status: set_status, blocked_reason, evidence };
81
- const updated = {
82
- ...task,
83
- ...Object.fromEntries(Object.entries(updates).filter(([_, v]) => v !== undefined)),
84
- updated_at: new Date().toISOString(),
85
- };
86
- saveTask(updated, storageOptions);
87
- return { content: [{ type: 'text', text: `Updated ${id}` }] };
88
- }
59
+ }, async ({ title, description }) => {
60
+ const task = createTask({ title, description }, storage.list());
61
+ storage.add(task);
62
+ return { content: [{ type: 'text', text: `Created ${task.id}` }] };
63
+ });
64
+ server.registerTool('backlog_update', {
65
+ description: 'Update an existing task.',
66
+ inputSchema: {
67
+ id: z.string().describe('Task ID to update'),
68
+ title: z.string().optional().describe('New title'),
69
+ description: z.string().optional().describe('New description'),
70
+ status: z.enum(STATUSES).optional().describe('New status'),
71
+ blocked_reason: z.string().optional().describe('Reason if status is blocked'),
72
+ evidence: z.array(z.string()).optional().describe('Proof of completion when marking done - links to PRs, docs, or notes'),
73
+ },
74
+ }, async ({ id, title, description, status, blocked_reason, evidence }) => {
75
+ const task = storage.get(id);
76
+ if (!task) {
77
+ return { content: [{ type: 'text', text: `Not found: ${id}` }], isError: true };
89
78
  }
79
+ const updates = { title, description, status, blocked_reason, evidence };
80
+ const updated = {
81
+ ...task,
82
+ ...Object.fromEntries(Object.entries(updates).filter(([_, v]) => v !== undefined)),
83
+ updated_at: new Date().toISOString(),
84
+ };
85
+ storage.save(updated);
86
+ return { content: [{ type: 'text', text: `Updated ${id}` }] };
87
+ });
88
+ server.registerTool('backlog_delete', {
89
+ description: 'Permanently delete a task from the backlog.',
90
+ inputSchema: {
91
+ id: z.string().describe('Task ID to delete'),
92
+ },
93
+ }, async ({ id }) => {
94
+ const deleted = storage.delete(id);
95
+ if (!deleted) {
96
+ return { content: [{ type: 'text', text: `Not found: ${id}` }], isError: true };
97
+ }
98
+ return { content: [{ type: 'text', text: `Deleted ${id}` }] };
90
99
  });
91
100
  // ============================================================================
92
101
  // Main
93
102
  // ============================================================================
94
103
  async function main() {
95
- // Start HTTP viewer in background
96
- const dataDir = storageOptions.dataDir ?? 'data';
97
104
  const viewerPort = parseInt(process.env.BACKLOG_VIEWER_PORT || '3030');
98
- startViewer(dataDir, viewerPort);
105
+ startViewer(viewerPort);
99
106
  const transport = new StdioServerTransport();
100
107
  await server.connect(transport);
101
108
  }
102
- main().catch((error) => {
103
- console.error('Fatal error:', error);
104
- process.exit(1);
105
- });
109
+ main().catch(console.error);
106
110
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,9 @@
1
1
  import { fetchTask } from '../utils/api.js';
2
+ import { copyIcon } from '../icons/index.js';
3
+ function linkify(text) {
4
+ const urlRegex = /(https?:\/\/[^\s<>"']+)/g;
5
+ return text.replace(urlRegex, '<a href="$1" target="_blank" rel="noopener">$1</a>');
6
+ }
2
7
  export class TaskDetail extends HTMLElement {
3
8
  connectedCallback() {
4
9
  this.showEmpty();
@@ -14,18 +19,24 @@ export class TaskDetail extends HTMLElement {
14
19
  async loadTask(taskId) {
15
20
  try {
16
21
  const task = await fetchTask(taskId);
22
+ const headerHtml = `
23
+ <div class="task-header-left">
24
+ <button class="btn-outline task-id-btn" onclick="navigator.clipboard.writeText('${task.id}')" title="Copy ID">${task.id} ${copyIcon}</button>
25
+ <span class="status-badge status-${task.status || 'open'}">${(task.status || 'open').replace('_', ' ')}</span>
26
+ ${task.filePath ? `
27
+ <div class="task-meta-path">
28
+ <a href="#" class="open-link" onclick="fetch('http://localhost:3030/open/${task.id}');return false;" title="Open in editor">${task.filePath}</a>
29
+ </div>
30
+ ` : ''}
31
+ </div>
32
+ <button class="copy-btn copy-raw btn-outline" title="Copy markdown">Copy Markdown ${copyIcon}</button>
33
+ `;
34
+ const paneHeader = document.getElementById('task-pane-header');
35
+ if (paneHeader) {
36
+ paneHeader.innerHTML = headerHtml;
37
+ }
17
38
  const metaHtml = `
18
39
  <div class="task-meta-card">
19
- <div class="task-meta-header">
20
- <span class="task-meta-id">${task.id || ''}</span>
21
- <span class="status-badge status-${task.status || 'open'}">${(task.status || 'open').replace('_', ' ')}</span>
22
- ${task.filePath ? `
23
- <div class="task-meta-path">
24
- <a href="#" class="open-link" onclick="fetch('http://localhost:3030/open/${task.id}');return false;" title="Open in editor">${task.filePath}</a>
25
- <button class="copy-btn" onclick="navigator.clipboard.writeText('${task.filePath}')">📋</button>
26
- </div>
27
- ` : ''}
28
- </div>
29
40
  <h1 class="task-meta-title">${task.title || ''}</h1>
30
41
  <div class="task-meta-dates">
31
42
  <span>Created: ${task.created_at ? new Date(task.created_at).toLocaleDateString() : ''}</span>
@@ -34,7 +45,7 @@ export class TaskDetail extends HTMLElement {
34
45
  ${task.evidence?.length ? `
35
46
  <div class="task-meta-evidence">
36
47
  <div class="task-meta-evidence-label">Evidence:</div>
37
- <ul>${task.evidence.map((e) => `<li>${e}</li>`).join('')}</ul>
48
+ <ul>${task.evidence.map((e) => `<li>${linkify(e)}</li>`).join('')}</ul>
38
49
  </div>
39
50
  ` : ''}
40
51
  </div>
@@ -47,6 +58,11 @@ export class TaskDetail extends HTMLElement {
47
58
  article.appendChild(mdBlock);
48
59
  this.innerHTML = '';
49
60
  this.appendChild(article);
61
+ // Bind copy raw button (in pane header)
62
+ const copyRawBtn = paneHeader?.querySelector('.copy-raw');
63
+ if (copyRawBtn && task.raw) {
64
+ copyRawBtn.addEventListener('click', () => navigator.clipboard.writeText(task.raw));
65
+ }
50
66
  }
51
67
  catch (error) {
52
68
  this.innerHTML = `<div class="error">Failed to load task: ${error.message}</div>`;
@@ -1,3 +1,8 @@
1
+ const FILTERS = [
2
+ { key: 'active', label: 'Active' },
3
+ { key: 'completed', label: 'Completed' },
4
+ { key: 'all', label: 'All' },
5
+ ];
1
6
  export class TaskFilterBar extends HTMLElement {
2
7
  currentFilter = 'active';
3
8
  connectedCallback() {
@@ -5,30 +10,22 @@ export class TaskFilterBar extends HTMLElement {
5
10
  this.attachListeners();
6
11
  }
7
12
  render() {
8
- this.innerHTML = `
9
- <div class="filter-bar">
10
- <button class="filter-btn ${this.currentFilter === 'active' ? 'active' : ''}" data-filter="active">Active</button>
11
- <button class="filter-btn ${this.currentFilter === 'done' ? 'active' : ''}" data-filter="done">Completed</button>
12
- <button class="filter-btn ${this.currentFilter === 'all' ? 'active' : ''}" data-filter="all">All</button>
13
- </div>
14
- `;
13
+ const buttons = FILTERS.map(f => `<button class="filter-btn ${this.currentFilter === f.key ? 'active' : ''}" data-filter="${f.key}">${f.label}</button>`).join('');
14
+ this.innerHTML = `<div class="filter-bar">${buttons}</div>`;
15
15
  }
16
16
  attachListeners() {
17
17
  this.querySelectorAll('.filter-btn').forEach(btn => {
18
18
  btn.addEventListener('click', (e) => {
19
- const target = e.target;
20
- const filter = target.dataset.filter;
21
- if (!filter)
22
- return;
23
- this.setFilter(filter);
19
+ const filter = e.target.dataset.filter;
20
+ if (filter)
21
+ this.setFilter(filter);
24
22
  });
25
23
  });
26
24
  }
27
25
  setFilter(filter) {
28
26
  this.currentFilter = filter;
29
27
  this.querySelectorAll('.filter-btn').forEach(btn => {
30
- const btnElement = btn;
31
- btn.classList.toggle('active', btnElement.dataset.filter === filter);
28
+ btn.classList.toggle('active', btn.dataset.filter === filter);
32
29
  });
33
30
  document.dispatchEvent(new CustomEvent('filter-change', { detail: { filter } }));
34
31
  }
@@ -0,0 +1 @@
1
+ export const copyIcon = `<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"/><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z"/></svg>`;