harmony-mcp 1.1.0 → 1.2.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
@@ -70,12 +70,12 @@ Or add to `~/.claude/settings.json`:
70
70
 
71
71
  ## Supported AI Agents
72
72
 
73
- | Agent | MCP Config | Workflow Command |
74
- |-------|-----------|------------------|
75
- | **Claude Code** | `~/.claude/settings.json` | `/hmy #42` |
76
- | **OpenAI Codex** | `~/.codex/config.toml` | `/prompts:hmy #42` |
77
- | **Cursor** | `.cursor/mcp.json` | MCP tools auto-available |
78
- | **Windsurf** | `~/.codeium/windsurf/mcp_config.json` | MCP tools auto-available |
73
+ | Agent | MCP Config | Workflow Command |
74
+ | ---------------- | ------------------------------------- | ------------------------ |
75
+ | **Claude Code** | `~/.claude/settings.json` | `/hmy #42` |
76
+ | **OpenAI Codex** | `~/.codex/config.toml` | `/prompts:hmy #42` |
77
+ | **Cursor** | `.cursor/mcp.json` | MCP tools auto-available |
78
+ | **Windsurf** | `~/.codeium/windsurf/mcp_config.json` | MCP tools auto-available |
79
79
 
80
80
  ## Card Workflow
81
81
 
@@ -106,6 +106,7 @@ harmony-mcp serve # Start MCP server
106
106
  ## Available Tools
107
107
 
108
108
  ### Card Operations
109
+
109
110
  - `harmony_create_card` - Create a new card
110
111
  - `harmony_update_card` - Update card properties
111
112
  - `harmony_move_card` - Move card to different column
@@ -115,21 +116,25 @@ harmony-mcp serve # Start MCP server
115
116
  - `harmony_get_card` - Get card details
116
117
 
117
118
  ### Column Operations
119
+
118
120
  - `harmony_create_column` - Create new column
119
121
  - `harmony_update_column` - Update column properties
120
122
  - `harmony_delete_column` - Delete column
121
123
 
122
124
  ### Label Operations
125
+
123
126
  - `harmony_create_label` - Create new label
124
127
  - `harmony_add_label_to_card` - Add label to card
125
128
  - `harmony_remove_label_from_card` - Remove label
126
129
 
127
130
  ### Subtask Operations
131
+
128
132
  - `harmony_create_subtask` - Create subtask
129
133
  - `harmony_toggle_subtask` - Toggle completion
130
134
  - `harmony_delete_subtask` - Delete subtask
131
135
 
132
136
  ### Context Operations
137
+
133
138
  - `harmony_list_workspaces` - List workspaces
134
139
  - `harmony_list_projects` - List projects
135
140
  - `harmony_get_board` - Get full board state
@@ -139,9 +144,11 @@ harmony-mcp serve # Start MCP server
139
144
  - `harmony_get_context` - Get current context
140
145
 
141
146
  ### Natural Language
147
+
142
148
  - `harmony_process_command` - Process natural language commands
143
149
 
144
150
  ### Agent Session Tracking
151
+
145
152
  - `harmony_start_agent_session` - Start tracking work on a card
146
153
  - `harmony_update_agent_progress` - Update progress, status, blockers
147
154
  - `harmony_end_agent_session` - End session (completed/paused)
@@ -158,28 +165,28 @@ curl -X GET "https://gethmy.com/api/workspaces" \
158
165
 
159
166
  ### API Endpoints
160
167
 
161
- | Endpoint | Method | Description |
162
- |----------|--------|-------------|
163
- | `/workspaces` | GET | List workspaces |
164
- | `/workspaces/:id/projects` | GET | List projects in workspace |
165
- | `/workspaces/:id/members` | GET | Get workspace members |
166
- | `/board/:projectId` | GET | Get full board state |
167
- | `/cards` | POST | Create card |
168
- | `/cards/:id` | GET | Get card |
169
- | `/cards/:id` | PATCH | Update card |
170
- | `/cards/:id` | DELETE | Delete card |
171
- | `/cards/:id/move` | POST | Move card |
172
- | `/search?q=query` | GET | Search cards |
173
- | `/columns` | POST | Create column |
174
- | `/columns/:id` | PATCH | Update column |
175
- | `/columns/:id` | DELETE | Delete column |
176
- | `/labels` | POST | Create label |
177
- | `/cards/:id/labels` | POST | Add label to card |
178
- | `/cards/:id/labels/:labelId` | DELETE | Remove label |
179
- | `/subtasks` | POST | Create subtask |
180
- | `/subtasks/:id/toggle` | POST | Toggle subtask |
181
- | `/subtasks/:id` | DELETE | Delete subtask |
182
- | `/nlu` | POST | Process natural language |
168
+ | Endpoint | Method | Description |
169
+ | ---------------------------- | ------ | -------------------------- |
170
+ | `/workspaces` | GET | List workspaces |
171
+ | `/workspaces/:id/projects` | GET | List projects in workspace |
172
+ | `/workspaces/:id/members` | GET | Get workspace members |
173
+ | `/board/:projectId` | GET | Get full board state |
174
+ | `/cards` | POST | Create card |
175
+ | `/cards/:id` | GET | Get card |
176
+ | `/cards/:id` | PATCH | Update card |
177
+ | `/cards/:id` | DELETE | Delete card |
178
+ | `/cards/:id/move` | POST | Move card |
179
+ | `/search?q=query` | GET | Search cards |
180
+ | `/columns` | POST | Create column |
181
+ | `/columns/:id` | PATCH | Update column |
182
+ | `/columns/:id` | DELETE | Delete column |
183
+ | `/labels` | POST | Create label |
184
+ | `/cards/:id/labels` | POST | Add label to card |
185
+ | `/cards/:id/labels/:labelId` | DELETE | Remove label |
186
+ | `/subtasks` | POST | Create subtask |
187
+ | `/subtasks/:id/toggle` | POST | Toggle subtask |
188
+ | `/subtasks/:id` | DELETE | Delete subtask |
189
+ | `/nlu` | POST | Process natural language |
183
190
 
184
191
  ## Configuration
185
192
 
@@ -211,13 +218,14 @@ Your configuration is stored in `~/.harmony-mcp/config.json`:
211
218
 
212
219
 
213
220
  ┌──────────────────┐
214
- Supabase
215
- │ (Database) │
221
+ Database
222
+ │ (Supabase) │
216
223
  └──────────────────┘
217
224
  ```
218
225
 
219
226
  **Key Benefits:**
220
- - No Supabase credentials needed - just a Harmony API key
227
+
228
+ - No Database credentials needed - just a Harmony API key
221
229
  - Any Harmony user can use it
222
230
  - Business logic stays in Harmony
223
231
  - Centralized security and rate limiting
package/dist/cli.js CHANGED
@@ -25384,6 +25384,15 @@ class HarmonyApiClient {
25384
25384
  async removeLabelFromCard(cardId, labelId) {
25385
25385
  return this.request("DELETE", `/cards/${cardId}/labels/${labelId}`);
25386
25386
  }
25387
+ async addLinkToCard(sourceCardId, targetCardId, linkType) {
25388
+ return this.request("POST", `/cards/${sourceCardId}/links`, { targetCardId, linkType });
25389
+ }
25390
+ async removeLinkFromCard(linkId) {
25391
+ return this.request("DELETE", `/card-links/${linkId}`);
25392
+ }
25393
+ async getCardLinks(cardId) {
25394
+ return this.request("GET", `/cards/${cardId}/links`);
25395
+ }
25387
25396
  async createColumn(projectId, name) {
25388
25397
  return this.request("POST", "/columns", { projectId, name });
25389
25398
  }
@@ -25433,6 +25442,312 @@ function getClient() {
25433
25442
  return client;
25434
25443
  }
25435
25444
 
25445
+ // src/prompt-builder.ts
25446
+ var LABEL_CATEGORY_MAP = {
25447
+ bug: "bug",
25448
+ fix: "bug",
25449
+ hotfix: "bug",
25450
+ defect: "bug",
25451
+ issue: "bug",
25452
+ error: "bug",
25453
+ feature: "feature",
25454
+ enhancement: "feature",
25455
+ improvement: "feature",
25456
+ new: "feature",
25457
+ design: "design",
25458
+ ui: "design",
25459
+ ux: "design",
25460
+ frontend: "design",
25461
+ styling: "design",
25462
+ review: "review",
25463
+ "code review": "review",
25464
+ pr: "review",
25465
+ feedback: "review",
25466
+ onboarding: "onboarding",
25467
+ documentation: "onboarding",
25468
+ docs: "onboarding",
25469
+ guide: "onboarding",
25470
+ tutorial: "onboarding",
25471
+ epic: "epic",
25472
+ initiative: "epic",
25473
+ project: "epic",
25474
+ milestone: "epic"
25475
+ };
25476
+ var DEFAULT_ROLE_FRAMINGS = {
25477
+ bug: {
25478
+ category: "bug",
25479
+ role: "Senior QA Engineer and Software Developer",
25480
+ perspective: "You are investigating and fixing a bug report.",
25481
+ focus: [
25482
+ "Root cause analysis",
25483
+ "Steps to reproduce",
25484
+ "Impact assessment",
25485
+ "Fix implementation",
25486
+ "Regression prevention",
25487
+ "Test cases to prevent recurrence"
25488
+ ],
25489
+ outputSuggestions: [
25490
+ "Bug triage summary",
25491
+ "Root cause explanation",
25492
+ "Fix implementation plan",
25493
+ "Test cases"
25494
+ ]
25495
+ },
25496
+ feature: {
25497
+ category: "feature",
25498
+ role: "Product Engineer",
25499
+ perspective: "You are implementing a new feature or enhancement.",
25500
+ focus: [
25501
+ "User requirements",
25502
+ "Technical specification",
25503
+ "Implementation approach",
25504
+ "Edge cases",
25505
+ "Acceptance criteria",
25506
+ "Integration points"
25507
+ ],
25508
+ outputSuggestions: [
25509
+ "Technical specification",
25510
+ "Implementation tasks",
25511
+ "Acceptance criteria checklist",
25512
+ "API design"
25513
+ ]
25514
+ },
25515
+ design: {
25516
+ category: "design",
25517
+ role: "UX Designer and Frontend Developer",
25518
+ perspective: "You are designing and implementing a user interface.",
25519
+ focus: [
25520
+ "User experience flow",
25521
+ "Visual design consistency",
25522
+ "Accessibility (WCAG)",
25523
+ "Responsive behavior",
25524
+ "Component architecture",
25525
+ "Interaction patterns"
25526
+ ],
25527
+ outputSuggestions: [
25528
+ "User flow diagram",
25529
+ "Component specifications",
25530
+ "Accessibility checklist",
25531
+ "Responsive breakpoints"
25532
+ ]
25533
+ },
25534
+ review: {
25535
+ category: "review",
25536
+ role: "Code Reviewer and Technical Lead",
25537
+ perspective: "You are reviewing code for quality, correctness, and maintainability.",
25538
+ focus: [
25539
+ "Code correctness",
25540
+ "Performance implications",
25541
+ "Security considerations",
25542
+ "Testing coverage",
25543
+ "Documentation",
25544
+ "Best practices adherence"
25545
+ ],
25546
+ outputSuggestions: [
25547
+ "Review checklist",
25548
+ "Suggested improvements",
25549
+ "Test scenarios",
25550
+ "Security audit"
25551
+ ]
25552
+ },
25553
+ onboarding: {
25554
+ category: "onboarding",
25555
+ role: "Technical Writer and Developer Advocate",
25556
+ perspective: "You are creating documentation or onboarding materials.",
25557
+ focus: [
25558
+ "Clear step-by-step instructions",
25559
+ "Prerequisites and setup",
25560
+ "Common pitfalls",
25561
+ "Examples and use cases",
25562
+ "Troubleshooting guide",
25563
+ "Related resources"
25564
+ ],
25565
+ outputSuggestions: [
25566
+ "Getting started guide",
25567
+ "Step-by-step tutorial",
25568
+ "FAQ section",
25569
+ "Troubleshooting guide"
25570
+ ]
25571
+ },
25572
+ epic: {
25573
+ category: "epic",
25574
+ role: "Technical Project Manager and Architect",
25575
+ perspective: "You are planning and coordinating a large initiative.",
25576
+ focus: [
25577
+ "Scope definition",
25578
+ "Task breakdown",
25579
+ "Dependencies",
25580
+ "Risk assessment",
25581
+ "Timeline considerations",
25582
+ "Success metrics"
25583
+ ],
25584
+ outputSuggestions: [
25585
+ "Epic breakdown into stories",
25586
+ "Dependency graph",
25587
+ "Risk mitigation plan",
25588
+ "Success criteria"
25589
+ ]
25590
+ },
25591
+ custom: {
25592
+ category: "custom",
25593
+ role: "Software Engineer",
25594
+ perspective: "You are working on a software task.",
25595
+ focus: [
25596
+ "Understanding requirements",
25597
+ "Implementation approach",
25598
+ "Quality considerations",
25599
+ "Testing strategy"
25600
+ ],
25601
+ outputSuggestions: [
25602
+ "Implementation plan",
25603
+ "Technical notes",
25604
+ "Task checklist"
25605
+ ]
25606
+ }
25607
+ };
25608
+ var VARIANT_INSTRUCTIONS = {
25609
+ analysis: `ANALYSIS MODE: Analyze this task thoroughly. Identify requirements, constraints, edge cases, and potential challenges. Do NOT implement anything yet - focus on understanding and planning.`,
25610
+ draft: `DRAFT MODE: Create a detailed implementation plan with code structure, key decisions, and approach. Include pseudocode or skeleton code where helpful. This is for review before full implementation.`,
25611
+ execute: `EXECUTE MODE: Implement this task completely. Write production-ready code following best practices. Include necessary tests and documentation.`
25612
+ };
25613
+ function inferCategoryFromLabels(labels) {
25614
+ for (const label of labels) {
25615
+ const normalizedName = label.name.toLowerCase().trim();
25616
+ if (LABEL_CATEGORY_MAP[normalizedName]) {
25617
+ return LABEL_CATEGORY_MAP[normalizedName];
25618
+ }
25619
+ for (const [key, category] of Object.entries(LABEL_CATEGORY_MAP)) {
25620
+ if (normalizedName.includes(key) || key.includes(normalizedName)) {
25621
+ return category;
25622
+ }
25623
+ }
25624
+ }
25625
+ return "custom";
25626
+ }
25627
+ function getRoleFraming(category) {
25628
+ return DEFAULT_ROLE_FRAMINGS[category];
25629
+ }
25630
+ function estimateTokens(text) {
25631
+ return Math.ceil(text.length / 4);
25632
+ }
25633
+ function formatSubtasks(subtasks) {
25634
+ if (subtasks.length === 0)
25635
+ return "";
25636
+ const completed = subtasks.filter((s) => s.completed).length;
25637
+ const lines = subtasks.map((s) => ` ${s.completed ? "[x]" : "[ ]"} ${s.title}`);
25638
+ return `
25639
+ ## Subtasks (${completed}/${subtasks.length} completed)
25640
+ ${lines.join(`
25641
+ `)}`;
25642
+ }
25643
+ function formatLabels(labels) {
25644
+ if (labels.length === 0)
25645
+ return "";
25646
+ return `
25647
+ **Labels:** ${labels.map((l) => l.name).join(", ")}`;
25648
+ }
25649
+ function formatLinkedCards(links) {
25650
+ if (!links || links.length === 0)
25651
+ return "";
25652
+ const lines = links.map((link) => {
25653
+ const prefix = link.direction === "outgoing" ? "->" : "<-";
25654
+ return ` ${prefix} #${link.target_card.short_id}: ${link.target_card.title} (${link.display_type})`;
25655
+ });
25656
+ return `
25657
+ ## Related Cards
25658
+ ${lines.join(`
25659
+ `)}`;
25660
+ }
25661
+ function generatePrompt(options) {
25662
+ const { card, column, variant, customConstraints } = options;
25663
+ const contextOpts = {
25664
+ includeTitle: true,
25665
+ includeDescription: true,
25666
+ includeLabels: true,
25667
+ includeSubtasks: true,
25668
+ includeActivity: false,
25669
+ includeAssignee: true,
25670
+ includeDueDate: true,
25671
+ includePriority: true,
25672
+ includeLinks: true,
25673
+ includeColumn: true,
25674
+ ...options.contextOptions
25675
+ };
25676
+ const labels = card.labels || [];
25677
+ const subtasks = card.subtasks || [];
25678
+ const links = card.links || [];
25679
+ const category = inferCategoryFromLabels(labels);
25680
+ const roleFraming = getRoleFraming(category);
25681
+ const sections = [];
25682
+ sections.push(`# Role: ${roleFraming.role}
25683
+ `);
25684
+ sections.push(roleFraming.perspective);
25685
+ sections.push("");
25686
+ sections.push(VARIANT_INSTRUCTIONS[variant]);
25687
+ sections.push("");
25688
+ sections.push(`# Task: ${card.title}`);
25689
+ if (contextOpts.includeColumn && column) {
25690
+ sections.push(`**Status:** ${column.name}`);
25691
+ }
25692
+ if (contextOpts.includePriority) {
25693
+ sections.push(`**Priority:** ${card.priority}`);
25694
+ }
25695
+ if (contextOpts.includeDueDate && card.due_date) {
25696
+ sections.push(`**Due:** ${card.due_date}`);
25697
+ }
25698
+ if (contextOpts.includeAssignee && card.assignee) {
25699
+ sections.push(`**Assignee:** ${card.assignee.full_name || card.assignee.email}`);
25700
+ }
25701
+ if (contextOpts.includeLabels && labels.length > 0) {
25702
+ sections.push(formatLabels(labels));
25703
+ }
25704
+ if (contextOpts.includeDescription && card.description) {
25705
+ sections.push(`
25706
+ ## Description
25707
+ ${card.description}`);
25708
+ }
25709
+ if (contextOpts.includeSubtasks && subtasks.length > 0) {
25710
+ sections.push(formatSubtasks(subtasks));
25711
+ }
25712
+ if (contextOpts.includeLinks && links.length > 0) {
25713
+ sections.push(formatLinkedCards(links));
25714
+ }
25715
+ sections.push(`
25716
+ ## Focus Areas`);
25717
+ roleFraming.focus.forEach((f) => {
25718
+ sections.push(`- ${f}`);
25719
+ });
25720
+ sections.push(`
25721
+ ## Suggested Outputs`);
25722
+ roleFraming.outputSuggestions.forEach((s) => {
25723
+ sections.push(`- ${s}`);
25724
+ });
25725
+ if (customConstraints) {
25726
+ sections.push(`
25727
+ ## Additional Instructions
25728
+ ${customConstraints}`);
25729
+ }
25730
+ sections.push(`
25731
+ ---
25732
+ *Card #${card.short_id} | Generated for ${variant} mode*`);
25733
+ const prompt = sections.join(`
25734
+ `);
25735
+ return {
25736
+ prompt,
25737
+ variant,
25738
+ category,
25739
+ role: roleFraming.role,
25740
+ contextSummary: {
25741
+ hasDescription: !!card.description,
25742
+ labelCount: labels.length,
25743
+ subtaskCount: subtasks.length,
25744
+ completedSubtasks: subtasks.filter((s) => s.completed).length,
25745
+ linkedCardCount: links.length
25746
+ },
25747
+ tokenEstimate: estimateTokens(prompt)
25748
+ };
25749
+ }
25750
+
25436
25751
  // src/server.ts
25437
25752
  var TOOLS = {
25438
25753
  harmony_create_card: {
@@ -25596,6 +25911,42 @@ var TOOLS = {
25596
25911
  required: ["cardId", "labelId"]
25597
25912
  }
25598
25913
  },
25914
+ harmony_add_link_to_card: {
25915
+ description: "Create a link between two cards. Link types: relates_to, blocks, duplicates, is_part_of",
25916
+ inputSchema: {
25917
+ type: "object",
25918
+ properties: {
25919
+ sourceCardId: { type: "string", description: "The card creating the link from" },
25920
+ targetCardId: { type: "string", description: "The card being linked to" },
25921
+ linkType: {
25922
+ type: "string",
25923
+ enum: ["relates_to", "blocks", "duplicates", "is_part_of"],
25924
+ description: "Type of relationship: relates_to (generic), blocks (source blocks target), duplicates (source duplicates target), is_part_of (source is part of target)"
25925
+ }
25926
+ },
25927
+ required: ["sourceCardId", "targetCardId", "linkType"]
25928
+ }
25929
+ },
25930
+ harmony_remove_link_from_card: {
25931
+ description: "Remove a link between cards",
25932
+ inputSchema: {
25933
+ type: "object",
25934
+ properties: {
25935
+ linkId: { type: "string", description: "The link ID to remove" }
25936
+ },
25937
+ required: ["linkId"]
25938
+ }
25939
+ },
25940
+ harmony_get_card_links: {
25941
+ description: "Get all links for a card",
25942
+ inputSchema: {
25943
+ type: "object",
25944
+ properties: {
25945
+ cardId: { type: "string", description: "Card ID to get links for" }
25946
+ },
25947
+ required: ["cardId"]
25948
+ }
25949
+ },
25599
25950
  harmony_create_subtask: {
25600
25951
  description: "Create a subtask on a card",
25601
25952
  inputSchema: {
@@ -25759,6 +26110,27 @@ var TOOLS = {
25759
26110
  },
25760
26111
  required: ["cardId"]
25761
26112
  }
26113
+ },
26114
+ harmony_generate_prompt: {
26115
+ description: "Generate an AI-ready prompt from a card. Automatically infers role and focus based on labels (bug, feature, design, etc.). Use this to create context-rich prompts for working on cards.",
26116
+ inputSchema: {
26117
+ type: "object",
26118
+ properties: {
26119
+ cardId: { type: "string", description: "Card ID (UUID) to generate prompt from" },
26120
+ shortId: { type: "number", description: "Card short ID (e.g., 42 for #42) - alternative to cardId" },
26121
+ projectId: { type: "string", description: "Project ID (required if using shortId, optional if context set)" },
26122
+ variant: {
26123
+ type: "string",
26124
+ enum: ["analysis", "draft", "execute"],
26125
+ description: "Prompt variant: analysis (understand/plan), draft (design solution), execute (implement fully). Default: execute"
26126
+ },
26127
+ includeSubtasks: { type: "boolean", description: "Include subtasks in prompt (default: true)" },
26128
+ includeLinks: { type: "boolean", description: "Include linked cards in prompt (default: true)" },
26129
+ includeDescription: { type: "boolean", description: "Include description in prompt (default: true)" },
26130
+ customConstraints: { type: "string", description: "Additional instructions to append to the prompt" }
26131
+ },
26132
+ required: []
26133
+ }
25762
26134
  }
25763
26135
  };
25764
26136
  var RESOURCES = [
@@ -26001,6 +26373,23 @@ Include: cards moved recently, current in-progress items, blocked or high-priori
26001
26373
  await client2.removeLabelFromCard(cardId, labelId);
26002
26374
  return { success: true };
26003
26375
  }
26376
+ case "harmony_add_link_to_card": {
26377
+ const sourceCardId = exports_external.string().uuid().parse(args.sourceCardId);
26378
+ const targetCardId = exports_external.string().uuid().parse(args.targetCardId);
26379
+ const linkType = exports_external.enum(["relates_to", "blocks", "duplicates", "is_part_of"]).parse(args.linkType);
26380
+ const result = await client2.addLinkToCard(sourceCardId, targetCardId, linkType);
26381
+ return { success: true, ...result };
26382
+ }
26383
+ case "harmony_remove_link_from_card": {
26384
+ const linkId = exports_external.string().uuid().parse(args.linkId);
26385
+ await client2.removeLinkFromCard(linkId);
26386
+ return { success: true };
26387
+ }
26388
+ case "harmony_get_card_links": {
26389
+ const cardId = exports_external.string().uuid().parse(args.cardId);
26390
+ const result = await client2.getCardLinks(cardId);
26391
+ return result;
26392
+ }
26004
26393
  case "harmony_create_subtask": {
26005
26394
  const cardId = exports_external.string().uuid().parse(args.cardId);
26006
26395
  const title = exports_external.string().min(1).parse(args.title);
@@ -26118,6 +26507,61 @@ Include: cards moved recently, current in-progress items, blocked or high-priori
26118
26507
  });
26119
26508
  return { success: true, ...result };
26120
26509
  }
26510
+ case "harmony_generate_prompt": {
26511
+ let cardData;
26512
+ let columnData = null;
26513
+ if (args.cardId) {
26514
+ const cardId = exports_external.string().uuid().parse(args.cardId);
26515
+ const cardResult = await client2.getCard(cardId);
26516
+ cardData = cardResult.card;
26517
+ } else if (args.shortId !== undefined) {
26518
+ const shortId = exports_external.number().int().positive().parse(args.shortId);
26519
+ const projectId = args.projectId || getActiveProjectId();
26520
+ if (!projectId) {
26521
+ throw new Error("Project ID required when using shortId. Use harmony_set_project_context or provide projectId.");
26522
+ }
26523
+ const cardResult = await client2.getCardByShortId(projectId, shortId);
26524
+ cardData = cardResult.card;
26525
+ } else {
26526
+ throw new Error("Either cardId or shortId must be provided");
26527
+ }
26528
+ const projectIdForBoard = args.projectId || getActiveProjectId() || cardData.project_id;
26529
+ if (projectIdForBoard) {
26530
+ try {
26531
+ const board = await client2.getBoard(projectIdForBoard, { summary: true });
26532
+ const columnId = cardData.column_id;
26533
+ const column = board.columns.find((col) => col.id === columnId);
26534
+ if (column) {
26535
+ columnData = { name: column.name };
26536
+ }
26537
+ } catch {}
26538
+ }
26539
+ const variant = args.variant || "execute";
26540
+ const contextOptions = {};
26541
+ if (args.includeSubtasks !== undefined) {
26542
+ contextOptions.includeSubtasks = args.includeSubtasks === true || args.includeSubtasks === "true";
26543
+ }
26544
+ if (args.includeLinks !== undefined) {
26545
+ contextOptions.includeLinks = args.includeLinks === true || args.includeLinks === "true";
26546
+ }
26547
+ if (args.includeDescription !== undefined) {
26548
+ contextOptions.includeDescription = args.includeDescription === true || args.includeDescription === "true";
26549
+ }
26550
+ const result = generatePrompt({
26551
+ card: cardData,
26552
+ column: columnData,
26553
+ variant,
26554
+ contextOptions,
26555
+ customConstraints: args.customConstraints
26556
+ });
26557
+ return {
26558
+ success: true,
26559
+ cardId: cardData.id,
26560
+ shortId: cardData.short_id,
26561
+ title: cardData.title,
26562
+ ...result
26563
+ };
26564
+ }
26121
26565
  default:
26122
26566
  throw new Error(`Unknown tool: ${name}`);
26123
26567
  }
package/dist/index.js CHANGED
@@ -23146,6 +23146,15 @@ class HarmonyApiClient {
23146
23146
  async removeLabelFromCard(cardId, labelId) {
23147
23147
  return this.request("DELETE", `/cards/${cardId}/labels/${labelId}`);
23148
23148
  }
23149
+ async addLinkToCard(sourceCardId, targetCardId, linkType) {
23150
+ return this.request("POST", `/cards/${sourceCardId}/links`, { targetCardId, linkType });
23151
+ }
23152
+ async removeLinkFromCard(linkId) {
23153
+ return this.request("DELETE", `/card-links/${linkId}`);
23154
+ }
23155
+ async getCardLinks(cardId) {
23156
+ return this.request("GET", `/cards/${cardId}/links`);
23157
+ }
23149
23158
  async createColumn(projectId, name) {
23150
23159
  return this.request("POST", "/columns", { projectId, name });
23151
23160
  }
@@ -23195,6 +23204,312 @@ function getClient() {
23195
23204
  return client;
23196
23205
  }
23197
23206
 
23207
+ // src/prompt-builder.ts
23208
+ var LABEL_CATEGORY_MAP = {
23209
+ bug: "bug",
23210
+ fix: "bug",
23211
+ hotfix: "bug",
23212
+ defect: "bug",
23213
+ issue: "bug",
23214
+ error: "bug",
23215
+ feature: "feature",
23216
+ enhancement: "feature",
23217
+ improvement: "feature",
23218
+ new: "feature",
23219
+ design: "design",
23220
+ ui: "design",
23221
+ ux: "design",
23222
+ frontend: "design",
23223
+ styling: "design",
23224
+ review: "review",
23225
+ "code review": "review",
23226
+ pr: "review",
23227
+ feedback: "review",
23228
+ onboarding: "onboarding",
23229
+ documentation: "onboarding",
23230
+ docs: "onboarding",
23231
+ guide: "onboarding",
23232
+ tutorial: "onboarding",
23233
+ epic: "epic",
23234
+ initiative: "epic",
23235
+ project: "epic",
23236
+ milestone: "epic"
23237
+ };
23238
+ var DEFAULT_ROLE_FRAMINGS = {
23239
+ bug: {
23240
+ category: "bug",
23241
+ role: "Senior QA Engineer and Software Developer",
23242
+ perspective: "You are investigating and fixing a bug report.",
23243
+ focus: [
23244
+ "Root cause analysis",
23245
+ "Steps to reproduce",
23246
+ "Impact assessment",
23247
+ "Fix implementation",
23248
+ "Regression prevention",
23249
+ "Test cases to prevent recurrence"
23250
+ ],
23251
+ outputSuggestions: [
23252
+ "Bug triage summary",
23253
+ "Root cause explanation",
23254
+ "Fix implementation plan",
23255
+ "Test cases"
23256
+ ]
23257
+ },
23258
+ feature: {
23259
+ category: "feature",
23260
+ role: "Product Engineer",
23261
+ perspective: "You are implementing a new feature or enhancement.",
23262
+ focus: [
23263
+ "User requirements",
23264
+ "Technical specification",
23265
+ "Implementation approach",
23266
+ "Edge cases",
23267
+ "Acceptance criteria",
23268
+ "Integration points"
23269
+ ],
23270
+ outputSuggestions: [
23271
+ "Technical specification",
23272
+ "Implementation tasks",
23273
+ "Acceptance criteria checklist",
23274
+ "API design"
23275
+ ]
23276
+ },
23277
+ design: {
23278
+ category: "design",
23279
+ role: "UX Designer and Frontend Developer",
23280
+ perspective: "You are designing and implementing a user interface.",
23281
+ focus: [
23282
+ "User experience flow",
23283
+ "Visual design consistency",
23284
+ "Accessibility (WCAG)",
23285
+ "Responsive behavior",
23286
+ "Component architecture",
23287
+ "Interaction patterns"
23288
+ ],
23289
+ outputSuggestions: [
23290
+ "User flow diagram",
23291
+ "Component specifications",
23292
+ "Accessibility checklist",
23293
+ "Responsive breakpoints"
23294
+ ]
23295
+ },
23296
+ review: {
23297
+ category: "review",
23298
+ role: "Code Reviewer and Technical Lead",
23299
+ perspective: "You are reviewing code for quality, correctness, and maintainability.",
23300
+ focus: [
23301
+ "Code correctness",
23302
+ "Performance implications",
23303
+ "Security considerations",
23304
+ "Testing coverage",
23305
+ "Documentation",
23306
+ "Best practices adherence"
23307
+ ],
23308
+ outputSuggestions: [
23309
+ "Review checklist",
23310
+ "Suggested improvements",
23311
+ "Test scenarios",
23312
+ "Security audit"
23313
+ ]
23314
+ },
23315
+ onboarding: {
23316
+ category: "onboarding",
23317
+ role: "Technical Writer and Developer Advocate",
23318
+ perspective: "You are creating documentation or onboarding materials.",
23319
+ focus: [
23320
+ "Clear step-by-step instructions",
23321
+ "Prerequisites and setup",
23322
+ "Common pitfalls",
23323
+ "Examples and use cases",
23324
+ "Troubleshooting guide",
23325
+ "Related resources"
23326
+ ],
23327
+ outputSuggestions: [
23328
+ "Getting started guide",
23329
+ "Step-by-step tutorial",
23330
+ "FAQ section",
23331
+ "Troubleshooting guide"
23332
+ ]
23333
+ },
23334
+ epic: {
23335
+ category: "epic",
23336
+ role: "Technical Project Manager and Architect",
23337
+ perspective: "You are planning and coordinating a large initiative.",
23338
+ focus: [
23339
+ "Scope definition",
23340
+ "Task breakdown",
23341
+ "Dependencies",
23342
+ "Risk assessment",
23343
+ "Timeline considerations",
23344
+ "Success metrics"
23345
+ ],
23346
+ outputSuggestions: [
23347
+ "Epic breakdown into stories",
23348
+ "Dependency graph",
23349
+ "Risk mitigation plan",
23350
+ "Success criteria"
23351
+ ]
23352
+ },
23353
+ custom: {
23354
+ category: "custom",
23355
+ role: "Software Engineer",
23356
+ perspective: "You are working on a software task.",
23357
+ focus: [
23358
+ "Understanding requirements",
23359
+ "Implementation approach",
23360
+ "Quality considerations",
23361
+ "Testing strategy"
23362
+ ],
23363
+ outputSuggestions: [
23364
+ "Implementation plan",
23365
+ "Technical notes",
23366
+ "Task checklist"
23367
+ ]
23368
+ }
23369
+ };
23370
+ var VARIANT_INSTRUCTIONS = {
23371
+ analysis: `ANALYSIS MODE: Analyze this task thoroughly. Identify requirements, constraints, edge cases, and potential challenges. Do NOT implement anything yet - focus on understanding and planning.`,
23372
+ draft: `DRAFT MODE: Create a detailed implementation plan with code structure, key decisions, and approach. Include pseudocode or skeleton code where helpful. This is for review before full implementation.`,
23373
+ execute: `EXECUTE MODE: Implement this task completely. Write production-ready code following best practices. Include necessary tests and documentation.`
23374
+ };
23375
+ function inferCategoryFromLabels(labels) {
23376
+ for (const label of labels) {
23377
+ const normalizedName = label.name.toLowerCase().trim();
23378
+ if (LABEL_CATEGORY_MAP[normalizedName]) {
23379
+ return LABEL_CATEGORY_MAP[normalizedName];
23380
+ }
23381
+ for (const [key, category] of Object.entries(LABEL_CATEGORY_MAP)) {
23382
+ if (normalizedName.includes(key) || key.includes(normalizedName)) {
23383
+ return category;
23384
+ }
23385
+ }
23386
+ }
23387
+ return "custom";
23388
+ }
23389
+ function getRoleFraming(category) {
23390
+ return DEFAULT_ROLE_FRAMINGS[category];
23391
+ }
23392
+ function estimateTokens(text) {
23393
+ return Math.ceil(text.length / 4);
23394
+ }
23395
+ function formatSubtasks(subtasks) {
23396
+ if (subtasks.length === 0)
23397
+ return "";
23398
+ const completed = subtasks.filter((s) => s.completed).length;
23399
+ const lines = subtasks.map((s) => ` ${s.completed ? "[x]" : "[ ]"} ${s.title}`);
23400
+ return `
23401
+ ## Subtasks (${completed}/${subtasks.length} completed)
23402
+ ${lines.join(`
23403
+ `)}`;
23404
+ }
23405
+ function formatLabels(labels) {
23406
+ if (labels.length === 0)
23407
+ return "";
23408
+ return `
23409
+ **Labels:** ${labels.map((l) => l.name).join(", ")}`;
23410
+ }
23411
+ function formatLinkedCards(links) {
23412
+ if (!links || links.length === 0)
23413
+ return "";
23414
+ const lines = links.map((link) => {
23415
+ const prefix = link.direction === "outgoing" ? "->" : "<-";
23416
+ return ` ${prefix} #${link.target_card.short_id}: ${link.target_card.title} (${link.display_type})`;
23417
+ });
23418
+ return `
23419
+ ## Related Cards
23420
+ ${lines.join(`
23421
+ `)}`;
23422
+ }
23423
+ function generatePrompt(options) {
23424
+ const { card, column, variant, customConstraints } = options;
23425
+ const contextOpts = {
23426
+ includeTitle: true,
23427
+ includeDescription: true,
23428
+ includeLabels: true,
23429
+ includeSubtasks: true,
23430
+ includeActivity: false,
23431
+ includeAssignee: true,
23432
+ includeDueDate: true,
23433
+ includePriority: true,
23434
+ includeLinks: true,
23435
+ includeColumn: true,
23436
+ ...options.contextOptions
23437
+ };
23438
+ const labels = card.labels || [];
23439
+ const subtasks = card.subtasks || [];
23440
+ const links = card.links || [];
23441
+ const category = inferCategoryFromLabels(labels);
23442
+ const roleFraming = getRoleFraming(category);
23443
+ const sections = [];
23444
+ sections.push(`# Role: ${roleFraming.role}
23445
+ `);
23446
+ sections.push(roleFraming.perspective);
23447
+ sections.push("");
23448
+ sections.push(VARIANT_INSTRUCTIONS[variant]);
23449
+ sections.push("");
23450
+ sections.push(`# Task: ${card.title}`);
23451
+ if (contextOpts.includeColumn && column) {
23452
+ sections.push(`**Status:** ${column.name}`);
23453
+ }
23454
+ if (contextOpts.includePriority) {
23455
+ sections.push(`**Priority:** ${card.priority}`);
23456
+ }
23457
+ if (contextOpts.includeDueDate && card.due_date) {
23458
+ sections.push(`**Due:** ${card.due_date}`);
23459
+ }
23460
+ if (contextOpts.includeAssignee && card.assignee) {
23461
+ sections.push(`**Assignee:** ${card.assignee.full_name || card.assignee.email}`);
23462
+ }
23463
+ if (contextOpts.includeLabels && labels.length > 0) {
23464
+ sections.push(formatLabels(labels));
23465
+ }
23466
+ if (contextOpts.includeDescription && card.description) {
23467
+ sections.push(`
23468
+ ## Description
23469
+ ${card.description}`);
23470
+ }
23471
+ if (contextOpts.includeSubtasks && subtasks.length > 0) {
23472
+ sections.push(formatSubtasks(subtasks));
23473
+ }
23474
+ if (contextOpts.includeLinks && links.length > 0) {
23475
+ sections.push(formatLinkedCards(links));
23476
+ }
23477
+ sections.push(`
23478
+ ## Focus Areas`);
23479
+ roleFraming.focus.forEach((f) => {
23480
+ sections.push(`- ${f}`);
23481
+ });
23482
+ sections.push(`
23483
+ ## Suggested Outputs`);
23484
+ roleFraming.outputSuggestions.forEach((s) => {
23485
+ sections.push(`- ${s}`);
23486
+ });
23487
+ if (customConstraints) {
23488
+ sections.push(`
23489
+ ## Additional Instructions
23490
+ ${customConstraints}`);
23491
+ }
23492
+ sections.push(`
23493
+ ---
23494
+ *Card #${card.short_id} | Generated for ${variant} mode*`);
23495
+ const prompt = sections.join(`
23496
+ `);
23497
+ return {
23498
+ prompt,
23499
+ variant,
23500
+ category,
23501
+ role: roleFraming.role,
23502
+ contextSummary: {
23503
+ hasDescription: !!card.description,
23504
+ labelCount: labels.length,
23505
+ subtaskCount: subtasks.length,
23506
+ completedSubtasks: subtasks.filter((s) => s.completed).length,
23507
+ linkedCardCount: links.length
23508
+ },
23509
+ tokenEstimate: estimateTokens(prompt)
23510
+ };
23511
+ }
23512
+
23198
23513
  // src/server.ts
23199
23514
  var TOOLS = {
23200
23515
  harmony_create_card: {
@@ -23358,6 +23673,42 @@ var TOOLS = {
23358
23673
  required: ["cardId", "labelId"]
23359
23674
  }
23360
23675
  },
23676
+ harmony_add_link_to_card: {
23677
+ description: "Create a link between two cards. Link types: relates_to, blocks, duplicates, is_part_of",
23678
+ inputSchema: {
23679
+ type: "object",
23680
+ properties: {
23681
+ sourceCardId: { type: "string", description: "The card creating the link from" },
23682
+ targetCardId: { type: "string", description: "The card being linked to" },
23683
+ linkType: {
23684
+ type: "string",
23685
+ enum: ["relates_to", "blocks", "duplicates", "is_part_of"],
23686
+ description: "Type of relationship: relates_to (generic), blocks (source blocks target), duplicates (source duplicates target), is_part_of (source is part of target)"
23687
+ }
23688
+ },
23689
+ required: ["sourceCardId", "targetCardId", "linkType"]
23690
+ }
23691
+ },
23692
+ harmony_remove_link_from_card: {
23693
+ description: "Remove a link between cards",
23694
+ inputSchema: {
23695
+ type: "object",
23696
+ properties: {
23697
+ linkId: { type: "string", description: "The link ID to remove" }
23698
+ },
23699
+ required: ["linkId"]
23700
+ }
23701
+ },
23702
+ harmony_get_card_links: {
23703
+ description: "Get all links for a card",
23704
+ inputSchema: {
23705
+ type: "object",
23706
+ properties: {
23707
+ cardId: { type: "string", description: "Card ID to get links for" }
23708
+ },
23709
+ required: ["cardId"]
23710
+ }
23711
+ },
23361
23712
  harmony_create_subtask: {
23362
23713
  description: "Create a subtask on a card",
23363
23714
  inputSchema: {
@@ -23521,6 +23872,27 @@ var TOOLS = {
23521
23872
  },
23522
23873
  required: ["cardId"]
23523
23874
  }
23875
+ },
23876
+ harmony_generate_prompt: {
23877
+ description: "Generate an AI-ready prompt from a card. Automatically infers role and focus based on labels (bug, feature, design, etc.). Use this to create context-rich prompts for working on cards.",
23878
+ inputSchema: {
23879
+ type: "object",
23880
+ properties: {
23881
+ cardId: { type: "string", description: "Card ID (UUID) to generate prompt from" },
23882
+ shortId: { type: "number", description: "Card short ID (e.g., 42 for #42) - alternative to cardId" },
23883
+ projectId: { type: "string", description: "Project ID (required if using shortId, optional if context set)" },
23884
+ variant: {
23885
+ type: "string",
23886
+ enum: ["analysis", "draft", "execute"],
23887
+ description: "Prompt variant: analysis (understand/plan), draft (design solution), execute (implement fully). Default: execute"
23888
+ },
23889
+ includeSubtasks: { type: "boolean", description: "Include subtasks in prompt (default: true)" },
23890
+ includeLinks: { type: "boolean", description: "Include linked cards in prompt (default: true)" },
23891
+ includeDescription: { type: "boolean", description: "Include description in prompt (default: true)" },
23892
+ customConstraints: { type: "string", description: "Additional instructions to append to the prompt" }
23893
+ },
23894
+ required: []
23895
+ }
23524
23896
  }
23525
23897
  };
23526
23898
  var RESOURCES = [
@@ -23763,6 +24135,23 @@ Include: cards moved recently, current in-progress items, blocked or high-priori
23763
24135
  await client2.removeLabelFromCard(cardId, labelId);
23764
24136
  return { success: true };
23765
24137
  }
24138
+ case "harmony_add_link_to_card": {
24139
+ const sourceCardId = exports_external.string().uuid().parse(args.sourceCardId);
24140
+ const targetCardId = exports_external.string().uuid().parse(args.targetCardId);
24141
+ const linkType = exports_external.enum(["relates_to", "blocks", "duplicates", "is_part_of"]).parse(args.linkType);
24142
+ const result = await client2.addLinkToCard(sourceCardId, targetCardId, linkType);
24143
+ return { success: true, ...result };
24144
+ }
24145
+ case "harmony_remove_link_from_card": {
24146
+ const linkId = exports_external.string().uuid().parse(args.linkId);
24147
+ await client2.removeLinkFromCard(linkId);
24148
+ return { success: true };
24149
+ }
24150
+ case "harmony_get_card_links": {
24151
+ const cardId = exports_external.string().uuid().parse(args.cardId);
24152
+ const result = await client2.getCardLinks(cardId);
24153
+ return result;
24154
+ }
23766
24155
  case "harmony_create_subtask": {
23767
24156
  const cardId = exports_external.string().uuid().parse(args.cardId);
23768
24157
  const title = exports_external.string().min(1).parse(args.title);
@@ -23880,6 +24269,61 @@ Include: cards moved recently, current in-progress items, blocked or high-priori
23880
24269
  });
23881
24270
  return { success: true, ...result };
23882
24271
  }
24272
+ case "harmony_generate_prompt": {
24273
+ let cardData;
24274
+ let columnData = null;
24275
+ if (args.cardId) {
24276
+ const cardId = exports_external.string().uuid().parse(args.cardId);
24277
+ const cardResult = await client2.getCard(cardId);
24278
+ cardData = cardResult.card;
24279
+ } else if (args.shortId !== undefined) {
24280
+ const shortId = exports_external.number().int().positive().parse(args.shortId);
24281
+ const projectId = args.projectId || getActiveProjectId();
24282
+ if (!projectId) {
24283
+ throw new Error("Project ID required when using shortId. Use harmony_set_project_context or provide projectId.");
24284
+ }
24285
+ const cardResult = await client2.getCardByShortId(projectId, shortId);
24286
+ cardData = cardResult.card;
24287
+ } else {
24288
+ throw new Error("Either cardId or shortId must be provided");
24289
+ }
24290
+ const projectIdForBoard = args.projectId || getActiveProjectId() || cardData.project_id;
24291
+ if (projectIdForBoard) {
24292
+ try {
24293
+ const board = await client2.getBoard(projectIdForBoard, { summary: true });
24294
+ const columnId = cardData.column_id;
24295
+ const column = board.columns.find((col) => col.id === columnId);
24296
+ if (column) {
24297
+ columnData = { name: column.name };
24298
+ }
24299
+ } catch {}
24300
+ }
24301
+ const variant = args.variant || "execute";
24302
+ const contextOptions = {};
24303
+ if (args.includeSubtasks !== undefined) {
24304
+ contextOptions.includeSubtasks = args.includeSubtasks === true || args.includeSubtasks === "true";
24305
+ }
24306
+ if (args.includeLinks !== undefined) {
24307
+ contextOptions.includeLinks = args.includeLinks === true || args.includeLinks === "true";
24308
+ }
24309
+ if (args.includeDescription !== undefined) {
24310
+ contextOptions.includeDescription = args.includeDescription === true || args.includeDescription === "true";
24311
+ }
24312
+ const result = generatePrompt({
24313
+ card: cardData,
24314
+ column: columnData,
24315
+ variant,
24316
+ contextOptions,
24317
+ customConstraints: args.customConstraints
24318
+ });
24319
+ return {
24320
+ success: true,
24321
+ cardId: cardData.id,
24322
+ shortId: cardData.short_id,
24323
+ title: cardData.title,
24324
+ ...result
24325
+ };
24326
+ }
23883
24327
  default:
23884
24328
  throw new Error(`Unknown tool: ${name}`);
23885
24329
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harmony-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "MCP server for Harmony Kanban board - enables AI coding agents to manage your boards",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -13,11 +13,11 @@
13
13
  ],
14
14
  "repository": {
15
15
  "type": "git",
16
- "url": "git+https://github.com/nicholasgriffintn/harmony.git"
16
+ "url": "https://github.com/Way/getharmony/tree/main/packages/mcp-server"
17
17
  },
18
18
  "homepage": "https://gethmy.com",
19
19
  "bugs": {
20
- "url": "https://github.com/nicholasgriffintn/harmony/issues"
20
+ "url": "https://github.com/Way/getharmony/issues"
21
21
  },
22
22
  "keywords": [
23
23
  "mcp",