Logo Image

ZigNet: How I Built an MCP Server for Zig in 1.5 Days

ยท 12m ยท

ZigNet's hybrid architecture combining Zig compiler and LLM

The Initial Spark

It all started with a simple frustration: โ€œAI is cool and all, but it just canโ€™t keep up with how fast Zig evolves.โ€ Regular LLMs kept giving me garbage suggestions, mixing up old syntax with new, making up APIs that never existed.

So I asked myself: what would it actually cost to build my own?

The questions bouncing around my head:

After digging around a bit, I realized the solution wasnโ€™t some gigantic LLM that knows everything about Zig, but a hybrid system:

Enter Anthropicโ€™s Model Context Protocol (MCP). MCP let me bridge these two worlds: giving Claude access to the real Zig compiler AND a specialized model, all completely transparent to the user.

The Research Phase: What Does a Custom LLM Actually Cost?

Before diving into code, I did my homework. Hereโ€™s what I discovered:

Hardware Costs

Model Sizes (The Big Surprise)

I tested various base models:

Llama3.2-3B     โ†’ 2GB quantized  โ†’ Fast but dumb with Zig
CodeLlama-7B    โ†’ 4GB quantized  โ†’ Confuses Zig with Rust
Qwen2.5-7B      โ†’ 4GB quantized  โ†’ Excellent! Already understands Zig pretty well
Mistral-7B      โ†’ 4GB quantized  โ†’ Good but doesn't excel
DeepSeek-33B    โ†’ 16GB quantized โ†’ Total overkill for my use case

The revelation: You donโ€™t need GPT-4! A well-trained 7B is more than enough for a specific domain like Zig.

The Hybrid Plan

Instead of trying to teach the model EVERYTHING, I split the responsibilities:

TaskSolutionWhy
Syntax validationzig ast-check100% accurate, zero training needed
Formattingzig fmtOfficial standard, deterministic
DocumentationFine-tuned LLMNeeds creativity and context
Fix suggestionsFine-tuned LLMRequires semantic understanding
Type checkingzig ast-checkThe compiler knows best

This approach drastically cut down requirements:

Why Zig Needs ZigNet

Zig is a young language that moves fast. Its unique features like comptime, explicit error handling, and generics make it powerful but also tricky to analyze. Regular LLMs:

ZigNet solves this by directly integrating the official Zig compiler.

The Architecture: Simple but Effective

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                 Claude / MCP Client                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚ MCP Protocol (JSON-RPC)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚            ZigNet MCP Server (TypeScript)          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚              Tool Handlers                   โ”‚  โ”‚
โ”‚  โ”‚  - analyze_zig: Syntax and type analysis     โ”‚  โ”‚
โ”‚  โ”‚  - compile_zig: Code formatting              โ”‚  โ”‚
โ”‚  โ”‚  - get_zig_docs: AI-powered documentation    โ”‚  โ”‚
โ”‚  โ”‚  - suggest_fix: Smart suggestions            โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                โ–ผ                                   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚        Zig Compiler Integration              โ”‚  โ”‚
โ”‚  โ”‚  - zig ast-check (syntax/type validation)    โ”‚  โ”‚
โ”‚  โ”‚  - zig fmt (official formatter)              โ”‚  โ”‚
โ”‚  โ”‚  - Multi-version (0.13, 0.14, 0.15)          โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                โ–ผ                                   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚     Fine-tuned LLM (Qwen2.5-Coder-7B)        โ”‚  โ”‚
โ”‚  โ”‚  - 13,756 training examples                  โ”‚  โ”‚
โ”‚  โ”‚  - Specialized on modern Zig idioms          โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Key Decision #1: Use the Official Compiler

Instead of writing a custom parser (like many language servers do), I went straight for the Zig compiler:

// src/zig/executor.ts
export class ZigExecutor {
  async analyze(code: string): Promise<AnalysisResult> {
    // Save code to a temp file
    const tempFile = await this.createTempFile(code);
    
    // Use zig ast-check for analysis
    const result = await execAsync(
      `${this.zigPath} ast-check ${tempFile}`
    );
    
    // Parse compiler output
    return this.parseCompilerOutput(result);
  }
}

Benefits:

Key Decision #2: Smart Multi-versioning

Zig developers use different versions. ZigNet handles this automatically:

// src/zig/manager.ts
export class ZigManager {
  async getZigExecutable(version?: string): Promise<string> {
    // First check if Zig is installed on the system
    const systemZig = await this.findSystemZig();
    if (systemZig && (!version || systemZig.version === version)) {
      return systemZig.path;
    }
    
    // Otherwise download the requested version
    return this.downloadZig(version || 'latest');
  }
}

The caching system is smart:

Key Decision #3: Fine-tuned LLM for Zig

For the advanced features (docs and suggestions), I trained a specialized model:

# scripts/train-qwen-standard.py
def prepare_dataset():
    """13,756 examples from real Zig repositories"""
    examples = []
    
    # 97% code from GitHub (Zig 0.13-0.15)
    for repo in zig_repos:
        examples.extend(extract_zig_patterns(repo))
    
    # 3% official documentation
    examples.extend(parse_zig_docs())
    
    return train_test_split(examples)

The fine-tuning process:

  1. Base model: Qwen2.5-Coder-7B-Instruct (best Zig understanding in benchmarks)
  2. Technique: QLoRA 4-bit (efficient training on RTX 3090)
  3. Dataset: Focus on modern idioms (comptime, generics, error handling)
  4. Output: Q4_K_M quantized model (~4GB for local inference)

Technical Challenges I Faced

Challenge #1: Parsing Compiler Errors

The Zig compiler is verbose. I had to parse complex output:

// A typical Zig error
error: expected type 'i32', found '[]const u8'
    const x: i32 = "hello";
             ^~~

// The parser needs to extract:
// - Error type
// - Position (line, column)
// - Types involved
// - Contextual hints

Challenge #2: LLM Performance

Inference on a 7B model can be slow. Hereโ€™s what I optimized:

// src/llm/session.ts
export class LLMSession {
  private model: LlamaModel;
  private contextCache: Map<string, LlamaContext>;
  
  async suggest(code: string, error: string) {
    // Reuse contexts for similar queries
    const cacheKey = this.getCacheKey(code, error);
    let context = this.contextCache.get(cacheKey);
    
    if (!context) {
      context = await this.model.createContext({
        contextSize: 2048,  // Limited for speed
        threads: 8,          // Parallelization
      });
    }
    
    // Zig-specific prompt engineering
    const prompt = this.buildZigPrompt(code, error);
    return context.evaluate(prompt);
  }
}

Results:

Challenge #3: End-to-End Testing

How do you test a system that depends on compiler + LLM?

// tests/e2e/mcp-integration.test.ts
describe('ZigNet E2E Tests', () => {
  // Deterministic tests (always run)
  test('analyze_zig - syntax error', async () => {
    const result = await mcp.call('analyze_zig', {
      code: 'fn main() { invalid syntax }'
    });
    expect(result.errors).toContain('expected');
  });
  
  // LLM tests (auto-skip if model not present)
  test('suggest_fix - type mismatch', async () => {
    if (!modelAvailable()) {
      console.log('Skipping LLM test - model not found');
      return;
    }
    
    const result = await mcp.call('suggest_fix', {
      code: 'var x: i32 = "hello";',
      error: 'type mismatch'
    });
    
    // Verify it suggests at least one valid fix
    expect(result.suggestions).toContainValidZigCode();
  });
});

Testing strategy:

Claude Integration: The MCP Magic

The integration is surprisingly simple:

// claude_desktop_config.json
{
  "mcpServers": {
    "zignet": {
      "command": "npx",
      "args": ["-y", "zignet"]
    }
  }
}

Once configured, the user experience feels natural:

You: "Check this Zig code for errors"
[paste code]

Claude: [automatically uses analyze_zig]
"Found 2 errors:
1. Line 5: Type mismatch - variable 'x' expects i32 but got []const u8
2. Line 12: Function 'prozess' undefined, did you mean 'process'?"

You: "Can you format it properly?"

Claude: [uses compile_zig]
"Here's the code formatted with zig fmt:
[clean, formatted code]"

Lessons Learned

1. You Donโ€™t Need a Giant LLM

My biggest discovery: for a specific domain like Zig, a well-trained 7B beats a generic GPT-4. Itโ€™s about specialization, not size.

2. Hybrid > Pure ML

Combining deterministic tools (compiler) with ML (suggestions) gives you the best of both worlds: accuracy where it matters, creativity where it helps.

3. Itโ€™s Actually Affordable

Fine-tuning on consumer hardware? Totally doable!

4. Reuse Existing Tools

The Zig compiler already does everything needed for validation. Why reinvent the wheel when you can focus on whatโ€™s actually missing?

5. UX is Everything

Users shouldnโ€™t know thereโ€™s a hybrid system behind the scenes. It should be transparent and โ€œjust work.โ€

6. Separate Tests for Deterministic and Stochastic Components

Compiler tests are always reproducible. LLM tests can vary - plan accordingly.

7. Open Source from Day 1

Publishing the code forced me to maintain high standards and clear documentation. Plus, the Zig community is amazing for feedback.

Project Stats

Conclusions

ZigNet proves you donโ€™t need GPT-4 or $100k clusters for specialized AI. With a smart hybrid approach, you can get excellent results:

The key was understanding I didnโ€™t need to replace everything with ML, just the parts where AI actually adds value:

  1. Identify what can be deterministic (validation โ†’ compiler)
  2. Identify what needs โ€œintelligenceโ€ (suggestions โ†’ LLM)
  3. Pick the right model (Qwen2.5-7B, not GPT-4)
  4. Targeted training (13k Zig examples, not billions of generic data)
  5. Seamless integration (MCP does the magic)

The result? A system that:

If youโ€™re thinking โ€œIโ€™d love a specialized LLM for X but itโ€™s too expensive,โ€ think again. With the right approach, you probably need way less than you think.

The code is completely open source. If youโ€™re curious how a hybrid deterministic/stochastic system actually works, check it out:

VSCode package: https://marketplace.visualstudio.com/items?itemName=Fulgidus.zignet
๐Ÿ”— Repository: github.com/fulgidus/zignet
๐Ÿค– Model: huggingface.co/fulgidus/zignet-qwen2.5-coder-7b

Got questions? Want to build something similar for another language? Open an issue on GitHub or reach out. The project is WTFPL - literally do whatever you want with the code!


P.S.: Next time someone tells you that you need millions for custom AI, show them ZigNet. Sometimes all it takes is a gaming GPU, a free weekend, and the willingness to try. The future of specialized AI is accessible to everyone. ๐Ÿš€