midnight-mcp 0.2.3 → 0.2.4

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 CHANGED
@@ -104,11 +104,11 @@ Add to `~/.codeium/windsurf/mcp_config.json`:
104
104
 
105
105
  ## What's Included
106
106
 
107
- ### 27 Tools
107
+ ### 28 Tools
108
108
 
109
109
  | Category | Tools | Description |
110
110
  | ----------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
111
- | **Search** | `search-compact`, `search-typescript`, `search-docs` | Semantic search across Midnight codebase |
111
+ | **Search** | `search-compact`, `search-typescript`, `search-docs`, `fetch-docs` | Semantic search + live docs fetching |
112
112
  | **Analysis** | `analyze-contract`, `explain-circuit`, `extract-contract-structure` | Static analysis with 15+ checks (P0-P2 severity) |
113
113
  | **Repository** | `get-file`, `list-examples`, `get-latest-updates` | Access files and examples |
114
114
  | **Versioning** | `get-version-info`, `check-breaking-changes`, `get-migration-guide`, `get-file-at-version`, `compare-syntax`, `get-latest-syntax` | Version tracking and migration |
@@ -123,7 +123,7 @@ All tools are prefixed with `midnight-` (e.g., `midnight-search-compact`).
123
123
 
124
124
  | Capability | Feature |
125
125
  | --------------- | ----------------------------------------------- |
126
- | **Tools** | 27 tools with `listChanged` notifications |
126
+ | **Tools** | 28 tools with `listChanged` notifications |
127
127
  | **Resources** | 9 embedded resources with subscription support |
128
128
  | **Prompts** | 5 workflow prompts |
129
129
  | **Logging** | Client-controllable log level |
package/dist/bin.js CHANGED
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  startHttpServer,
4
4
  startServer
5
- } from "./chunk-ZYCQLQEX.js";
5
+ } from "./chunk-5DMOVW6Q.js";
6
6
  import {
7
7
  setOutputFormat
8
- } from "./chunk-EBFMM2DS.js";
8
+ } from "./chunk-KB6HV57P.js";
9
9
 
10
10
  // src/bin.ts
11
11
  import { config } from "dotenv";
@@ -13,7 +13,7 @@ import { resolve } from "path";
13
13
  import yargs from "yargs";
14
14
  import { hideBin } from "yargs/helpers";
15
15
  config({ path: resolve(process.cwd(), ".env") });
16
- var CURRENT_VERSION = "0.2.3";
16
+ var CURRENT_VERSION = "0.2.4";
17
17
  process.on("uncaughtException", (error) => {
18
18
  console.error("Uncaught exception:", error);
19
19
  process.exit(1);
@@ -25,7 +25,7 @@ import {
25
25
  validateNumber,
26
26
  validateQuery,
27
27
  vectorStore
28
- } from "./chunk-EBFMM2DS.js";
28
+ } from "./chunk-KB6HV57P.js";
29
29
 
30
30
  // src/tools/search/schemas.ts
31
31
  import { z } from "zod";
@@ -48,6 +48,14 @@ var SearchDocsInputSchema = z.object({
48
48
  category: z.enum(["guides", "api", "concepts", "all"]).optional().default("all").describe("Filter by documentation category"),
49
49
  limit: z.number().optional().default(10)
50
50
  });
51
+ var FetchDocsInputSchema = z.object({
52
+ path: z.string().describe(
53
+ "Documentation page path (e.g., '/develop/faq', '/getting-started/installation', '/compact')"
54
+ ),
55
+ extractSection: z.string().optional().describe(
56
+ "Optional: Extract only a specific section by heading (e.g., 'General questions')"
57
+ )
58
+ });
51
59
 
52
60
  // src/tools/search/handlers.ts
53
61
  function validateSearchInput(query, limit) {
@@ -235,13 +243,16 @@ async function searchDocs(input) {
235
243
  );
236
244
  const cached = checkSearchCache(cacheKey);
237
245
  if (cached) return cached;
246
+ const freshnessHint = "For guaranteed freshness, use midnight-fetch-docs with the path from these results (e.g., /develop/faq)";
238
247
  const hostedResult = await tryHostedSearch(
239
248
  "docs",
240
249
  () => searchDocsHosted(sanitizedQuery, limit, input.category),
241
250
  cacheKey,
242
251
  warnings
243
252
  );
244
- if (hostedResult) return hostedResult.result;
253
+ if (hostedResult) {
254
+ return { ...hostedResult.result, hint: freshnessHint };
255
+ }
245
256
  const filter = {
246
257
  language: "markdown"
247
258
  };
@@ -261,10 +272,169 @@ async function searchDocs(input) {
261
272
  })),
262
273
  totalResults: results.length,
263
274
  query: sanitizedQuery,
264
- category: input.category
275
+ category: input.category,
276
+ hint: "For guaranteed freshness, use midnight-fetch-docs with the path from these results (e.g., /develop/faq)"
265
277
  };
266
278
  return finalizeResponse(response, cacheKey, warnings);
267
279
  }
280
+ var DOCS_BASE_URL = "https://docs.midnight.network";
281
+ var FETCH_TIMEOUT = 15e3;
282
+ var KNOWN_DOC_PATHS = [
283
+ "/develop/faq",
284
+ "/develop/getting-help",
285
+ "/develop/tutorial/building",
286
+ "/develop/how-midnight-works",
287
+ "/getting-started/installation",
288
+ "/getting-started/create-mn-app",
289
+ "/getting-started/deploy-mn-app",
290
+ "/getting-started/interact-with-mn-app",
291
+ "/compact",
292
+ "/learn/what-is-midnight",
293
+ "/learn/glossary",
294
+ "/develop/reference/midnight-api",
295
+ "/relnotes/overview",
296
+ "/blog"
297
+ ];
298
+ function extractContentFromHtml(html, extractSection) {
299
+ const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
300
+ const title = titleMatch ? titleMatch[1].replace(" | Midnight Docs", "").trim() : "Unknown";
301
+ const lastUpdatedMatch = html.match(
302
+ /<time[^>]*datetime="([^"]+)"[^>]*itemprop="dateModified"/i
303
+ );
304
+ const lastUpdated = lastUpdatedMatch ? lastUpdatedMatch[1] : void 0;
305
+ const articleMatch = html.match(/<article[^>]*>([\s\S]*?)<\/article>/i);
306
+ const articleHtml = articleMatch ? articleMatch[1] : html;
307
+ const headings = [];
308
+ const headingRegex = /<h([1-6])[^>]*class="[^"]*anchor[^"]*"[^>]*id="([^"]*)"[^>]*>([^<]*(?:<[^/][^>]*>[^<]*<\/[^>]+>)*[^<]*)/gi;
309
+ let headingMatch;
310
+ while ((headingMatch = headingRegex.exec(articleHtml)) !== null) {
311
+ const text = headingMatch[3].replace(/<[^>]+>/g, "").replace(/\u200B/g, "").replace(/​/g, "").trim();
312
+ if (text) {
313
+ headings.push({
314
+ level: parseInt(headingMatch[1]),
315
+ text,
316
+ id: headingMatch[2]
317
+ });
318
+ }
319
+ }
320
+ let content = articleHtml.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<svg[^>]*>[\s\S]*?<\/svg>/gi, "").replace(/<nav[^>]*class="[^"]*breadcrumb[^"]*"[^>]*>[\s\S]*?<\/nav>/gi, "").replace(/<footer[^>]*>[\s\S]*?<\/footer>/gi, "").replace(/<a[^>]*class="[^"]*hash-link[^"]*"[^>]*>[\s\S]*?<\/a>/gi, "").replace(/<button[^>]*>[\s\S]*?<\/button>/gi, "");
321
+ content = content.replace(/<h1[^>]*>/gi, "\n# ").replace(/<\/h1>/gi, "\n").replace(/<h2[^>]*>/gi, "\n## ").replace(/<\/h2>/gi, "\n").replace(/<h3[^>]*>/gi, "\n### ").replace(/<\/h3>/gi, "\n").replace(/<h4[^>]*>/gi, "\n#### ").replace(/<\/h4>/gi, "\n").replace(/<p[^>]*>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<ul[^>]*>/gi, "\n").replace(/<\/ul>/gi, "\n").replace(/<ol[^>]*>/gi, "\n").replace(/<\/ol>/gi, "\n").replace(/<li[^>]*>/gi, "\u2022 ").replace(/<\/li>/gi, "\n").replace(/<pre[^>]*><code[^>]*>/gi, "\n```\n").replace(/<\/code><\/pre>/gi, "\n```\n").replace(/<code[^>]*>/gi, "`").replace(/<\/code>/gi, "`").replace(/<a[^>]*href="([^"]*)"[^>]*>([^<]*)<\/a>/gi, "$2 ($1)").replace(/<strong[^>]*>/gi, "**").replace(/<\/strong>/gi, "**").replace(/<em[^>]*>/gi, "_").replace(/<\/em>/gi, "_").replace(/<[^>]+>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#x27;/g, "'").replace(/&nbsp;/g, " ").replace(/\n{3,}/g, "\n\n").trim();
322
+ if (extractSection) {
323
+ const sectionLower = extractSection.toLowerCase();
324
+ const lines = content.split("\n");
325
+ const result = [];
326
+ let inSection = false;
327
+ let sectionLevel = 0;
328
+ for (const line of lines) {
329
+ const headerMatch = line.match(/^(#{1,4})\s+(.+)$/);
330
+ if (headerMatch) {
331
+ const level = headerMatch[1].length;
332
+ const headerText = headerMatch[2].toLowerCase();
333
+ if (headerText.includes(sectionLower)) {
334
+ inSection = true;
335
+ sectionLevel = level;
336
+ result.push(line);
337
+ } else if (inSection && level <= sectionLevel) {
338
+ break;
339
+ } else if (inSection) {
340
+ result.push(line);
341
+ }
342
+ } else if (inSection) {
343
+ result.push(line);
344
+ }
345
+ }
346
+ if (result.length > 0) {
347
+ content = result.join("\n").trim();
348
+ }
349
+ }
350
+ return { title, content, headings, lastUpdated };
351
+ }
352
+ async function fetchDocs(input) {
353
+ const { path: path2, extractSection } = input;
354
+ let normalizedPath = path2.startsWith("/") ? path2 : `/${path2}`;
355
+ normalizedPath = normalizedPath.replace(/\/$/, "");
356
+ if (/[<>"\s]/.test(normalizedPath)) {
357
+ return {
358
+ error: "Invalid path",
359
+ details: ["Path contains invalid characters"],
360
+ suggestion: `Use a clean path like '/develop/faq' or '/getting-started/installation'`
361
+ };
362
+ }
363
+ const url = `${DOCS_BASE_URL}${normalizedPath}`;
364
+ logger.debug("Fetching live documentation", { url, extractSection });
365
+ try {
366
+ const controller = new AbortController();
367
+ const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
368
+ const response = await fetch(url, {
369
+ signal: controller.signal,
370
+ headers: {
371
+ "User-Agent": "midnight-mcp/1.0 (Documentation Fetcher)",
372
+ Accept: "text/html"
373
+ }
374
+ });
375
+ clearTimeout(timeoutId);
376
+ if (!response.ok) {
377
+ if (response.status === 404) {
378
+ return {
379
+ error: "Page not found",
380
+ path: normalizedPath,
381
+ suggestion: `Try one of these known paths: ${KNOWN_DOC_PATHS.slice(0, 5).join(", ")}`,
382
+ knownPaths: KNOWN_DOC_PATHS
383
+ };
384
+ }
385
+ return {
386
+ error: `Failed to fetch documentation: ${response.status} ${response.statusText}`,
387
+ path: normalizedPath,
388
+ suggestion: "Try again later or check if the URL is correct"
389
+ };
390
+ }
391
+ const html = await response.text();
392
+ if (!html.includes("<article") && !html.includes("<main")) {
393
+ return {
394
+ error: "Page returned but content not found",
395
+ path: normalizedPath,
396
+ suggestion: "The page may not have main content. Try a different path."
397
+ };
398
+ }
399
+ const { title, content, headings, lastUpdated } = extractContentFromHtml(
400
+ html,
401
+ extractSection
402
+ );
403
+ const MAX_CONTENT_LENGTH = 15e3;
404
+ const truncated = content.length > MAX_CONTENT_LENGTH;
405
+ const finalContent = truncated ? content.slice(0, MAX_CONTENT_LENGTH) + "\n\n[Content truncated...]" : content;
406
+ return {
407
+ title,
408
+ path: normalizedPath,
409
+ url,
410
+ content: finalContent,
411
+ headings: headings.length > 0 ? headings : void 0,
412
+ lastUpdated,
413
+ truncated,
414
+ contentLength: content.length,
415
+ note: extractSection ? `Extracted section matching: "${extractSection}"` : "Full page content"
416
+ };
417
+ } catch (error) {
418
+ if (error instanceof Error) {
419
+ if (error.name === "AbortError") {
420
+ return {
421
+ error: "Request timed out",
422
+ path: normalizedPath,
423
+ suggestion: "The documentation site may be slow. Try again or use midnight-search-docs for cached content."
424
+ };
425
+ }
426
+ return {
427
+ error: `Failed to fetch: ${error.message}`,
428
+ path: normalizedPath,
429
+ suggestion: "Check your network connection or try midnight-search-docs for cached content."
430
+ };
431
+ }
432
+ return {
433
+ error: "Unknown error fetching documentation",
434
+ path: normalizedPath
435
+ };
436
+ }
437
+ }
268
438
 
269
439
  // src/tools/search/tools.ts
270
440
  var searchResultSchema = {
@@ -429,6 +599,80 @@ USAGE GUIDANCE:
429
599
  title: "Search Documentation"
430
600
  },
431
601
  handler: searchDocs
602
+ },
603
+ {
604
+ name: "midnight-fetch-docs",
605
+ description: `\u{1F310} LIVE FETCH: Retrieve latest documentation directly from docs.midnight.network (SSG-enabled).
606
+
607
+ Unlike midnight-search-docs which uses pre-indexed content, this tool fetches LIVE documentation pages in real-time. Use when you need:
608
+ \u2022 The absolute latest content (just updated docs)
609
+ \u2022 A specific page you know the path to
610
+ \u2022 Full page content rather than search snippets
611
+
612
+ COMMON PATHS:
613
+ \u2022 /develop/faq - Frequently asked questions
614
+ \u2022 /getting-started/installation - Installation guide
615
+ \u2022 /getting-started/create-mn-app - Create an MN app
616
+ \u2022 /compact - Compact language reference
617
+ \u2022 /develop/tutorial/building - Build guide
618
+ \u2022 /develop/reference/midnight-api - API documentation
619
+ \u2022 /learn/what-is-midnight - What is Midnight
620
+ \u2022 /blog - Dev diaries
621
+
622
+ USAGE GUIDANCE:
623
+ \u2022 Use extractSection to get only a specific heading (e.g., "Developer questions")
624
+ \u2022 Prefer midnight-search-docs for discovery, use this for known pages
625
+ \u2022 Content is truncated at 15KB for token efficiency`,
626
+ inputSchema: {
627
+ type: "object",
628
+ properties: {
629
+ path: {
630
+ type: "string",
631
+ description: "Documentation page path (e.g., '/develop/faq', '/getting-started/installation')"
632
+ },
633
+ extractSection: {
634
+ type: "string",
635
+ description: "Optional: Extract only a specific section by heading text"
636
+ }
637
+ },
638
+ required: ["path"]
639
+ },
640
+ outputSchema: {
641
+ type: "object",
642
+ properties: {
643
+ title: { type: "string", description: "Page title" },
644
+ path: { type: "string", description: "Normalized path" },
645
+ url: { type: "string", description: "Full URL" },
646
+ content: { type: "string", description: "Extracted page content" },
647
+ headings: {
648
+ type: "array",
649
+ description: "Page headings/table of contents",
650
+ items: {
651
+ type: "object",
652
+ properties: {
653
+ level: { type: "number" },
654
+ text: { type: "string" },
655
+ id: { type: "string" }
656
+ }
657
+ }
658
+ },
659
+ lastUpdated: { type: "string", description: "Last update timestamp" },
660
+ truncated: {
661
+ type: "boolean",
662
+ description: "Whether content was truncated"
663
+ }
664
+ },
665
+ required: ["title", "path", "content"],
666
+ description: "Live documentation page content"
667
+ },
668
+ annotations: {
669
+ readOnlyHint: true,
670
+ openWorldHint: true,
671
+ // Fetches from external URL
672
+ title: "\u{1F310} Fetch Live Docs",
673
+ category: "search"
674
+ },
675
+ handler: fetchDocs
432
676
  }
433
677
  ];
434
678
 
@@ -8483,6 +8727,20 @@ var INTENT_TO_TOOL = [
8483
8727
  tool: "midnight-search-docs",
8484
8728
  reason: "Search official Midnight documentation"
8485
8729
  },
8730
+ {
8731
+ patterns: [
8732
+ "fetch docs",
8733
+ "get docs",
8734
+ "latest docs",
8735
+ "live docs",
8736
+ "docs page",
8737
+ "faq",
8738
+ "installation guide",
8739
+ "getting started page"
8740
+ ],
8741
+ tool: "midnight-fetch-docs",
8742
+ reason: "Fetch live documentation directly from docs.midnight.network"
8743
+ },
8486
8744
  // Code Generation
8487
8745
  {
8488
8746
  patterns: [
@@ -8918,7 +9176,7 @@ var suggestToolOutputSchema = {
8918
9176
  var metaTools = [
8919
9177
  {
8920
9178
  name: "midnight-list-tool-categories",
8921
- description: "\u{1F4CB} DISCOVERY TOOL: List available tool categories for progressive exploration. Use this FIRST to understand what capabilities are available, then drill into specific categories with midnight-list-category-tools. Reduces cognitive load by organizing 27 tools into 7 logical groups.",
9179
+ description: "\u{1F4CB} DISCOVERY TOOL: List available tool categories for progressive exploration. Use this FIRST to understand what capabilities are available, then drill into specific categories with midnight-list-category-tools. Reduces cognitive load by organizing 28 tools into 7 logical groups.",
8922
9180
  inputSchema: {
8923
9181
  type: "object",
8924
9182
  properties: {
@@ -9034,4 +9292,4 @@ export {
9034
9292
  startServer,
9035
9293
  startHttpServer
9036
9294
  };
9037
- //# sourceMappingURL=chunk-ZYCQLQEX.js.map
9295
+ //# sourceMappingURL=chunk-5DMOVW6Q.js.map
@@ -1594,7 +1594,7 @@ async function checkGitHubAPI() {
1594
1594
  }
1595
1595
  async function checkVectorStore() {
1596
1596
  try {
1597
- const { vectorStore: vectorStore2 } = await import("./db-ILWTH42C.js");
1597
+ const { vectorStore: vectorStore2 } = await import("./db-K7ADM57F.js");
1598
1598
  if (vectorStore2) {
1599
1599
  return {
1600
1600
  status: "pass",
@@ -2025,7 +2025,7 @@ function serialize(data) {
2025
2025
  }
2026
2026
 
2027
2027
  // src/utils/version.ts
2028
- var CURRENT_VERSION = "0.2.3";
2028
+ var CURRENT_VERSION = "0.2.4";
2029
2029
 
2030
2030
  // src/db/vectorStore.ts
2031
2031
  var VectorStore = class {
@@ -2234,4 +2234,4 @@ export {
2234
2234
  serialize,
2235
2235
  CURRENT_VERSION
2236
2236
  };
2237
- //# sourceMappingURL=chunk-EBFMM2DS.js.map
2237
+ //# sourceMappingURL=chunk-KB6HV57P.js.map
@@ -0,0 +1,7 @@
1
+ import {
2
+ vectorStore
3
+ } from "./chunk-KB6HV57P.js";
4
+ export {
5
+ vectorStore
6
+ };
7
+ //# sourceMappingURL=db-K7ADM57F.js.map
package/dist/index.js CHANGED
@@ -9,10 +9,10 @@ import {
9
9
  promptDefinitions,
10
10
  startHttpServer,
11
11
  startServer
12
- } from "./chunk-ZYCQLQEX.js";
12
+ } from "./chunk-5DMOVW6Q.js";
13
13
  import {
14
14
  logger
15
- } from "./chunk-EBFMM2DS.js";
15
+ } from "./chunk-KB6HV57P.js";
16
16
  export {
17
17
  allResources,
18
18
  allTools,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "midnight-mcp",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Model Context Protocol Server for Midnight Blockchain Development",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,7 +0,0 @@
1
- import {
2
- vectorStore
3
- } from "./chunk-EBFMM2DS.js";
4
- export {
5
- vectorStore
6
- };
7
- //# sourceMappingURL=db-ILWTH42C.js.map