@happyvertical/sdk-mcp 0.79.0 → 0.80.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.
package/AGENT.md CHANGED
@@ -7,7 +7,7 @@ MCP server for HAVE SDK - Routes queries to package experts using AGENT.md files
7
7
  ## Package Map
8
8
  - Package: `@happyvertical/sdk-mcp`
9
9
  - Hierarchy path: `@happyvertical/sdk > packages > sdk-mcp`
10
- - Workspace position: `23 of 31` local packages
10
+ - Workspace position: `23 of 32` local packages
11
11
  - Internal dependencies: `@happyvertical/ai`, `@happyvertical/files`, `@happyvertical/utils`
12
12
  - Internal dependents: none
13
13
  - Knowledge graph files: `AGENT.md`, `metadata.json`, `ecosystem-manifest.json`
@@ -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
- const Dirname = dirname(fileURLToPath(import.meta.url));
6
- const pkgRoot = join(Dirname, "../..");
7
- const targetDir = join(process.cwd(), ".claude");
8
- if (!existsSync(targetDir)) {
9
- mkdirSync(targetDir, { recursive: true });
10
- }
11
- const pkgName = "sdk-mcp";
12
- const agentMdSrc = existsSync(join(pkgRoot, "AGENT.md")) ? join(pkgRoot, "AGENT.md") : join(pkgRoot, "CLAUDE.md");
13
- const metaSrc = existsSync(join(pkgRoot, "metadata.json")) ? join(pkgRoot, "metadata.json") : join(pkgRoot, ".claude-meta.json");
14
- if (existsSync(agentMdSrc)) {
15
- copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));
16
- }
17
- if (existsSync(metaSrc)) {
18
- copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));
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 { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5
5
  import { getAI } from "@happyvertical/ai";
6
- import { readdir, readFile } from "node:fs/promises";
6
+ import { readFile, readdir } from "node:fs/promises";
7
7
  import { dirname, join } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
- const Filename = fileURLToPath(import.meta.url);
10
- const Dirname = dirname(Filename);
11
- const PACKAGE_KEYWORDS = {
12
- ai: [
13
- "ai",
14
- "llm",
15
- "gpt",
16
- "claude",
17
- "openai",
18
- "anthropic",
19
- "model",
20
- "completion",
21
- "chat",
22
- "embedding",
23
- "gemini",
24
- "bedrock",
25
- "huggingface"
26
- ],
27
- sql: [
28
- "database",
29
- "sql",
30
- "sqlite",
31
- "postgres",
32
- "duckdb",
33
- "query",
34
- "table",
35
- "schema",
36
- "json"
37
- ],
38
- files: [
39
- "file",
40
- "filesystem",
41
- "read",
42
- "write",
43
- "download",
44
- "upload",
45
- "path",
46
- "storage"
47
- ],
48
- spider: [
49
- "crawl",
50
- "scrape",
51
- "web",
52
- "html",
53
- "website",
54
- "page",
55
- "link",
56
- "civicweb"
57
- ],
58
- pdf: ["pdf", "document", "extract", "parse", "acrobat"],
59
- ocr: ["ocr", "image", "text extraction", "tesseract", "vision"],
60
- geo: ["location", "map", "coordinates", "geocode", "address", "gis"],
61
- translator: ["translate", "language", "translation", "localization"],
62
- weather: [
63
- "weather",
64
- "forecast",
65
- "temperature",
66
- "precipitation",
67
- "conditions",
68
- "wind",
69
- "humidity",
70
- "openweathermap",
71
- "environment canada"
72
- ],
73
- utils: ["id", "uuid", "slug", "date", "format", "utility", "helper"],
74
- cache: ["cache", "caching", "redis", "memory", "store"],
75
- logger: ["log", "logging", "logger", "debug", "error"],
76
- documents: ["document processing", "content extraction", "analysis"],
77
- email: [
78
- "email",
79
- "mail",
80
- "smtp",
81
- "imap",
82
- "pop3",
83
- "gmail",
84
- "mailbox",
85
- "send",
86
- "receive",
87
- "inbox",
88
- "folder"
89
- ],
90
- "github-actions": [
91
- "github",
92
- "actions",
93
- "workflow",
94
- "ci",
95
- "cd",
96
- "automation",
97
- "issue",
98
- "pr",
99
- "pull request",
100
- "triage"
101
- ],
102
- directory: [
103
- "directory",
104
- "identity",
105
- "provisioning",
106
- "kanidm",
107
- "stalwart",
108
- "postgres",
109
- "aws",
110
- "iam",
111
- "user",
112
- "group",
113
- "oauth",
114
- "mail",
115
- "domain",
116
- "dkim",
117
- "role",
118
- "tenant"
119
- ],
120
- analytics: [
121
- "analytics",
122
- "tracking",
123
- "ga4",
124
- "google analytics",
125
- "plausible",
126
- "metrics",
127
- "dimensions",
128
- "report",
129
- "pageview",
130
- "event",
131
- "conversion",
132
- "measurement protocol"
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
- const packageCache = /* @__PURE__ */ new Map();
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
- return join(Dirname, "..", "..", "..");
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
- const purposeMatch = content.match(
141
- /##\s+Purpose and Responsibilities\s+([^\n]+(?:\n(?!##)[^\n]+)*)/i
142
- );
143
- if (purposeMatch) {
144
- const purpose = purposeMatch[1].trim().replace(/\n/g, " ").replace(/\s+/g, " ");
145
- const firstSentence = purpose.match(/^[^.!?]+[.!?]/);
146
- if (firstSentence) {
147
- return firstSentence[0].trim();
148
- }
149
- return `${purpose.substring(0, 200).trim()}...`;
150
- }
151
- const lines = content.split("\n");
152
- let foundTitle = false;
153
- for (const line of lines) {
154
- if (line.startsWith("# ") || line.startsWith("## ")) {
155
- foundTitle = true;
156
- continue;
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
- if (packageCache.size > 0) {
166
- return packageCache;
167
- }
168
- const sdkRoot = getSDKRoot();
169
- const packagesDir = join(sdkRoot, "packages");
170
- try {
171
- const entries = await readdir(packagesDir, { withFileTypes: true });
172
- for (const entry of entries) {
173
- if (!entry.isDirectory()) continue;
174
- const packageName = entry.name;
175
- const agentMdPath = join(packagesDir, packageName, "AGENT.md");
176
- const legacyClaudePath = join(packagesDir, packageName, "CLAUDE.md");
177
- try {
178
- let agentMd;
179
- try {
180
- agentMd = await readFile(agentMdPath, "utf-8");
181
- } catch {
182
- agentMd = await readFile(legacyClaudePath, "utf-8");
183
- }
184
- const description = extractDescription(agentMd);
185
- const keywords = PACKAGE_KEYWORDS[packageName] || [];
186
- packageCache.set(packageName, {
187
- name: packageName,
188
- path: join(packagesDir, packageName),
189
- description,
190
- agentMd,
191
- keywords
192
- });
193
- } catch (_error) {
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
- const registry = await buildPackageRegistry();
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
- const registry = await buildPackageRegistry();
209
- return Array.from(registry.values());
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
- const pkg = await getPackage(name);
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
- return query.toLowerCase().split(/\W+/).filter((word) => word.length > 2);
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
- const matched = [];
220
- let score = 0;
221
- for (const queryKeyword of queryKeywords) {
222
- for (const packageKeyword of packageKeywords) {
223
- if (queryKeyword === packageKeyword) {
224
- score += 10;
225
- matched.push(packageKeyword);
226
- } else if (queryKeyword.includes(packageKeyword) || packageKeyword.includes(queryKeyword)) {
227
- score += 5;
228
- if (!matched.includes(packageKeyword)) {
229
- matched.push(packageKeyword);
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
- const packages = await getAllPackages();
243
- const queryKeywords = extractQueryKeywords(query);
244
- const matches = [];
245
- for (const pkg of packages) {
246
- const { score, matched } = calculateScore(queryKeywords, pkg.keywords);
247
- if (score >= minScore) {
248
- matches.push({
249
- package: pkg,
250
- score,
251
- matchedKeywords: matched
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
- const packages = await getAllPackages();
260
- const nameSet = new Set(names.map((n) => n.toLowerCase()));
261
- return packages.filter((pkg) => nameSet.has(pkg.name.toLowerCase()));
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
- try {
265
- return await getAI({});
266
- } catch (error) {
267
- throw new Error(
268
- `AI client initialization failed. Please configure AI provider using HAVE_AI_* environment variables. Error: ${error instanceof Error ? error.message : String(error)}`
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
- if (packages.length === 0) {
274
- return "No relevant packages found.";
275
- }
276
- const contextParts = [];
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
- const { query, packages: requestedPackages } = input;
291
- try {
292
- let packages;
293
- if (requestedPackages && requestedPackages.length > 0) {
294
- packages = await getPackagesByNames(requestedPackages);
295
- if (packages.length === 0) {
296
- return {
297
- content: [
298
- {
299
- type: "text",
300
- text: `None of the requested packages (${requestedPackages.join(", ")}) were found. Use list-packages to see available packages.`
301
- }
302
- ],
303
- isError: true
304
- };
305
- }
306
- } else {
307
- const matches = await routeQuery(query);
308
- if (matches.length === 0) {
309
- return {
310
- content: [
311
- {
312
- type: "text",
313
- text: `No relevant packages found for query: "${query}". Try using list-packages to browse available packages or specify packages explicitly.`
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
- const response = await ai.chat(
339
- [
340
- { role: "system", content: systemPrompt },
341
- { role: "user", content: query }
342
- ],
343
- {
344
- temperature: 0.7,
345
- maxTokens: 2e3
346
- }
347
- );
348
- const packageList = packages.map((p) => `@happyvertical/${p.name}`).join(", ");
349
- const footer = `
350
-
351
- ---
352
- *Consulted packages: ${packageList}*`;
353
- return {
354
- content: [
355
- {
356
- type: "text",
357
- text: response.content + footer
358
- }
359
- ]
360
- };
361
- } catch (error) {
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
- const docs = await getPackageDocs(packageName);
375
- if (!docs) {
376
- return {
377
- content: [
378
- {
379
- type: "text",
380
- text: `Package "${packageName}" not found. Use list-packages to see available packages.`
381
- }
382
- ],
383
- isError: true
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
- const packages = await getAllPackages();
397
- return {
398
- content: [
399
- {
400
- type: "text",
401
- text: JSON.stringify(
402
- {
403
- packages: packages.map((pkg) => ({
404
- name: pkg.name,
405
- description: pkg.description,
406
- keywords: pkg.keywords
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
- const server = new SDKMCPServer();
526
- server.run().catch((error) => {
527
- console.error("Fatal error:", error);
528
- process.exit(1);
529
- });
530
- const PACKAGE_VERSION_INITIALIZED = true;
531
- export {
532
- PACKAGE_VERSION_INITIALIZED
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/metadata.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "path": "packages/sdk-mcp",
4
4
  "position": {
5
5
  "index": 23,
6
- "count": 31
6
+ "count": 32
7
7
  },
8
8
  "description": "MCP server for HAVE SDK - Routes queries to package experts using AGENT.md files",
9
9
  "provides": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/sdk-mcp",
3
- "version": "0.79.0",
3
+ "version": "0.80.1",
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/ai": "0.79.0",
42
- "@happyvertical/utils": "0.79.0",
43
- "@happyvertical/files": "0.79.0"
41
+ "@happyvertical/ai": "0.80.1",
42
+ "@happyvertical/files": "0.80.1",
43
+ "@happyvertical/utils": "0.80.1"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "25.0.10",
47
- "typescript": "^5.9.3",
48
- "vite": "7.3.2",
47
+ "typescript": "5.9.3",
48
+ "vite": "8.1.4",
49
49
  "vite-plugin-dts": "4.5.4",
50
- "vitest": "^4.1.5"
50
+ "vitest": "4.1.10"
51
51
  },
52
52
  "scripts": {
53
53
  "test": "vitest --config ../../vitest.package.config.ts run --passWithNoTests",