@gethmy/mcp 2.0.0 → 2.1.1

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 (62) hide show
  1. package/README.md +6 -1
  2. package/dist/cli.js +711 -59
  3. package/dist/index.js +5 -3
  4. package/dist/lib/__tests__/active-learning.test.js +386 -0
  5. package/dist/lib/__tests__/agent-performance-profiles.test.js +325 -0
  6. package/dist/lib/__tests__/auto-session.test.js +661 -0
  7. package/dist/lib/__tests__/context-assembly.test.js +362 -0
  8. package/dist/lib/__tests__/graph-expansion.test.js +150 -0
  9. package/dist/lib/__tests__/integration-memory-crud.test.js +797 -0
  10. package/dist/lib/__tests__/integration-memory-system.test.js +281 -0
  11. package/dist/lib/__tests__/lifecycle-maintenance.test.js +207 -0
  12. package/dist/lib/__tests__/pattern-detection.test.js +295 -0
  13. package/dist/lib/__tests__/prompt-builder.test.js +418 -0
  14. package/dist/lib/active-learning.js +878 -0
  15. package/dist/lib/api-client.js +550 -0
  16. package/dist/lib/auto-session.js +173 -0
  17. package/dist/lib/cli.js +127 -0
  18. package/dist/lib/config.js +205 -0
  19. package/dist/lib/consolidation.js +243 -0
  20. package/dist/lib/context-assembly.js +606 -0
  21. package/dist/lib/graph-expansion.js +163 -0
  22. package/dist/lib/http.js +174 -0
  23. package/dist/lib/index.js +7 -0
  24. package/dist/lib/lifecycle-maintenance.js +88 -0
  25. package/dist/lib/prompt-builder.js +483 -0
  26. package/dist/lib/remote.js +166 -0
  27. package/dist/lib/server.js +3132 -0
  28. package/dist/lib/tui/agents.js +116 -0
  29. package/dist/lib/tui/docs.js +744 -0
  30. package/dist/lib/tui/setup.js +1068 -0
  31. package/dist/lib/tui/theme.js +95 -0
  32. package/dist/lib/tui/writer.js +200 -0
  33. package/package.json +15 -6
  34. package/src/__tests__/active-learning.test.ts +483 -0
  35. package/src/__tests__/agent-performance-profiles.test.ts +468 -0
  36. package/src/__tests__/auto-session.test.ts +912 -0
  37. package/src/__tests__/context-assembly.test.ts +506 -0
  38. package/src/__tests__/graph-expansion.test.ts +285 -0
  39. package/src/__tests__/integration-memory-crud.test.ts +948 -0
  40. package/src/__tests__/integration-memory-system.test.ts +321 -0
  41. package/src/__tests__/lifecycle-maintenance.test.ts +238 -0
  42. package/src/__tests__/pattern-detection.test.ts +438 -0
  43. package/src/__tests__/prompt-builder.test.ts +505 -0
  44. package/src/active-learning.ts +1227 -0
  45. package/src/api-client.ts +969 -0
  46. package/src/auto-session.ts +218 -0
  47. package/src/cli.ts +166 -0
  48. package/src/config.ts +285 -0
  49. package/src/consolidation.ts +314 -0
  50. package/src/context-assembly.ts +842 -0
  51. package/src/graph-expansion.ts +234 -0
  52. package/src/http.ts +265 -0
  53. package/src/index.ts +8 -0
  54. package/src/lifecycle-maintenance.ts +120 -0
  55. package/src/prompt-builder.ts +681 -0
  56. package/src/remote.ts +227 -0
  57. package/src/server.ts +3858 -0
  58. package/src/tui/agents.ts +154 -0
  59. package/src/tui/docs.ts +863 -0
  60. package/src/tui/setup.ts +1281 -0
  61. package/src/tui/theme.ts +114 -0
  62. package/src/tui/writer.ts +260 -0
package/src/remote.ts ADDED
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Remote MCP Server for Harmony
5
+ *
6
+ * Hosted MCP endpoint that any AI agent can connect to via HTTP.
7
+ * Auth via API key passed as Bearer token, validated against the Harmony API.
8
+ *
9
+ * Usage:
10
+ * Claude.ai → POST https://mcp.gethmy.com/mcp (Bearer: hmy_xxx)
11
+ *
12
+ * Env vars:
13
+ * HARMONY_API_URL - Harmony API base URL (default: https://gethmy.com/api)
14
+ * PORT - Listen port (default: 3002)
15
+ */
16
+
17
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
18
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
19
+ import { serve } from "bun";
20
+ import { Hono } from "hono";
21
+ import { cors } from "hono/cors";
22
+ import { HarmonyApiClient } from "./api-client.js";
23
+ import { registerHandlers, type ToolDeps } from "./server.js";
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Config from env
27
+ // ---------------------------------------------------------------------------
28
+ const HARMONY_API_URL = process.env.HARMONY_API_URL || "https://gethmy.com/api";
29
+ const PORT = parseInt(process.env.PORT || "3002", 10);
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // API key validation via Harmony API
33
+ // ---------------------------------------------------------------------------
34
+ interface ApiKeyInfo {
35
+ workspaceId: string | null;
36
+ }
37
+
38
+ async function validateApiKey(apiKey: string): Promise<ApiKeyInfo | null> {
39
+ try {
40
+ const response = await fetch(`${HARMONY_API_URL}/v1/workspaces`, {
41
+ headers: { "X-API-Key": apiKey },
42
+ });
43
+ if (!response.ok) return null;
44
+
45
+ const data = (await response.json()) as {
46
+ workspaces?: Array<{ id: string }>;
47
+ };
48
+ const firstWorkspace = data.workspaces?.[0];
49
+ return { workspaceId: firstWorkspace?.id ?? null };
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Session management
57
+ // ---------------------------------------------------------------------------
58
+ interface McpSession {
59
+ transport: WebStandardStreamableHTTPServerTransport;
60
+ server: Server;
61
+ apiKey: string;
62
+ activeWorkspaceId: string | null;
63
+ activeProjectId: string | null;
64
+ createdAt: number;
65
+ }
66
+
67
+ const sessions = new Map<string, McpSession>();
68
+
69
+ // Clean up stale sessions every 30 minutes
70
+ setInterval(
71
+ () => {
72
+ const now = Date.now();
73
+ const maxAge = 60 * 60 * 1000; // 1 hour
74
+ for (const [id, session] of sessions) {
75
+ if (now - session.createdAt > maxAge) {
76
+ session.transport.close().catch(() => {});
77
+ sessions.delete(id);
78
+ }
79
+ }
80
+ },
81
+ 30 * 60 * 1000,
82
+ );
83
+
84
+ function createSession(apiKey: string, keyInfo: ApiKeyInfo): McpSession {
85
+ const transport = new WebStandardStreamableHTTPServerTransport({
86
+ sessionIdGenerator: () => crypto.randomUUID(),
87
+ enableJsonResponse: true,
88
+ });
89
+
90
+ const server = new Server(
91
+ { name: "harmony-mcp-remote", version: "1.0.0" },
92
+ { capabilities: { tools: {}, resources: {} } },
93
+ );
94
+
95
+ const session: McpSession = {
96
+ transport,
97
+ server,
98
+ apiKey,
99
+ activeWorkspaceId: keyInfo.workspaceId,
100
+ activeProjectId: null,
101
+ createdAt: Date.now(),
102
+ };
103
+
104
+ // Create per-session deps
105
+ const client = new HarmonyApiClient({ apiKey, apiUrl: HARMONY_API_URL });
106
+
107
+ const deps: ToolDeps = {
108
+ getClient: () => client,
109
+ isConfigured: () => true,
110
+ getActiveProjectId: () => session.activeProjectId,
111
+ getActiveWorkspaceId: () => session.activeWorkspaceId,
112
+ setActiveProject: (id) => {
113
+ session.activeProjectId = id;
114
+ },
115
+ setActiveWorkspace: (id) => {
116
+ session.activeWorkspaceId = id;
117
+ },
118
+ getApiUrl: () => HARMONY_API_URL,
119
+ getMemoryDir: () => null, // No local filesystem in remote mode
120
+ getUserEmail: () => null,
121
+ saveConfig: () => {}, // No-op in remote mode
122
+ resetClient: () => {}, // No-op in remote mode
123
+ };
124
+
125
+ registerHandlers(server, deps);
126
+
127
+ // Clean up session when transport closes
128
+ transport.onclose = () => {
129
+ if (transport.sessionId) {
130
+ sessions.delete(transport.sessionId);
131
+ }
132
+ };
133
+
134
+ return session;
135
+ }
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // Hono app
139
+ // ---------------------------------------------------------------------------
140
+ const app = new Hono();
141
+
142
+ app.use(
143
+ "/*",
144
+ cors({
145
+ origin: "*",
146
+ allowMethods: ["GET", "POST", "DELETE", "OPTIONS"],
147
+ allowHeaders: [
148
+ "Content-Type",
149
+ "Authorization",
150
+ "Mcp-Session-Id",
151
+ "Mcp-Protocol-Version",
152
+ ],
153
+ exposeHeaders: ["Mcp-Session-Id"],
154
+ }),
155
+ );
156
+
157
+ // Health check
158
+ app.get("/health", (c) =>
159
+ c.json({
160
+ status: "ok",
161
+ service: "harmony-mcp-remote",
162
+ sessions: sessions.size,
163
+ }),
164
+ );
165
+
166
+ // MCP endpoint - handles POST (JSON-RPC), GET (SSE), DELETE (session close)
167
+ app.all("/mcp", async (c) => {
168
+ const method = c.req.method;
169
+
170
+ // Extract API key from Authorization header
171
+ const authHeader = c.req.header("Authorization");
172
+ if (!authHeader?.startsWith("Bearer ")) {
173
+ return c.json({ error: "Missing Authorization: Bearer <api-key>" }, 401);
174
+ }
175
+ const apiKey = authHeader.slice(7);
176
+
177
+ // Check for existing session
178
+ const sessionId = c.req.header("Mcp-Session-Id");
179
+
180
+ if (sessionId && sessions.has(sessionId)) {
181
+ // Existing session - forward request
182
+ const session = sessions.get(sessionId)!;
183
+ return session.transport.handleRequest(c.req.raw);
184
+ }
185
+
186
+ if (method === "POST") {
187
+ // Could be a new session (initialize) or an existing session we don't know about
188
+ // Validate API key
189
+ const keyInfo = await validateApiKey(apiKey);
190
+ if (!keyInfo) {
191
+ return c.json({ error: "Invalid API key" }, 401);
192
+ }
193
+
194
+ // Create new session
195
+ const session = createSession(apiKey, keyInfo);
196
+
197
+ // Connect server to transport
198
+ await session.server.connect(session.transport);
199
+
200
+ // Store session once transport has a session ID
201
+ const origOnSessionInitialized = session.transport._onsessioninitialized;
202
+ session.transport._onsessioninitialized = (sid: string) => {
203
+ sessions.set(sid, session);
204
+ origOnSessionInitialized?.(sid);
205
+ };
206
+
207
+ // Handle the initialize request
208
+ return session.transport.handleRequest(c.req.raw);
209
+ }
210
+
211
+ // GET or DELETE without a valid session
212
+ return c.json({ error: "Invalid or missing session" }, 404);
213
+ });
214
+
215
+ // ---------------------------------------------------------------------------
216
+ // Start server
217
+ // ---------------------------------------------------------------------------
218
+ console.log(`Starting Harmony Remote MCP server on port ${PORT}...`);
219
+
220
+ serve({
221
+ fetch: app.fetch,
222
+ port: PORT,
223
+ });
224
+
225
+ console.log(`Harmony Remote MCP server running at http://localhost:${PORT}`);
226
+ console.log(`MCP endpoint: http://localhost:${PORT}/mcp`);
227
+ console.log(`Health check: http://localhost:${PORT}/health`);