-
Couldn't load subscription status.
- Fork 551
ToolFilters (like ASP.NET Core ActionFilters but for tools) #606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
ToolFilters are similar to ASP.NET Core ActionFilters. They offer three injection points: - OnToolListed: before a tool is listed, allowing the tool to be filtered - OnToolCalling: before a tool is called, allowing to return a response and bypass the tool - OnToolCalled: after a tool is called, allowing to change the response
|
|
||
| public override ValueTask<CallToolResult>? OnToolCalling(Tool tool, RequestContext<CallToolRequestParams> context) | ||
| { | ||
| _callCount++; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This technically has a race condition, and should be using Interlocked.Add
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I'll update the example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done ✅
| public interface IToolFilter | ||
| { | ||
| /// TODO: | ||
| public int Order { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be necessary, just like it is not necessary for aspnetcore filters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Order is defined on aspnetcore's ActionFilterAttribute.
It's not strictly necessary, but without it filters' implementors cannot define fine grained execution order.
You could not including it today, but I suppose that we would soon see issues created for "how can I make filter X always run before filter Y?"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I could remove the order from IToolFilter and let it on the ToolFilterAttribute?
- Non-attribute filters can be provided in
McpServerToolCreateOptions.Filters, which is an array. So the order can be explicited here. No need for an order property here. - Attribute filters are extracted using
method.GetCustomAttributes<>()(which as a non-guaranteed order), so the order property can be used to defining ordering among attribute-based filters.
| newOptions.Description ??= descAttr.Description; | ||
| } | ||
|
|
||
| var filters = method.GetCustomAttributes<ToolFilter>().OrderBy(f => f.Order).ToArray<IToolFilter>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't be compatible with aot compilation. That is likely a blocker.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How so? If you mean that the trimmer could have removed attribute annotations that's generally not the case. As long as you have safely looked up the MethodInfo you're guaranteed to have access to all its annotations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetCustomAttribute<> is already used (5 lines above my code 🙂).
That's how the tools metadata and description are extracted from [McpServerTool] and [Description].
+ add a few comments
Attributes cannot be extracted with a guaranteed order, so an Order property is useful for attribute-based filters, but not required for non-attribute based ones.
|
Hi! Thank you for taking the time to contribute this PR. Filters/middleware are an often-requested feature that we've already discussed a bit in #267. Long term, we would like to introduce a pipeline for all requests and notifications, not just tool calls. I think we need to consider the design of this entire pipeline before adding a tool-specific feature like this, but we don't want to design this feature by committee in a PR. If you're still interested in guiding the design of this feature, please follow #267 and feel free to provide any API suggestion there. Once we reach an agreement on the API shape, we'll figure out how to proceed with an implementation and who should do it. Thanks again! |
|
I'll have a look :) |
Motivation and Context
MCP uses HTTP, but mostly relies on streaming, with means several tool calls can happen within a single HTTP connection.
As such, we cannot easily rely on existing ASP.NET Core patterns (middleware/ActionFilters/...) to implement reusable behavior when a tool is called (logging custom metrics, performing custom authz checks, ...).
Taking inspiration on ActionFilters, this PR introduces
IToolFilter(and an abstractToolFilterAttribute) as a convenient way to inject custom behavior within a tool lifecycle.How Has This Been Tested?
ℹ️ I'm waiting for an approval on the design to write dedicated tests.
But an example of usage is available in the AspNetCoreSseServer sample.
Breaking Changes
No breaking change.
Types of changes
Checklist
Additional context
Cf #598