@gethmy/mcp 1.0.0 → 2.1.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 +201 -36
- package/dist/cli.js +20938 -20249
- package/dist/http.js +1957 -0
- package/dist/index.js +17833 -17888
- package/dist/lib/__tests__/active-learning.test.js +386 -0
- package/dist/lib/__tests__/agent-performance-profiles.test.js +325 -0
- package/dist/lib/__tests__/auto-session.test.js +661 -0
- package/dist/lib/__tests__/context-assembly.test.js +362 -0
- package/dist/lib/__tests__/graph-expansion.test.js +150 -0
- package/dist/lib/__tests__/integration-memory-crud.test.js +797 -0
- package/dist/lib/__tests__/integration-memory-system.test.js +281 -0
- package/dist/lib/__tests__/lifecycle-maintenance.test.js +207 -0
- package/dist/lib/__tests__/pattern-detection.test.js +295 -0
- package/dist/lib/__tests__/prompt-builder.test.js +418 -0
- package/dist/lib/active-learning.js +878 -0
- package/dist/lib/api-client.js +548 -0
- package/dist/lib/auto-session.js +173 -0
- package/dist/lib/cli.js +127 -0
- package/dist/lib/config.js +205 -0
- package/dist/lib/consolidation.js +243 -0
- package/dist/lib/context-assembly.js +606 -0
- package/dist/lib/graph-expansion.js +163 -0
- package/dist/lib/http.js +174 -0
- package/dist/lib/index.js +7 -0
- package/dist/lib/lifecycle-maintenance.js +88 -0
- package/dist/lib/prompt-builder.js +483 -0
- package/dist/lib/remote.js +166 -0
- package/dist/lib/server.js +3132 -0
- package/dist/lib/tui/agents.js +116 -0
- package/dist/lib/tui/docs.js +558 -0
- package/dist/lib/tui/setup.js +1068 -0
- package/dist/lib/tui/theme.js +95 -0
- package/dist/lib/tui/writer.js +200 -0
- package/dist/remote.js +34534 -0
- package/dist/server.js +31967 -0
- package/package.json +20 -7
- package/src/__tests__/active-learning.test.ts +483 -0
- package/src/__tests__/agent-performance-profiles.test.ts +468 -0
- package/src/__tests__/auto-session.test.ts +912 -0
- package/src/__tests__/context-assembly.test.ts +506 -0
- package/src/__tests__/graph-expansion.test.ts +285 -0
- package/src/__tests__/integration-memory-crud.test.ts +948 -0
- package/src/__tests__/integration-memory-system.test.ts +321 -0
- package/src/__tests__/lifecycle-maintenance.test.ts +238 -0
- package/src/__tests__/pattern-detection.test.ts +438 -0
- package/src/__tests__/prompt-builder.test.ts +505 -0
- package/src/active-learning.ts +1227 -0
- package/src/api-client.ts +963 -0
- package/src/auto-session.ts +218 -0
- package/src/cli.ts +166 -0
- package/src/config.ts +285 -0
- package/src/consolidation.ts +314 -0
- package/src/context-assembly.ts +842 -0
- package/src/graph-expansion.ts +234 -0
- package/src/http.ts +265 -0
- package/src/index.ts +8 -0
- package/src/lifecycle-maintenance.ts +120 -0
- package/src/prompt-builder.ts +681 -0
- package/src/remote.ts +227 -0
- package/src/server.ts +3858 -0
- package/src/tui/agents.ts +154 -0
- package/src/tui/docs.ts +650 -0
- package/src/tui/setup.ts +1281 -0
- package/src/tui/theme.ts +114 -0
- 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`);
|