Skip to content

Commit 35d53c1

Browse files
authored
Merge pull request #2 from tech4242/feature/stdio-sniffing
feature/stdio-support
2 parents 49747d0 + a2f66ff commit 35d53c1

File tree

23 files changed

+1657
-136
lines changed

23 files changed

+1657
-136
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@ jobs:
5555
- name: Upload coverage to Codecov
5656
uses: codecov/codecov-action@v5
5757
with:
58-
file: ./coverage.xml
58+
files: ./coverage.xml
5959
flags: unittests
6060
name: codecov-${{ matrix.python-version }}
61-
token: ${{ secrets.CODECOV_TOKEN }}
61+
token: ${{ secrets.CODECOV_TOKEN }}
62+
override_commit: ${{ github.event.pull_request.head.sha }}
63+
override_pr: ${{ github.event.number }}
64+
override_branch: ${{ github.head_ref }}
65+
verbose: true

README.md

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@
1111
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
1212
</div>
1313

14-
MCPHawk is a passive network analyzer for **Model Context Protocol (MCP)** traffic, providing deep visibility into MCP client-server interactions. Think Wireshark meets mcpinspector, purpose-built for the MCP ecosystem.
14+
MCPHawk is a new Logging & Monitoring solution for **Model Context Protocol (MCP)** traffic, providing deep visibility into MCP client-server interactions. It started off as a mix between Wireshark and mcpinspector, purpose-built for the MCP ecosystem, and is now slowly turning into something more.
1515

1616
**Key Capabilities:**
17-
- **Protocol-Aware Capture**: Understands MCP's JSON-RPC 2.0 transport layer, capturing and reassembling messages from raw TCP streams
18-
- **Transport Agnostic**: Monitors MCP traffic across all standard transports
19-
- **Zero-Configuration Monitoring**: Passively observes MCP communication without proxies, certificates, or modifications to clients/servers
20-
- **Full Message Reconstruction**: Advanced TCP stream reassembly handles fragmented packets, chunked HTTP transfers, and SSE streams
17+
- **Protocol-Aware Capture**: Understands MCP's JSON-RPC 2.0 transport layer, capturing and reassembling messages from stdio pipes and HTTP streams
18+
- **Transport Agnostic**: Monitors MCP traffic across all standard transports (stdio, HTTP Streaming, HTTP+SSE)
19+
- **Full Message Reconstruction**: Advanced stream reassembly handles fragmented packets, chunked HTTP transfers, SSE streams, and stdio pipes
2120

2221
<img src="examples/branding/mcphawk_screenshot.png" alt="MCPHawk Screenshot" width="100%">
2322

@@ -29,9 +28,7 @@ MCPHawk is a passive network analyzer for **Model Context Protocol (MCP)** traff
2928
- **Responses**: Success results and error responses with matching IDs
3029
- **Notifications**: Fire-and-forget method calls without IDs
3130
- **Batch Operations**: Support for JSON-RPC batch requests/responses
32-
- **Transport-Specific Handling**:
33-
- **HTTP/SSE**: Full support for MCP's streaming HTTP transport with Server-Sent Events
34-
- **TCP Direct**: Raw TCP stream reconstruction for custom implementations
31+
- **Transport-Specific Handling**: See MCP Transport Support table below for full details
3532
- **Chunked Transfer**: Handles HTTP chunked transfer encoding transparently
3633
- **Protocol Compliance**: Validates JSON-RPC 2.0 structure and MCP-specific extensions
3734

@@ -67,11 +64,11 @@ MCPHawk is a passive network analyzer for **Model Context Protocol (MCP)** traff
6764

6865
| Official MCP Transport | Protocol Version | Capture Support | Details |
6966
|------------------------|------------------|:---------------:|---------|
70-
| **stdio** | All versions | coming soon :) | secret |
71-
| **HTTP** (Streamable HTTP) | 2025-03-26+ | ✅ Full | HTTP POST with optional SSE streaming responses |
67+
| **stdio** | All versions | ✅ Full | Process wrapper transparently captures stdin/stdout between client and server |
68+
| **HTTP Streaming** | 2025-03-26+ | ✅ Full | HTTP POST with optional SSE streaming responses |
7269
| **HTTP+SSE** (deprecated) | 2024-11-05 | ✅ Full | Legacy transport with separate SSE endpoint |
7370

74-
Disclaimer: TCP direct traffic with JSON-RPC is also captured and marked as unknown (should you have custom stuff you shouldn't)
71+
Note: Raw TCP traffic with JSON-RPC is also captured and marked as "unknown" transport type
7572

7673
## Comparison with Similar Tools
7774

@@ -87,7 +84,7 @@ Disclaimer: TCP direct traffic with JSON-RPC is also captured and marked as unkn
8784
| MCP server for data access ||||
8885
| No client/server config needed ||||
8986
| Interactive testing/debugging ||||
90-
| Proxy/MITM capabilities | |||
87+
| Proxy/MITM capabilities | ✅ (stdio) |||
9188

9289
**When to use each tool:**
9390
- **MCPHawk**: Passive monitoring, protocol analysis, debugging MCP implementations, understanding traffic patterns
@@ -163,6 +160,22 @@ sudo mcphawk web --port 3000 --host 0.0.0.0 --web-port 9000
163160
sudo mcphawk sniff --port 3000 --debug
164161
sudo mcphawk web --port 3000 --debug
165162

163+
# Wrap an MCP server to capture stdio traffic
164+
mcphawk wrap /path/to/mcp-server --arg1 --arg2
165+
166+
# Example: Wrap Context7 MCP server to monitor Claude Desktop's documentation lookups
167+
mcphawk wrap npx -y @upstash/context7-mcp@latest
168+
169+
# Claude Desktop config to use the wrapped version:
170+
# {
171+
# "mcpServers": {
172+
# "context7": {
173+
# "command": "mcphawk",
174+
# "args": ["wrap", "npx", "-y", "@upstash/context7-mcp@latest"]
175+
# }
176+
# }
177+
# }
178+
166179
# Start MCP server with Streamable HTTP transport (default)
167180
mcphawk mcp --transport http --mcp-port 8765
168181

@@ -282,7 +295,7 @@ Vote for features by opening a GitHub issue!
282295

283296
- [x] **Auto-detect MCP traffic** - Automatically discover MCP traffic on any port without prior configuration
284297
- [x] **MCP Server Interface** - Expose captured traffic via MCP server for AI agents to query and analyze traffic patterns
285-
- [ ] **Stdio capture** - eBPF Integration (Linux/macOS) Trace read/write system calls for pipe communication
298+
- [x] **Stdio capture** - Transparent process wrapper to capture stdin/stdout communication
286299
- [ ] **Protocol Version Detection** - Identify and display MCP protocol version from captured traffic
287300
- [ ] **Smart Search & Filtering** - Search by method name, params, or any JSON field with regex support
288301
- [ ] **Performance Analytics** - Request/response timing, method frequency charts, and latency distribution

codecov.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
# Minimal codecov configuration
21
coverage:
32
status:
43
project:
54
default:
6-
target: 80 # Automatically set coverage target
7-
threshold: 5% # Allow 5% drop in coverage
5+
target: 80
6+
threshold: 5
7+
8+
comment:
9+
layout: "reach, diff, flags, files"
10+
behavior: default
11+
require_changes: false
23.7 KB
Loading
14 KB
Loading

frontend/src/components/LogTable/LogFilters.vue

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,32 @@
3333
<MagnifyingGlassIcon class="absolute left-3 top-2.5 h-5 w-5 text-gray-400" />
3434
</div>
3535

36+
<!-- Transport type filter -->
37+
<select
38+
v-model="selectedTransport"
39+
@change="logStore.setTransportFilter(selectedTransport)"
40+
class="px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-mcp-blue focus:border-transparent"
41+
>
42+
<option value="all">All Transports</option>
43+
<option value="streamable_http">Streamable HTTP</option>
44+
<option value="http_sse">HTTP+SSE</option>
45+
<option value="stdio">stdio</option>
46+
<option value="unknown">Unknown</option>
47+
</select>
48+
49+
<!-- Server filter -->
50+
<select
51+
v-if="logStore.uniqueServers.length > 0"
52+
v-model="selectedServer"
53+
@change="logStore.setServerFilter(selectedServer)"
54+
class="px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-mcp-blue focus:border-transparent"
55+
>
56+
<option value="all">All Servers</option>
57+
<option v-for="server in logStore.uniqueServers" :key="server" :value="server">
58+
{{ server }}
59+
</option>
60+
</select>
61+
3662
<!-- Expand all -->
3763
<button
3864
@click="logStore.toggleExpandAll"
@@ -48,20 +74,6 @@
4874
<span class="hidden sm:inline">{{ logStore.expandAll ? 'Collapse' : 'Expand' }}</span>
4975
</button>
5076

51-
<!-- Toggle MCPHawk traffic -->
52-
<button
53-
@click="logStore.toggleMcpHawkTraffic"
54-
class="px-4 py-2 rounded-lg font-medium transition-colors flex items-center gap-2"
55-
:class="[
56-
logStore.showMcpHawkTraffic
57-
? 'bg-purple-600 text-white'
58-
: 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600'
59-
]"
60-
:title="logStore.showMcpHawkTraffic ? 'Hide MCPHawk\'s own MCP traffic' : 'Show MCPHawk\'s own MCP traffic'"
61-
>
62-
<FunnelIcon class="h-5 w-5" />
63-
<span class="hidden lg:inline">MCPHawk</span>
64-
</button>
6577

6678
<!-- Clear logs -->
6779
<button
@@ -87,11 +99,13 @@
8799
<script setup>
88100
import { computed, ref, watch } from 'vue'
89101
import { useLogStore } from '@/stores/logs'
90-
import { MagnifyingGlassIcon, TrashIcon, ArrowPathIcon, CodeBracketIcon, FunnelIcon } from '@heroicons/vue/24/outline'
102+
import { MagnifyingGlassIcon, TrashIcon, ArrowPathIcon, CodeBracketIcon } from '@heroicons/vue/24/outline'
91103
92104
const logStore = useLogStore()
93105
94106
const searchQuery = ref('')
107+
const selectedTransport = ref('all')
108+
const selectedServer = ref('all')
95109
96110
const filters = computed(() => [
97111
{ label: 'All', value: 'all', count: logStore.stats.total },

frontend/src/components/LogTable/LogRow.vue

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<tr>
3-
<td colspan="7" class="p-0">
3+
<td colspan="10" class="p-0">
44
<div
55
class="cursor-pointer transition-all relative"
66
:class="{
@@ -17,19 +17,23 @@
1717
<td class="px-4 py-3 text-left w-32 text-sm text-gray-900 dark:text-gray-100">
1818
{{ formatTimestamp(log.timestamp) }}
1919
</td>
20-
<td class="px-4 py-3 text-left w-40">
21-
<div class="flex items-center gap-2 whitespace-nowrap">
22-
<MessageTypeBadge :type="messageType" />
23-
<span v-if="isMcpHawkTraffic"
24-
class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200"
25-
title="MCPHawk's own MCP traffic">
26-
MCP🦅
27-
</span>
28-
</div>
20+
<td class="px-4 py-3 text-left w-32">
21+
<MessageTypeBadge :type="messageType" />
22+
</td>
23+
<td class="px-4 py-3 text-left w-20 text-sm text-gray-900 dark:text-gray-100 font-mono">
24+
<span class="text-gray-600 dark:text-gray-400">{{ messageId }}</span>
2925
</td>
3026
<td class="px-4 py-3 text-left text-sm text-gray-900 dark:text-gray-100 font-mono truncate">
3127
{{ messageSummary }}
3228
</td>
29+
<td class="px-4 py-3 text-left w-40 text-sm text-gray-500 dark:text-gray-400">
30+
<span v-if="serverInfo"
31+
class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-200"
32+
:title="`${serverInfo.name} v${serverInfo.version}`">
33+
{{ serverInfo.name }}
34+
</span>
35+
<span v-else class="text-gray-400 dark:text-gray-600">-</span>
36+
</td>
3337
<td class="px-4 py-3 text-left w-32 text-sm text-gray-500 dark:text-gray-400 font-mono">
3438
<span
3539
class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium"
@@ -48,6 +52,9 @@
4852
<td class="px-4 py-3 text-left w-24 text-sm text-gray-500 dark:text-gray-400 font-mono">
4953
{{ portInfo }}
5054
</td>
55+
<td class="px-4 py-3 text-left w-20 text-sm text-gray-500 dark:text-gray-400 font-mono">
56+
{{ pidInfo }}
57+
</td>
5158
</tr>
5259
</table>
5360
</div>
@@ -99,24 +106,26 @@ defineEmits(['click'])
99106
const messageType = computed(() => getMessageType(props.log.message))
100107
const messageSummary = computed(() => getMessageSummary(props.log.message))
101108
const portInfo = computed(() => getPortInfo(props.log))
102-
const directionIcon = computed(() => getDirectionIcon(props.log.direction))
103109
104-
const formattedJson = computed(() => {
110+
const messageId = computed(() => {
105111
try {
106112
const parsed = JSON.parse(props.log.message)
107-
return JSON.stringify(parsed, null, 2)
113+
if (parsed && parsed.id !== undefined) {
114+
return parsed.id
115+
}
108116
} catch {
109-
return props.log.message
117+
// ignore
110118
}
119+
return '-'
111120
})
121+
const directionIcon = computed(() => getDirectionIcon(props.log.direction))
112122
113-
const isMcpHawkTraffic = computed(() => {
114-
if (!props.log.metadata) return false
123+
const formattedJson = computed(() => {
115124
try {
116-
const meta = JSON.parse(props.log.metadata)
117-
return meta.source === 'mcphawk-mcp'
125+
const parsed = JSON.parse(props.log.message)
126+
return JSON.stringify(parsed, null, 2)
118127
} catch {
119-
return false
128+
return props.log.message
120129
}
121130
})
122131
@@ -128,4 +137,28 @@ const transportTypeColor = computed(() => {
128137
return getTransportTypeColor(props.log.transport_type || props.log.traffic_type || 'unknown')
129138
})
130139
140+
const pidInfo = computed(() => {
141+
// For stdio transport, show PID; for network transports, show empty
142+
if (props.log.transport_type === 'stdio' && props.log.pid) {
143+
return props.log.pid.toString()
144+
}
145+
return '-'
146+
})
147+
148+
const serverInfo = computed(() => {
149+
if (!props.log.metadata) return null
150+
try {
151+
const meta = JSON.parse(props.log.metadata)
152+
if (meta.server_name) {
153+
return {
154+
name: meta.server_name,
155+
version: meta.server_version || ''
156+
}
157+
}
158+
} catch {
159+
// ignore
160+
}
161+
return null
162+
})
163+
131164
</script>

frontend/src/components/LogTable/LogTable.vue

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,18 @@
1616
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-32">
1717
Time
1818
</th>
19-
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-40">
19+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-32">
2020
Type
2121
</th>
22+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-20">
23+
ID
24+
</th>
2225
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
2326
Message
2427
</th>
28+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-40">
29+
Server
30+
</th>
2531
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-32">
2632
Transport
2733
</th>
@@ -31,6 +37,9 @@
3137
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-24">
3238
Port
3339
</th>
40+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-20">
41+
PID
42+
</th>
3443
</tr>
3544
</thead>
3645
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
@@ -46,7 +55,7 @@
4655

4756
<!-- Empty state -->
4857
<tr v-if="!logStore.loading && displayLogs.length === 0">
49-
<td colspan="7" class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
58+
<td colspan="10" class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
5059
<div class="flex flex-col items-center">
5160
<svg class="w-12 h-12 mb-4 text-gray-300 dark:text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5261
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />

0 commit comments

Comments
 (0)