mcp: add mcp method and tool logging to authorize (#5668)

## Summary

Adds support for extending authorization log with Model Context Protocol
details.

i.e. 
```json
{
  "level": "info",
  "server-name": "all",
  "service": "authorize",
  "mcp-method": "tools/call",
  "mcp-tool": "describe_table",
  "mcp-tool-parameters": { "table_name": "Categories" },
  "allow": true,
  "allow-why-true": ["email-ok", "mcp-tool-ok"],
  "deny": false,
  "deny-why-false": [],
  "time": "2025-06-24T17:40:41-04:00",
  "message": "authorize check"
}
```

## Related issues

Fixes
https://linear.app/pomerium/issue/ENG-2393/mcp-authorize-each-incoming-request-to-an-mcp-route

## 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
This commit is contained in:
Denis Mishin 2025-06-24 17:58:51 -07:00 committed by GitHub
parent eacf19cd64
commit 9363457849
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 271 additions and 82 deletions

View file

@ -87,6 +87,7 @@ func Test_getEvaluatorRequest(t *testing.T) {
},
},
},
false, // mcp disabled
)
require.NoError(t, err)
expect := &evaluator.Request{
@ -144,7 +145,7 @@ func Test_getEvaluatorRequestWithPortInHostHeader(t *testing.T) {
},
},
},
})
}, false) // mcp disabled
require.NoError(t, err)
expect := &evaluator.Request{
Policy: &a.currentConfig.Load().Options.Policies[0],
@ -168,6 +169,53 @@ func Test_getEvaluatorRequestWithPortInHostHeader(t *testing.T) {
assert.Equal(t, expect, actual)
}
func Test_MCP_TraceAttributes(t *testing.T) {
t.Parallel()
// Test MCP request parsing
mcpBody := `{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"database_query","arguments":{"query":"SELECT * FROM users","limit":10}}}`
req := &envoy_service_auth_v3.CheckRequest{
Attributes: &envoy_service_auth_v3.AttributeContext{
Request: &envoy_service_auth_v3.AttributeContext_Request{
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
Body: mcpBody,
},
},
},
}
mcpReq, ok := evaluator.RequestMCPFromCheckRequest(req)
require.True(t, ok, "should successfully parse MCP request")
assert.Equal(t, "tools/call", mcpReq.Method)
require.NotNil(t, mcpReq.ToolCall)
assert.Equal(t, "database_query", mcpReq.ToolCall.Name)
assert.NotNil(t, mcpReq.ToolCall.Arguments)
assert.Equal(t, "SELECT * FROM users", mcpReq.ToolCall.Arguments["query"])
assert.Equal(t, float64(10), mcpReq.ToolCall.Arguments["limit"])
// Test non-tools/call method
mcpBodyList := `{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}`
req.Attributes.Request.Http.Body = mcpBodyList
mcpReq, ok = evaluator.RequestMCPFromCheckRequest(req)
require.True(t, ok, "should successfully parse MCP list request")
assert.Equal(t, "tools/list", mcpReq.Method)
assert.Nil(t, mcpReq.ToolCall)
// Test invalid JSON
req.Attributes.Request.Http.Body = `invalid json`
mcpReq, ok = evaluator.RequestMCPFromCheckRequest(req)
assert.False(t, ok, "should fail to parse invalid JSON")
// Test empty body
req.Attributes.Request.Http.Body = ""
mcpReq, ok = evaluator.RequestMCPFromCheckRequest(req)
assert.False(t, ok, "should fail to parse empty body")
}
type mockDataBrokerServiceClient struct {
databroker.DataBrokerServiceClient