Non‑Intrusive MCP Observability with eBPF: Introducing MCPSpy
The article explains how the emerging Model Context Protocol (MCP) for AI tools lacks visibility, outlines security and monitoring challenges, compares alternative tracing methods, and presents MCPSpy—a Linux‑only eBPF‑based, non‑intrusive solution that captures MCP stdio traffic, parses JSON‑RPC messages, and outputs human‑readable or JSON logs.
1. Introduction
MCP (Model Context Protocol) is a new JSON‑RPC‑based standard that lets AI assistants communicate with external tools, resources, and prompts. Because MCP traffic is opaque, developers cannot see what data is sent or received, which raises security and debugging concerns.
2. Why MCP Matters
MCP provides a universal interface for AI products, similar to how HTTP standardizes web services. Major AI platforms such as Claude Desktop, GitHub Copilot, and Cursor already act as MCP clients, making MCP the de‑facto standard for third‑party AI functionality.
2.1 Security Concerns
When an AI assistant issues MCP commands, users may not know which data is transmitted. Risks include accidental or malicious tool usage, privilege‑token abuse, and AI hallucinations that could issue destructive commands.
3. Choosing a Monitoring Approach
Several methods can observe MCP traffic on a host:
LD_PRELOAD Hooking : Injects a shared library to intercept read/write calls. Drawbacks are the need to start each process with the library, limited kernel visibility, and complex testing across libc versions.
Custom Kernel Module : Provides deep kernel access but has a steep learning curve, high crash risk, and difficulty attaching to encrypted TLS streams.
eBPF : Loads small, verified programs into the kernel at runtime and attaches them to hooks such as fexit/vfs_read and fexit/vfs_write. The verifier guarantees safety (no infinite loops, bounded memory), making eBPF suitable for production environments.
4. Implementation Details
4.1 eBPF Program
The program attaches to vfs_read and vfs_write to capture every file‑system read/write operation. It filters for JSON‑RPC payloads, copies the data into a ring buffer, and records PID, command name, and data size.
// "SEC" macro places the function in a specific ELF section
SEC("fexit/vfs_read")
int exit_vfs_read(struct file *file, const char *buf, size_t count,
loff_t *_pos, ssize_t ret) {
if (ret <= 0) return 0; // ignore empty reads
if (!is_mcp_data(buf, ret)) return 0; // filter non‑MCP data
struct data_event *event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
if (!event) return 0;
event->header.event_type = EVENT_READ;
event->header.pid = bpf_get_current_pid_tgid() >> 32;
bpf_get_current_comm(&event->header.comm, sizeof(event->header.comm));
event->size = ret;
event->buf_size = ret < MAX_BUF_SIZE ? ret : MAX_BUF_SIZE;
bpf_probe_read(event->buf, event->buf_size, buf);
bpf_ringbuf_submit(event, 0);
return 0;
}4.2 Loader (Go)
The bpf2go tool from the Cilium eBPF project generates Go bindings for the compiled eBPF object. The loader attaches the program using link.AttachTracing with ebpf.AttachTraceFExit for both read and write hooks.
readEnterLink, err := link.AttachTracing(link.TracingOptions{
Program: l.objs.ExitVfsRead,
AttachType: ebpf.AttachTraceFExit,
})
if err != nil {
return fmt.Errorf("failed to attach %s tracepoint: %w", l.objs.ExitVfsRead.String(), err)
}4.3 Event Handler
A dedicated goroutine reads raw events from the ring buffer, decodes them into Go structs, and forwards them through a channel for further processing.
go func() {
for {
select {
case <-ctx.Done():
return
default:
record, err := l.reader.Read()
if err != nil { continue }
eventType := mcpevents.EventType(record.RawSample[0])
switch eventType {
case mcpevents.EventTypeFSRead, mcpevents.EventTypeFSWrite:
var dataEvent mcpevents.FSDataEvent
if err := binary.Read(reader, binary.LittleEndian, &dataEvent); err != nil { continue }
l.eventCh <- &dataEvent
}
}
}
}()4.4 MCP Parser
The parser receives the raw JSON‑RPC bytes, distinguishes requests, responses, and notifications, and extracts fields such as method, id, and result. It also implements a simple correlation mechanism: when a write event (client → server) is seen, the message is stored by hash; the subsequent matching read event (server → client) is then linked, allowing identification of client/server PIDs.
4.5 Display Layer
Two output modes are provided: a human‑friendly console view and a line‑delimited JSON log suitable for downstream processing.
5. Limitations
Linux‑only: MCPSpy relies on eBPF, which is not yet fully supported on Windows or macOS.
It monitors MCP usage but does not protect against malicious MCP servers; separate threat models apply.
Vulnerabilities in the MCP server itself (e.g., RCE bugs) are outside the scope of MCPSpy.
6. Conclusion
Real‑time visibility into MCP communication is the first step toward securing AI‑driven tools. MCPSpy demonstrates that eBPF can safely and efficiently provide this observability on Linux. Future work will extend the tool to handle TLS‑encrypted HTTP transports by attaching user‑space uprobes to libraries such as OpenSSL.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Linux Code Review Hub
A professional Linux technology community and learning platform covering the kernel, memory management, process management, file system and I/O, performance tuning, device drivers, virtualization, and cloud computing.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
