DEV Community

manja316
manja316

Posted on

Using midnight-mcp for Contract Development with AI Assistants

I spent the last week building Compact smart contracts for Midnight with an AI assistant wired up through MCP. Here's what I learned, what broke, and how midnight-mcp turned my Claude setup into something actually useful for ZK contract development.

What is midnight-mcp?

midnight-mcp is an MCP server that plugs into Claude Desktop, Cursor, VS Code Copilot, or Windsurf. It gives your AI assistant direct access to Midnight's toolchain — a real hosted Compact compiler, static analysis, docs search, and 102+ indexed repos from the Midnight ecosystem.

29 tools total. No API keys needed for the default hosted mode. That last part surprised me — I expected some kind of auth dance, but it just works out of the box.

Installation

Claude Desktop

Edit your claude_desktop_config.json:

macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
Linux: ~/.config/Claude/claude_desktop_config.json

{
  "mcpServers": {
    "midnight": {
      "command": "npx",
      "args": ["-y", "midnight-mcp@latest"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

If you use nvm and Claude can't find Node:

{
  "mcpServers": {
    "midnight": {
      "command": "/bin/sh",
      "args": [
        "-c",
        "source ~/.nvm/nvm.sh && nvm use 20 >/dev/null 2>&1 && npx -y midnight-mcp@latest"
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Cursor

Drop this into .cursor/mcp.json in your project root:

{
  "mcpServers": {
    "midnight": {
      "command": "npx",
      "args": ["-y", "midnight-mcp@latest"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

VS Code Copilot

Add to .vscode/mcp.json, or open Command Palette -> MCP: Add Server -> pick "command (stdio)" -> enter npx -y midnight-mcp@latest.

Restart your editor after adding the config. That's it. No tokens, no environment variables, no docker containers.

Verifying It Works

After restarting, ask your AI assistant:

"Use midnight-health-check to verify the server is running."

You should get back a status object showing the server version (currently 0.2.18), compiler availability, and how many repos are indexed. If the compiler shows as available, you're good to go.

The Compilation Endpoint — Where It Gets Interesting

The killer feature is midnight-compile-contract. This isn't some regex linter pretending to be a compiler. It hits a real hosted Compact compiler (v0.29.0 as of writing) and returns actual compilation results.

Two modes:

  • Fast mode (skipZk: true): Syntax and type checking in 1-2 seconds. Use this while iterating.
  • Full mode (fullCompile: true): Generates actual ZK circuits. Takes 10-30 seconds. Use this before deploying.

Here's a basic contract to test with. I'll use a simple token ledger:

pragma language_version >= 0.22;

export ledger balance: Uint<64>;
export ledger owner: Bytes<32>;

export circuit initialize(initialOwner: Bytes<32>): [] {
  owner = disclose(initialOwner);
  balance = disclose(0 as Uint<64>);
}

export circuit deposit(amount: Uint<64>): [] {
  const currentBalance = balance;
  const newBalance = currentBalance + disclose(amount);
  balance = newBalance;
}

export circuit withdraw(amount: Uint<64>, caller: Bytes<32>): [] {
  assert caller == owner "only owner can withdraw";
  const currentBalance = balance;
  assert currentBalance >= disclose(amount) "insufficient balance";
  balance = currentBalance - disclose(amount);
}
Enter fullscreen mode Exit fullscreen mode

Ask Claude: "Compile this contract using midnight-compile-contract with skipZk set to true."

If the contract compiles, you'll see something like:

Compilation successful (Compiler v0.29.0) in 1847ms
Circuits: initialize, deposit, withdraw
Enter fullscreen mode Exit fullscreen mode

Now let me show you what happens when things go wrong — which is where this tool actually earns its keep.

Catching Real Bugs: A Walkthrough

I was building a voting contract and hit a bug that would have wasted hours without midnight-mcp catching it. Here's the broken version I started with:

pragma language_version >= 0.22;

enum VoteChoice {
  Yes,
  No,
  Abstain
}

ledger {
  yesVotes: Uint<32>;
  noVotes: Uint<32>;
  hasVoted: Map<Bytes<32>, Boolean>;
}

export circuit castVote(voter: Bytes<32>, choice: VoteChoice): Void {
  assert !hasVoted[voter] "already voted";
  hasVoted[voter] = true;

  if (choice == VoteChoice::Yes) {
    yesVotes = yesVotes + 1;
  } else if (choice == VoteChoice::No) {
    noVotes = noVotes + 1;
  }
}
Enter fullscreen mode Exit fullscreen mode

Looks reasonable if you're coming from Solidity, right? I asked Claude to compile it, and midnight-mcp lit up with errors. Three separate issues, all caught before I spent any time debugging.

Bug 1: Deprecated Ledger Block (P0)

The ledger { ... } block syntax is deprecated. Midnight moved to individual export ledger declarations. The static analysis (midnight-extract-contract-structure) flags this as a P0 severity issue:

deprecated_ledger_block: Use 'export ledger field: Type;' 
instead of block-style 'ledger { }' 
Enter fullscreen mode Exit fullscreen mode

Fix: break it into separate lines.

export ledger yesVotes: Uint<32>;
export ledger noVotes: Uint<32>;
export ledger hasVoted: Map<Bytes<32>, Boolean>;
Enter fullscreen mode Exit fullscreen mode

Bug 2: Invalid Void Return Type (P0)

Void doesn't exist in Compact. The empty return type is [] (empty tuple). If you've written Rust or TypeScript, this one bites you.

invalid_void_type: 'Void' is not valid. 
Use '[]' (empty tuple) for void returns.
Enter fullscreen mode Exit fullscreen mode

Fix:

export circuit castVote(voter: Bytes<32>, choice: VoteChoice): [] {
Enter fullscreen mode Exit fullscreen mode

Bug 3: Unexported Enum (P1)

The VoteChoice enum compiles fine in Compact, but without export the TypeScript SDK can't see it. You'd only discover this when trying to call castVote from your DApp frontend — by then you've already deployed.

unexported_enum: Enums need 'export' for TypeScript access
Enter fullscreen mode Exit fullscreen mode

Fix:

export enum VoteChoice {
  Yes,
  No,
  Abstain
}
Enter fullscreen mode Exit fullscreen mode

The Fixed Contract

Here's the corrected version after applying all three fixes:

pragma language_version >= 0.22;

export enum VoteChoice {
  Yes,
  No,
  Abstain
}

export ledger yesVotes: Uint<32>;
export ledger noVotes: Uint<32>;
export ledger hasVoted: Map<Bytes<32>, Boolean>;

export circuit castVote(voter: Bytes<32>, choice: VoteChoice): [] {
  assert !hasVoted[voter] "already voted";
  hasVoted[voter] = disclose(true);

  if (choice == VoteChoice::Yes) {
    yesVotes = yesVotes + disclose(1 as Uint<32>);
  } else if (choice == VoteChoice::No) {
    noVotes = noVotes + disclose(1 as Uint<32>);
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice I also added disclose() around the values being stored to ledger. This is the Compact privacy model — function parameters are private by default, and you must explicitly mark what gets disclosed to on-chain state. Without disclose(), the compiler rejects assignments from private values to public ledger fields. Coming from Solidity where everything is public unless you go out of your way, this inversion catches people.

Run midnight-compile-contract with skipZk: true on this one and it passes cleanly.

Contract Analysis for Security Patterns

Beyond compilation, midnight-analyze-contract runs static analysis looking for patterns that compile fine but are still problematic.

Ask your assistant: "Analyze this contract for security issues using midnight-analyze-contract."

The analyzer checks for:

Pattern What it catches
Missing assertions State changes without access control
Overflow potential Arithmetic on Uint types without bounds checking
Division hazards Division by zero potential
Unrestricted writes Ledger modifications without caller verification
Missing disclose Privacy leaks or compiler errors waiting to happen

For the voting contract above, the analyzer would flag castVote — the voter parameter is caller-supplied with no on-chain verification. Anyone can vote as anyone. In a real contract, you'd want to verify the voter's identity through Midnight's credential system or a signature check.

This is the kind of thing that compiles perfectly and passes tests but blows up in production. Having the AI flag it during development is genuinely useful.

Searching Docs and Code Examples

Two tools I use constantly:

midnight-search-docs

Search across Midnight's entire documentation. Way faster than manually browsing docs.midnight.network.

"Search Midnight docs for how disclose works with private state"

Returns relevant doc sections with context, so you can understand things like the privacy model without leaving your editor.

midnight-search-compact

Semantic search across all indexed Compact code in Midnight's 102+ repos. This is huge for learning patterns.

"Search for Compact examples that use Map types with access control"

It'll pull actual code from example-counter, example-bboard, example-dex, and other repos showing real-world patterns. I used this to figure out how to properly structure ledger state for a multi-user contract — the examples in the official repos were more helpful than anything I found on forums.

midnight-list-examples

Get a list of all example repos with descriptions:

"List available Midnight code examples"

Returns repos like example-counter, example-bboard (bulletin board), example-dex (decentralized exchange), and example-DAO. Each one is a full working DApp you can study.

midnight-fetch-docs

Pull a specific docs page directly into your conversation:

"Fetch the Midnight docs page at /compact"

Returns the rendered content right in your chat. Useful when you need to reference a specific API or syntax detail without context-switching.

Tips From Actually Using This

Start every contract session with a health check. The hosted compiler occasionally goes down. A quick midnight-health-check at the start saves you from wondering why compilation is returning weird errors ten minutes in.

Use skipZk: true while iterating, fullCompile: true before committing. The fast mode catches 95% of issues. Full compilation catches the remaining edge cases in ZK circuit generation, but it's slow enough that you don't want it in your tight feedback loop.

Run midnight-extract-contract-structure before midnight-compile-contract. The structure extractor does static analysis that catches deprecated patterns instantly. The compiler catches deeper semantic issues. Use both, in that order.

Ask the AI to explain circuits. The midnight-explain-circuit tool breaks down what a specific circuit does in plain English. When you're reading someone else's contract code, this saves a lot of head-scratching.

Check for breaking changes when updating. Midnight is moving fast — the Compact language version went from 0.16 to 0.22 recently. Use midnight-check-breaking-changes to see what changed between versions and midnight-get-migration-guide to get specific upgrade instructions.

The Development Loop

Here's the workflow that works for me:

  1. Write a Compact contract in your editor
  2. Ask Claude to extract the structure (midnight-extract-contract-structure) — catches P0 issues instantly
  3. Ask Claude to compile with fast mode (midnight-compile-contract with skipZk: true) — catches type errors and semantic issues
  4. Ask Claude to analyze for security (midnight-analyze-contract) — catches logic bugs
  5. Fix issues, repeat 2-4
  6. When it's clean, run full compilation (fullCompile: true) to generate ZK circuits
  7. Deploy to local devnet and test

Steps 2-5 take seconds with midnight-mcp. Without it, you're copy-pasting code into browser tools or running a local compiler setup. The feedback loop is dramatically tighter.

Wrapping Up

midnight-mcp isn't a toy. The hosted compiler endpoint is the real deal — same Compact compiler (v0.29.0) that the production toolchain uses. The static analysis catches bugs that would otherwise only surface at deployment. And the docs/code search across 102 repos means you're never stuck wondering "how does anyone actually do this?"

The zero-config setup is the right call. I've abandoned too many dev tools because they required Docker, API keys, and a 45-minute setup just to try them. npx -y midnight-mcp@latest in a JSON config and you're compiling Compact contracts from your editor. That's how developer tooling should work.

Resources:

Top comments (0)