-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Expand file tree
/
Copy pathmcp_parse.go
More file actions
126 lines (110 loc) · 3.36 KB
/
mcp_parse.go
File metadata and controls
126 lines (110 loc) · 3.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package middleware
import (
"bytes"
"encoding/json"
"io"
"net/http"
ghcontext "github.com/github/github-mcp-server/pkg/context"
)
// mcpJSONRPCRequest represents the structure of an MCP JSON-RPC request.
// We only parse the fields needed for routing and optimization.
type mcpJSONRPCRequest struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params struct {
// For tools/call
Name string `json:"name,omitempty"`
Arguments json.RawMessage `json:"arguments,omitempty"`
// For prompts/get
// Name is shared with tools/call
// For resources/read
URI string `json:"uri,omitempty"`
} `json:"params"`
}
// WithMCPParse creates a middleware that parses MCP JSON-RPC requests early in the
// request lifecycle and stores the parsed information in the request context.
// This enables:
// - Registry filtering via ForMCPRequest (only register needed tools/resources/prompts)
// - Avoiding duplicate JSON parsing in downstream middlewares
// - Access to owner/repo for secret-scanning middleware
//
// The middleware reads the request body, parses it, restores the body for downstream
// handlers, and stores the parsed MCPMethodInfo in the request context.
func WithMCPParse() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Skip health check endpoints
if r.URL.Path == "/_ping" {
next.ServeHTTP(w, r)
return
}
// Only parse POST requests (MCP uses JSON-RPC over POST)
if r.Method != http.MethodPost {
next.ServeHTTP(w, r)
return
}
// Read the request body
body, err := io.ReadAll(r.Body)
if err != nil {
// Log but continue - don't block requests on parse errors
next.ServeHTTP(w, r)
return
}
// Restore the body for downstream handlers
r.Body = io.NopCloser(bytes.NewReader(body))
// Skip empty bodies
if len(body) == 0 {
next.ServeHTTP(w, r)
return
}
// Parse the JSON-RPC request
var mcpReq mcpJSONRPCRequest
err = json.Unmarshal(body, &mcpReq)
if err != nil {
// Log but continue - could be a non-MCP request or malformed JSON
next.ServeHTTP(w, r)
return
}
// Skip if not a valid JSON-RPC 2.0 request
if mcpReq.JSONRPC != "2.0" || mcpReq.Method == "" {
next.ServeHTTP(w, r)
return
}
// Build the MCPMethodInfo
methodInfo := &ghcontext.MCPMethodInfo{
Method: mcpReq.Method,
}
// Extract item name based on method type
switch mcpReq.Method {
case "tools/call":
methodInfo.ItemName = mcpReq.Params.Name
// Parse arguments if present
if len(mcpReq.Params.Arguments) > 0 {
var args map[string]any
err := json.Unmarshal(mcpReq.Params.Arguments, &args)
if err == nil {
methodInfo.Arguments = args
// Extract owner and repo if present
if owner, ok := args["owner"].(string); ok {
methodInfo.Owner = owner
}
if repo, ok := args["repo"].(string); ok {
methodInfo.Repo = repo
}
}
}
case "prompts/get":
methodInfo.ItemName = mcpReq.Params.Name
case "resources/read":
methodInfo.ItemName = mcpReq.Params.URI
default:
// Whatever
}
// Store the parsed info in context
ctx = ghcontext.WithMCPMethodInfo(ctx, methodInfo)
next.ServeHTTP(w, r.WithContext(ctx))
}
return http.HandlerFunc(fn)
}
}