midnight-mcp 0.2.16 → 0.2.18

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
@@ -8,6 +8,8 @@
8
8
 
9
9
  MCP server that gives AI assistants access to Midnight blockchain—search contracts, analyze code, and explore documentation.
10
10
 
11
+ This project extends the Midnight Network with additional developer tooling.
12
+
11
13
  ## Requirements
12
14
 
13
15
  - **Node.js 20+** (LTS recommended)
@@ -124,7 +126,7 @@ All tools are prefixed with `midnight-` (e.g., `midnight-search-compact`).
124
126
  The `midnight-compile-contract` tool validates Compact code using a hosted compiler service:
125
127
 
126
128
  ```
127
- ✅ Compilation successful (Compiler v0.18.0) in 2841ms
129
+ ✅ Compilation successful (Compiler v0.29.0) in 2841ms
128
130
  ```
129
131
 
130
132
  - **Fast mode** (`skipZk=true`): Syntax validation in ~1-2 seconds
@@ -137,7 +139,7 @@ This catches semantic errors that static analysis misses (sealed fields, disclos
137
139
 
138
140
  | Capability | Feature |
139
141
  | --------------- | ----------------------------------------------- |
140
- | **Tools** | 28 tools with `listChanged` notifications |
142
+ | **Tools** | 29 tools with `listChanged` notifications |
141
143
  | **Resources** | 9 embedded resources with subscription support |
142
144
  | **Prompts** | 5 workflow prompts |
143
145
  | **Logging** | Client-controllable log level |
@@ -149,7 +151,7 @@ This catches semantic errors that static analysis misses (sealed fields, disclos
149
151
 
150
152
  Quick references available offline:
151
153
 
152
- - Compact syntax guide (v0.16-0.18)
154
+ - Compact syntax guide (v0.16-0.21)
153
155
  - SDK API reference
154
156
  - OpenZeppelin contracts
155
157
  - Tokenomics overview
@@ -164,7 +166,7 @@ Quick references available offline:
164
166
  | ------------------------- | -------- | ------------------------------------------------------- |
165
167
  | `deprecated_ledger_block` | P0 | Catches `ledger { }` → use `export ledger field: Type;` |
166
168
  | `invalid_void_type` | P0 | Catches `Void` → use `[]` (empty tuple) |
167
- | `invalid_pragma_format` | P0 | Catches old pragma → use `>= 0.16 && <= 0.18` |
169
+ | `invalid_pragma_format` | P0 | Catches old pragma → use `>= 0.16 && <= 0.21` |
168
170
  | `unexported_enum` | P1 | Enums need `export` for TypeScript access |
169
171
  | `module_level_const` | P0 | Use `pure circuit` instead |
170
172
  | + 10 more checks | P1-P2 | Overflow, division, assertions, etc. |
@@ -269,6 +271,11 @@ Add `"GITHUB_TOKEN": "ghp_..."` for higher GitHub API rate limits (60 → 5000 r
269
271
  ```bash
270
272
  git clone https://github.com/Olanetsoft/midnight-mcp.git && cd midnight-mcp
271
273
  npm install && npm run build && npm test
274
+
275
+ # Lint & format
276
+ npm run lint # ESLint (typescript-eslint)
277
+ npm run lint:fix # Auto-fix lint issues
278
+ npm run format # Prettier
272
279
  ```
273
280
 
274
281
  The hosted API runs on Cloudflare Workers + Vectorize. See [api/README.md](./api/README.md) for backend details.
package/dist/bin.js CHANGED
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  startHttpServer,
4
4
  startServer
5
- } from "./chunk-TJ7QA5XT.js";
5
+ } from "./chunk-TSXIIBEH.js";
6
6
  import {
7
7
  setOutputFormat
8
- } from "./chunk-P3MTQHP6.js";
8
+ } from "./chunk-WVMAQ2LK.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.16";
16
+ var CURRENT_VERSION = "0.2.18";
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-P3MTQHP6.js";
28
+ } from "./chunk-WVMAQ2LK.js";
29
29
 
30
30
  // src/tools/search/schemas.ts
31
31
  import { z } from "zod";
@@ -171,38 +171,53 @@ function finalizeResponse(response, cacheKey, warnings) {
171
171
  });
172
172
  return finalResponse;
173
173
  }
174
- async function searchCompact(input) {
175
- const validation = validateSearchInput(input.query, input.limit);
174
+ async function performSearch(query, limit, config) {
175
+ const validation = validateSearchInput(query, limit);
176
176
  if (!validation.success) {
177
177
  return validation.error;
178
178
  }
179
- const { sanitizedQuery, limit, warnings } = validation.context;
180
- logger.debug("Searching Compact code", {
179
+ const { sanitizedQuery, limit: validatedLimit, warnings } = validation.context;
180
+ logger.debug(`Searching ${config.searchType}`, {
181
181
  query: sanitizedQuery,
182
182
  mode: isHostedMode() ? "hosted" : "local"
183
183
  });
184
184
  const cacheKey = createCacheKey(
185
- "compact",
185
+ config.searchType,
186
186
  sanitizedQuery,
187
- limit,
188
- input.filter?.repository
187
+ validatedLimit,
188
+ ...config.cacheKeyExtra
189
189
  );
190
190
  const cached = checkSearchCache(cacheKey);
191
191
  if (cached) return cached;
192
192
  const hostedResult = await tryHostedSearch(
193
- "compact",
194
- () => searchCompactHosted(sanitizedQuery, limit),
193
+ config.searchType,
194
+ () => config.hostedSearchFn(sanitizedQuery, validatedLimit),
195
195
  cacheKey,
196
196
  warnings
197
197
  );
198
- if (hostedResult) return hostedResult.result;
199
- const filter = {
200
- language: "compact",
201
- ...input.filter
202
- };
203
- const results = await vectorStore.search(sanitizedQuery, limit, filter);
198
+ if (hostedResult) {
199
+ return { ...hostedResult.result, ...config.hostedResultExtra };
200
+ }
201
+ const filter = config.buildFilter();
202
+ let results = await vectorStore.search(sanitizedQuery, validatedLimit, filter);
203
+ if (config.postFilter) {
204
+ results = config.postFilter(results);
205
+ }
204
206
  const response = {
205
- results: results.map((r) => ({
207
+ results: results.map(config.transformResult),
208
+ totalResults: results.length,
209
+ query: sanitizedQuery,
210
+ ...config.extraFields
211
+ };
212
+ return finalizeResponse(response, cacheKey, warnings);
213
+ }
214
+ async function searchCompact(input) {
215
+ return performSearch(input.query, input.limit, {
216
+ searchType: "compact",
217
+ cacheKeyExtra: [input.filter?.repository],
218
+ hostedSearchFn: (query, limit) => searchCompactHosted(query, limit),
219
+ buildFilter: () => ({ language: "compact", ...input.filter }),
220
+ transformResult: (r) => ({
206
221
  code: r.content,
207
222
  relevanceScore: r.score,
208
223
  source: {
@@ -212,49 +227,19 @@ async function searchCompact(input) {
212
227
  },
213
228
  codeType: r.metadata.codeType,
214
229
  name: r.metadata.codeName
215
- })),
216
- totalResults: results.length,
217
- query: sanitizedQuery
218
- };
219
- return finalizeResponse(response, cacheKey, warnings);
230
+ })
231
+ });
220
232
  }
221
233
  async function searchTypeScript(input) {
222
- const validation = validateSearchInput(input.query, input.limit);
223
- if (!validation.success) {
224
- return validation.error;
225
- }
226
- const { sanitizedQuery, limit, warnings } = validation.context;
227
- logger.debug("Searching TypeScript code", {
228
- query: sanitizedQuery,
229
- mode: isHostedMode() ? "hosted" : "local"
230
- });
231
- const cacheKey = createCacheKey(
232
- "typescript",
233
- sanitizedQuery,
234
- limit,
235
- input.includeTypes
236
- );
237
- const cached = checkSearchCache(cacheKey);
238
- if (cached) return cached;
239
- const hostedResult = await tryHostedSearch(
240
- "typescript",
241
- () => searchTypeScriptHosted(sanitizedQuery, limit, input.includeTypes),
242
- cacheKey,
243
- warnings
244
- );
245
- if (hostedResult) return hostedResult.result;
246
- const filter = {
247
- language: "typescript"
248
- };
249
- const results = await vectorStore.search(sanitizedQuery, limit, filter);
250
- let filteredResults = results;
251
- if (!input.includeTypes) {
252
- filteredResults = results.filter(
234
+ return performSearch(input.query, input.limit, {
235
+ searchType: "typescript",
236
+ cacheKeyExtra: [input.includeTypes],
237
+ hostedSearchFn: (query, limit) => searchTypeScriptHosted(query, limit, input.includeTypes),
238
+ buildFilter: () => ({ language: "typescript" }),
239
+ postFilter: (results) => input.includeTypes ? results : results.filter(
253
240
  (r) => r.metadata.codeType !== "type" && r.metadata.codeType !== "interface"
254
- );
255
- }
256
- const response = {
257
- results: filteredResults.map((r) => ({
241
+ ),
242
+ transformResult: (r) => ({
258
243
  code: r.content,
259
244
  relevanceScore: r.score,
260
245
  source: {
@@ -265,49 +250,23 @@ async function searchTypeScript(input) {
265
250
  codeType: r.metadata.codeType,
266
251
  name: r.metadata.codeName,
267
252
  isExported: r.metadata.isPublic
268
- })),
269
- totalResults: filteredResults.length,
270
- query: sanitizedQuery
271
- };
272
- return finalizeResponse(response, cacheKey, warnings);
253
+ })
254
+ });
273
255
  }
274
256
  async function searchDocs(input) {
275
- const validation = validateSearchInput(input.query, input.limit);
276
- if (!validation.success) {
277
- return validation.error;
278
- }
279
- const { sanitizedQuery, limit, warnings } = validation.context;
280
- logger.debug("Searching documentation", {
281
- query: sanitizedQuery,
282
- mode: isHostedMode() ? "hosted" : "local"
283
- });
284
- const cacheKey = createCacheKey(
285
- "docs",
286
- sanitizedQuery,
287
- limit,
288
- input.category
289
- );
290
- const cached = checkSearchCache(cacheKey);
291
- if (cached) return cached;
292
257
  const freshnessHint = "For guaranteed freshness, use midnight-fetch-docs with the path from these results (e.g., /develop/faq)";
293
- const hostedResult = await tryHostedSearch(
294
- "docs",
295
- () => searchDocsHosted(sanitizedQuery, limit, input.category),
296
- cacheKey,
297
- warnings
298
- );
299
- if (hostedResult) {
300
- return { ...hostedResult.result, hint: freshnessHint };
301
- }
302
- const filter = {
303
- language: "markdown"
304
- };
305
- if (input.category !== "all") {
306
- filter.repository = "midnightntwrk/midnight-docs";
307
- }
308
- const results = await vectorStore.search(sanitizedQuery, limit, filter);
309
- const response = {
310
- results: results.map((r) => ({
258
+ return performSearch(input.query, input.limit, {
259
+ searchType: "docs",
260
+ cacheKeyExtra: [input.category],
261
+ hostedSearchFn: (query, limit) => searchDocsHosted(query, limit, input.category),
262
+ buildFilter: () => {
263
+ const filter = { language: "markdown" };
264
+ if (input.category !== "all") {
265
+ filter.repository = "midnightntwrk/midnight-docs";
266
+ }
267
+ return filter;
268
+ },
269
+ transformResult: (r) => ({
311
270
  content: r.content,
312
271
  relevanceScore: r.score,
313
272
  source: {
@@ -315,13 +274,13 @@ async function searchDocs(input) {
315
274
  filePath: r.metadata.filePath,
316
275
  section: r.metadata.codeName
317
276
  }
318
- })),
319
- totalResults: results.length,
320
- query: sanitizedQuery,
321
- category: input.category,
322
- hint: "For guaranteed freshness, use midnight-fetch-docs with the path from these results (e.g., /develop/faq)"
323
- };
324
- return finalizeResponse(response, cacheKey, warnings);
277
+ }),
278
+ extraFields: {
279
+ category: input.category,
280
+ hint: freshnessHint
281
+ },
282
+ hostedResultExtra: { hint: freshnessHint }
283
+ });
325
284
  }
326
285
  var DOCS_BASE_URL = "https://docs.midnight.network";
327
286
  var FETCH_TIMEOUT = 15e3;
@@ -354,7 +313,7 @@ function extractContentFromHtml(html, extractSection) {
354
313
  const headingRegex = /<h([1-6])[^>]*class="[^"]*anchor[^"]*"[^>]*id="([^"]*)"[^>]*>([^<]*(?:<[^/][^>]*>[^<]*<\/[^>]+>)*[^<]*)/gi;
355
314
  let headingMatch;
356
315
  while ((headingMatch = headingRegex.exec(articleHtml)) !== null) {
357
- const text = headingMatch[3].replace(/<[^>]+>/g, "").replace(/\u200B/g, "").replace(/​/g, "").trim();
316
+ const text = headingMatch[3].replace(/<[^>]+>/g, "").replace(/\u200B/g, "").trim();
358
317
  if (text) {
359
318
  headings.push({
360
319
  level: parseInt(headingMatch[1]),
@@ -406,7 +365,24 @@ async function fetchDocs(input) {
406
365
  suggestion: `Use a clean path like '/develop/faq' or '/getting-started/installation'`
407
366
  };
408
367
  }
409
- const url = `${DOCS_BASE_URL}${normalizedPath}`;
368
+ let url;
369
+ try {
370
+ const constructed = new URL(normalizedPath, DOCS_BASE_URL);
371
+ if (constructed.origin !== new URL(DOCS_BASE_URL).origin) {
372
+ return {
373
+ error: "Invalid path",
374
+ details: ["Path resulted in a URL outside the documentation domain"],
375
+ suggestion: `Use a clean path like '/develop/faq' or '/getting-started/installation'`
376
+ };
377
+ }
378
+ url = constructed.href;
379
+ } catch {
380
+ return {
381
+ error: "Invalid path",
382
+ details: ["Could not construct a valid URL from the path"],
383
+ suggestion: `Use a clean path like '/develop/faq' or '/getting-started/installation'`
384
+ };
385
+ }
410
386
  logger.debug("Fetching live documentation", { url, extractSection });
411
387
  try {
412
388
  const controller = new AbortController();
@@ -800,7 +776,19 @@ async function compileContract(code, options = {}) {
800
776
  serviceAvailable: response.status < 500
801
777
  };
802
778
  }
803
- const result = await response.json();
779
+ const rawResult = await response.json();
780
+ if (typeof rawResult !== "object" || rawResult === null || !("success" in rawResult)) {
781
+ logger.error("Compiler API returned unexpected response format", {
782
+ response: JSON.stringify(rawResult).slice(0, 200)
783
+ });
784
+ return {
785
+ success: false,
786
+ message: "Compiler service returned an unexpected response format",
787
+ error: "INVALID_RESPONSE",
788
+ serviceAvailable: true
789
+ };
790
+ }
791
+ const result = rawResult;
804
792
  if (result.success) {
805
793
  const outputInfo = typeof result.output === "object" && result.output !== null ? result.output : {};
806
794
  logger.info("Compilation successful", {
@@ -1710,10 +1698,7 @@ var REPO_ALIASES = {
1710
1698
  lucentlabs: { owner: "statera-protocol", repo: "statera-protocol-midnight" },
1711
1699
  stablecoin: { owner: "statera-protocol", repo: "statera-protocol-midnight" },
1712
1700
  "midnight-bank": { owner: "nel349", repo: "midnight-bank" },
1713
- bank: { owner: "nel349", repo: "midnight-bank" },
1714
- zkbadge: { owner: "Imdavyking", repo: "zkbadge" },
1715
- badge: { owner: "Imdavyking", repo: "zkbadge" },
1716
- davyking: { owner: "Imdavyking", repo: "zkbadge" }
1701
+ bank: { owner: "nel349", repo: "midnight-bank" }
1717
1702
  };
1718
1703
  var EXAMPLES = [
1719
1704
  {
@@ -1837,21 +1822,6 @@ var EXAMPLES = [
1837
1822
  "Private transfers"
1838
1823
  ]
1839
1824
  },
1840
- {
1841
- name: "zkBadge (Davyking)",
1842
- repository: "Imdavyking/zkbadge",
1843
- description: "3rd place Mini DApp winner. Privacy-preserving identity and access control. Issue verifiable credentials (e.g., age proof) without revealing personal data. Only 'verified' status stored on-chain.",
1844
- category: "identity",
1845
- complexity: "intermediate",
1846
- mainFile: "contract/src/zkbadge.compact",
1847
- features: [
1848
- "ZK credentials",
1849
- "Off-chain verification",
1850
- "On-chain badges",
1851
- "Access control",
1852
- "Reputation system"
1853
- ]
1854
- },
1855
1825
  // Core Partner - PaimaStudios (Gaming Infrastructure)
1856
1826
  {
1857
1827
  name: "Midnight Game 2 (PaimaStudios)",
@@ -1944,7 +1914,7 @@ import { randomUUID } from "crypto";
1944
1914
 
1945
1915
  // src/resources/content/docs-content.ts
1946
1916
  var EMBEDDED_DOCS = {
1947
- "midnight://docs/compact-reference": `# Compact Language Syntax Reference (v0.16 - v0.18)
1917
+ "midnight://docs/compact-reference": `# Compact Language Syntax Reference (v0.16 - v0.21)
1948
1918
 
1949
1919
  > **CRITICAL**: This reference is derived from **actual compiling contracts** in the Midnight ecosystem.
1950
1920
  > Always verify syntax against this reference before generating contracts.
@@ -1954,7 +1924,7 @@ var EMBEDDED_DOCS = {
1954
1924
  Use this as a starting point - it compiles successfully:
1955
1925
 
1956
1926
  \`\`\`compact
1957
- pragma language_version >= 0.16 && <= 0.18;
1927
+ pragma language_version >= 0.16 && <= 0.21;
1958
1928
 
1959
1929
  import CompactStandardLibrary;
1960
1930
 
@@ -1977,13 +1947,13 @@ export circuit increment(): [] {
1977
1947
 
1978
1948
  **CORRECT** - use bounded range without patch version:
1979
1949
  \`\`\`compact
1980
- pragma language_version >= 0.16 && <= 0.18;
1950
+ pragma language_version >= 0.16 && <= 0.21;
1981
1951
  \`\`\`
1982
1952
 
1983
1953
  **WRONG** - these will cause parse errors:
1984
1954
  \`\`\`compact
1985
1955
  pragma language_version >= 0.14.0; // \u274C patch version not needed
1986
- pragma language_version >= 0.16.0 < 0.19.0; // \u274C wrong operator format
1956
+ pragma language_version >= 0.20.0 < 0.22.0; // \u274C wrong operator format
1987
1957
  \`\`\`
1988
1958
 
1989
1959
  ---
@@ -2231,7 +2201,7 @@ export circuit authenticated_action(): [] {
2231
2201
 
2232
2202
  ### Commit-Reveal Pattern (COMPLETE, VALIDATED)
2233
2203
  \`\`\`compact
2234
- pragma language_version >= 0.16 && <= 0.18;
2204
+ pragma language_version >= 0.16 && <= 0.21;
2235
2205
 
2236
2206
  import CompactStandardLibrary;
2237
2207
 
@@ -2471,7 +2441,7 @@ assert(disclose(caller == owner), "Not authorized");
2471
2441
  |---------|---------|
2472
2442
  | \`ledger { field: Type; }\` | \`export ledger field: Type;\` |
2473
2443
  | \`circuit fn(): Void\` | \`circuit fn(): []\` |
2474
- | \`pragma >= 0.16.0\` | \`pragma >= 0.16 && <= 0.18\` |
2444
+ | \`pragma >= 0.20.0\` | \`pragma >= 0.16 && <= 0.21\` |
2475
2445
  | \`enum State { ... }\` | \`export enum State { ... }\` |
2476
2446
  | \`if (witness_val == x)\` | \`if (disclose(witness_val == x))\` |
2477
2447
  | \`Cell<Field>\` | \`Field\` (Cell is deprecated) |
@@ -2804,7 +2774,7 @@ npm install @openzeppelin/compact-contracts
2804
2774
  ## Usage Example
2805
2775
 
2806
2776
  \`\`\`compact
2807
- pragma language_version >= 0.18.0;
2777
+ pragma language_version >= 0.16 && <= 0.21;
2808
2778
 
2809
2779
  import CompactStandardLibrary;
2810
2780
  import "@openzeppelin/compact-contracts/src/token/FungibleToken"
@@ -2845,7 +2815,7 @@ The recommended standard for privacy-preserving tokens on Midnight.
2845
2815
  ## Basic Usage
2846
2816
 
2847
2817
  \`\`\`compact
2848
- pragma language_version >= 0.18.0;
2818
+ pragma language_version >= 0.16 && <= 0.21;
2849
2819
 
2850
2820
  import CompactStandardLibrary;
2851
2821
  import "@openzeppelin/compact-contracts/src/token/FungibleToken"
@@ -3436,7 +3406,7 @@ var EMBEDDED_CODE = {
3436
3406
  "midnight://code/examples/counter": `// Counter Example Contract
3437
3407
  // A simple contract demonstrating basic Compact concepts
3438
3408
 
3439
- pragma language_version >= 0.16 && <= 0.18;
3409
+ pragma language_version >= 0.16 && <= 0.21;
3440
3410
 
3441
3411
  import CompactStandardLibrary;
3442
3412
 
@@ -3485,7 +3455,7 @@ export circuit isLessThan(threshold: Uint<64>): Boolean {
3485
3455
  "midnight://code/examples/bboard": `// Bulletin Board Example Contract
3486
3456
  // Demonstrates private messaging with selective disclosure
3487
3457
 
3488
- pragma language_version >= 0.16 && <= 0.18;
3458
+ pragma language_version >= 0.16 && <= 0.21;
3489
3459
 
3490
3460
  import CompactStandardLibrary;
3491
3461
 
@@ -3534,7 +3504,7 @@ export circuit getMessageCount(): Uint<64> {
3534
3504
  "midnight://code/patterns/state-management": `// State Management Pattern
3535
3505
  // Best practices for managing public and private state
3536
3506
 
3537
- pragma language_version >= 0.16 && <= 0.18;
3507
+ pragma language_version >= 0.16 && <= 0.21;
3538
3508
 
3539
3509
  import CompactStandardLibrary;
3540
3510
 
@@ -3585,7 +3555,7 @@ export circuit revealBalance(user: Opaque<"address">): Field {
3585
3555
  "midnight://code/patterns/access-control": `// Access Control Pattern
3586
3556
  // Implementing permissions and authorization
3587
3557
 
3588
- pragma language_version >= 0.16 && <= 0.18;
3558
+ pragma language_version >= 0.16 && <= 0.21;
3589
3559
 
3590
3560
  import CompactStandardLibrary;
3591
3561
 
@@ -3640,7 +3610,7 @@ export circuit timeLockedAction(unlockTime: Field): [] {
3640
3610
  "midnight://code/patterns/privacy-preserving": `// Privacy-Preserving Patterns
3641
3611
  // Techniques for maintaining privacy in smart contracts
3642
3612
 
3643
- pragma language_version >= 0.16 && <= 0.18;
3613
+ pragma language_version >= 0.16 && <= 0.21;
3644
3614
 
3645
3615
  import CompactStandardLibrary;
3646
3616
 
@@ -3745,7 +3715,7 @@ export circuit proveMembership(
3745
3715
  "midnight://code/templates/token": `// Privacy-Preserving Token Template
3746
3716
  // Starter template for token contracts with privacy features
3747
3717
 
3748
- pragma language_version >= 0.16 && <= 0.18;
3718
+ pragma language_version >= 0.16 && <= 0.21;
3749
3719
 
3750
3720
  import CompactStandardLibrary;
3751
3721
 
@@ -3804,7 +3774,7 @@ export circuit mint(to: Opaque<"address">, amount: Uint<64>): Boolean {
3804
3774
  "midnight://code/templates/voting": `// Private Voting Template
3805
3775
  // Starter template for privacy-preserving voting contracts
3806
3776
 
3807
- pragma language_version >= 0.16 && <= 0.18;
3777
+ pragma language_version >= 0.16 && <= 0.21;
3808
3778
 
3809
3779
  import CompactStandardLibrary;
3810
3780
 
@@ -3884,7 +3854,7 @@ export circuit addVoter(voter: Opaque<"address">): [] {
3884
3854
  "midnight://code/examples/nullifier": `// Nullifier Pattern Example
3885
3855
  // Demonstrates how to create and use nullifiers to prevent double-spending/actions
3886
3856
 
3887
- pragma language_version >= 0.16 && <= 0.18;
3857
+ pragma language_version >= 0.16 && <= 0.21;
3888
3858
 
3889
3859
  import CompactStandardLibrary;
3890
3860
 
@@ -3951,7 +3921,7 @@ export circuit voteWithNullifier(
3951
3921
  "midnight://code/examples/hash": `// Hash Functions in Compact
3952
3922
  // Examples of using hash functions for various purposes
3953
3923
 
3954
- pragma language_version >= 0.16 && <= 0.18;
3924
+ pragma language_version >= 0.16 && <= 0.21;
3955
3925
 
3956
3926
  import CompactStandardLibrary;
3957
3927
 
@@ -3998,7 +3968,7 @@ export circuit reveal(value: Field, randomness: Field): Field {
3998
3968
  "midnight://code/examples/simple-counter": `// Simple Counter Contract
3999
3969
  // Minimal example for learning Compact basics
4000
3970
 
4001
- pragma language_version >= 0.16 && <= 0.18;
3971
+ pragma language_version >= 0.16 && <= 0.21;
4002
3972
 
4003
3973
  import CompactStandardLibrary;
4004
3974
 
@@ -4036,7 +4006,7 @@ export circuit reset(): [] {
4036
4006
  "midnight://code/templates/basic": `// Basic Compact Contract Template
4037
4007
  // Starting point for new contracts
4038
4008
 
4039
- pragma language_version >= 0.16 && <= 0.18;
4009
+ pragma language_version >= 0.16 && <= 0.21;
4040
4010
 
4041
4011
  import CompactStandardLibrary;
4042
4012
 
@@ -4176,7 +4146,7 @@ async function getDocumentation(uri) {
4176
4146
  if (file) {
4177
4147
  return file.content;
4178
4148
  }
4179
- } catch (error) {
4149
+ } catch (_error) {
4180
4150
  logger.warn(`Could not fetch doc from GitHub: ${uri}`);
4181
4151
  }
4182
4152
  }
@@ -4282,7 +4252,7 @@ async function getCode(uri) {
4282
4252
  return file.content;
4283
4253
  }
4284
4254
  }
4285
- } catch (error) {
4255
+ } catch (_error) {
4286
4256
  logger.warn(`Could not fetch code from GitHub: ${uri}`);
4287
4257
  }
4288
4258
  }
@@ -4826,7 +4796,7 @@ function generateCreateContractPrompt(args) {
4826
4796
  Call \`midnight-get-latest-syntax\` FIRST to get:
4827
4797
  - The \`quickStartTemplate\` (use as your base)
4828
4798
  - The \`commonMistakes\` array (avoid these errors)
4829
- - Current pragma format: \`pragma language_version >= 0.16 && <= 0.18;\`
4799
+ - Current pragma format: \`pragma language_version >= 0.16 && <= 0.21;\`
4830
4800
 
4831
4801
  ### Step 2: Generate Contract
4832
4802
  Based on syntax reference, generate the contract using:
@@ -4927,7 +4897,7 @@ ${contractCode}
4927
4897
  Call \`midnight-extract-contract-structure\` with the contract code to check for:
4928
4898
  - deprecated_ledger_block (should use \`export ledger field: Type;\`)
4929
4899
  - invalid_void_type (should use \`[]\` not \`Void\`)
4930
- - invalid_pragma_format (should use \`>= 0.16 && <= 0.18\`)
4900
+ - invalid_pragma_format (should use \`>= 0.16 && <= 0.21\`)
4931
4901
  - unexported_enum (enums need \`export\`)
4932
4902
  - deprecated_cell_wrapper
4933
4903
 
@@ -5096,7 +5066,7 @@ ${contractCode}
5096
5066
  Call \`midnight-extract-contract-structure\` FIRST to check for common syntax errors:
5097
5067
  - deprecated_ledger_block \u2192 should use \`export ledger field: Type;\`
5098
5068
  - invalid_void_type \u2192 should use \`[]\` not \`Void\`
5099
- - invalid_pragma_format \u2192 should use \`>= 0.16 && <= 0.18\`
5069
+ - invalid_pragma_format \u2192 should use \`>= 0.16 && <= 0.21\`
5100
5070
  - unexported_enum \u2192 enums need \`export\` keyword
5101
5071
 
5102
5072
  ### Step 2: Get Correct Syntax
@@ -5198,7 +5168,8 @@ async function requestCompletion(messages, options = {}) {
5198
5168
  );
5199
5169
  markSamplingFailed();
5200
5170
  throw new Error(
5201
- "Sampling not supported by this client - use Claude Desktop for this feature"
5171
+ "Sampling not supported by this client - use Claude Desktop for this feature",
5172
+ { cause: error }
5202
5173
  );
5203
5174
  }
5204
5175
  throw error;
@@ -5219,7 +5190,7 @@ Key Compact syntax (REQUIRED):
5219
5190
  - \`export ledger field: Type;\` - Individual ledger declarations (NOT ledger { } blocks)
5220
5191
  - \`export circuit fn(): []\` - Public functions return empty tuple [] (NOT Void)
5221
5192
  - \`witness fn(): T;\` - Declaration only, no body
5222
- - \`pragma language_version >= 0.16 && <= 0.18;\` - Version pragma
5193
+ - \`pragma language_version >= 0.16 && <= 0.21;\` - Version pragma
5223
5194
  - \`import CompactStandardLibrary;\` - Standard imports
5224
5195
  - \`Counter\`, \`Map<K,V>\`, \`Set<T>\` - Built-in collection types
5225
5196
  - \`Field\`, \`Boolean\`, \`Uint<N>\`, \`Bytes<N>\` - Primitive types
@@ -5880,7 +5851,10 @@ async function checkForUpdates() {
5880
5851
  lastChecked: Date.now()
5881
5852
  };
5882
5853
  }
5883
- } catch {
5854
+ } catch (error) {
5855
+ logger.debug("Version check failed (non-blocking)", {
5856
+ error: error instanceof Error ? error.message : String(error)
5857
+ });
5884
5858
  }
5885
5859
  }
5886
5860
  function maybeCheckForUpdates() {
@@ -5889,7 +5863,10 @@ function maybeCheckForUpdates() {
5889
5863
  toolCallCount = 0;
5890
5864
  const fiveMinutes = 5 * 60 * 1e3;
5891
5865
  if (Date.now() - versionCheckResult.lastChecked > fiveMinutes) {
5892
- checkForUpdates().catch(() => {
5866
+ checkForUpdates().catch((error) => {
5867
+ logger.debug("Periodic version check failed", {
5868
+ error: error instanceof Error ? error.message : String(error)
5869
+ });
5893
5870
  });
5894
5871
  }
5895
5872
  }
@@ -5956,7 +5933,10 @@ function sendLogToClient(level, loggerName, data) {
5956
5933
  data
5957
5934
  }
5958
5935
  });
5959
- } catch {
5936
+ } catch (error) {
5937
+ logger.debug("Failed to send log notification to client", {
5938
+ error: error instanceof Error ? error.message : String(error)
5939
+ });
5960
5940
  }
5961
5941
  }
5962
5942
  function sendProgressNotification(progressToken, progress, total, message) {
@@ -5971,7 +5951,10 @@ function sendProgressNotification(progressToken, progress, total, message) {
5971
5951
  ...message && { message }
5972
5952
  }
5973
5953
  });
5974
- } catch {
5954
+ } catch (error) {
5955
+ logger.debug("Failed to send progress notification", {
5956
+ error: error instanceof Error ? error.message : String(error)
5957
+ });
5975
5958
  }
5976
5959
  }
5977
5960
  function createServer() {
@@ -6404,7 +6387,10 @@ function setupSampling(server) {
6404
6387
  }
6405
6388
  async function initializeServer() {
6406
6389
  logger.info("Initializing Midnight MCP Server...");
6407
- checkForUpdates().catch(() => {
6390
+ checkForUpdates().catch((error) => {
6391
+ logger.debug("Startup version check failed (non-blocking)", {
6392
+ error: error instanceof Error ? error.message : String(error)
6393
+ });
6408
6394
  });
6409
6395
  try {
6410
6396
  await vectorStore.initialize();
@@ -6554,9 +6540,9 @@ var COMPACT_VERSION = {
6554
6540
  /** Minimum supported version */
6555
6541
  min: "0.16",
6556
6542
  /** Maximum supported version */
6557
- max: "0.18",
6543
+ max: "0.21",
6558
6544
  /** When this config was last updated */
6559
- lastUpdated: "2025-01-26",
6545
+ lastUpdated: "2026-03-24",
6560
6546
  /** Source of truth for syntax patterns */
6561
6547
  referenceSource: "https://github.com/piotr-iohk/template-contract"
6562
6548
  };
@@ -7401,7 +7387,7 @@ async function extractContractStructure(input) {
7401
7387
  type: "invalid_pragma_format",
7402
7388
  line: lineNum,
7403
7389
  message: `Pragma includes patch version which may cause parse errors`,
7404
- suggestion: `Use bounded range format: 'pragma language_version >= 0.16 && <= 0.18;'`,
7390
+ suggestion: `Use bounded range format: 'pragma language_version >= 0.16 && <= 0.21;'`,
7405
7391
  severity: "error"
7406
7392
  });
7407
7393
  }
@@ -7874,7 +7860,7 @@ async function getFile(input) {
7874
7860
  );
7875
7861
  }
7876
7862
  let content = file.content;
7877
- let totalLines = content.split("\n").length;
7863
+ const totalLines = content.split("\n").length;
7878
7864
  let lineRange;
7879
7865
  if (input.startLine || input.endLine) {
7880
7866
  const lines = content.split("\n");
@@ -11186,4 +11172,4 @@ export {
11186
11172
  startServer,
11187
11173
  startHttpServer
11188
11174
  };
11189
- //# sourceMappingURL=chunk-TJ7QA5XT.js.map
11175
+ //# sourceMappingURL=chunk-TSXIIBEH.js.map
@@ -245,13 +245,6 @@ var DEFAULT_REPOSITORIES = [
245
245
  patterns: ["**/*.compact", "**/*.ts", "**/*.md"],
246
246
  exclude: ["node_modules/**", "dist/**"]
247
247
  },
248
- {
249
- owner: "Imdavyking",
250
- repo: "zkbadge",
251
- branch: "main",
252
- patterns: ["**/*.compact", "**/*.ts", "**/*.md"],
253
- exclude: ["node_modules/**", "dist/**"]
254
- },
255
248
  // Core Partner - PaimaStudios (Gaming Infrastructure)
256
249
  {
257
250
  owner: "PaimaStudios",
@@ -858,13 +851,15 @@ var GitHubClient = class {
858
851
  * This is safe because malicious patterns could only come from internal config.
859
852
  */
860
853
  filterFilesByPatterns(files, patterns, exclude) {
861
- const matchPattern = (file, pattern) => {
854
+ const compilePattern = (pattern) => {
862
855
  const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\./g, "\\.");
863
- return new RegExp(`^${regexPattern}$`).test(file);
856
+ return new RegExp(`^${regexPattern}$`);
864
857
  };
858
+ const includeRegexes = patterns.map(compilePattern);
859
+ const excludeRegexes = exclude.map(compilePattern);
865
860
  return files.filter((file) => {
866
- const matchesInclude = patterns.some((p) => matchPattern(file, p));
867
- const matchesExclude = exclude.some((p) => matchPattern(file, p));
861
+ const matchesInclude = includeRegexes.some((re) => re.test(file));
862
+ const matchesExclude = excludeRegexes.some((re) => re.test(file));
868
863
  return matchesInclude && !matchesExclude;
869
864
  });
870
865
  }
@@ -883,11 +878,19 @@ var GitHubClient = class {
883
878
  logger.info(
884
879
  `Found ${filteredFiles.length} matching files in ${owner}/${repo}`
885
880
  );
881
+ const BATCH_SIZE = 5;
886
882
  const files = [];
887
- for (const filePath of filteredFiles) {
888
- const file = await this.getFileContent(owner, repo, filePath, branch);
889
- if (file) {
890
- files.push(file);
883
+ for (let i = 0; i < filteredFiles.length; i += BATCH_SIZE) {
884
+ const batch = filteredFiles.slice(i, i + BATCH_SIZE);
885
+ const results = await Promise.all(
886
+ batch.map(
887
+ (filePath) => this.getFileContent(owner, repo, filePath, branch)
888
+ )
889
+ );
890
+ for (const file of results) {
891
+ if (file) {
892
+ files.push(file);
893
+ }
891
894
  }
892
895
  }
893
896
  return files;
@@ -1088,6 +1091,25 @@ function parseCompactFile(path, content) {
1088
1091
  });
1089
1092
  }
1090
1093
  }
1094
+ const newLedgerRegex = /(?:(export)\s+)?ledger\s+(\w+)\s*:\s*([^;]+);/gm;
1095
+ let newLedgerMatch;
1096
+ while ((newLedgerMatch = newLedgerRegex.exec(content)) !== null) {
1097
+ hasLedger = true;
1098
+ const isExport = newLedgerMatch[1] === "export";
1099
+ const fieldName = newLedgerMatch[2];
1100
+ const fieldType = newLedgerMatch[3].trim();
1101
+ const startLine = content.substring(0, newLedgerMatch.index).split("\n").length;
1102
+ codeUnits.push({
1103
+ type: "ledger",
1104
+ name: fieldName,
1105
+ code: newLedgerMatch[0].trim(),
1106
+ startLine,
1107
+ endLine: startLine,
1108
+ isPublic: isExport,
1109
+ isPrivate: !isExport,
1110
+ returnType: fieldType
1111
+ });
1112
+ }
1091
1113
  const circuitRegex = /(export\s+)?circuit\s+(\w+)\s*\(([^)]*)\)\s*(?::\s*(\w+))?\s*\{/g;
1092
1114
  let circuitMatch;
1093
1115
  while ((circuitMatch = circuitRegex.exec(content)) !== null) {
@@ -1232,9 +1254,18 @@ var EmbeddingGenerator = class {
1232
1254
  model: this.model,
1233
1255
  input: text
1234
1256
  });
1257
+ const embedding = response.data[0].embedding;
1258
+ const EXPECTED_DIMENSIONS = 1536;
1259
+ if (!embedding || embedding.length !== EXPECTED_DIMENSIONS) {
1260
+ logger.warn("Unexpected embedding dimensions", {
1261
+ expected: EXPECTED_DIMENSIONS,
1262
+ actual: embedding?.length ?? 0,
1263
+ model: this.model
1264
+ });
1265
+ }
1235
1266
  return {
1236
1267
  text,
1237
- embedding: response.data[0].embedding,
1268
+ embedding,
1238
1269
  model: this.model,
1239
1270
  tokenCount: response.usage?.total_tokens
1240
1271
  };
@@ -1593,7 +1624,7 @@ var releaseTracker = new ReleaseTracker();
1593
1624
 
1594
1625
  // src/utils/health.ts
1595
1626
  var startTime = Date.now();
1596
- var VERSION = "0.2.16";
1627
+ var VERSION = "0.2.18";
1597
1628
  async function checkGitHubAPI() {
1598
1629
  const start = Date.now();
1599
1630
  try {
@@ -1621,7 +1652,7 @@ async function checkGitHubAPI() {
1621
1652
  }
1622
1653
  async function checkVectorStore() {
1623
1654
  try {
1624
- const { vectorStore: vectorStore2 } = await import("./db-72ZOGII3.js");
1655
+ const { vectorStore: vectorStore2 } = await import("./db-B4AOK42O.js");
1625
1656
  if (vectorStore2) {
1626
1657
  return {
1627
1658
  status: "pass",
@@ -1806,6 +1837,9 @@ var Cache = class {
1806
1837
  if (this.cache.size >= this.options.maxSize) {
1807
1838
  this.evictOldest();
1808
1839
  }
1840
+ if (this.cache.size > 0 && this.cache.size % 100 === 0) {
1841
+ this.prune();
1842
+ }
1809
1843
  const now = Date.now();
1810
1844
  this.cache.set(key, {
1811
1845
  value,
@@ -1917,12 +1951,6 @@ var metadataCache = new Cache({
1917
1951
  maxSize: 100,
1918
1952
  name: "metadata"
1919
1953
  });
1920
- function pruneAllCaches() {
1921
- searchCache.prune();
1922
- fileCache.prune();
1923
- metadataCache.prune();
1924
- }
1925
- setInterval(pruneAllCaches, 5 * 60 * 1e3);
1926
1954
 
1927
1955
  // src/utils/hosted-api.ts
1928
1956
  var API_TIMEOUT = 15e3;
@@ -1991,7 +2019,8 @@ async function makeRequest(url, endpoint, options) {
1991
2019
  } catch (error) {
1992
2020
  if (error instanceof Error && error.name === "AbortError") {
1993
2021
  throw new Error(
1994
- `Request to ${endpoint} timed out after ${API_TIMEOUT / 1e3}s.`
2022
+ `Request to ${endpoint} timed out after ${API_TIMEOUT / 1e3}s.`,
2023
+ { cause: error }
1995
2024
  );
1996
2025
  }
1997
2026
  throw error;
@@ -2065,7 +2094,10 @@ function trackToolCall(tool, success, durationMs, version) {
2065
2094
  apiRequest("/v1/track/tool", {
2066
2095
  method: "POST",
2067
2096
  body: JSON.stringify({ tool, success, durationMs, version })
2068
- }).catch(() => {
2097
+ }).catch((error) => {
2098
+ logger.debug("Tracking call failed (non-blocking)", {
2099
+ error: String(error)
2100
+ });
2069
2101
  });
2070
2102
  }
2071
2103
 
@@ -2096,7 +2128,7 @@ function serialize(data) {
2096
2128
  }
2097
2129
 
2098
2130
  // src/utils/version.ts
2099
- var CURRENT_VERSION = "0.2.16";
2131
+ var CURRENT_VERSION = "0.2.18";
2100
2132
 
2101
2133
  // src/db/vectorStore.ts
2102
2134
  var VectorStore = class {
@@ -2137,9 +2169,23 @@ var VectorStore = class {
2137
2169
  return;
2138
2170
  }
2139
2171
  try {
2140
- const ids = documents.map((d) => d.id);
2141
- const embeddings = documents.map((d) => d.embedding);
2142
- const metadatas = documents.map((d) => ({
2172
+ const validDocuments = documents.filter((d) => {
2173
+ if (!d.embedding || d.embedding.length === 0) {
2174
+ logger.warn("Document missing embedding, skipping", {
2175
+ id: d.id,
2176
+ filePath: d.metadata.filePath
2177
+ });
2178
+ return false;
2179
+ }
2180
+ return true;
2181
+ });
2182
+ if (validDocuments.length === 0) {
2183
+ logger.warn("No documents with valid embeddings to store");
2184
+ return;
2185
+ }
2186
+ const ids = validDocuments.map((d) => d.id);
2187
+ const embeddings = validDocuments.map((d) => d.embedding);
2188
+ const metadatas = validDocuments.map((d) => ({
2143
2189
  repository: d.metadata.repository,
2144
2190
  filePath: d.metadata.filePath,
2145
2191
  language: d.metadata.language,
@@ -2149,14 +2195,14 @@ var VectorStore = class {
2149
2195
  codeName: d.metadata.codeName,
2150
2196
  isPublic: d.metadata.isPublic
2151
2197
  }));
2152
- const documentContents = documents.map((d) => d.content);
2198
+ const documentContents = validDocuments.map((d) => d.content);
2153
2199
  await this.collection.add({
2154
2200
  ids,
2155
2201
  embeddings,
2156
2202
  metadatas,
2157
2203
  documents: documentContents
2158
2204
  });
2159
- logger.debug(`Added ${documents.length} documents to vector store`);
2205
+ logger.debug(`Added ${validDocuments.length} documents to vector store`);
2160
2206
  } catch (error) {
2161
2207
  logger.error("Failed to add documents to vector store", {
2162
2208
  error: String(error)
@@ -2305,4 +2351,4 @@ export {
2305
2351
  serialize,
2306
2352
  CURRENT_VERSION
2307
2353
  };
2308
- //# sourceMappingURL=chunk-P3MTQHP6.js.map
2354
+ //# sourceMappingURL=chunk-WVMAQ2LK.js.map
@@ -0,0 +1,7 @@
1
+ import {
2
+ vectorStore
3
+ } from "./chunk-WVMAQ2LK.js";
4
+ export {
5
+ vectorStore
6
+ };
7
+ //# sourceMappingURL=db-B4AOK42O.js.map
package/dist/index.js CHANGED
@@ -9,10 +9,10 @@ import {
9
9
  promptDefinitions,
10
10
  startHttpServer,
11
11
  startServer
12
- } from "./chunk-TJ7QA5XT.js";
12
+ } from "./chunk-TSXIIBEH.js";
13
13
  import {
14
14
  logger
15
- } from "./chunk-P3MTQHP6.js";
15
+ } from "./chunk-WVMAQ2LK.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.16",
3
+ "version": "0.2.18",
4
4
  "description": "Model Context Protocol Server for Midnight Blockchain Development",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -15,9 +15,11 @@
15
15
  "dev": "NODE_ENV=development tsup --watch",
16
16
  "test": "vitest",
17
17
  "test:coverage": "vitest --coverage",
18
+ "lint": "eslint src/",
19
+ "lint:fix": "eslint src/ --fix",
18
20
  "format": "prettier --write src/**/*.ts",
19
21
  "prepublishOnly": "npm run typecheck && npm run build",
20
- "ci": "npm run typecheck && npm run build && npm test"
22
+ "ci": "npm run typecheck && npm run lint && npm run build && npm test"
21
23
  },
22
24
  "keywords": [
23
25
  "midnight",
@@ -43,16 +45,19 @@
43
45
  "zod": "^3.22.4"
44
46
  },
45
47
  "devDependencies": {
48
+ "@eslint/js": "^10.0.1",
46
49
  "@types/express": "^5.0.6",
47
50
  "@types/js-yaml": "^4.0.9",
48
51
  "@types/node": "^20.10.0",
49
52
  "@types/tar": "^6.1.13",
50
53
  "@types/yargs": "^17.0.35",
54
+ "eslint": "^10.0.2",
51
55
  "prettier": "^3.1.0",
52
56
  "tar": "^7.5.2",
53
57
  "tsup": "^8.5.1",
54
58
  "tsx": "^4.6.2",
55
59
  "typescript": "^5.3.2",
60
+ "typescript-eslint": "^8.56.1",
56
61
  "vitest": "^1.0.0"
57
62
  },
58
63
  "engines": {
@@ -1,7 +0,0 @@
1
- import {
2
- vectorStore
3
- } from "./chunk-P3MTQHP6.js";
4
- export {
5
- vectorStore
6
- };
7
- //# sourceMappingURL=db-72ZOGII3.js.map