@gethmy/mcp 2.3.1 → 2.3.3

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.
Files changed (34) hide show
  1. package/dist/lib/api-client.js +2099 -648
  2. package/dist/lib/config.js +217 -201
  3. package/package.json +9 -5
  4. package/src/memory-cleanup.ts +2 -4
  5. package/dist/lib/__tests__/active-learning.test.js +0 -386
  6. package/dist/lib/__tests__/agent-performance-profiles.test.js +0 -325
  7. package/dist/lib/__tests__/auto-session.test.js +0 -661
  8. package/dist/lib/__tests__/context-assembly.test.js +0 -362
  9. package/dist/lib/__tests__/graph-expansion.test.js +0 -150
  10. package/dist/lib/__tests__/integration-memory-crud.test.js +0 -797
  11. package/dist/lib/__tests__/integration-memory-system.test.js +0 -281
  12. package/dist/lib/__tests__/lifecycle-maintenance.test.js +0 -207
  13. package/dist/lib/__tests__/pattern-detection.test.js +0 -295
  14. package/dist/lib/__tests__/prompt-builder.test.js +0 -418
  15. package/dist/lib/active-learning.js +0 -822
  16. package/dist/lib/auto-session.js +0 -214
  17. package/dist/lib/cli.js +0 -138
  18. package/dist/lib/consolidation.js +0 -303
  19. package/dist/lib/context-assembly.js +0 -884
  20. package/dist/lib/graph-expansion.js +0 -163
  21. package/dist/lib/http.js +0 -175
  22. package/dist/lib/index.js +0 -7
  23. package/dist/lib/lifecycle-maintenance.js +0 -88
  24. package/dist/lib/memory-cleanup.js +0 -455
  25. package/dist/lib/onboard.js +0 -36
  26. package/dist/lib/prompt-builder.js +0 -488
  27. package/dist/lib/remote.js +0 -166
  28. package/dist/lib/server.js +0 -3365
  29. package/dist/lib/skills.js +0 -593
  30. package/dist/lib/tui/agents.js +0 -116
  31. package/dist/lib/tui/docs.js +0 -744
  32. package/dist/lib/tui/setup.js +0 -934
  33. package/dist/lib/tui/theme.js +0 -95
  34. package/dist/lib/tui/writer.js +0 -200
@@ -1,163 +0,0 @@
1
- /**
2
- * Auto Knowledge Graph Expansion
3
- *
4
- * When a new entity is created, find semantically similar existing entities
5
- * and automatically create `relates_to` relations between them.
6
- */
7
- /**
8
- * Automatically expand the knowledge graph by linking a newly-created entity
9
- * to semantically similar existing entities via `relates_to` relations.
10
- *
11
- * Non-fatal: errors are caught and silently ignored so callers are never blocked.
12
- */
13
- export async function autoExpandGraph(client, entityId, title, content, _tags, workspaceId, projectId, maxRelations = 5) {
14
- try {
15
- // Build a search query from the title + first 200 chars of content
16
- const contentSnippet = content.slice(0, 200).trim();
17
- const query = [title, contentSnippet].filter(Boolean).join(" ");
18
- let candidates = [];
19
- // First attempt
20
- const { entities } = await client.searchMemoryEntities(workspaceId, query, {
21
- project_id: projectId,
22
- limit: 20,
23
- });
24
- candidates = entities
25
- .filter((e) => e.id !== entityId)
26
- .slice(0, maxRelations);
27
- // Retry once after 2s if no candidates found (handles embedding generation race)
28
- if (candidates.length === 0) {
29
- await new Promise((resolve) => setTimeout(resolve, 2000));
30
- const retry = await client.searchMemoryEntities(workspaceId, query, {
31
- project_id: projectId,
32
- limit: 20,
33
- });
34
- candidates = retry.entities
35
- .filter((e) => e.id !== entityId)
36
- .slice(0, maxRelations);
37
- }
38
- let relationsCreated = 0;
39
- for (const candidate of candidates) {
40
- try {
41
- await client.createMemoryRelation({
42
- source_id: entityId,
43
- target_id: candidate.id,
44
- relation_type: "relates_to",
45
- confidence: 0.6,
46
- });
47
- relationsCreated++;
48
- }
49
- catch (err) {
50
- // Silently skip 409 Conflict (relation already exists) and other errors
51
- const status = err?.status;
52
- if (status !== 409) {
53
- // Non-409 errors are still non-fatal; just skip
54
- }
55
- }
56
- }
57
- return { relationsCreated };
58
- }
59
- catch {
60
- // Never block callers due to graph expansion failures
61
- return { relationsCreated: 0 };
62
- }
63
- }
64
- /**
65
- * Find entities semantically similar to the given title+content using
66
- * hybrid FTS+vector search. Returns entities sorted by relevance.
67
- */
68
- export async function findSimilarEntities(client, title, content, workspaceId, options) {
69
- const contentSnippet = content.slice(0, 200).trim();
70
- const query = [title, contentSnippet].filter(Boolean).join(" ");
71
- try {
72
- const { entities } = await client.searchMemoryEntities(workspaceId, query, {
73
- project_id: options?.projectId,
74
- limit: options?.limit ?? 20,
75
- type: options?.type,
76
- });
77
- const minScore = options?.minRrfScore ?? 0;
78
- const excludeSet = new Set(options?.excludeIds || []);
79
- return entities.filter((e) => {
80
- if (excludeSet.has(e.id))
81
- return false;
82
- if (minScore > 0 && (e.rrf_score ?? 0) < minScore)
83
- return false;
84
- return true;
85
- });
86
- }
87
- catch {
88
- return [];
89
- }
90
- }
91
- /**
92
- * Causal lookup table: maps an entity type to the target types it should
93
- * be linked to, along with the relation type and direction.
94
- *
95
- * "forward" means source=newEntity → target=match.
96
- * "reverse" means source=match → target=newEntity.
97
- */
98
- const CAUSAL_LOOKUP = [
99
- {
100
- sourceType: "error",
101
- targetType: "solution",
102
- relation: "resolved_by",
103
- direction: "forward",
104
- },
105
- {
106
- sourceType: "solution",
107
- targetType: "error",
108
- relation: "resolved_by",
109
- direction: "reverse",
110
- },
111
- {
112
- sourceType: "lesson",
113
- targetType: "error",
114
- relation: "learned_from",
115
- direction: "forward",
116
- },
117
- ];
118
- /**
119
- * Link a newly created entity to causally-related entities of *other* types.
120
- *
121
- * For example, a new `error` entity gets linked to similar `solution` entities
122
- * via `resolved_by`, enabling agents to discover "what solved similar problems."
123
- *
124
- * Non-fatal: all errors are caught silently.
125
- */
126
- export async function linkCrossTypeNeighbors(client, entityId, entityType, title, content, workspaceId, projectId) {
127
- const rules = CAUSAL_LOOKUP.filter((r) => r.sourceType === entityType);
128
- if (rules.length === 0)
129
- return { relationsCreated: 0 };
130
- let relationsCreated = 0;
131
- for (const rule of rules) {
132
- try {
133
- const matches = await findSimilarEntities(client, title, content, workspaceId, {
134
- projectId,
135
- limit: 10,
136
- minRrfScore: 0.04,
137
- excludeIds: [entityId],
138
- type: rule.targetType,
139
- });
140
- // Cap at 3 matches per target type
141
- for (const match of matches.slice(0, 3)) {
142
- const sourceId = rule.direction === "forward" ? entityId : match.id;
143
- const targetId = rule.direction === "forward" ? match.id : entityId;
144
- try {
145
- await client.createMemoryRelation({
146
- source_id: sourceId,
147
- target_id: targetId,
148
- relation_type: rule.relation,
149
- confidence: 0.65,
150
- });
151
- relationsCreated++;
152
- }
153
- catch {
154
- // Skip duplicate/failed relations silently
155
- }
156
- }
157
- }
158
- catch {
159
- // Non-fatal: search failure for one rule shouldn't block others
160
- }
161
- }
162
- return { relationsCreated };
163
- }
package/dist/lib/http.js DELETED
@@ -1,175 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * HTTP REST Adapter for Harmony MCP
4
- *
5
- * This provides a REST API that forwards requests to the Harmony API.
6
- * It accepts Bearer tokens (JWT) or X-API-Key headers for authentication.
7
- *
8
- * This adapter is useful for non-MCP clients that want to interact with
9
- * Harmony via HTTP instead of the MCP stdio protocol.
10
- */
11
- import { serve } from "bun";
12
- import { Hono } from "hono";
13
- import { cors } from "hono/cors";
14
- import { loadConfig } from "./config.js";
15
- const app = new Hono();
16
- // CORS configuration
17
- app.use("/*", cors({
18
- origin: [
19
- "https://gethmy.com",
20
- "https://app.gethmy.com",
21
- "http://localhost:8080",
22
- "http://localhost:3000",
23
- ],
24
- allowMethods: ["GET", "POST", "PATCH", "DELETE", "OPTIONS"],
25
- allowHeaders: ["Content-Type", "Authorization", "X-API-Key"],
26
- }));
27
- // Get API URL from config or use default
28
- function getApiUrl() {
29
- const config = loadConfig();
30
- return config.apiUrl;
31
- }
32
- // Forward request to Harmony API
33
- async function forwardToApi(method, path, authHeader, apiKey, body) {
34
- const apiUrl = getApiUrl();
35
- const headers = {
36
- "Content-Type": "application/json",
37
- };
38
- // Use API key if provided, otherwise forward the Authorization header
39
- if (apiKey) {
40
- headers["X-API-Key"] = apiKey;
41
- }
42
- else if (authHeader) {
43
- headers.Authorization = authHeader;
44
- }
45
- else {
46
- throw new Error("Unauthorized: Missing API key or Authorization header");
47
- }
48
- const response = await fetch(`${apiUrl}${path}`, {
49
- method,
50
- headers,
51
- body: body ? JSON.stringify(body) : undefined,
52
- });
53
- const data = await response.json();
54
- return { data, status: response.status };
55
- }
56
- // Health check
57
- app.get("/health", (c) => c.json({ status: "ok", service: "harmony-mcp-http" }));
58
- // List available endpoints
59
- app.get("/", (c) => c.json({
60
- service: "Harmony MCP HTTP Adapter",
61
- endpoints: {
62
- workspaces: {
63
- list: "GET /workspaces",
64
- members: "GET /workspaces/:id/members",
65
- projects: "GET /workspaces/:id/projects",
66
- },
67
- board: {
68
- get: "GET /board/:projectId",
69
- },
70
- cards: {
71
- create: "POST /cards",
72
- get: "GET /cards/:id",
73
- getByShortId: "GET /projects/:projectId/cards/:shortId",
74
- update: "PATCH /cards/:id",
75
- delete: "DELETE /cards/:id",
76
- move: "POST /cards/:id/move",
77
- search: "GET /search?q=query",
78
- },
79
- columns: {
80
- create: "POST /columns",
81
- update: "PATCH /columns/:id",
82
- delete: "DELETE /columns/:id",
83
- },
84
- labels: {
85
- create: "POST /labels",
86
- addToCard: "POST /cards/:id/labels",
87
- removeFromCard: "DELETE /cards/:id/labels/:labelId",
88
- },
89
- subtasks: {
90
- create: "POST /subtasks",
91
- toggle: "POST /subtasks/:id/toggle",
92
- delete: "DELETE /subtasks/:id",
93
- },
94
- nlu: {
95
- process: "POST /nlu",
96
- },
97
- },
98
- authentication: "Include X-API-Key header or Authorization: Bearer <token>",
99
- }));
100
- // Generic proxy handler
101
- async function handleRequest(c, method, path) {
102
- try {
103
- const authHeader = c.req.header("Authorization");
104
- const apiKey = c.req.header("X-API-Key");
105
- let body;
106
- if (["POST", "PATCH", "PUT"].includes(method)) {
107
- try {
108
- body = await c.json();
109
- }
110
- catch {
111
- // No body or invalid JSON
112
- }
113
- }
114
- const { data, status } = await forwardToApi(method, path, authHeader, apiKey, body);
115
- return new Response(JSON.stringify(data), {
116
- status,
117
- headers: { "Content-Type": "application/json" },
118
- });
119
- }
120
- catch (error) {
121
- const message = error instanceof Error ? error.message : String(error);
122
- const status = message.includes("Unauthorized") ? 401 : 500;
123
- return new Response(JSON.stringify({ error: message }), {
124
- status,
125
- headers: { "Content-Type": "application/json" },
126
- });
127
- }
128
- }
129
- // Workspaces
130
- app.get("/workspaces", (c) => handleRequest(c, "GET", "/workspaces"));
131
- app.get("/workspaces/:id/members", (c) => handleRequest(c, "GET", `/workspaces/${c.req.param("id")}/members`));
132
- app.get("/workspaces/:id/projects", (c) => handleRequest(c, "GET", `/workspaces/${c.req.param("id")}/projects`));
133
- // Projects
134
- app.get("/projects", (c) => {
135
- const workspaceId = new URL(c.req.url).searchParams.get("workspace_id");
136
- return handleRequest(c, "GET", `/projects?workspace_id=${workspaceId}`);
137
- });
138
- // Get card by short ID: GET /projects/:projectId/cards/:shortId
139
- app.get("/projects/:projectId/cards/:shortId", (c) => handleRequest(c, "GET", `/projects/${c.req.param("projectId")}/cards/${c.req.param("shortId")}`));
140
- // Board
141
- app.get("/board/:projectId", (c) => handleRequest(c, "GET", `/board/${c.req.param("projectId")}`));
142
- // Cards
143
- app.get("/cards/:id", (c) => handleRequest(c, "GET", `/cards/${c.req.param("id")}`));
144
- app.post("/cards", (c) => handleRequest(c, "POST", "/cards"));
145
- app.patch("/cards/:id", (c) => handleRequest(c, "PATCH", `/cards/${c.req.param("id")}`));
146
- app.delete("/cards/:id", (c) => handleRequest(c, "DELETE", `/cards/${c.req.param("id")}`));
147
- app.post("/cards/:id/move", (c) => handleRequest(c, "POST", `/cards/${c.req.param("id")}/move`));
148
- app.post("/cards/:id/labels", (c) => handleRequest(c, "POST", `/cards/${c.req.param("id")}/labels`));
149
- app.delete("/cards/:id/labels/:labelId", (c) => handleRequest(c, "DELETE", `/cards/${c.req.param("id")}/labels/${c.req.param("labelId")}`));
150
- // Search
151
- app.get("/search", (c) => {
152
- const params = new URL(c.req.url).searchParams;
153
- return handleRequest(c, "GET", `/search?${params.toString()}`);
154
- });
155
- // Columns
156
- app.post("/columns", (c) => handleRequest(c, "POST", "/columns"));
157
- app.patch("/columns/:id", (c) => handleRequest(c, "PATCH", `/columns/${c.req.param("id")}`));
158
- app.delete("/columns/:id", (c) => handleRequest(c, "DELETE", `/columns/${c.req.param("id")}`));
159
- // Labels
160
- app.post("/labels", (c) => handleRequest(c, "POST", "/labels"));
161
- // Subtasks
162
- app.post("/subtasks", (c) => handleRequest(c, "POST", "/subtasks"));
163
- app.post("/subtasks/:id/toggle", (c) => handleRequest(c, "POST", `/subtasks/${c.req.param("id")}/toggle`));
164
- app.delete("/subtasks/:id", (c) => handleRequest(c, "DELETE", `/subtasks/${c.req.param("id")}`));
165
- // NLU
166
- app.post("/nlu", (c) => handleRequest(c, "POST", "/nlu"));
167
- app.post("/nlu/process", (c) => handleRequest(c, "POST", "/nlu"));
168
- // Start server
169
- const port = parseInt(process.env.PORT || "3001", 10);
170
- console.log(`Starting Harmony MCP HTTP adapter on port ${port}...`);
171
- serve({
172
- fetch: app.fetch,
173
- port,
174
- });
175
- console.log(`Harmony MCP HTTP adapter running at http://localhost:${port}`);
package/dist/lib/index.js DELETED
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env node
2
- import { HarmonyMCPServer } from "./server.js";
3
- const server = new HarmonyMCPServer();
4
- server.run().catch((error) => {
5
- console.error("Failed to start MCP server:", error);
6
- process.exit(1);
7
- });
@@ -1,88 +0,0 @@
1
- /**
2
- * Automatic Memory Lifecycle Maintenance
3
- *
4
- * Runs at session end to enforce decay/archival rules:
5
- * - Archive entities with confidence < 0.3
6
- * - Delete stale drafts (>30 days old with low decay score)
7
- * - Auto-promote eligible entities (draft→episode, episode→reference)
8
- *
9
- * All operations are non-fatal — failures are logged but never block session end.
10
- */
11
- import { evaluateLifecycle } from "@harmony/memory";
12
- /**
13
- * Run lifecycle maintenance for a workspace.
14
- * Called automatically at session end alongside consolidation.
15
- */
16
- export async function runLifecycleMaintenance(client, workspaceId, projectId) {
17
- const result = {
18
- archived: 0,
19
- pruned: 0,
20
- promoted: 0,
21
- reviewed: 0,
22
- errors: 0,
23
- };
24
- let entities;
25
- try {
26
- const listResult = await client.listMemoryEntities({
27
- workspace_id: workspaceId,
28
- project_id: projectId,
29
- limit: 200,
30
- });
31
- entities = (listResult.entities || []);
32
- }
33
- catch {
34
- return result;
35
- }
36
- if (entities.length === 0)
37
- return result;
38
- const now = Date.now();
39
- const STALE_DRAFT_MAX_AGE_DAYS = 30;
40
- for (const entity of entities) {
41
- try {
42
- const lifecycle = evaluateLifecycle(entity);
43
- // 1. Archive low-confidence entities (confidence < 0.3)
44
- if (lifecycle.shouldArchive) {
45
- await client.deleteMemoryEntity(entity.id);
46
- result.archived++;
47
- continue;
48
- }
49
- // 2. Prune stale drafts (>30 days old with low decay score)
50
- if (entity.memory_tier === "draft") {
51
- const ageDays = (now - new Date(entity.created_at).getTime()) / (1000 * 60 * 60 * 24);
52
- if (ageDays > STALE_DRAFT_MAX_AGE_DAYS && lifecycle.decay.score < 0.3) {
53
- await client.deleteMemoryEntity(entity.id);
54
- result.pruned++;
55
- continue;
56
- }
57
- }
58
- // 3. Auto-promote eligible entities
59
- if (lifecycle.promotion.eligible && lifecycle.promotion.targetTier) {
60
- await client.updateMemoryEntity(entity.id, {
61
- memory_tier: lifecycle.promotion.targetTier,
62
- metadata: {
63
- promoted_at: new Date().toISOString(),
64
- promotion_reason: lifecycle.promotion.reason,
65
- promoted_from: entity.memory_tier,
66
- },
67
- });
68
- result.promoted++;
69
- continue;
70
- }
71
- // 4. Flag stale entities for review (>90 days, <3 accesses)
72
- if (lifecycle.shouldFlagForReview) {
73
- await client.updateMemoryEntity(entity.id, {
74
- metadata: {
75
- needs_review: true,
76
- review_reason: lifecycle.reviewReason,
77
- flagged_at: new Date().toISOString(),
78
- },
79
- });
80
- result.reviewed++;
81
- }
82
- }
83
- catch {
84
- result.errors++;
85
- }
86
- }
87
- return result;
88
- }