Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go: ["1.24", "1.25"]
go: ["1.25", "1.26"]
steps:
- name: Check out code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
Expand All @@ -73,8 +73,8 @@ jobs:
- name: Set up Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version: "1.24"
go-version: "1.25"
- name: Test with -race
run: |
cd go/sdk
go test -v -race ./...
go test -v -race ./...
5 changes: 2 additions & 3 deletions .github/workflows/status-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,8 @@ jobs:
],
go: [
'Go CI / Linting',
'Go CI / Unit Tests (1.23)',
'Go CI / Unit Tests (1.24)',
'Go CI / Unit Tests (1.25)',
'Go CI / Unit Tests (1.26)',
'Go CI / Race Detection'
],
typescript: [
Expand Down Expand Up @@ -145,4 +144,4 @@ jobs:
core.setFailed(`Required checks have not passed:\\n${failureMessage}`);
} else {
console.log(`All required checks passed:\\n${failureMessage}`);
}
}
6 changes: 3 additions & 3 deletions go/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
Go implementation of the MCP Interceptor Extension based on
[SEP-2624](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/2624).

Note: Currently the MCP SDK is vendored, in-order to add the Protocol Methods needed for interceptors.

## Quick Start

```go
Expand Down Expand Up @@ -36,7 +34,9 @@ ext.AddInterceptor(&interceptors.Validator{
})

// Install on the server and create a chain for middleware.
ext.Install(mcpServer)
if err := ext.Install(mcpServer); err != nil {
log.Fatal(err)
}
chain, err := ext.LocalChain(ctx, mcpServer)
mcpServer.AddReceivingMiddleware(gomiddleware.Middleware(chain))

Expand Down
16 changes: 9 additions & 7 deletions go/sdk/doc/CONFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ Status of this Go SDK implementation against the SEP-2624 interceptor proposal.
| Validation interceptors | Parallel execution, severity-based blocking, fail-open support |
| Mutation interceptors | Sequential execution, priority ordering, payload threading via `json.RawMessage` |
| Interceptor metadata | Name, version, description, events, phase, priorityHint (polymorphic JSON), compat, configSchema, mode, failOpen |
| Event names | Constants for all standard server-side MCP methods; JSON-RPC method names used directly as event names |
| Event names | Constants for common server-side MCP methods; other JSON-RPC method names can be used directly as event names |
| Protocol methods | `interceptors/list` for discovery, `interceptor/invoke` for invocation — both registered as custom JSON-RPC methods |
| MCP Go SDK integration | Uses `github.com/modelcontextprotocol/go-sdk` custom method APIs (`AddReceivingCustomMethod`, `AddSendingCustomMethod`, `CallCustomMethod`, `ParamsBase`, `ResultBase`) from v1.7.0; no vendored SDK patch is required |
| `InterceptorChain` (SEP) | `chain.Chain` type with `chain.ChainEntry` objects pairing interceptor descriptors with MCP client sessions |
| `ChainEntry` (SEP) | `chain.ChainEntry` struct holds `InterceptorInfo` + `*mcp.ClientSession` |
| Chain discovery | `chain.Chain.AddMCPServer()` calls `interceptors/list` to discover interceptors from an MCP server |
Expand All @@ -23,17 +24,18 @@ Status of this Go SDK implementation against the SEP-2624 interceptor proposal.
| Fail-open behavior | `FailOpen: true` interceptors log errors without aborting the chain |
| Audit mode | `ModeAudit` records results without blocking; mutated payloads not propagated |
| Timeout & context | Per-interceptor timeouts via `InvokeParams.TimeoutMs`, chain-level context cancellation, `InvocationContext` with principal/traceId |
| Receiving direction (client → server) | All server-side method calls intercepted via `AddReceivingMiddleware` |
| Receiving direction (client → server) | Server-side messages routed through `AddReceivingMiddleware` are intercepted, except skipped lifecycle and interceptor methods |
| Capability declaration | Interceptor metadata injected into `initialize` response via `Capabilities.Extensions` |
| First-party (in-process) deployment | `Server.LocalChain()` creates an in-memory transport `chain.Chain`; interceptors invoked via JSON-RPC even in-process |
| First-party (in-process) deployment | `extension.Extension.LocalChain()` creates an in-memory transport `chain.Chain`; interceptors invoked via JSON-RPC even in-process |
| Third-party and hybrid deployment | Chain entries can point to any `*mcp.ClientSession` — local (in-memory), stdio, or HTTP transport |
| `json.RawMessage` payloads | Interceptor handlers receive `json.RawMessage` via `interceptor/invoke`, matching the SEP's JSON-level payload model |

## Not Implemented

| Area | SEP expects | Notes |
|------|-------------|-------|
| Wildcard event matching | `"*/request"`, `"*/response"`, `"*"` | `matchesEvent` does exact match only; wildcard patterns are planned |
| Server → client interception | Client features as interceptable events: `"sampling/createMessage"`, `"elicitation/create"`, `"roots/list"` | Requires `Server.AddSendingMiddleware` support in the go-sdk |
| Per-interceptor config passthrough | `ChainExecutionParams.Config` map | Wired through to `InvokeParams.Config` but not yet used by middleware |
| Remote interceptor servers | Connecting chain to external MCP servers over stdio/HTTP | Infrastructure is ready (`chain.Chain.AddMCPServer` accepts any `*mcp.ClientSession`); no convenience helpers yet |
| Wildcard event matching | `"*"` MUST match all lifecycle events; namespace wildcards such as `"tools/*"` MAY be supported | `matchesHooks` does exact event matching only today |
| Sending-side interception | Sending flow uses `Mutate → Validate → Send` for client→server and server→client boundaries | Only the server receiving middleware integration exists. `go-sdk` v1.7.0 now provides `Client.AddSendingMiddleware` and `Server.AddSendingMiddleware`, so this is a local implementation gap rather than an upstream SDK blocker |
| Client feature events | `"sampling/createMessage"`, `"elicitation/create"`, `"roots/list"` | No client-side middleware integration yet; callers may still use raw method-name strings as events |
| Per-interceptor config source and validation | `ChainExecutionParams.Config` overrides and `configSchema` validation | `ExecutionParams.Config` is passed through to `InvokeParams.Config`, but middleware does not load config and `configSchema` is not validated |
| Remote interceptor server helpers | Convenient setup for stdio/HTTP interceptor servers | Infrastructure is ready (`chain.Chain.AddMCPServer` accepts any `*mcp.ClientSession`); no config-driven or transport-specific helper APIs yet |
43 changes: 25 additions & 18 deletions go/sdk/doc/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ server via in-memory transport. Under the hood it:
1. Creates an `InMemoryTransport` pair via `mcp.NewInMemoryTransports()`
2. Connects the server side via `mcp.Server.Connect(ctx, serverTransport)`
3. Creates an MCP client and connects via the client transport
4. Calls `chain.AddMCPServer(ctx, clientSession)` which discovers
4. Registers the interceptor custom sending methods on the client
5. Calls `chain.AddMCPServer(ctx, clientSession)` which discovers
interceptors via `interceptors/list`
5. Returns the ready-to-use chain
6. Returns the ready-to-use chain

This means interceptors are invoked through the full MCP JSON-RPC
pathway — even when running in the same process — ensuring the same
Expand All @@ -59,30 +60,33 @@ The capability payload includes:

## Request/Response Lifecycle

When a JSON-RPC request arrives, `gomiddleware.Middleware` runs:
When a JSON-RPC request or notification reaches the server receiving
middleware, `gomiddleware.Middleware` runs:

```
0. If method is skipped (initialize, notifications/*, interceptor/*) → passthrough
0. If method is skipped → passthrough
1. Marshal request params to json.RawMessage
2. chain.Execute(ctx, {event, PhaseRequest, payload})
→ For each interceptor: interceptor/invoke RPC via ClientSession
3. If aborted → return JSON-RPC error
4. Unmarshal mutated payload back into request params
5. Call next handler next(ctx, method, req)
6. Marshal response result to json.RawMessage
7. chain.Execute(ctx, {event, PhaseResponse, payload})
6. If the handler returns nil result (notification) → return
7. Marshal response result to json.RawMessage
8. chain.Execute(ctx, {event, PhaseResponse, payload})
→ For each interceptor: interceptor/invoke RPC via ClientSession
8. If aborted → return JSON-RPC error
9. Unmarshal mutated payload back into response result
10. Return result
9. If aborted → return JSON-RPC error
10. Unmarshal mutated payload back into response result
11. Return result
```

### JSON-RPC Payload Model

Interceptor handlers receive `json.RawMessage` as `inv.Payload` when
invoked via `interceptor/invoke`. This is the SEP-correct behavior —
payloads are JSON at the protocol level. Handlers unmarshal, inspect or
modify, and (for mutators) set the updated JSON back on `inv.Payload`:
modify, and (for mutators) return the updated JSON in
`MutationResult.Payload`:

```go
// Validator — unmarshal, inspect, return:
Expand Down Expand Up @@ -118,7 +122,9 @@ avoid intercepting lifecycle events:

### Intercepted

All JSON-RPC **method calls** routed through the server's receiving middleware:
All JSON-RPC messages routed through the server's receiving middleware
except the skipped methods above. The current integration is server-side
receiving middleware only.

| Method | Event |
|--------|-------|
Expand All @@ -130,8 +136,9 @@ All JSON-RPC **method calls** routed through the server's receiving middleware:
| `resources/list` | `EventResourcesList` |
| `resources/subscribe` | `EventResourcesSubscribe` |

Unknown methods pass through the middleware and are intercepted normally
(the JSON-RPC method name is used as the event name).
Custom methods registered with the Go SDK's receiving custom-method API
also pass through the middleware and are intercepted using the JSON-RPC
method name as the event name.

### Not Intercepted

Expand Down Expand Up @@ -175,9 +182,9 @@ Response-phase validators see the post-mutation payload.
### Mutator execution
- Mutators run sequentially, ordered by `PriorityHint.Resolve(phase)`
(ascending), with alphabetical name tiebreak.
- Each mutator receives `json.RawMessage`, unmarshals, modifies, and sets
the updated JSON back on `inv.Payload`. The chain passes the mutated
payload from each mutator to the next.
- Each mutator receives `json.RawMessage`, unmarshals, modifies, and
returns updated JSON in `MutationResult.Payload`. The chain passes the
returned payload from each mutator to the next.
- If any mutator fails (and is not `FailOpen`), the chain aborts.
- In `ModeAudit`, the mutated payload is not propagated to subsequent
interceptors.
Expand All @@ -198,7 +205,7 @@ Response-phase validators see the post-mutation payload.
|------|---------------|
| `interceptor.go` | Interceptor interface, enums (Phase, Mode, InterceptorType, Severity), Metadata/Compat/Hook, Validator/Mutator structs and handler types, Invocation/InvocationContext/Principal, result types (ValidationResult, MutationResult) |
| `priority.go` | Priority struct, NewPriority, Resolve, MarshalJSON, UnmarshalJSON |
| `wire.go` | JSON-RPC method/event constants, wire types (ListParams, ListResult, InvokeParams, InvokeResult, InterceptorInfo) |
| `wire.go` | Interceptor JSON-RPC method names, public event constants for common server methods, wire types (ListParams, ListResult, InvokeParams, InvokeResult, InterceptorInfo) |

### `interceptors/chain/` — chain orchestrator

Expand All @@ -212,8 +219,8 @@ Response-phase validators see the post-mutation payload.
| File | Responsibility |
|------|---------------|
| `server.go` | `Extension`, interceptor management, `Install`, `LocalChain`, capability declaration |
| `client.go` | `RegisterSendingMethods`, which registers interceptor custom methods on an MCP client before connecting |
| `rpc.go` | `handleList` and `handleInvoke` JSON-RPC method handlers |
| `events.go` | Event name constants for standard MCP methods |

### `interceptors/integrations/gomiddleware/` — middleware

Expand Down
6 changes: 3 additions & 3 deletions go/sdk/doc/PERFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Analysis of per-request costs and allocation patterns.

The interceptor chain follows the SEP execution model: each interceptor
is invoked via `interceptor/invoke` JSON-RPC calls through an MCP client
session. For in-process interceptors, `Server.LocalChain()` creates an
in-memory transport, so the JSON-RPC overhead is minimal (no network I/O).
session. For in-process interceptors, `Extension.LocalChain(ctx, server)`
creates an in-memory transport, so there is no network I/O.

Payloads are `json.RawMessage` at the protocol level. The middleware
marshals request params and response results once per phase, and
Expand Down Expand Up @@ -37,7 +37,7 @@ With interceptors active, each active phase incurs:
| 1 | Marshal request params / response result | 1 `json.RawMessage` | 1 marshal |
| 2 | `ChainExecutionResult` struct (with pre-allocated `Results` slice) | 1 struct + 1 slice | 0 |
| 3 | Per-interceptor `interceptor/invoke` RPC | 1 `InvokeParams` + 1 `InvokeResult` per interceptor | 1 marshal + 1 unmarshal per interceptor (handled by go-sdk JSON-RPC layer) |
| 4 | Validator execution (N=1 inline, N>1 goroutines) | 0 (N=1) / goroutines (N>1) | Handler-specific |
| 4 | Validator execution | 1 goroutine per validator | Handler-specific |
| 5 | Mutator payload threading | 0 | Payload passed as `json.RawMessage` between mutators |
| 6 | Unmarshal mutated payload back to params/result | 0 | 1 unmarshal (if mutated) |

Expand Down
4 changes: 3 additions & 1 deletion go/sdk/examples/mutator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ func main() {
// resources via interceptors/list and interceptor/invoke).
ext := extension.New()
ext.AddInterceptor(m)
ext.Install(mcpServer)
if err := ext.Install(mcpServer); err != nil {
log.Fatal(err)
}

// Create a chain connected via in-memory transport.
chain, err := ext.LocalChain(context.Background(), mcpServer)
Expand Down
4 changes: 3 additions & 1 deletion go/sdk/examples/validator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ func main() {
// resources via interceptors/list and interceptor/invoke).
ext := extension.New()
ext.AddInterceptor(v)
ext.Install(mcpServer)
if err := ext.Install(mcpServer); err != nil {
log.Fatal(err)
}

// Create a chain connected via in-memory transport.
chain, err := ext.LocalChain(context.Background(), mcpServer)
Expand Down
16 changes: 8 additions & 8 deletions go/sdk/go.mod
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
module github.com/modelcontextprotocol/ext-interceptors/go/sdk

go 1.24.0

toolchain go1.24.3
go 1.25.0

require (
github.com/modelcontextprotocol/go-sdk v1.4.0
github.com/modelcontextprotocol/go-sdk v1.7.0
github.com/stretchr/testify v1.11.1
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/jsonschema-go v0.4.2 // indirect
github.com/google/jsonschema-go v0.4.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/segmentio/asm v1.1.3 // indirect
github.com/segmentio/encoding v0.5.3 // indirect
github.com/segmentio/encoding v0.5.4 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/time v0.15.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
32 changes: 18 additions & 14 deletions go/sdk/go.sum
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/modelcontextprotocol/go-sdk v1.4.0 h1:u0kr8lbJc1oBcawK7Df+/ajNMpIDFE41OEPxdeTLOn8=
github.com/modelcontextprotocol/go-sdk v1.4.0/go.mod h1:Nxc2n+n/GdCebUaqCOhTetptS17SXXNu9IfNTaLDi1E=
github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0=
github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/modelcontextprotocol/go-sdk v1.7.0 h1:yqjY2dsbKAC0LSuWZVBMrHgiG8ukXv6NRo0JiALay44=
github.com/modelcontextprotocol/go-sdk v1.7.0/go.mod h1:dL7u98E/zjJTGzEq+j30jQ8K2k1mb6LeAH4inEcSGts=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
github.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQccx6w=
github.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0=
github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
Loading
Loading