pomerium/internal/jsonrpc/messages.go
Denis Mishin f5c5326c72
mcp: respond with jsonrpc error when MCP request is denied (#5694)
## 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
2025-07-08 09:07:26 -06:00

141 lines
3.2 KiB
Go

package jsonrpc
import (
"bytes"
"encoding/json"
"fmt"
)
func ParseRequest(data []byte) (*Request, error) {
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.UseNumber()
var req Request
if err := decoder.Decode(&req); err != nil {
return nil, fmt.Errorf("failed to parse JSON-RPC request: %w", err)
}
if req.JSONRPC != Version {
return nil, fmt.Errorf("invalid JSON-RPC version: %s, expected %s", req.JSONRPC, Version)
}
if req.Method == "" {
return nil, fmt.Errorf("missing method in JSON-RPC request")
}
return &req, nil
}
func NewErrorResponse(code ErrorCode, id ID, message string, data any) Response {
return Response{
JSONRPC: Version,
ID: id,
Error: &ErrorResponse{
Code: code,
Message: message,
Data: data,
},
}
}
// ID
// An identifier established by the Client that MUST contain a String, Number, or NULL value if included. If it is not included it is assumed to be a notification.
type ID struct {
value any
}
func (id ID) IsZero() bool {
return id.value == nil
}
func (id ID) MarshalJSON() ([]byte, error) {
if id.value == nil {
return json.Marshal(nil)
}
return json.Marshal(id.value)
}
func (id *ID) UnmarshalJSON(data []byte) error {
if data == nil || string(data) == "null" {
id.value = nil
return nil
}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.UseNumber()
var v any
if err := decoder.Decode(&v); err != nil {
return err
}
switch val := v.(type) {
case string:
id.value = val
case json.Number:
id.value = val
case nil:
id.value = nil
default:
return fmt.Errorf("field 'id' must be a string, number, or null, got %T", v)
}
return nil
}
func NewNumberID(value int) ID {
return ID{value: json.Number(fmt.Sprintf("%d", value))}
}
func NewStringID(value string) ID {
return ID{value: value}
}
func NewJSONNumberID(value json.Number) ID {
return ID{value: value}
}
type Request struct {
JSONRPC string `json:"jsonrpc"`
ID ID `json:"id"`
Method string `json:"method"`
Params json.RawMessage `json:"params"`
}
type Response struct {
JSONRPC string `json:"jsonrpc"`
ID ID `json:"id,omitempty"`
Error *ErrorResponse `json:"error,omitempty"`
}
type ErrorResponse struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
Data any `json:"data,omitempty"`
}
type ErrorCode int
// JSON-RPC error codes as defined in the specification
// https://www.jsonrpc.org/specification#error_object
const (
// ErrorCodeParseError - Invalid JSON was received by the server.
// An error occurred on the server while parsing the JSON text.
ErrorCodeParseError = ErrorCode(-32700)
// ErrorCodeInvalidRequest - The JSON sent is not a valid Request object.
ErrorCodeInvalidRequest = ErrorCode(-32600)
// ErrorCodeMethodNotFound - The method does not exist / is not available.
ErrorCodeMethodNotFound = ErrorCode(-32601)
// ErrorCodeInvalidParams - Invalid method parameter(s).
ErrorCodeInvalidParams = ErrorCode(-32602)
// ErrorCodeInternalError - Internal JSON-RPC error.
ErrorCodeInternalError = ErrorCode(-32603)
// Server error range - Reserved for implementation-defined server-errors.
// Error codes from -32000 to -32099 are reserved for server errors.
)
const Version = "2.0"