@damper/mcp 0.3.13 → 0.3.15

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.
@@ -17,6 +17,7 @@ export interface StartTaskResult {
17
17
  }>;
18
18
  relevantSections?: string[];
19
19
  hint?: string;
20
+ criticalRules?: string[];
20
21
  };
21
22
  }
22
23
  export interface CompleteTaskResult {
@@ -9,22 +9,29 @@
9
9
  */
10
10
  export function formatStartTaskResponse(result) {
11
11
  const lines = [`āœ… Started ${result.id}: ${result.message}`];
12
+ // Critical rules section - shown first and prominently
13
+ if (result.context?.criticalRules && result.context.criticalRules.length > 0) {
14
+ lines.push('\n🚨 **CRITICAL RULES (do not skip):**');
15
+ for (const rule of result.context.criticalRules) {
16
+ lines.push(`• ${rule}`);
17
+ }
18
+ }
12
19
  // Context section - emphatic about reading it first
13
20
  if (result.context) {
14
21
  if (result.context.isEmpty) {
15
22
  lines.push(`\nšŸ“š ${result.context.hint || 'No project context available.'}`);
16
23
  }
17
24
  else {
18
- lines.push('\nāš ļø **BEFORE YOU START:** Read project context to understand patterns and conventions.');
25
+ lines.push('\nšŸ“š **Project context available.** Read sections relevant to your task:');
19
26
  lines.push('');
20
27
  if (result.context.relevantSections && result.context.relevantSections.length > 0) {
21
- lines.push(`**Required reading for this task:**`);
28
+ lines.push(`**Suggested for this task:**`);
22
29
  for (const section of result.context.relevantSections) {
23
30
  lines.push(`→ \`get_context_section("${section}")\``);
24
31
  }
25
32
  lines.push('');
26
33
  }
27
- lines.push('**All available sections:**');
34
+ lines.push('**All sections:**');
28
35
  for (const s of result.context.index) {
29
36
  const relevant = result.context.relevantSections?.includes(s.section) ? ' ⭐' : '';
30
37
  lines.push(`• ${s.section}${relevant}`);
@@ -32,12 +39,11 @@ export function formatStartTaskResponse(result) {
32
39
  }
33
40
  }
34
41
  lines.push('\n---');
35
- lines.push('**šŸ“‹ Required workflow:**');
36
- lines.push('1. **Read context sections above** (contains patterns you must follow)');
37
- lines.push('2. `add_note`: "Session started: <goal>"');
38
- lines.push('3. Work: use `add_commit` for commits, `add_note` for decisions');
39
- lines.push('4. `add_note`: "Session end: <summary>"');
40
- lines.push('5. `complete_task` or `abandon_task` (NEVER skip this)');
42
+ lines.push('**šŸ“‹ Workflow:**');
43
+ lines.push('1. `add_note`: "Session started: <goal>"');
44
+ lines.push('2. Work: use `add_commit` for commits, `add_note` for decisions');
45
+ lines.push('3. `add_note`: "Session end: <summary>"');
46
+ lines.push('4. `complete_task` or `abandon_task`');
41
47
  return lines.join('\n');
42
48
  }
43
49
  /**
package/dist/index.js CHANGED
@@ -360,6 +360,7 @@ const ContextIndexSchema = z.object({
360
360
  })),
361
361
  relevantSections: z.array(z.string()).optional(),
362
362
  hint: z.string().optional(),
363
+ criticalRules: z.array(z.string()).optional(),
363
364
  });
364
365
  // Tool: Start task
365
366
  server.registerTool('start_task', {
@@ -653,7 +654,7 @@ server.registerTool('get_agent_instructions', {
653
654
  openWorldHint: false,
654
655
  },
655
656
  }, async ({ format = 'section' }) => {
656
- const lastModified = '2025-02-04';
657
+ const lastModified = '2025-02-05';
657
658
  const section = `## Task Management with Damper MCP
658
659
 
659
660
  > Last updated: ${lastModified}
@@ -662,7 +663,10 @@ This project uses Damper MCP for task tracking. **You MUST follow this workflow.
662
663
 
663
664
  ### At Session Start (MANDATORY)
664
665
  1. \`get_project_context\` - **READ THIS FIRST.** Contains architecture, conventions, and critical project info.
665
- 2. \`get_context_section\` - Fetch full content for sections relevant to your task
666
+ 2. Load relevant architecture context using blocks (token-efficient):
667
+ - If TASK_CONTEXT.md has an "Available Architecture Context" section, use \`get_section_block_content(section, blockIds)\` to load only the blocks relevant to your task
668
+ - Otherwise, use \`get_section_blocks(section)\` to see what's available, then fetch specific blocks
669
+ - Only fall back to \`get_context_section\` if you need an entire section
666
670
  3. \`list_tasks\` - Check for existing tasks to work on
667
671
  4. If working on a task: \`start_task\` to lock it
668
672
 
@@ -671,13 +675,15 @@ This project uses Damper MCP for task tracking. **You MUST follow this workflow.
671
675
  - \`add_note\` for decisions: "Decision: chose X because Y"
672
676
  - \`update_subtask\` to mark subtask progress
673
677
  - **Follow patterns from project context** - Don't reinvent; use existing conventions
678
+ - When you need architecture context mid-task, use \`get_section_block_content\` to load specific blocks rather than full sections
674
679
 
675
680
  ### Feedback & Changelog Integration
676
681
  - \`link_feedback_to_task\` - Link user feedback IDs to your task (helps track what customer requests led to the feature)
677
682
  - When completing a **public** task, it auto-adds to the project's draft changelog
678
683
  - \`list_changelogs\` - View existing changelogs and drafts
679
- - \`create_changelog\` / \`update_changelog\` - Create or edit changelog entries
684
+ - \`create_changelog\` / \`update_changelog\` - Create or edit changelog entries (use \`summary\` for social-friendly posts)
680
685
  - \`add_to_changelog\` - Manually add completed tasks to a changelog
686
+ - \`publish_changelog\` - Publish with optional email notifications and Twitter posting
681
687
 
682
688
  ### At Session End (MANDATORY)
683
689
  - ALWAYS call \`add_note\` with session summary before stopping
@@ -813,6 +819,10 @@ server.registerTool('update_context_section', {
813
819
  'names like `overview`, `conventions` are sufficient.\n\n' +
814
820
  '**When to add tags**: Only if you have multiple related sections that should surface ' +
815
821
  'together (e.g., all "backend" docs). Skip for simple projects.\n\n' +
822
+ '**criticalRules**: Short, actionable rules that agents MUST follow. These are surfaced ' +
823
+ 'automatically in `start_task` and `get_project_context` responses without requiring ' +
824
+ 'agents to read the full section. Use for rules that, if skipped, cause real problems ' +
825
+ '(e.g., "Use migrations for DB changes: bunx prisma migrate dev --name <feature>").\n\n' +
816
826
  'āš ļø SECURITY WARNING: Do NOT include sensitive information:\n' +
817
827
  '- API keys, secrets, passwords, tokens\n' +
818
828
  '- Database connection strings\n' +
@@ -826,12 +836,14 @@ server.registerTool('update_context_section', {
826
836
  content: z.string().describe('Markdown documentation for this section. Must NOT contain secrets, API keys, or sensitive data.'),
827
837
  tags: z.array(z.string()).optional().describe('Categorization tags (e.g., ["backend", "critical"]). Helps match sections to task labels.'),
828
838
  appliesTo: z.array(z.string()).optional().describe('Module names this section applies to (e.g., ["server", "dashboard"]).'),
839
+ criticalRules: z.array(z.string()).optional().describe('Must-follow rules shown in start_task/get_project_context (e.g., ["Use migrations: bunx prisma migrate dev --name <feature>"])'),
829
840
  }),
830
841
  outputSchema: z.object({
831
842
  success: z.boolean(),
832
843
  section: z.string(),
833
844
  tags: z.array(z.string()).optional(),
834
845
  appliesTo: z.array(z.string()).optional(),
846
+ criticalRules: z.array(z.string()).optional(),
835
847
  warnings: z.array(z.string()).optional(),
836
848
  }),
837
849
  annotations: {
@@ -840,8 +852,13 @@ server.registerTool('update_context_section', {
840
852
  idempotentHint: true,
841
853
  openWorldHint: false,
842
854
  },
843
- }, async ({ section, content, tags, appliesTo }) => {
844
- const result = await api('POST', `/api/agent/context/${encodeURIComponent(section)}`, { content, tags, appliesTo });
855
+ }, async ({ section, content, tags, appliesTo, criticalRules }) => {
856
+ // Encode each path segment individually but preserve slashes for hierarchical sections
857
+ const encodedSection = section
858
+ .split('/')
859
+ .map(part => encodeURIComponent(part))
860
+ .join('/');
861
+ const result = await api('POST', `/api/agent/context/${encodedSection}`, { content, tags, appliesTo, criticalRules });
845
862
  let text = `šŸ“ Updated context section: ${result.section}`;
846
863
  if (result.tags && result.tags.length > 0) {
847
864
  text += ` [${result.tags.join(', ')}]`;
@@ -904,6 +921,7 @@ server.registerTool('sync_project_context', {
904
921
  content: z.string().describe('Markdown documentation (no secrets!)'),
905
922
  tags: z.array(z.string()).optional().describe('Categorization tags'),
906
923
  appliesTo: z.array(z.string()).optional().describe('Module names this applies to'),
924
+ criticalRules: z.array(z.string()).optional().describe('Must-follow rules shown automatically'),
907
925
  })).describe('Array of sections to upload'),
908
926
  }),
909
927
  outputSchema: z.object({
@@ -944,6 +962,7 @@ server.registerTool('get_project_context', {
944
962
  })),
945
963
  relevantSections: z.array(z.string()).optional(),
946
964
  hint: z.string().optional(),
965
+ criticalRules: z.array(z.string()).optional(),
947
966
  }),
948
967
  annotations: {
949
968
  readOnlyHint: true,
@@ -960,7 +979,16 @@ server.registerTool('get_project_context', {
960
979
  structuredContent: data,
961
980
  };
962
981
  }
963
- const lines = ['**Available context sections:**'];
982
+ const lines = [];
983
+ // Critical rules first - most important
984
+ if (data.criticalRules && data.criticalRules.length > 0) {
985
+ lines.push('🚨 **CRITICAL RULES (do not skip):**');
986
+ for (const rule of data.criticalRules) {
987
+ lines.push(`• ${rule}`);
988
+ }
989
+ lines.push('');
990
+ }
991
+ lines.push('**Available context sections:**');
964
992
  for (const s of data.index) {
965
993
  const relevant = data.relevantSections?.includes(s.section) ? ' ⭐' : '';
966
994
  const tags = s.tags && s.tags.length > 0 ? ` [${s.tags.join(', ')}]` : '';
@@ -974,6 +1002,91 @@ server.registerTool('get_project_context', {
974
1002
  structuredContent: data,
975
1003
  };
976
1004
  });
1005
+ // Tool: Get section blocks (heading index)
1006
+ server.registerTool('get_section_blocks', {
1007
+ title: 'Get Section Blocks',
1008
+ description: 'Get a block index for a context section. Returns a list of headings (## and ###) with their ' +
1009
+ 'character counts, without the full content. Use this to see what\'s in a section and then ' +
1010
+ 'fetch only the blocks you need with `get_section_block_content`.\n\n' +
1011
+ '**When to use:**\n' +
1012
+ '- Before fetching a large section, check what blocks it contains\n' +
1013
+ '- To selectively load only relevant parts of a section\n' +
1014
+ '- To reduce token usage by skipping irrelevant subsections',
1015
+ inputSchema: z.object({
1016
+ section: z.string().describe('Section name (e.g., "api", "testing")'),
1017
+ }),
1018
+ outputSchema: z.object({
1019
+ section: z.string(),
1020
+ blocks: z.array(z.object({
1021
+ id: z.string(),
1022
+ heading: z.string().nullable(),
1023
+ level: z.number(),
1024
+ charCount: z.number(),
1025
+ })),
1026
+ totalChars: z.number(),
1027
+ }),
1028
+ annotations: {
1029
+ readOnlyHint: true,
1030
+ destructiveHint: false,
1031
+ idempotentHint: true,
1032
+ openWorldHint: false,
1033
+ },
1034
+ }, async ({ section }) => {
1035
+ const encodedSection = encodeURIComponent(section);
1036
+ const data = await api('GET', `/api/agent/context/${encodedSection}/blocks`);
1037
+ const lines = [`**Blocks in "${data.section}"** (${data.totalChars} chars total):\n`];
1038
+ for (const block of data.blocks) {
1039
+ const heading = block.heading || '(intro)';
1040
+ lines.push(`• ${block.id}: ${heading} (${block.charCount} chars)`);
1041
+ }
1042
+ return {
1043
+ content: [{ type: 'text', text: lines.join('\n') }],
1044
+ structuredContent: data,
1045
+ };
1046
+ });
1047
+ // Tool: Get section block content
1048
+ server.registerTool('get_section_block_content', {
1049
+ title: 'Get Section Block Content',
1050
+ description: 'Get the full content of specific blocks within a section. Use `get_section_blocks` first ' +
1051
+ 'to see available block IDs, then fetch only the ones you need.\n\n' +
1052
+ '**Example workflow:**\n' +
1053
+ '1. `get_section_blocks("api")` → see list of blocks\n' +
1054
+ '2. `get_section_block_content("api", ["authentication", "agent-endpoints"])` → get only relevant blocks',
1055
+ inputSchema: z.object({
1056
+ section: z.string().describe('Section name (e.g., "api", "testing")'),
1057
+ blockIds: z.array(z.string()).describe('Block IDs to fetch (from get_section_blocks)'),
1058
+ }),
1059
+ outputSchema: z.object({
1060
+ section: z.string(),
1061
+ blocks: z.array(z.object({
1062
+ id: z.string(),
1063
+ heading: z.string().nullable(),
1064
+ level: z.number(),
1065
+ content: z.string(),
1066
+ charCount: z.number(),
1067
+ })),
1068
+ totalChars: z.number(),
1069
+ }),
1070
+ annotations: {
1071
+ readOnlyHint: true,
1072
+ destructiveHint: false,
1073
+ idempotentHint: true,
1074
+ openWorldHint: false,
1075
+ },
1076
+ }, async ({ section, blockIds }) => {
1077
+ const encodedSection = encodeURIComponent(section);
1078
+ const encodedBlockIds = blockIds.map(id => encodeURIComponent(id)).join(',');
1079
+ const data = await api('GET', `/api/agent/context/${encodedSection}/blocks/${encodedBlockIds}`);
1080
+ const lines = [];
1081
+ for (const block of data.blocks) {
1082
+ lines.push(block.content);
1083
+ lines.push('');
1084
+ }
1085
+ return {
1086
+ content: [{ type: 'text', text: lines.join('\n').trim() }],
1087
+ structuredContent: data,
1088
+ };
1089
+ });
977
1090
  // Tool: List feedback
978
1091
  server.registerTool('list_feedback', {
979
1092
  title: 'List Feedback',
@@ -1485,6 +1598,7 @@ server.registerTool('create_changelog', {
1485
1598
  title: z.string().describe('Changelog title (e.g., "v2.1.0" or "January 2025 Release")'),
1486
1599
  content: z.string().optional().describe('Initial changelog content (markdown)'),
1487
1600
  version: z.string().optional().describe('Version number'),
1601
+ summary: z.string().max(280).optional().describe('Short summary for social sharing and email subject (max 280 chars)'),
1488
1602
  status: z.enum(['draft', 'published']).optional().describe('Status (default: draft)'),
1489
1603
  }),
1490
1604
  outputSchema: z.object({
@@ -1510,13 +1624,14 @@ server.registerTool('create_changelog', {
1510
1624
  // Tool: Update changelog
1511
1625
  server.registerTool('update_changelog', {
1512
1626
  title: 'Update Changelog',
1513
- description: 'Update a changelog entry. Can update title, content, version, or status.\n\n' +
1514
- '**Publishing:** Set status to "published" to make the changelog public.',
1627
+ description: 'Update a changelog entry. Can update title, content, version, summary, or status.\n\n' +
1628
+ '**Publishing:** Use `publish_changelog` instead to publish with email and Twitter options.',
1515
1629
  inputSchema: z.object({
1516
1630
  changelogId: z.string().describe('Changelog ID'),
1517
1631
  title: z.string().optional().describe('New title'),
1518
1632
  content: z.string().optional().describe('New content (markdown)'),
1519
1633
  version: z.string().optional().describe('Version number'),
1634
+ summary: z.string().max(280).optional().describe('Short summary for social sharing and email subject (max 280 chars)'),
1520
1635
  status: z.enum(['draft', 'published']).optional().describe('Status'),
1521
1636
  }),
1522
1637
  outputSchema: z.object({
@@ -1584,6 +1699,80 @@ server.registerTool('add_to_changelog', {
1584
1699
  structuredContent: result,
1585
1700
  };
1586
1701
  });
1702
+ // Tool: Publish changelog
1703
+ server.registerTool('publish_changelog', {
1704
+ title: 'Publish Changelog',
1705
+ description: 'Publish a changelog with optional email and Twitter notifications.\n\n' +
1706
+ '**Options:**\n' +
1707
+ '- `sendEmail`: Send to subscribers who opted in (default: true if subscribers exist)\n' +
1708
+ '- `postToTwitter`: Post to connected Twitter account (default: false)\n' +
1709
+ '- `summary`: Short text for email subject and tweet (max 280 chars)\n\n' +
1710
+ '**Example workflow:**\n' +
1711
+ '1. Review draft with `list_changelogs`\n' +
1712
+ '2. Update content with `update_changelog` if needed\n' +
1713
+ '3. Publish with `publish_changelog` and notification options',
1714
+ inputSchema: z.object({
1715
+ changelogId: z.string().describe('Changelog ID to publish'),
1716
+ title: z.string().optional().describe('Update title before publishing'),
1717
+ version: z.string().optional().describe('Set version before publishing'),
1718
+ summary: z.string().max(280).optional().describe('Summary for email subject and tweet (max 280 chars)'),
1719
+ sendEmail: z.boolean().optional().describe('Send email to subscribers (default: true if subscribers exist)'),
1720
+ postToTwitter: z.boolean().optional().describe('Post to Twitter if connected (default: false)'),
1721
+ }),
1722
+ outputSchema: z.object({
1723
+ id: z.string(),
1724
+ title: z.string(),
1725
+ publishedAt: z.string(),
1726
+ emailsSent: z.number().optional(),
1727
+ twitterPost: z.object({
1728
+ success: z.boolean(),
1729
+ postUrl: z.string().optional(),
1730
+ error: z.string().optional(),
1731
+ }).optional(),
1732
+ }),
1733
+ annotations: {
1734
+ readOnlyHint: false,
1735
+ destructiveHint: false,
1736
+ idempotentHint: false,
1737
+ openWorldHint: false,
1738
+ },
1739
+ }, async ({ changelogId, title, version, summary, sendEmail, postToTwitter }) => {
1740
+ const body = {};
1741
+ if (title !== undefined)
1742
+ body.title = title;
1743
+ if (version !== undefined)
1744
+ body.version = version;
1745
+ if (summary !== undefined)
1746
+ body.summary = summary;
1747
+ if (sendEmail !== undefined)
1748
+ body.sendEmail = sendEmail;
1749
+ if (postToTwitter !== undefined)
1750
+ body.postToTwitter = postToTwitter;
1751
+ const result = await api('POST', `/api/agent/changelogs/${changelogId}/publish`, body);
1752
+ // Build response message
1753
+ const parts = [`āœ… Published "${result.title}"`];
1754
+ if (result.emailsSent && result.emailsSent > 0) {
1755
+ parts.push(`šŸ“§ ${result.emailsSent} email${result.emailsSent !== 1 ? 's' : ''} sent`);
1756
+ }
1757
+ if (result.twitterPost) {
1758
+ if (result.twitterPost.success && result.twitterPost.postUrl) {
1759
+ parts.push(`🐦 Posted to Twitter: ${result.twitterPost.postUrl}`);
1760
+ }
1761
+ else if (!result.twitterPost.success && result.twitterPost.error) {
1762
+ parts.push(`āš ļø Twitter failed: ${result.twitterPost.error}`);
1763
+ }
1764
+ }
1765
+ return {
1766
+ content: [{ type: 'text', text: parts.join('\n') }],
1767
+ structuredContent: {
1768
+ id: result.id,
1769
+ title: result.title,
1770
+ publishedAt: result.publishedAt,
1771
+ emailsSent: result.emailsSent,
1772
+ twitterPost: result.twitterPost,
1773
+ },
1774
+ };
1775
+ });
1587
1776
  // Start
1588
1777
  async function main() {
1589
1778
  const transport = new StdioServerTransport();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@damper/mcp",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
4
4
  "description": "MCP server for Damper task management",
5
5
  "author": "Damper <hello@usedamper.com>",
6
6
  "repository": {