microsoft / microsoft/agent-governance-toolkit
Introduce an IResponseSanitizer seam so MCP response sanitization can be replaced or extended
- Dominant language
- Python
- Stars
- 6.3k
- Forks
- 1.1k
- Avg merge
- 5d 11h
- Merged PRs (30d)
- 142
Description
## Summary
`McpResponseSanitizer` is `sealed` with no interface, and `McpGovernanceRuntime` depends on the concrete type. There's no supported way to replace or extend response sanitization with a custom implementation. Could the toolkit introduce an `IResponseSanitizer` abstraction that `McpGovernanceRuntime` depends on, resolved from DI?
Related: #3775 (async `PolicyEngine`) — same theme of making the toolkit's seams pluggable for custom/external backends.
## Current behavior (v5.0.0)
- `McpResponseSanitizer` is `public sealed`, exposing `McpSanitizedResponse ScanText(string text)`, with no interface.
- `McpGovernanceRuntime` takes the concrete `McpResponseSanitizer` and calls it in `Sanitize(...)`.
- `WithGovernance` registers it via `TryAddSingleton()`.
Because the dependency is the concrete sealed type, a consumer can't substitute their own sanitizer or run an additional one through the same pipeline.
## Why it's a problem
We're building a custom response sanitizer backed by a managed service that redacts additional categories (for example, a broad set of PII entity types) beyond what the built-in sanitizer covers (prompt-injection tags, imperative phrasing, exfiltration URLs, and credentials). With no seam, our only option is to layer a **second** `IPostConfigureOptions` that wraps every tool in another decorator, parallel to the toolkit's own. That works, but:
- It double-wraps tools and duplicates the decorator plumbing.
- Ordering between the two sanitizers is implicit.
- It can't cleanly *replace* the built-in sanitizer, only run beside it.
## Proposed solution
Introduce an interface the runtime depends on:
```csharp
public interface IResponseSanitizer
{
McpSanitizedResponse ScanText(string text);
}
```
- `McpResponseSanitizer` implements `IResponseSanitizer` (no behavior change).
- `McpGovernanceRuntime` depends on `IResponseSanitizer`; `WithGovernance` registers the default via `TryAddSingleton()`, so a consumer can replace it.
- Optionally support composition (an ordered set of `IResponseSanitizer`) so custom sanitizers run alongside the built-in one through a single pipeline.
## Alternatives considered
- **A parallel post-configure decorator** (current workaround): functional but double-wraps tools and can only layer beside the built-in sanitizer, not replace it.
## Willing to contribute
Happy to open a PR (interface extraction + DI registration, optionally the composition model) if the maintainers are open to it. Wanted to align on the shape first.
Contributor guide
Research direction
Start with McpResponseSanitizer, McpGovernanceRuntime, and the WithGovernance registration described in the issue. Trace how Sanitize is called and how the current singleton is resolved from DI, then determine whether replacement alone or ordered composition is in scope. Done means the runtime uses the abstraction, the default behavior remains unchanged, and consumers can replace the sanitizer through DI.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- backend-api-design, security
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100