@gethmy/mcp 2.9.7 → 2.9.9
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 +4 -4
- package/dist/cli.js +33 -4
- package/dist/index.js +24 -1
- package/dist/lib/api-client.js +9 -1
- package/package.json +1 -1
- package/src/api-client.ts +58 -0
- package/src/server.ts +17 -0
- package/src/tui/setup.ts +16 -3
package/README.md
CHANGED
|
@@ -5,8 +5,8 @@ Enables AI coding agents (Claude Code, OpenAI Codex, Cursor) to interact with yo
|
|
|
5
5
|
|
|
6
6
|
## Features
|
|
7
7
|
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
8
|
+
- **70 MCP Tools** for full board control, knowledge graph, and workflow plans
|
|
9
|
+
- **7 Global Skills** — `/hmy`, `/hmy-plan`, `/hmy-cleanup`, `/hmy-standup`, `/hmy-memory-prune`, `/hmy-upgrade`, `/hmy-review`, served from the DB-backed [skill hub](../../docs/skills.md) with auto-update and admin-managed versioning
|
|
10
10
|
- **Knowledge Graph Memory** — Phase 1 surface: hybrid retrieval (vector + lexical + RRF), session-scoped working memory, activity feed. See [docs/memory.md](../../docs/memory.md)
|
|
11
11
|
- **GSD Workflow Plans** - plan/execute/verify/done lifecycle with auto card creation
|
|
12
12
|
- **Card Linking** - create relationships between cards (blocks, relates_to, duplicates, is_part_of)
|
|
@@ -89,7 +89,7 @@ If you prefer to configure manually (e.g., in Claude.ai's UI):
|
|
|
89
89
|
1. Get an API key from [Harmony](https://gethmy.com/user/keys)
|
|
90
90
|
2. In Claude.ai, add a remote MCP server with URL `https://mcp.gethmy.com/mcp`
|
|
91
91
|
3. Set the Authorization header to `Bearer hmy_your_key_here`
|
|
92
|
-
4. All
|
|
92
|
+
4. All 70 Harmony tools become available in your conversation
|
|
93
93
|
|
|
94
94
|
**Session management** is automatic - sessions have a 1-hour TTL and are created/renewed transparently.
|
|
95
95
|
|
|
@@ -150,7 +150,7 @@ npx @gethmy/mcp serve # Start MCP server
|
|
|
150
150
|
|
|
151
151
|
## Skills
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
Seven global skills ship with the MCP server and are installed automatically by `npx @gethmy/mcp setup`. They live in the `skill_resource` Postgres table, are fetched via `GET /v1/skills/<name>`, and render-time composed with a shared auto-update preamble.
|
|
154
154
|
|
|
155
155
|
For the full skill hub architecture (storage, versioning, auto-update, admin management), see [docs/skills.md](../../docs/skills.md).
|
|
156
156
|
|
package/dist/cli.js
CHANGED
|
@@ -1385,7 +1385,6 @@ import {
|
|
|
1385
1385
|
ReadResourceRequestSchema
|
|
1386
1386
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
1387
1387
|
import { z } from "zod";
|
|
1388
|
-
|
|
1389
1388
|
// ../harmony-shared/dist/cardLinks.js
|
|
1390
1389
|
var LINK_TYPE_INVERSES = {
|
|
1391
1390
|
relates_to: "relates_to",
|
|
@@ -1859,6 +1858,9 @@ class HarmonyApiClient {
|
|
|
1859
1858
|
async getCardExternalLinks(cardId) {
|
|
1860
1859
|
return this.request("GET", `/cards/${cardId}/external-links`);
|
|
1861
1860
|
}
|
|
1861
|
+
async classifyCard(cardId) {
|
|
1862
|
+
return this.request("POST", `/cards/${cardId}/classify`);
|
|
1863
|
+
}
|
|
1862
1864
|
async createColumn(projectId, name) {
|
|
1863
1865
|
return this.request("POST", "/columns", { projectId, name });
|
|
1864
1866
|
}
|
|
@@ -1920,9 +1922,15 @@ class HarmonyApiClient {
|
|
|
1920
1922
|
async flushActivityLog(cardId, data) {
|
|
1921
1923
|
return this.request("POST", `/cards/${cardId}/agent-activity-log`, data);
|
|
1922
1924
|
}
|
|
1925
|
+
async appendAgentRunEvents(cardId, data) {
|
|
1926
|
+
return this.request("POST", `/cards/${cardId}/agent-run-events`, data);
|
|
1927
|
+
}
|
|
1923
1928
|
async getActivityLog(cardId, sessionId) {
|
|
1924
1929
|
return this.request("GET", `/cards/${cardId}/agent-activity-log?sessionId=${sessionId}`);
|
|
1925
1930
|
}
|
|
1931
|
+
async getPendingUserMessages(cardId, sessionId, sinceSeq) {
|
|
1932
|
+
return this.request("GET", `/cards/${cardId}/agent-messages?sessionId=${sessionId}&sinceSeq=${sinceSeq}`);
|
|
1933
|
+
}
|
|
1926
1934
|
async getAgentSession(cardId, options) {
|
|
1927
1935
|
const params = new URLSearchParams;
|
|
1928
1936
|
if (options?.includeEnded)
|
|
@@ -3882,6 +3890,16 @@ var TOOLS = {
|
|
|
3882
3890
|
required: ["cardId"]
|
|
3883
3891
|
}
|
|
3884
3892
|
},
|
|
3893
|
+
harmony_classify_card: {
|
|
3894
|
+
description: "Classify a card with the LLM classifier: sets `intent` (plan/think/implement/review), `complexity_score` (0-10), and `model_tier` (simple/advanced/research), stamps `classified_at`, and applies the canonical type label (feature/bug/idea). Use this right after creating a card (e.g. in the `hmy-new` flow) so it's classified in-flow instead of waiting for it to surface on the web board. Idempotent — safe to re-run. Never touches the user-owned `model_override`.",
|
|
3895
|
+
inputSchema: {
|
|
3896
|
+
type: "object",
|
|
3897
|
+
properties: {
|
|
3898
|
+
cardId: { type: "string", description: "Card UUID" }
|
|
3899
|
+
},
|
|
3900
|
+
required: ["cardId"]
|
|
3901
|
+
}
|
|
3902
|
+
},
|
|
3885
3903
|
harmony_get_card_external_links: {
|
|
3886
3904
|
description: "Get external URL references attached to a card (links to docs, gists, dashboards, etc.).",
|
|
3887
3905
|
inputSchema: {
|
|
@@ -5349,6 +5367,11 @@ async function handleToolCall(name, args, deps) {
|
|
|
5349
5367
|
const result = await client3.getCardExternalLinks(cardId);
|
|
5350
5368
|
return result;
|
|
5351
5369
|
}
|
|
5370
|
+
case "harmony_classify_card": {
|
|
5371
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
5372
|
+
const result = await client3.classifyCard(cardId);
|
|
5373
|
+
return result;
|
|
5374
|
+
}
|
|
5352
5375
|
case "harmony_create_subtask": {
|
|
5353
5376
|
const cardId = z.string().uuid().parse(args.cardId);
|
|
5354
5377
|
const title = z.string().min(1).max(500).parse(args.title);
|
|
@@ -7821,12 +7844,13 @@ only for unattended CI where you accept that risk.`));
|
|
|
7821
7844
|
options: [
|
|
7822
7845
|
{
|
|
7823
7846
|
value: "browser",
|
|
7824
|
-
label: "Sign in
|
|
7825
|
-
hint: "recommended —
|
|
7847
|
+
label: "Sign in or create an account in your browser",
|
|
7848
|
+
hint: "recommended — new or existing users, no key handling"
|
|
7826
7849
|
},
|
|
7827
7850
|
{
|
|
7828
7851
|
value: "create",
|
|
7829
|
-
label: "Create
|
|
7852
|
+
label: "Create an account here in the terminal",
|
|
7853
|
+
hint: "no browser"
|
|
7830
7854
|
},
|
|
7831
7855
|
{
|
|
7832
7856
|
value: "apikey",
|
|
@@ -8378,6 +8402,11 @@ Specify the workspace with --workspace <id>, or select one below.`);
|
|
|
8378
8402
|
console.log(` ${colors.brand("Cursor:")} MCP tools available automatically`);
|
|
8379
8403
|
}
|
|
8380
8404
|
console.log("");
|
|
8405
|
+
console.log(` ${colors.bold("Next steps:")}`);
|
|
8406
|
+
console.log(` 1. Open Claude Code and say: ${colors.highlight('"Show me my board"')}`);
|
|
8407
|
+
console.log(` 2. Create a card: ${colors.highlight('"Create a card called Auth token refresh"')}`);
|
|
8408
|
+
console.log(` 3. Start the daemon: ${colors.highlight("npx @gethmy/agent")}`);
|
|
8409
|
+
console.log("");
|
|
8381
8410
|
console.log(` ${colors.dim("Add to new project: npx @gethmy/mcp setup")}`);
|
|
8382
8411
|
console.log(` ${colors.dim("Need help? Visit https://app.gethmy.com/docs/mcp")}`);
|
|
8383
8412
|
}
|
package/dist/index.js
CHANGED
|
@@ -1380,7 +1380,6 @@ import {
|
|
|
1380
1380
|
ReadResourceRequestSchema
|
|
1381
1381
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
1382
1382
|
import { z } from "zod";
|
|
1383
|
-
|
|
1384
1383
|
// ../harmony-shared/dist/cardLinks.js
|
|
1385
1384
|
var LINK_TYPE_INVERSES = {
|
|
1386
1385
|
relates_to: "relates_to",
|
|
@@ -1854,6 +1853,9 @@ class HarmonyApiClient {
|
|
|
1854
1853
|
async getCardExternalLinks(cardId) {
|
|
1855
1854
|
return this.request("GET", `/cards/${cardId}/external-links`);
|
|
1856
1855
|
}
|
|
1856
|
+
async classifyCard(cardId) {
|
|
1857
|
+
return this.request("POST", `/cards/${cardId}/classify`);
|
|
1858
|
+
}
|
|
1857
1859
|
async createColumn(projectId, name) {
|
|
1858
1860
|
return this.request("POST", "/columns", { projectId, name });
|
|
1859
1861
|
}
|
|
@@ -1915,9 +1917,15 @@ class HarmonyApiClient {
|
|
|
1915
1917
|
async flushActivityLog(cardId, data) {
|
|
1916
1918
|
return this.request("POST", `/cards/${cardId}/agent-activity-log`, data);
|
|
1917
1919
|
}
|
|
1920
|
+
async appendAgentRunEvents(cardId, data) {
|
|
1921
|
+
return this.request("POST", `/cards/${cardId}/agent-run-events`, data);
|
|
1922
|
+
}
|
|
1918
1923
|
async getActivityLog(cardId, sessionId) {
|
|
1919
1924
|
return this.request("GET", `/cards/${cardId}/agent-activity-log?sessionId=${sessionId}`);
|
|
1920
1925
|
}
|
|
1926
|
+
async getPendingUserMessages(cardId, sessionId, sinceSeq) {
|
|
1927
|
+
return this.request("GET", `/cards/${cardId}/agent-messages?sessionId=${sessionId}&sinceSeq=${sinceSeq}`);
|
|
1928
|
+
}
|
|
1921
1929
|
async getAgentSession(cardId, options) {
|
|
1922
1930
|
const params = new URLSearchParams;
|
|
1923
1931
|
if (options?.includeEnded)
|
|
@@ -3877,6 +3885,16 @@ var TOOLS = {
|
|
|
3877
3885
|
required: ["cardId"]
|
|
3878
3886
|
}
|
|
3879
3887
|
},
|
|
3888
|
+
harmony_classify_card: {
|
|
3889
|
+
description: "Classify a card with the LLM classifier: sets `intent` (plan/think/implement/review), `complexity_score` (0-10), and `model_tier` (simple/advanced/research), stamps `classified_at`, and applies the canonical type label (feature/bug/idea). Use this right after creating a card (e.g. in the `hmy-new` flow) so it's classified in-flow instead of waiting for it to surface on the web board. Idempotent — safe to re-run. Never touches the user-owned `model_override`.",
|
|
3890
|
+
inputSchema: {
|
|
3891
|
+
type: "object",
|
|
3892
|
+
properties: {
|
|
3893
|
+
cardId: { type: "string", description: "Card UUID" }
|
|
3894
|
+
},
|
|
3895
|
+
required: ["cardId"]
|
|
3896
|
+
}
|
|
3897
|
+
},
|
|
3880
3898
|
harmony_get_card_external_links: {
|
|
3881
3899
|
description: "Get external URL references attached to a card (links to docs, gists, dashboards, etc.).",
|
|
3882
3900
|
inputSchema: {
|
|
@@ -5344,6 +5362,11 @@ async function handleToolCall(name, args, deps) {
|
|
|
5344
5362
|
const result = await client3.getCardExternalLinks(cardId);
|
|
5345
5363
|
return result;
|
|
5346
5364
|
}
|
|
5365
|
+
case "harmony_classify_card": {
|
|
5366
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
5367
|
+
const result = await client3.classifyCard(cardId);
|
|
5368
|
+
return result;
|
|
5369
|
+
}
|
|
5347
5370
|
case "harmony_create_subtask": {
|
|
5348
5371
|
const cardId = z.string().uuid().parse(args.cardId);
|
|
5349
5372
|
const title = z.string().min(1).max(500).parse(args.title);
|
package/dist/lib/api-client.js
CHANGED
|
@@ -833,7 +833,6 @@ var init_oauth_refresh = __esm(() => {
|
|
|
833
833
|
init_config();
|
|
834
834
|
init_oauth_login();
|
|
835
835
|
});
|
|
836
|
-
|
|
837
836
|
// ../harmony-shared/dist/cardLinks.js
|
|
838
837
|
var LINK_TYPE_INVERSES = {
|
|
839
838
|
relates_to: "relates_to",
|
|
@@ -1307,6 +1306,9 @@ class HarmonyApiClient {
|
|
|
1307
1306
|
async getCardExternalLinks(cardId) {
|
|
1308
1307
|
return this.request("GET", `/cards/${cardId}/external-links`);
|
|
1309
1308
|
}
|
|
1309
|
+
async classifyCard(cardId) {
|
|
1310
|
+
return this.request("POST", `/cards/${cardId}/classify`);
|
|
1311
|
+
}
|
|
1310
1312
|
async createColumn(projectId, name) {
|
|
1311
1313
|
return this.request("POST", "/columns", { projectId, name });
|
|
1312
1314
|
}
|
|
@@ -1368,9 +1370,15 @@ class HarmonyApiClient {
|
|
|
1368
1370
|
async flushActivityLog(cardId, data) {
|
|
1369
1371
|
return this.request("POST", `/cards/${cardId}/agent-activity-log`, data);
|
|
1370
1372
|
}
|
|
1373
|
+
async appendAgentRunEvents(cardId, data) {
|
|
1374
|
+
return this.request("POST", `/cards/${cardId}/agent-run-events`, data);
|
|
1375
|
+
}
|
|
1371
1376
|
async getActivityLog(cardId, sessionId) {
|
|
1372
1377
|
return this.request("GET", `/cards/${cardId}/agent-activity-log?sessionId=${sessionId}`);
|
|
1373
1378
|
}
|
|
1379
|
+
async getPendingUserMessages(cardId, sessionId, sinceSeq) {
|
|
1380
|
+
return this.request("GET", `/cards/${cardId}/agent-messages?sessionId=${sessionId}&sinceSeq=${sinceSeq}`);
|
|
1381
|
+
}
|
|
1374
1382
|
async getAgentSession(cardId, options) {
|
|
1375
1383
|
const params = new URLSearchParams;
|
|
1376
1384
|
if (options?.includeEnded)
|
package/package.json
CHANGED
package/src/api-client.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type AgentRunEventDraft,
|
|
2
3
|
type Comment,
|
|
3
4
|
getDisplayLinkType,
|
|
4
5
|
serializeCommentThread,
|
|
@@ -151,6 +152,18 @@ export interface CardExternalLinkRow {
|
|
|
151
152
|
created_at: string;
|
|
152
153
|
}
|
|
153
154
|
|
|
155
|
+
/** Result of the classify-card classifier (card #415). Any field may be null
|
|
156
|
+
* if the LLM didn't return a usable value. `model_override` is never touched. */
|
|
157
|
+
export interface CardClassificationResult {
|
|
158
|
+
type: "feature" | "bug" | "idea" | null;
|
|
159
|
+
intent: "plan" | "think" | "implement" | "review" | null;
|
|
160
|
+
complexity_score: number | null;
|
|
161
|
+
model_tier: "simple" | "advanced" | "research" | null;
|
|
162
|
+
classified_at: string;
|
|
163
|
+
applied_type_label_id: string | null;
|
|
164
|
+
reasoning: string | null;
|
|
165
|
+
}
|
|
166
|
+
|
|
154
167
|
export class HarmonyApiClient {
|
|
155
168
|
private apiKey: string;
|
|
156
169
|
private apiUrl: string;
|
|
@@ -668,6 +681,12 @@ export class HarmonyApiClient {
|
|
|
668
681
|
return this.request("GET", `/cards/${cardId}/external-links`);
|
|
669
682
|
}
|
|
670
683
|
|
|
684
|
+
async classifyCard(
|
|
685
|
+
cardId: string,
|
|
686
|
+
): Promise<{ classification: CardClassificationResult }> {
|
|
687
|
+
return this.request("POST", `/cards/${cardId}/classify`);
|
|
688
|
+
}
|
|
689
|
+
|
|
671
690
|
// ============ COLUMN OPERATIONS ============
|
|
672
691
|
|
|
673
692
|
async createColumn(
|
|
@@ -880,6 +899,20 @@ export class HarmonyApiClient {
|
|
|
880
899
|
return this.request("POST", `/cards/${cardId}/agent-activity-log`, data);
|
|
881
900
|
}
|
|
882
901
|
|
|
902
|
+
/**
|
|
903
|
+
* Append events to a run's agent_run_events stream (card #417). Send drafts in
|
|
904
|
+
* chronological order — the server's seq trigger assigns the monotonic per-run order.
|
|
905
|
+
*/
|
|
906
|
+
async appendAgentRunEvents(
|
|
907
|
+
cardId: string,
|
|
908
|
+
data: {
|
|
909
|
+
sessionId: string;
|
|
910
|
+
events: (AgentRunEventDraft & { createdAt?: string })[];
|
|
911
|
+
},
|
|
912
|
+
): Promise<{ inserted: number }> {
|
|
913
|
+
return this.request("POST", `/cards/${cardId}/agent-run-events`, data);
|
|
914
|
+
}
|
|
915
|
+
|
|
883
916
|
async getActivityLog(
|
|
884
917
|
cardId: string,
|
|
885
918
|
sessionId: string,
|
|
@@ -900,6 +933,31 @@ export class HarmonyApiClient {
|
|
|
900
933
|
);
|
|
901
934
|
}
|
|
902
935
|
|
|
936
|
+
/**
|
|
937
|
+
* Drain queued chat-steering messages for a run (card #417). Returns `user_message`
|
|
938
|
+
* events with `seq` greater than `sinceSeq`, oldest first, so the daemon can continue
|
|
939
|
+
* the run via `claude --resume` at a turn boundary.
|
|
940
|
+
*/
|
|
941
|
+
async getPendingUserMessages(
|
|
942
|
+
cardId: string,
|
|
943
|
+
sessionId: string,
|
|
944
|
+
sinceSeq: number,
|
|
945
|
+
): Promise<{
|
|
946
|
+
messages: {
|
|
947
|
+
id: string;
|
|
948
|
+
seq: number;
|
|
949
|
+
text: string;
|
|
950
|
+
authorName?: string;
|
|
951
|
+
authorUserId?: string;
|
|
952
|
+
createdAt: string;
|
|
953
|
+
}[];
|
|
954
|
+
}> {
|
|
955
|
+
return this.request(
|
|
956
|
+
"GET",
|
|
957
|
+
`/cards/${cardId}/agent-messages?sessionId=${sessionId}&sinceSeq=${sinceSeq}`,
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
|
|
903
961
|
async getAgentSession(
|
|
904
962
|
cardId: string,
|
|
905
963
|
options?: { includeEnded?: boolean },
|
package/src/server.ts
CHANGED
|
@@ -686,6 +686,17 @@ export const TOOLS = {
|
|
|
686
686
|
required: ["cardId"],
|
|
687
687
|
},
|
|
688
688
|
},
|
|
689
|
+
harmony_classify_card: {
|
|
690
|
+
description:
|
|
691
|
+
"Classify a card with the LLM classifier: sets `intent` (plan/think/implement/review), `complexity_score` (0-10), and `model_tier` (simple/advanced/research), stamps `classified_at`, and applies the canonical type label (feature/bug/idea). Use this right after creating a card (e.g. in the `hmy-new` flow) so it's classified in-flow instead of waiting for it to surface on the web board. Idempotent — safe to re-run. Never touches the user-owned `model_override`.",
|
|
692
|
+
inputSchema: {
|
|
693
|
+
type: "object",
|
|
694
|
+
properties: {
|
|
695
|
+
cardId: { type: "string", description: "Card UUID" },
|
|
696
|
+
},
|
|
697
|
+
required: ["cardId"],
|
|
698
|
+
},
|
|
699
|
+
},
|
|
689
700
|
harmony_get_card_external_links: {
|
|
690
701
|
description:
|
|
691
702
|
"Get external URL references attached to a card (links to docs, gists, dashboards, etc.).",
|
|
@@ -2484,6 +2495,12 @@ async function handleToolCall(
|
|
|
2484
2495
|
return result;
|
|
2485
2496
|
}
|
|
2486
2497
|
|
|
2498
|
+
case "harmony_classify_card": {
|
|
2499
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
2500
|
+
const result = await client.classifyCard(cardId);
|
|
2501
|
+
return result;
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2487
2504
|
// Subtask operations
|
|
2488
2505
|
case "harmony_create_subtask": {
|
|
2489
2506
|
const cardId = z.string().uuid().parse(args.cardId);
|
package/src/tui/setup.ts
CHANGED
|
@@ -783,12 +783,13 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
|
|
|
783
783
|
options: [
|
|
784
784
|
{
|
|
785
785
|
value: "browser",
|
|
786
|
-
label: "Sign in
|
|
787
|
-
hint: "recommended —
|
|
786
|
+
label: "Sign in or create an account in your browser",
|
|
787
|
+
hint: "recommended — new or existing users, no key handling",
|
|
788
788
|
},
|
|
789
789
|
{
|
|
790
790
|
value: "create",
|
|
791
|
-
label: "Create
|
|
791
|
+
label: "Create an account here in the terminal",
|
|
792
|
+
hint: "no browser",
|
|
792
793
|
},
|
|
793
794
|
{
|
|
794
795
|
value: "apikey",
|
|
@@ -1599,6 +1600,18 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
|
|
|
1599
1600
|
);
|
|
1600
1601
|
}
|
|
1601
1602
|
|
|
1603
|
+
console.log("");
|
|
1604
|
+
console.log(` ${colors.bold("Next steps:")}`);
|
|
1605
|
+
console.log(
|
|
1606
|
+
` 1. Open Claude Code and say: ${colors.highlight('"Show me my board"')}`,
|
|
1607
|
+
);
|
|
1608
|
+
console.log(
|
|
1609
|
+
` 2. Create a card: ${colors.highlight('"Create a card called Auth token refresh"')}`,
|
|
1610
|
+
);
|
|
1611
|
+
console.log(
|
|
1612
|
+
` 3. Start the daemon: ${colors.highlight("npx @gethmy/agent")}`,
|
|
1613
|
+
);
|
|
1614
|
+
|
|
1602
1615
|
console.log("");
|
|
1603
1616
|
console.log(` ${colors.dim("Add to new project: npx @gethmy/mcp setup")}`);
|
|
1604
1617
|
console.log(
|