@happyvertical/sdk-mcp 0.80.0 → 0.80.2
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/dist/cli/claude-context.js +16 -16
- package/dist/index.js +535 -481
- package/package.json +7 -7
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, mkdirSync, copyFileSync } from "node:fs";
|
|
3
2
|
import { dirname, join } from "node:path";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (existsSync(
|
|
18
|
-
|
|
19
|
-
}
|
|
4
|
+
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
5
|
+
//#region src/cli/claude-context.ts
|
|
6
|
+
/**
|
|
7
|
+
* CLI script to install agent context for @happyvertical/sdk-mcp
|
|
8
|
+
* Run the published context installer binary for this package.
|
|
9
|
+
*/
|
|
10
|
+
var pkgRoot = join(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
11
|
+
var targetDir = join(process.cwd(), ".claude");
|
|
12
|
+
if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
|
|
13
|
+
var pkgName = "sdk-mcp";
|
|
14
|
+
var agentMdSrc = existsSync(join(pkgRoot, "AGENT.md")) ? join(pkgRoot, "AGENT.md") : join(pkgRoot, "CLAUDE.md");
|
|
15
|
+
var metaSrc = existsSync(join(pkgRoot, "metadata.json")) ? join(pkgRoot, "metadata.json") : join(pkgRoot, ".claude-meta.json");
|
|
16
|
+
if (existsSync(agentMdSrc)) copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));
|
|
17
|
+
if (existsSync(metaSrc)) copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));
|
|
20
18
|
console.log(`✓ Installed @happyvertical/${pkgName} context to .claude/`);
|
|
19
|
+
//#endregion
|
|
20
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,326 +1,408 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import {
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import { getAI } from "@happyvertical/ai";
|
|
6
|
-
import {
|
|
6
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
9
|
+
//#region src/registry.ts
|
|
10
|
+
var Dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
/**
|
|
12
|
+
* Keyword mapping for routing queries to packages
|
|
13
|
+
* Based on issue #237 specification
|
|
14
|
+
*/
|
|
15
|
+
var PACKAGE_KEYWORDS = {
|
|
16
|
+
ai: [
|
|
17
|
+
"ai",
|
|
18
|
+
"llm",
|
|
19
|
+
"gpt",
|
|
20
|
+
"claude",
|
|
21
|
+
"openai",
|
|
22
|
+
"anthropic",
|
|
23
|
+
"model",
|
|
24
|
+
"completion",
|
|
25
|
+
"chat",
|
|
26
|
+
"embedding",
|
|
27
|
+
"gemini",
|
|
28
|
+
"bedrock",
|
|
29
|
+
"huggingface"
|
|
30
|
+
],
|
|
31
|
+
sql: [
|
|
32
|
+
"database",
|
|
33
|
+
"sql",
|
|
34
|
+
"sqlite",
|
|
35
|
+
"postgres",
|
|
36
|
+
"duckdb",
|
|
37
|
+
"query",
|
|
38
|
+
"table",
|
|
39
|
+
"schema",
|
|
40
|
+
"json"
|
|
41
|
+
],
|
|
42
|
+
files: [
|
|
43
|
+
"file",
|
|
44
|
+
"filesystem",
|
|
45
|
+
"read",
|
|
46
|
+
"write",
|
|
47
|
+
"download",
|
|
48
|
+
"upload",
|
|
49
|
+
"path",
|
|
50
|
+
"storage"
|
|
51
|
+
],
|
|
52
|
+
spider: [
|
|
53
|
+
"crawl",
|
|
54
|
+
"scrape",
|
|
55
|
+
"web",
|
|
56
|
+
"html",
|
|
57
|
+
"website",
|
|
58
|
+
"page",
|
|
59
|
+
"link",
|
|
60
|
+
"civicweb"
|
|
61
|
+
],
|
|
62
|
+
pdf: [
|
|
63
|
+
"pdf",
|
|
64
|
+
"document",
|
|
65
|
+
"extract",
|
|
66
|
+
"parse",
|
|
67
|
+
"acrobat"
|
|
68
|
+
],
|
|
69
|
+
ocr: [
|
|
70
|
+
"ocr",
|
|
71
|
+
"image",
|
|
72
|
+
"text extraction",
|
|
73
|
+
"tesseract",
|
|
74
|
+
"vision"
|
|
75
|
+
],
|
|
76
|
+
geo: [
|
|
77
|
+
"location",
|
|
78
|
+
"map",
|
|
79
|
+
"coordinates",
|
|
80
|
+
"geocode",
|
|
81
|
+
"address",
|
|
82
|
+
"gis"
|
|
83
|
+
],
|
|
84
|
+
translator: [
|
|
85
|
+
"translate",
|
|
86
|
+
"language",
|
|
87
|
+
"translation",
|
|
88
|
+
"localization"
|
|
89
|
+
],
|
|
90
|
+
weather: [
|
|
91
|
+
"weather",
|
|
92
|
+
"forecast",
|
|
93
|
+
"temperature",
|
|
94
|
+
"precipitation",
|
|
95
|
+
"conditions",
|
|
96
|
+
"wind",
|
|
97
|
+
"humidity",
|
|
98
|
+
"openweathermap",
|
|
99
|
+
"environment canada"
|
|
100
|
+
],
|
|
101
|
+
utils: [
|
|
102
|
+
"id",
|
|
103
|
+
"uuid",
|
|
104
|
+
"slug",
|
|
105
|
+
"date",
|
|
106
|
+
"format",
|
|
107
|
+
"utility",
|
|
108
|
+
"helper"
|
|
109
|
+
],
|
|
110
|
+
cache: [
|
|
111
|
+
"cache",
|
|
112
|
+
"caching",
|
|
113
|
+
"redis",
|
|
114
|
+
"memory",
|
|
115
|
+
"store"
|
|
116
|
+
],
|
|
117
|
+
logger: [
|
|
118
|
+
"log",
|
|
119
|
+
"logging",
|
|
120
|
+
"logger",
|
|
121
|
+
"debug",
|
|
122
|
+
"error"
|
|
123
|
+
],
|
|
124
|
+
documents: [
|
|
125
|
+
"document processing",
|
|
126
|
+
"content extraction",
|
|
127
|
+
"analysis"
|
|
128
|
+
],
|
|
129
|
+
email: [
|
|
130
|
+
"email",
|
|
131
|
+
"mail",
|
|
132
|
+
"smtp",
|
|
133
|
+
"imap",
|
|
134
|
+
"pop3",
|
|
135
|
+
"gmail",
|
|
136
|
+
"mailbox",
|
|
137
|
+
"send",
|
|
138
|
+
"receive",
|
|
139
|
+
"inbox",
|
|
140
|
+
"folder"
|
|
141
|
+
],
|
|
142
|
+
"github-actions": [
|
|
143
|
+
"github",
|
|
144
|
+
"actions",
|
|
145
|
+
"workflow",
|
|
146
|
+
"ci",
|
|
147
|
+
"cd",
|
|
148
|
+
"automation",
|
|
149
|
+
"issue",
|
|
150
|
+
"pr",
|
|
151
|
+
"pull request",
|
|
152
|
+
"triage"
|
|
153
|
+
],
|
|
154
|
+
directory: [
|
|
155
|
+
"directory",
|
|
156
|
+
"identity",
|
|
157
|
+
"provisioning",
|
|
158
|
+
"kanidm",
|
|
159
|
+
"stalwart",
|
|
160
|
+
"postgres",
|
|
161
|
+
"aws",
|
|
162
|
+
"iam",
|
|
163
|
+
"user",
|
|
164
|
+
"group",
|
|
165
|
+
"oauth",
|
|
166
|
+
"mail",
|
|
167
|
+
"domain",
|
|
168
|
+
"dkim",
|
|
169
|
+
"role",
|
|
170
|
+
"tenant"
|
|
171
|
+
],
|
|
172
|
+
analytics: [
|
|
173
|
+
"analytics",
|
|
174
|
+
"tracking",
|
|
175
|
+
"ga4",
|
|
176
|
+
"google analytics",
|
|
177
|
+
"plausible",
|
|
178
|
+
"metrics",
|
|
179
|
+
"dimensions",
|
|
180
|
+
"report",
|
|
181
|
+
"pageview",
|
|
182
|
+
"event",
|
|
183
|
+
"conversion",
|
|
184
|
+
"measurement protocol"
|
|
185
|
+
]
|
|
134
186
|
};
|
|
135
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Cache for loaded AGENT.md files
|
|
189
|
+
*/
|
|
190
|
+
var packageCache = /* @__PURE__ */ new Map();
|
|
191
|
+
/**
|
|
192
|
+
* Get the root SDK directory
|
|
193
|
+
*/
|
|
136
194
|
function getSDKRoot() {
|
|
137
|
-
|
|
195
|
+
return join(Dirname, "..", "..", "..");
|
|
138
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Extract description from AGENT.md content
|
|
199
|
+
* Looks for "Purpose and Responsibilities" or first paragraph
|
|
200
|
+
*/
|
|
139
201
|
function extractDescription(content) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
if (foundTitle && line.trim().length > 0 && !line.startsWith("#")) {
|
|
159
|
-
return line.trim();
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return "No description available";
|
|
202
|
+
const purposeMatch = content.match(/##\s+Purpose and Responsibilities\s+([^\n]+(?:\n(?!##)[^\n]+)*)/i);
|
|
203
|
+
if (purposeMatch) {
|
|
204
|
+
const purpose = purposeMatch[1].trim().replace(/\n/g, " ").replace(/\s+/g, " ");
|
|
205
|
+
const firstSentence = purpose.match(/^[^.!?]+[.!?]/);
|
|
206
|
+
if (firstSentence) return firstSentence[0].trim();
|
|
207
|
+
return `${purpose.substring(0, 200).trim()}...`;
|
|
208
|
+
}
|
|
209
|
+
const lines = content.split("\n");
|
|
210
|
+
let foundTitle = false;
|
|
211
|
+
for (const line of lines) {
|
|
212
|
+
if (line.startsWith("# ") || line.startsWith("## ")) {
|
|
213
|
+
foundTitle = true;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (foundTitle && line.trim().length > 0 && !line.startsWith("#")) return line.trim();
|
|
217
|
+
}
|
|
218
|
+
return "No description available";
|
|
163
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Scan packages directory for AGENT.md files and build registry.
|
|
222
|
+
* Falls back to legacy CLAUDE.md files for compatibility.
|
|
223
|
+
* Results are cached after the first call.
|
|
224
|
+
*
|
|
225
|
+
* @returns Cached map of package name to metadata
|
|
226
|
+
* @throws If the packages directory cannot be read
|
|
227
|
+
*/
|
|
164
228
|
async function buildPackageRegistry() {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return packageCache;
|
|
197
|
-
} catch (error) {
|
|
198
|
-
throw new Error(
|
|
199
|
-
`Failed to build package registry: ${error instanceof Error ? error.message : String(error)}`
|
|
200
|
-
);
|
|
201
|
-
}
|
|
229
|
+
if (packageCache.size > 0) return packageCache;
|
|
230
|
+
const packagesDir = join(getSDKRoot(), "packages");
|
|
231
|
+
try {
|
|
232
|
+
const entries = await readdir(packagesDir, { withFileTypes: true });
|
|
233
|
+
for (const entry of entries) {
|
|
234
|
+
if (!entry.isDirectory()) continue;
|
|
235
|
+
const packageName = entry.name;
|
|
236
|
+
const agentMdPath = join(packagesDir, packageName, "AGENT.md");
|
|
237
|
+
const legacyClaudePath = join(packagesDir, packageName, "CLAUDE.md");
|
|
238
|
+
try {
|
|
239
|
+
let agentMd;
|
|
240
|
+
try {
|
|
241
|
+
agentMd = await readFile(agentMdPath, "utf-8");
|
|
242
|
+
} catch {
|
|
243
|
+
agentMd = await readFile(legacyClaudePath, "utf-8");
|
|
244
|
+
}
|
|
245
|
+
const description = extractDescription(agentMd);
|
|
246
|
+
const keywords = PACKAGE_KEYWORDS[packageName] || [];
|
|
247
|
+
packageCache.set(packageName, {
|
|
248
|
+
name: packageName,
|
|
249
|
+
path: join(packagesDir, packageName),
|
|
250
|
+
description,
|
|
251
|
+
agentMd,
|
|
252
|
+
keywords
|
|
253
|
+
});
|
|
254
|
+
} catch (_error) {}
|
|
255
|
+
}
|
|
256
|
+
return packageCache;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
throw new Error(`Failed to build package registry: ${error instanceof Error ? error.message : String(error)}`);
|
|
259
|
+
}
|
|
202
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Get package metadata by name.
|
|
263
|
+
*
|
|
264
|
+
* @param name - Short package name (e.g. `"ai"`, `"sql"`)
|
|
265
|
+
* @returns Package metadata, or `undefined` if not found
|
|
266
|
+
*/
|
|
203
267
|
async function getPackage(name) {
|
|
204
|
-
|
|
205
|
-
return registry.get(name);
|
|
268
|
+
return (await buildPackageRegistry()).get(name);
|
|
206
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* Get all registered packages as an array.
|
|
272
|
+
*
|
|
273
|
+
* @returns Array of all discovered package metadata
|
|
274
|
+
*/
|
|
207
275
|
async function getAllPackages() {
|
|
208
|
-
|
|
209
|
-
|
|
276
|
+
const registry = await buildPackageRegistry();
|
|
277
|
+
return Array.from(registry.values());
|
|
210
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Get the raw AGENT.md content for a package.
|
|
281
|
+
*
|
|
282
|
+
* @param name - Short package name (e.g. `"ai"`, `"sql"`)
|
|
283
|
+
* @returns AGENT.md content string, or `undefined` if the package is not found
|
|
284
|
+
*/
|
|
211
285
|
async function getPackageDocs(name) {
|
|
212
|
-
|
|
213
|
-
return pkg?.agentMd;
|
|
286
|
+
return (await getPackage(name))?.agentMd;
|
|
214
287
|
}
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/router.ts
|
|
290
|
+
/**
|
|
291
|
+
* Extract keywords from query text
|
|
292
|
+
* Converts to lowercase and splits on word boundaries
|
|
293
|
+
*/
|
|
215
294
|
function extractQueryKeywords(query) {
|
|
216
|
-
|
|
295
|
+
return query.toLowerCase().split(/\W+/).filter((word) => word.length > 2);
|
|
217
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* Calculate relevance score for a package based on keyword matches
|
|
299
|
+
*/
|
|
218
300
|
function calculateScore(queryKeywords, packageKeywords) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
for (const queryKeyword of queryKeywords) {
|
|
235
|
-
if (packageKeywords.some((pkg) => pkg.toLowerCase() === queryKeyword)) {
|
|
236
|
-
score += 15;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return { score, matched };
|
|
301
|
+
const matched = [];
|
|
302
|
+
let score = 0;
|
|
303
|
+
for (const queryKeyword of queryKeywords) for (const packageKeyword of packageKeywords) if (queryKeyword === packageKeyword) {
|
|
304
|
+
score += 10;
|
|
305
|
+
matched.push(packageKeyword);
|
|
306
|
+
} else if (queryKeyword.includes(packageKeyword) || packageKeyword.includes(queryKeyword)) {
|
|
307
|
+
score += 5;
|
|
308
|
+
if (!matched.includes(packageKeyword)) matched.push(packageKeyword);
|
|
309
|
+
}
|
|
310
|
+
for (const queryKeyword of queryKeywords) if (packageKeywords.some((pkg) => pkg.toLowerCase() === queryKeyword)) score += 15;
|
|
311
|
+
return {
|
|
312
|
+
score,
|
|
313
|
+
matched
|
|
314
|
+
};
|
|
240
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* Route query to relevant packages based on keyword matching
|
|
318
|
+
* Returns packages sorted by relevance score
|
|
319
|
+
*
|
|
320
|
+
* @param query - User query string
|
|
321
|
+
* @param minScore - Minimum score threshold for including a package (default: 5)
|
|
322
|
+
* @returns Array of package matches sorted by score (descending)
|
|
323
|
+
*/
|
|
241
324
|
async function routeQuery(query, minScore = 5) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
matches.sort((a, b) => b.score - a.score);
|
|
256
|
-
return matches;
|
|
325
|
+
const packages = await getAllPackages();
|
|
326
|
+
const queryKeywords = extractQueryKeywords(query);
|
|
327
|
+
const matches = [];
|
|
328
|
+
for (const pkg of packages) {
|
|
329
|
+
const { score, matched } = calculateScore(queryKeywords, pkg.keywords);
|
|
330
|
+
if (score >= minScore) matches.push({
|
|
331
|
+
package: pkg,
|
|
332
|
+
score,
|
|
333
|
+
matchedKeywords: matched
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
matches.sort((a, b) => b.score - a.score);
|
|
337
|
+
return matches;
|
|
257
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* Look up packages by explicit name list.
|
|
341
|
+
* Useful when the user specifies packages directly rather than relying on keyword routing.
|
|
342
|
+
*
|
|
343
|
+
* @param names - Array of short package names (e.g. `["ai", "sql"]`)
|
|
344
|
+
* @returns Array of matched package metadata (unmatched names are silently excluded)
|
|
345
|
+
*/
|
|
258
346
|
async function getPackagesByNames(names) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
347
|
+
const packages = await getAllPackages();
|
|
348
|
+
const nameSet = new Set(names.map((n) => n.toLowerCase()));
|
|
349
|
+
return packages.filter((pkg) => nameSet.has(pkg.name.toLowerCase()));
|
|
262
350
|
}
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region src/tools/ask.ts
|
|
353
|
+
/**
|
|
354
|
+
* Get AI client instance
|
|
355
|
+
* Uses environment variables for configuration (HAVE_AI_*)
|
|
356
|
+
*/
|
|
263
357
|
async function getAIClient() {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
);
|
|
270
|
-
}
|
|
358
|
+
try {
|
|
359
|
+
return await getAI({});
|
|
360
|
+
} catch (error) {
|
|
361
|
+
throw new Error(`AI client initialization failed. Please configure AI provider using HAVE_AI_* environment variables. Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
362
|
+
}
|
|
271
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Build context from package AGENT.md files
|
|
366
|
+
*/
|
|
272
367
|
function buildContext(packages) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
for (const pkg of packages) {
|
|
278
|
-
contextParts.push(
|
|
279
|
-
`## Package: @happyvertical/${pkg.name}
|
|
280
|
-
|
|
281
|
-
${pkg.agentMd}
|
|
282
|
-
|
|
283
|
-
---
|
|
284
|
-
`
|
|
285
|
-
);
|
|
286
|
-
}
|
|
287
|
-
return contextParts.join("\n");
|
|
368
|
+
if (packages.length === 0) return "No relevant packages found.";
|
|
369
|
+
const contextParts = [];
|
|
370
|
+
for (const pkg of packages) contextParts.push(`## Package: @happyvertical/${pkg.name}\n\n${pkg.agentMd}\n\n---\n`);
|
|
371
|
+
return contextParts.join("\n");
|
|
288
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* Ask tool - Routes queries to package experts and synthesizes responses
|
|
375
|
+
*
|
|
376
|
+
* @param input - Query and optional package list
|
|
377
|
+
* @returns AI-generated response based on package documentation
|
|
378
|
+
*/
|
|
289
379
|
async function ask(input) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
isError: false
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
packages = matches.slice(0, 3).map((m) => m.package);
|
|
320
|
-
}
|
|
321
|
-
const context = buildContext(packages);
|
|
322
|
-
const ai = await getAIClient();
|
|
323
|
-
const systemPrompt = `You are an expert SDK documentation assistant for the HAppy VErtical (HAVE) SDK.
|
|
380
|
+
const { query, packages: requestedPackages } = input;
|
|
381
|
+
try {
|
|
382
|
+
let packages;
|
|
383
|
+
if (requestedPackages && requestedPackages.length > 0) {
|
|
384
|
+
packages = await getPackagesByNames(requestedPackages);
|
|
385
|
+
if (packages.length === 0) return {
|
|
386
|
+
content: [{
|
|
387
|
+
type: "text",
|
|
388
|
+
text: `None of the requested packages (${requestedPackages.join(", ")}) were found. Use list-packages to see available packages.`
|
|
389
|
+
}],
|
|
390
|
+
isError: true
|
|
391
|
+
};
|
|
392
|
+
} else {
|
|
393
|
+
const matches = await routeQuery(query);
|
|
394
|
+
if (matches.length === 0) return {
|
|
395
|
+
content: [{
|
|
396
|
+
type: "text",
|
|
397
|
+
text: `No relevant packages found for query: "${query}". Try using list-packages to browse available packages or specify packages explicitly.`
|
|
398
|
+
}],
|
|
399
|
+
isError: false
|
|
400
|
+
};
|
|
401
|
+
packages = matches.slice(0, 3).map((m) => m.package);
|
|
402
|
+
}
|
|
403
|
+
const context = buildContext(packages);
|
|
404
|
+
const ai = await getAIClient();
|
|
405
|
+
const systemPrompt = `You are an expert SDK documentation assistant for the HAppy VErtical (HAVE) SDK.
|
|
324
406
|
You have access to the full documentation (AGENT.md files) for the following packages: ${packages.map((p) => `@happyvertical/${p.name}`).join(", ")}.
|
|
325
407
|
|
|
326
408
|
Your role is to:
|
|
@@ -335,199 +417,171 @@ Use the documentation provided below to answer the user's question accurately.
|
|
|
335
417
|
---
|
|
336
418
|
${context}
|
|
337
419
|
---`;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
return {
|
|
363
|
-
content: [
|
|
364
|
-
{
|
|
365
|
-
type: "text",
|
|
366
|
-
text: `Error processing query: ${error instanceof Error ? error.message : String(error)}`
|
|
367
|
-
}
|
|
368
|
-
],
|
|
369
|
-
isError: true
|
|
370
|
-
};
|
|
371
|
-
}
|
|
420
|
+
const response = await ai.chat([{
|
|
421
|
+
role: "system",
|
|
422
|
+
content: systemPrompt
|
|
423
|
+
}, {
|
|
424
|
+
role: "user",
|
|
425
|
+
content: query
|
|
426
|
+
}], {
|
|
427
|
+
temperature: .7,
|
|
428
|
+
maxTokens: 2e3
|
|
429
|
+
});
|
|
430
|
+
const footer = `\n\n---\n*Consulted packages: ${packages.map((p) => `@happyvertical/${p.name}`).join(", ")}*`;
|
|
431
|
+
return { content: [{
|
|
432
|
+
type: "text",
|
|
433
|
+
text: response.content + footer
|
|
434
|
+
}] };
|
|
435
|
+
} catch (error) {
|
|
436
|
+
return {
|
|
437
|
+
content: [{
|
|
438
|
+
type: "text",
|
|
439
|
+
text: `Error processing query: ${error instanceof Error ? error.message : String(error)}`
|
|
440
|
+
}],
|
|
441
|
+
isError: true
|
|
442
|
+
};
|
|
443
|
+
}
|
|
372
444
|
}
|
|
445
|
+
//#endregion
|
|
446
|
+
//#region src/tools/get-docs.ts
|
|
447
|
+
/**
|
|
448
|
+
* Get full AGENT.md documentation for a specific package
|
|
449
|
+
*
|
|
450
|
+
* @param packageName - Name of the package (e.g., 'ai', 'sql', 'spider')
|
|
451
|
+
* @returns Package documentation content
|
|
452
|
+
*/
|
|
373
453
|
async function getDocs(packageName) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
return {
|
|
387
|
-
content: [
|
|
388
|
-
{
|
|
389
|
-
type: "text",
|
|
390
|
-
text: docs
|
|
391
|
-
}
|
|
392
|
-
]
|
|
393
|
-
};
|
|
454
|
+
const docs = await getPackageDocs(packageName);
|
|
455
|
+
if (!docs) return {
|
|
456
|
+
content: [{
|
|
457
|
+
type: "text",
|
|
458
|
+
text: `Package "${packageName}" not found. Use list-packages to see available packages.`
|
|
459
|
+
}],
|
|
460
|
+
isError: true
|
|
461
|
+
};
|
|
462
|
+
return { content: [{
|
|
463
|
+
type: "text",
|
|
464
|
+
text: docs
|
|
465
|
+
}] };
|
|
394
466
|
}
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region src/tools/list-packages.ts
|
|
469
|
+
/**
|
|
470
|
+
* List all SDK packages with their descriptions
|
|
471
|
+
*
|
|
472
|
+
* @returns Object with packages array containing name and description
|
|
473
|
+
*/
|
|
395
474
|
async function listPackages() {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
total: packages.length
|
|
409
|
-
},
|
|
410
|
-
null,
|
|
411
|
-
2
|
|
412
|
-
)
|
|
413
|
-
}
|
|
414
|
-
]
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
class SDKMCPServer {
|
|
418
|
-
server;
|
|
419
|
-
constructor() {
|
|
420
|
-
this.server = new Server(
|
|
421
|
-
{
|
|
422
|
-
name: "happyvertical-sdk-mcp",
|
|
423
|
-
version: "0.1.0"
|
|
424
|
-
},
|
|
425
|
-
{
|
|
426
|
-
capabilities: {
|
|
427
|
-
tools: {}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
);
|
|
431
|
-
this.setupToolHandlers();
|
|
432
|
-
this.setupErrorHandling();
|
|
433
|
-
}
|
|
434
|
-
setupToolHandlers() {
|
|
435
|
-
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
436
|
-
tools: [
|
|
437
|
-
{
|
|
438
|
-
name: "ask",
|
|
439
|
-
description: "Ask a question about the HAVE SDK. Automatically routes your query to relevant package experts (AGENT.md files) and synthesizes a response using AI.",
|
|
440
|
-
inputSchema: {
|
|
441
|
-
type: "object",
|
|
442
|
-
properties: {
|
|
443
|
-
query: {
|
|
444
|
-
type: "string",
|
|
445
|
-
description: "Your question about SDK usage or capabilities"
|
|
446
|
-
},
|
|
447
|
-
packages: {
|
|
448
|
-
type: "array",
|
|
449
|
-
items: { type: "string" },
|
|
450
|
-
description: 'Optional: Specific packages to consult (e.g., ["ai", "sql"])'
|
|
451
|
-
}
|
|
452
|
-
},
|
|
453
|
-
required: ["query"]
|
|
454
|
-
}
|
|
455
|
-
},
|
|
456
|
-
{
|
|
457
|
-
name: "list-packages",
|
|
458
|
-
description: "List all available SDK packages with their descriptions and keywords",
|
|
459
|
-
inputSchema: {
|
|
460
|
-
type: "object",
|
|
461
|
-
properties: {}
|
|
462
|
-
}
|
|
463
|
-
},
|
|
464
|
-
{
|
|
465
|
-
name: "get-docs",
|
|
466
|
-
description: "Get the full AGENT.md documentation for a specific package",
|
|
467
|
-
inputSchema: {
|
|
468
|
-
type: "object",
|
|
469
|
-
properties: {
|
|
470
|
-
packageName: {
|
|
471
|
-
type: "string",
|
|
472
|
-
description: 'Name of the package (e.g., "ai", "sql", "spider")'
|
|
473
|
-
}
|
|
474
|
-
},
|
|
475
|
-
required: ["packageName"]
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
]
|
|
479
|
-
}));
|
|
480
|
-
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
481
|
-
try {
|
|
482
|
-
switch (request.params.name) {
|
|
483
|
-
case "ask": {
|
|
484
|
-
const input = request.params.arguments ?? {};
|
|
485
|
-
return await ask(input);
|
|
486
|
-
}
|
|
487
|
-
case "list-packages": {
|
|
488
|
-
return await listPackages();
|
|
489
|
-
}
|
|
490
|
-
case "get-docs": {
|
|
491
|
-
const args = request.params.arguments ?? {};
|
|
492
|
-
return await getDocs(args.packageName);
|
|
493
|
-
}
|
|
494
|
-
default:
|
|
495
|
-
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
496
|
-
}
|
|
497
|
-
} catch (error) {
|
|
498
|
-
return {
|
|
499
|
-
content: [
|
|
500
|
-
{
|
|
501
|
-
type: "text",
|
|
502
|
-
text: `Error executing tool: ${error instanceof Error ? error.message : String(error)}`
|
|
503
|
-
}
|
|
504
|
-
],
|
|
505
|
-
isError: true
|
|
506
|
-
};
|
|
507
|
-
}
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
setupErrorHandling() {
|
|
511
|
-
this.server.onerror = (error) => {
|
|
512
|
-
console.error("[MCP Error]", error);
|
|
513
|
-
};
|
|
514
|
-
process.on("SIGINT", async () => {
|
|
515
|
-
await this.server.close();
|
|
516
|
-
process.exit(0);
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
async run() {
|
|
520
|
-
const transport = new StdioServerTransport();
|
|
521
|
-
await this.server.connect(transport);
|
|
522
|
-
console.error("HappyVertical SDK MCP Server running on stdio");
|
|
523
|
-
}
|
|
475
|
+
const packages = await getAllPackages();
|
|
476
|
+
return { content: [{
|
|
477
|
+
type: "text",
|
|
478
|
+
text: JSON.stringify({
|
|
479
|
+
packages: packages.map((pkg) => ({
|
|
480
|
+
name: pkg.name,
|
|
481
|
+
description: pkg.description,
|
|
482
|
+
keywords: pkg.keywords
|
|
483
|
+
})),
|
|
484
|
+
total: packages.length
|
|
485
|
+
}, null, 2)
|
|
486
|
+
}] };
|
|
524
487
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
488
|
+
//#endregion
|
|
489
|
+
//#region src/index.ts
|
|
490
|
+
/**
|
|
491
|
+
* SDK MCP Server
|
|
492
|
+
* Routes developer queries to appropriate package experts using AGENT.md files
|
|
493
|
+
*/
|
|
494
|
+
var SDKMCPServer = class {
|
|
495
|
+
server;
|
|
496
|
+
constructor() {
|
|
497
|
+
this.server = new Server({
|
|
498
|
+
name: "happyvertical-sdk-mcp",
|
|
499
|
+
version: "0.1.0"
|
|
500
|
+
}, { capabilities: { tools: {} } });
|
|
501
|
+
this.setupToolHandlers();
|
|
502
|
+
this.setupErrorHandling();
|
|
503
|
+
}
|
|
504
|
+
setupToolHandlers() {
|
|
505
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [
|
|
506
|
+
{
|
|
507
|
+
name: "ask",
|
|
508
|
+
description: "Ask a question about the HAVE SDK. Automatically routes your query to relevant package experts (AGENT.md files) and synthesizes a response using AI.",
|
|
509
|
+
inputSchema: {
|
|
510
|
+
type: "object",
|
|
511
|
+
properties: {
|
|
512
|
+
query: {
|
|
513
|
+
type: "string",
|
|
514
|
+
description: "Your question about SDK usage or capabilities"
|
|
515
|
+
},
|
|
516
|
+
packages: {
|
|
517
|
+
type: "array",
|
|
518
|
+
items: { type: "string" },
|
|
519
|
+
description: "Optional: Specific packages to consult (e.g., [\"ai\", \"sql\"])"
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
required: ["query"]
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
name: "list-packages",
|
|
527
|
+
description: "List all available SDK packages with their descriptions and keywords",
|
|
528
|
+
inputSchema: {
|
|
529
|
+
type: "object",
|
|
530
|
+
properties: {}
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
name: "get-docs",
|
|
535
|
+
description: "Get the full AGENT.md documentation for a specific package",
|
|
536
|
+
inputSchema: {
|
|
537
|
+
type: "object",
|
|
538
|
+
properties: { packageName: {
|
|
539
|
+
type: "string",
|
|
540
|
+
description: "Name of the package (e.g., \"ai\", \"sql\", \"spider\")"
|
|
541
|
+
} },
|
|
542
|
+
required: ["packageName"]
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
] }));
|
|
546
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
547
|
+
try {
|
|
548
|
+
switch (request.params.name) {
|
|
549
|
+
case "ask": return await ask(request.params.arguments ?? {});
|
|
550
|
+
case "list-packages": return await listPackages();
|
|
551
|
+
case "get-docs": return await getDocs((request.params.arguments ?? {}).packageName);
|
|
552
|
+
default: throw new Error(`Unknown tool: ${request.params.name}`);
|
|
553
|
+
}
|
|
554
|
+
} catch (error) {
|
|
555
|
+
return {
|
|
556
|
+
content: [{
|
|
557
|
+
type: "text",
|
|
558
|
+
text: `Error executing tool: ${error instanceof Error ? error.message : String(error)}`
|
|
559
|
+
}],
|
|
560
|
+
isError: true
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
setupErrorHandling() {
|
|
566
|
+
this.server.onerror = (error) => {
|
|
567
|
+
console.error("[MCP Error]", error);
|
|
568
|
+
};
|
|
569
|
+
process.on("SIGINT", async () => {
|
|
570
|
+
await this.server.close();
|
|
571
|
+
process.exit(0);
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
async run() {
|
|
575
|
+
const transport = new StdioServerTransport();
|
|
576
|
+
await this.server.connect(transport);
|
|
577
|
+
console.error("HappyVertical SDK MCP Server running on stdio");
|
|
578
|
+
}
|
|
533
579
|
};
|
|
580
|
+
new SDKMCPServer().run().catch((error) => {
|
|
581
|
+
console.error("Fatal error:", error);
|
|
582
|
+
process.exit(1);
|
|
583
|
+
});
|
|
584
|
+
/** @internal */
|
|
585
|
+
var PACKAGE_VERSION_INITIALIZED = true;
|
|
586
|
+
//#endregion
|
|
587
|
+
export { PACKAGE_VERSION_INITIALIZED };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/sdk-mcp",
|
|
3
|
-
"version": "0.80.
|
|
3
|
+
"version": "0.80.2",
|
|
4
4
|
"description": "MCP server for HAVE SDK - Routes queries to package experts using AGENT.md files",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -38,16 +38,16 @@
|
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
41
|
-
"@happyvertical/
|
|
42
|
-
"@happyvertical/
|
|
43
|
-
"@happyvertical/
|
|
41
|
+
"@happyvertical/files": "0.80.2",
|
|
42
|
+
"@happyvertical/ai": "0.80.2",
|
|
43
|
+
"@happyvertical/utils": "0.80.2"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "25.0.10",
|
|
47
|
-
"typescript": "
|
|
48
|
-
"vite": "
|
|
47
|
+
"typescript": "5.9.3",
|
|
48
|
+
"vite": "8.1.4",
|
|
49
49
|
"vite-plugin-dts": "4.5.4",
|
|
50
|
-
"vitest": "
|
|
50
|
+
"vitest": "4.1.10"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"test": "vitest --config ../../vitest.package.config.ts run --passWithNoTests",
|