惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

J
Java Code Geeks
Last Week in AI
Last Week in AI
T
Tailwind CSS Blog
WordPress大学
WordPress大学
B
Blog RSS Feed
T
The Blog of Author Tim Ferriss
F
Fortinet All Blogs
aimingoo的专栏
aimingoo的专栏
MongoDB | Blog
MongoDB | Blog
博客园 - Franky
C
Check Point Blog
P
Proofpoint News Feed
H
Help Net Security
月光博客
月光博客
博客园_首页
Stack Overflow Blog
Stack Overflow Blog
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
Recent Announcements
Recent Announcements
人人都是产品经理
人人都是产品经理
U
Unit 42
美团技术团队
I
InfoQ
A
About on SuperTechFans

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Heimdall MCP: Add OpenTelemetry tracing to any MCP server...
Enmanuel Mag · 2026-05-09 · via DEV Community

If you've been building with MCP servers lately, you've probably hit this wall: something goes wrong (or just feels slow) and you have zero visibility into what actually happened.

Which tool did Claude call? What input did it send? Did the server error silently? How long did it take?

That's the gap Heimdall fills.


What is Heimdall?

Heimdall is a transparent proxy for MCP servers. It sits between your MCP client (Claude Desktop, OpenCode, Cursor, or any other) and your MCP server — local or remote — and records every interaction as an OpenTelemetry span.

No modifications to your server. No SDK to integrate. No infra to run. Just wrap your existing server and start seeing what's happening inside.

The name comes from Norse mythology: Heimdall is the guardian of the Bifrost bridge, with the ability to see and hear everything that crosses between worlds. That's exactly the role this proxy plays.


The problem in one screenshot

You're running a Claude agent that orchestrates 5+ MCP tools. One of them is consistently slow. Another occasionally returns empty results. You have no way to know which one, when, or why — unless you dig into logs manually.

With Heimdall, every tool call becomes a structured span:

{
  "name": "mcp.tool.call",
  "attributes": {
    "gen_ai.tool.name": "search_documents",
    "mcp.duration_ms": 843,
    "mcp.status": "ok"
  },
  "events": [
    { "name": "request",  "body": { "query": "quarterly report" } },
    { "name": "response", "body": { "results": [...] } }
  ]
}

Enter fullscreen mode Exit fullscreen mode

Stored. Queryable. Yours.


Zero config wrapping via mcp.json

The easiest way to use Heimdall requires zero access to the server's source code. Just install it globally and update your mcp.json:

npm install -g @cardor/heimdall-mcp

Enter fullscreen mode Exit fullscreen mode

Before — your current config:

{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["my-server.js"]
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

After — wrapped with Heimdall:

{
  "mcpServers": {
    "my-server": {
      "command": "heimdall",
      "args": [
        "--store", "sqlite://~/.heimdall/traces.db",
        "--",
        "node", "my-server.js"
      ]
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

That's it. Restart your client and every tool call starts being recorded. The -- separator tells Heimdall where its args end and the real server command begins.

For remote servers (HTTP or SSE), it's just as simple:

{
  "mcpServers": {
    "remote-server": {
      "command": "heimdall",
      "args": [
        "--store", "postgres://user:pass@localhost/mydb",
        "--target", "http://my-remote-mcp.com/sse"
      ]
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Heimdall exposes stdio to your client, talks HTTP/SSE to the real server, and intercepts everything in between.


What gets recorded

Heimdall captures all standard MCP events:

Event What's recorded
tools/call Tool name, input args, response, duration, status
tools/list Available tools and their schemas
resources/read URI, MIME type, response size
prompts/get Prompt name, arguments, rendered output
initialize Client/server versions, negotiated capabilities
shutdown Total session duration

Every span follows the OpenTelemetry gen_ai.* semantic conventions,
so if you later want to send traces to Jaeger, Honeycomb, or Grafana Tempo, the data is already well-formed.


Storage options

Pick the backend that fits your setup:

SQLite — local file, zero infra

--store sqlite://./traces.db
# or with absolute path
--store sqlite:///Users/you/.heimdall/traces.db

Enter fullscreen mode Exit fullscreen mode

Best for: local development, single-machine setups, quick debugging sessions.

Uses @libsql/client under the hood — pure WASM, no native bindings, no node-gyp.

Postgres — your existing database

--store postgres://user:password@localhost:5432/mydb

Enter fullscreen mode Exit fullscreen mode

Best for: production environments, teams sharing observability data,
long-term storage with SQL queries across multiple servers.

MySQL

--store mysql://user:password@localhost:3306/mydb

Enter fullscreen mode Exit fullscreen mode

Same use case as Postgres. Pick whichever you already run.

All three use drizzle-orm with a shared schema, so the query experience is consistent regardless of which backend you choose.


Using it as a TypeScript library

If you have access to your server's code and want tighter integration, Heimdall ships as a fully-typed TypeScript library with a fluent builder API:

import { McpProxy } from 'heimdall-mcp'

const proxy = await McpProxy
  .create()
  .inbound({ transport: 'stdio' })
  .outbound({ transport: 'http', url: 'http://localhost:3001' })
  .store('sqlite://./traces.db')
  .build()

await proxy.start()

Enter fullscreen mode Exit fullscreen mode

You can also plug in custom interceptors — for example, to redact sensitive fields before they're persisted, or to add your own business logic on top of the tracing:

import { McpProxy, type Interceptor } from 'heimdall-mcp'

// Custom interceptor: redact API keys from tool inputs before storing
const redactSecrets: Interceptor = {
  name: 'redact-secrets',
  async intercept(request, ctx, next) {
    const sanitized = redactSensitiveFields(request)
    return next(sanitized)
  }
}

const proxy = await McpProxy
  .create()
  .inbound({ transport: 'stdio' })
  .outbound({ transport: 'http', url: 'http://localhost:3001' })
  .store('postgres://user:pass@host/db')
  .intercept(redactSecrets)
  .build()

Enter fullscreen mode Exit fullscreen mode


Design goals

A few decisions worth calling out:

No native dependencies. SQLite runs via WASM (@libsql/client), Postgres via the pure-JS postgres package, MySQL via mysql2. You can install and run Heimdall in any Node 22+ environment without compilation steps.

No policy opinions. Heimdall doesn't block, approve, or modify tool calls. It only observes and records. If you need access control or rate limiting, pair it with a tool like mcpwall.

Transport mixing. Your client connects via stdio. Your server can be stdio, HTTP, or SSE. Heimdall bridges them transparently — useful for wrapping local CLI servers behind an HTTP interface without touching their code.


Get started

npm install -g @cardor/heimdall-mcp

Enter fullscreen mode Exit fullscreen mode

GitHub: github.com/enmanuelmag/heimdall-mcp
Website: https://heimdall-mcp.cardor.dev

If you try it out, I'd love to hear what you think — especially feedback on the interceptor API design and the OTel schema. Open an issue or find me on X [@enmanuelmag].