mirror of
https://github.com/pomerium/pomerium.git
synced 2025-07-31 23:41:09 +02:00
## Summary Individual MCP method calls may be denied (i.e. via `mcp_tool` criterion) and Pomerium has to respond with MCP protocol error, which is JSON-RPC error message, rather then with HTTP level error which seems to break some MCP clients. ## Related issues Fix https://linear.app/pomerium/issue/ENG-2521/pomerium-does-not-return-an-mcp-error-when-a-tool-call-is-unauthorized ## User Explanation <!-- How would you explain this change to the user? If this change doesn't create any user-facing changes, you can leave this blank. If filled out, add the `docs` label --> ## Checklist - [x] reference any related issues - [x] updated unit tests - [x] add appropriate label (`enhancement`, `bug`, `breaking`, `dependencies`, `ci`) - [x] ready for review
71 lines
1.9 KiB
Go
71 lines
1.9 KiB
Go
package evaluator
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"mime"
|
|
"net/http"
|
|
|
|
envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
|
|
|
|
"github.com/pomerium/pomerium/internal/jsonrpc"
|
|
)
|
|
|
|
// RequestMCP is the MCP field in the request.
|
|
type RequestMCP struct {
|
|
ID jsonrpc.ID `json:"id"`
|
|
Method string `json:"method,omitempty"`
|
|
ToolCall *RequestMCPToolCall `json:"tool_call,omitempty"`
|
|
}
|
|
|
|
// RequestMCPToolCall represents a tool call within an MCP request.
|
|
type RequestMCPToolCall struct {
|
|
Name string `json:"name"`
|
|
Arguments map[string]any `json:"arguments"`
|
|
}
|
|
|
|
// RequestMCPFromCheckRequest populates a RequestMCP from an Envoy CheckRequest proto for MCP routes.
|
|
func RequestMCPFromCheckRequest(
|
|
in *envoy_service_auth_v3.CheckRequest,
|
|
) (RequestMCP, error) {
|
|
var mcpReq RequestMCP
|
|
|
|
ht := in.GetAttributes().GetRequest().GetHttp()
|
|
if ht.Method != http.MethodPost {
|
|
return mcpReq, nil
|
|
}
|
|
|
|
body := ht.GetBody()
|
|
if body == "" {
|
|
return mcpReq, errors.New("MCP request body is empty")
|
|
}
|
|
|
|
contentType := ht.GetHeaders()["content-type"]
|
|
mimeType, _, err := mime.ParseMediaType(contentType)
|
|
if err != nil {
|
|
return mcpReq, fmt.Errorf("failed to parse content-type %q: %w", contentType, err)
|
|
}
|
|
if mimeType != "application/json" {
|
|
return mcpReq, fmt.Errorf("unsupported content-type %q, expected application/json", mimeType)
|
|
}
|
|
|
|
jsonRPCReq, err := jsonrpc.ParseRequest([]byte(body))
|
|
if err != nil {
|
|
return mcpReq, fmt.Errorf("failed to parse MCP request: %w", err)
|
|
}
|
|
|
|
mcpReq.ID = jsonRPCReq.ID
|
|
mcpReq.Method = jsonRPCReq.Method
|
|
|
|
if jsonRPCReq.Method == "tools/call" {
|
|
var toolCall RequestMCPToolCall
|
|
err := json.Unmarshal(jsonRPCReq.Params, &toolCall)
|
|
if err != nil {
|
|
return mcpReq, fmt.Errorf("failed to unmarshal MCP tool call parameters: %w", err)
|
|
}
|
|
mcpReq.ToolCall = &toolCall
|
|
}
|
|
|
|
return mcpReq, nil
|
|
}
|