@damper/mcp 0.3.22 → 0.5.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
@@ -118,6 +118,26 @@ AI agents can store and retrieve project documentation to help future agents wor
118
118
 
119
119
  ⚠️ **Security:** Never include sensitive data (API keys, secrets, credentials, connection strings) in context uploads. Users can review and delete context from Settings → AI Context.
120
120
 
121
+ ### Project Settings
122
+
123
+ Configure agent-relevant project settings programmatically.
124
+
125
+ | Tool | Description |
126
+ |------|-------------|
127
+ | `get_project_settings` | View current settings (completion checklist, etc.) |
128
+ | `update_project_settings` | Configure completion checklist and other agent-relevant settings |
129
+
130
+ **Completion checklist workflow:**
131
+ 1. `update_project_settings` with `completionChecklist: ["All tests pass", "Build succeeds"]`
132
+ 2. Agents see the checklist when they call `start_task`
133
+ 3. Agents must confirm all items via `confirmations` when calling `complete_task`
134
+
135
+ ```
136
+ > View project settings
137
+ > Set completion checklist to require tests and builds
138
+ > Clear the completion checklist
139
+ ```
140
+
121
141
  ### Hierarchical Sections
122
142
 
123
143
  For monorepos or large projects, organize sections hierarchically:
@@ -8,6 +8,7 @@ export interface StartTaskResult {
8
8
  id: string;
9
9
  status: string;
10
10
  message: string;
11
+ completionChecklist?: string[];
11
12
  context?: {
12
13
  isEmpty: boolean;
13
14
  index: Array<{
@@ -16,6 +16,14 @@ export function formatStartTaskResponse(result) {
16
16
  lines.push(`• ${rule}`);
17
17
  }
18
18
  }
19
+ // Completion checklist - shown after critical rules so agent knows upfront
20
+ if (result.completionChecklist && result.completionChecklist.length > 0) {
21
+ lines.push('\n📋 **COMPLETION CHECKLIST (required for complete_task):**');
22
+ lines.push('You MUST verify each item and pass them as `confirmations` when calling `complete_task`:');
23
+ for (const item of result.completionChecklist) {
24
+ lines.push(` □ ${item}`);
25
+ }
26
+ }
19
27
  // Context section - emphatic about reading it first
20
28
  if (result.context) {
21
29
  if (result.context.isEmpty) {
@@ -43,7 +51,7 @@ export function formatStartTaskResponse(result) {
43
51
  lines.push('1. `add_note`: "Session started: <goal>"');
44
52
  lines.push('2. Work: use `add_commit` for commits, `add_note` for decisions');
45
53
  lines.push('3. `add_note`: "Session end: <summary>"');
46
- lines.push('4. `complete_task` or `abandon_task`');
54
+ lines.push('4. `complete_task` (with `confirmations` if checklist exists) or `abandon_task`');
47
55
  return lines.join('\n');
48
56
  }
49
57
  /**
package/dist/index.js CHANGED
@@ -28,6 +28,12 @@ async function api(method, path, body) {
28
28
  lockErr.lockInfo = err;
29
29
  throw lockErr;
30
30
  }
31
+ // Preserve checklist info for 400 checklist failures
32
+ if (res.status === 400 && err.missingItems) {
33
+ const checklistErr = new Error(err.error || `HTTP ${res.status}`);
34
+ checklistErr.checklistInfo = err;
35
+ throw checklistErr;
36
+ }
31
37
  throw new Error(err.error || `HTTP ${res.status}`);
32
38
  }
33
39
  return res.json();
@@ -35,7 +41,7 @@ async function api(method, path, body) {
35
41
  // Server
36
42
  const server = new McpServer({
37
43
  name: 'damper',
38
- version: '0.3.0',
44
+ version: '0.5.0',
39
45
  });
40
46
  // Output schemas
41
47
  const SubtaskProgressSchema = z.object({
@@ -403,6 +409,7 @@ server.registerTool('start_task', {
403
409
  lockedBy: z.string().optional(),
404
410
  lockedAt: z.string().optional(),
405
411
  context: ContextIndexSchema.optional(),
412
+ completionChecklist: z.array(z.string()).optional(),
406
413
  }),
407
414
  annotations: {
408
415
  readOnlyHint: false,
@@ -587,6 +594,9 @@ server.registerTool('complete_task', {
587
594
  '**Before calling:**\n' +
588
595
  '1. Push all commits\n' +
589
596
  '2. Check if project context docs need updating\n\n' +
597
+ '**Completion checklist:** If the project has a completion checklist (shown in `start_task` response), ' +
598
+ 'you MUST pass `confirmations` — an array echoing back each checklist item you\'ve verified. ' +
599
+ 'The server will reject completion if any items are missing.\n\n' +
590
600
  '**Commits:** Pass commits array to log them at completion (convenience for final commits).\n\n' +
591
601
  'Returns documentation update suggestions.',
592
602
  inputSchema: z.object({
@@ -596,6 +606,7 @@ server.registerTool('complete_task', {
596
606
  hash: z.string().describe('Commit hash (short or full)'),
597
607
  message: z.string().describe('Commit message'),
598
608
  })).optional().describe('Optional: commits to log at completion'),
609
+ confirmations: z.array(z.string()).optional().describe('Completion checklist confirmations — echo back each item from the checklist shown in start_task'),
599
610
  }),
600
611
  outputSchema: z.object({
601
612
  id: z.string(),
@@ -608,12 +619,33 @@ server.registerTool('complete_task', {
608
619
  idempotentHint: true,
609
620
  openWorldHint: false,
610
621
  },
611
- }, async ({ taskId, summary, commits }) => {
612
- const result = await api('POST', `/api/agent/tasks/${taskId}/complete`, { summary, commits });
613
- return {
614
- content: [{ type: 'text', text: formatCompleteTaskResponse(result) }],
615
- structuredContent: result,
616
- };
622
+ }, async ({ taskId, summary, commits, confirmations }) => {
623
+ try {
624
+ const result = await api('POST', `/api/agent/tasks/${taskId}/complete`, { summary, commits, confirmations });
625
+ return {
626
+ content: [{ type: 'text', text: formatCompleteTaskResponse(result) }],
627
+ structuredContent: result,
628
+ };
629
+ }
630
+ catch (err) {
631
+ const error = err;
632
+ if (error.checklistInfo) {
633
+ const { missingItems, checklist } = error.checklistInfo;
634
+ const lines = [
635
+ '❌ Completion blocked — checklist not fully confirmed.',
636
+ '',
637
+ `**Missing ${missingItems.length} of ${checklist.length} items:**`,
638
+ ...missingItems.map(item => ` • ${item}`),
639
+ '',
640
+ 'Pass ALL checklist items in `confirmations` to complete the task.',
641
+ ];
642
+ return {
643
+ content: [{ type: 'text', text: lines.join('\n') }],
644
+ isError: true,
645
+ };
646
+ }
647
+ throw error;
648
+ }
617
649
  });
618
650
  // Tool: Abandon task
619
651
  server.registerTool('abandon_task', {
@@ -690,7 +722,7 @@ server.registerTool('get_agent_instructions', {
690
722
  openWorldHint: false,
691
723
  },
692
724
  }, async ({ format = 'section' }) => {
693
- const lastModified = '2025-02-05';
725
+ const lastModified = '2025-02-06';
694
726
  const section = `## Task Management with Damper MCP
695
727
 
696
728
  > Last updated: ${lastModified}
@@ -721,9 +753,14 @@ This project uses Damper MCP for task tracking. **You MUST follow this workflow.
721
753
  - \`add_to_changelog\` - Manually add completed tasks to a changelog
722
754
  - \`publish_changelog\` - Publish with optional email notifications and Twitter posting
723
755
 
756
+ ### Project Settings
757
+ - \`get_project_settings\` - View current settings (completion checklist, etc.)
758
+ - \`update_project_settings\` - Configure completion checklist and other agent-relevant settings
759
+
724
760
  ### At Session End (MANDATORY)
725
761
  - ALWAYS call \`complete_task\` (if done) or \`abandon_task\` (if stopping early)
726
762
  - NEVER leave a started task without completing or abandoning it
763
+ - If the project has a **completion checklist** (shown in \`start_task\` response), you MUST pass all items as \`confirmations\` when calling \`complete_task\`
727
764
  - If you learned something about the codebase, consider updating project context
728
765
 
729
766
  ### Why This Matters
@@ -1842,6 +1879,74 @@ server.registerTool('publish_changelog', {
1842
1879
  },
1843
1880
  };
1844
1881
  });
1882
+ // ==================== Project Settings Tools ====================
1883
+ // Tool: Get project settings
1884
+ server.registerTool('get_project_settings', {
1885
+ title: 'Get Project Settings',
1886
+ description: 'View current project settings relevant to agents (completion checklist, etc.).\n\n' +
1887
+ '**When to use**: Check what completion checklist items are configured, or verify settings before updating them.',
1888
+ inputSchema: z.object({}),
1889
+ outputSchema: z.object({
1890
+ completionChecklist: z.array(z.string()),
1891
+ }),
1892
+ annotations: {
1893
+ readOnlyHint: true,
1894
+ destructiveHint: false,
1895
+ idempotentHint: true,
1896
+ openWorldHint: false,
1897
+ },
1898
+ }, async () => {
1899
+ const data = await api('GET', '/api/agent/project/settings');
1900
+ if (!data.completionChecklist || data.completionChecklist.length === 0) {
1901
+ return {
1902
+ content: [{ type: 'text', text: 'No completion checklist configured.' }],
1903
+ structuredContent: data,
1904
+ };
1905
+ }
1906
+ const lines = data.completionChecklist.map((item, i) => `${i + 1}. ${item}`);
1907
+ return {
1908
+ content: [{ type: 'text', text: `Completion checklist:\n${lines.join('\n')}` }],
1909
+ structuredContent: data,
1910
+ };
1911
+ });
1912
+ // Tool: Update project settings
1913
+ server.registerTool('update_project_settings', {
1914
+ title: 'Update Project Settings',
1915
+ description: 'Configure agent-relevant project settings.\n\n' +
1916
+ '**completionChecklist**: Items agents must confirm before completing tasks. ' +
1917
+ 'Pass an empty array to clear the checklist.\n\n' +
1918
+ 'Example items: "All tests pass", "Build succeeds", "Migration created if schema changed"',
1919
+ inputSchema: z.object({
1920
+ completionChecklist: z.array(z.string()).optional()
1921
+ .describe('Checklist items agents must confirm when completing tasks. Pass [] to clear.'),
1922
+ }),
1923
+ outputSchema: z.object({
1924
+ completionChecklist: z.array(z.string()),
1925
+ message: z.string(),
1926
+ }),
1927
+ annotations: {
1928
+ readOnlyHint: false,
1929
+ destructiveHint: false,
1930
+ idempotentHint: true,
1931
+ openWorldHint: false,
1932
+ },
1933
+ }, async ({ completionChecklist }) => {
1934
+ const body = {};
1935
+ if (completionChecklist !== undefined)
1936
+ body.completionChecklist = completionChecklist;
1937
+ const result = await api('PATCH', '/api/agent/project/settings', body);
1938
+ if (result.completionChecklist.length === 0) {
1939
+ return {
1940
+ content: [{ type: 'text', text: 'Completion checklist cleared.' }],
1941
+ structuredContent: result,
1942
+ };
1943
+ }
1944
+ const lines = result.completionChecklist.map((item, i) => `${i + 1}. ${item}`);
1945
+ return {
1946
+ content: [{ type: 'text', text: `${result.message}\n${lines.join('\n')}` }],
1947
+ structuredContent: result,
1948
+ };
1949
+ });
1845
1950
  // Start
1846
1951
  async function main() {
1847
1952
  const transport = new StdioServerTransport();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@damper/mcp",
3
- "version": "0.3.22",
3
+ "version": "0.5.0",
4
4
  "description": "MCP server for Damper task management",
5
5
  "author": "Damper <hello@usedamper.com>",
6
6
  "repository": {
@@ -1,4 +0,0 @@
1
- /**
2
- * Tests for MCP response formatters
3
- */
4
- export {};
@@ -1,126 +0,0 @@
1
- /**
2
- * Tests for MCP response formatters
3
- */
4
- import { describe, it, expect } from 'bun:test';
5
- import { formatStartTaskResponse, formatCompleteTaskResponse, formatAbandonTaskResponse, } from './formatters';
6
- describe('formatStartTaskResponse', () => {
7
- it('includes workflow steps', () => {
8
- const result = formatStartTaskResponse({
9
- id: 'TASK-1',
10
- status: 'in_progress',
11
- message: 'Task started',
12
- });
13
- expect(result).toContain('**📋 Workflow:**');
14
- expect(result).toContain('`add_note`: "Session started: <goal>"');
15
- expect(result).toContain('`add_note`: "Session end: <summary, next steps>"');
16
- expect(result).toContain('`complete_task` or `abandon_task`');
17
- });
18
- it('shows empty context hint when context is empty', () => {
19
- const result = formatStartTaskResponse({
20
- id: 'TASK-1',
21
- status: 'in_progress',
22
- message: 'Task started',
23
- context: {
24
- isEmpty: true,
25
- index: [],
26
- hint: 'No docs available yet',
27
- },
28
- });
29
- expect(result).toContain('📚 No docs available yet');
30
- });
31
- it('lists context sections with star for relevant ones', () => {
32
- const result = formatStartTaskResponse({
33
- id: 'TASK-1',
34
- status: 'in_progress',
35
- message: 'Task started',
36
- context: {
37
- isEmpty: false,
38
- index: [
39
- { section: 'overview', preview: 'Project overview', updatedAt: '2024-01-01' },
40
- { section: 'api', preview: 'API docs', updatedAt: '2024-01-01' },
41
- { section: 'testing', preview: 'Testing guide', updatedAt: '2024-01-01' },
42
- ],
43
- relevantSections: ['api', 'testing'],
44
- },
45
- });
46
- expect(result).toContain('**Project context available:**');
47
- expect(result).toContain('• overview');
48
- expect(result).toContain('• api ⭐');
49
- expect(result).toContain('• testing ⭐');
50
- expect(result).toContain('Relevant for this task: api, testing');
51
- expect(result).toContain('Use get_context_section to fetch full content.');
52
- });
53
- it('shows basic message and task ID', () => {
54
- const result = formatStartTaskResponse({
55
- id: 'TASK-123',
56
- status: 'in_progress',
57
- message: 'Locked successfully',
58
- });
59
- expect(result).toContain('Started TASK-123: Locked successfully');
60
- });
61
- });
62
- describe('formatCompleteTaskResponse', () => {
63
- it('shows completion message', () => {
64
- const result = formatCompleteTaskResponse({
65
- id: 'TASK-1',
66
- status: 'done',
67
- });
68
- expect(result).toContain('✅ Completed TASK-1');
69
- });
70
- it('shows documentation section with affected sections', () => {
71
- const result = formatCompleteTaskResponse({
72
- id: 'TASK-1',
73
- status: 'done',
74
- documentation: {
75
- hasContext: true,
76
- affectedSections: ['api', 'overview'],
77
- reminder: 'Please review and update docs',
78
- },
79
- });
80
- expect(result).toContain('**📚 Documentation:**');
81
- expect(result).toContain('May need updates: api, overview');
82
- expect(result).toContain('Please review and update docs');
83
- });
84
- it('skips affected sections line when empty', () => {
85
- const result = formatCompleteTaskResponse({
86
- id: 'TASK-1',
87
- status: 'done',
88
- documentation: {
89
- hasContext: true,
90
- affectedSections: [],
91
- reminder: 'No changes needed',
92
- },
93
- });
94
- expect(result).toContain('**📚 Documentation:**');
95
- expect(result).not.toContain('May need updates:');
96
- expect(result).toContain('No changes needed');
97
- });
98
- it('handles missing documentation gracefully', () => {
99
- const result = formatCompleteTaskResponse({
100
- id: 'TASK-1',
101
- status: 'done',
102
- });
103
- expect(result).not.toContain('**📚 Documentation:**');
104
- expect(result).toBe('✅ Completed TASK-1');
105
- });
106
- });
107
- describe('formatAbandonTaskResponse', () => {
108
- it('shows tip when no summary provided', () => {
109
- const result = formatAbandonTaskResponse({ id: 'TASK-1', status: 'planned', message: 'Task abandoned' }, false);
110
- expect(result).toContain('⏸️ Abandoned TASK-1: Task abandoned');
111
- expect(result).toContain('⚠️ Tip: Include a summary next time for better handoff.');
112
- expect(result).toContain('💡 Task returned to "planned". Notes preserved for next agent.');
113
- });
114
- it('does not show tip when summary provided', () => {
115
- const result = formatAbandonTaskResponse({ id: 'TASK-1', status: 'planned', message: 'Task abandoned with handoff' }, true);
116
- expect(result).toContain('⏸️ Abandoned TASK-1');
117
- expect(result).not.toContain('⚠️ Tip:');
118
- expect(result).toContain('💡 Task returned to "planned"');
119
- });
120
- it('always shows notes preserved message', () => {
121
- const withSummary = formatAbandonTaskResponse({ id: 'TASK-1', status: 'planned', message: 'Done' }, true);
122
- const withoutSummary = formatAbandonTaskResponse({ id: 'TASK-1', status: 'planned', message: 'Done' }, false);
123
- expect(withSummary).toContain('Notes preserved for next agent');
124
- expect(withoutSummary).toContain('Notes preserved for next agent');
125
- });
126
- });