fastmcp icon indicating copy to clipboard operation
fastmcp copied to clipboard

feat(tool): add support for excluding arguments from tool definition

Open deepak-stratforge opened this issue 7 months ago • 5 comments

Summary

Adds support for an exclude_args parameter that allows specific arguments (such as state, memory, or credentials) to be omitted from the tool's parameter definition.

Motivation

While this is not currently used in MCP directly, it will be helpful for downstream use cases involving custom tool management and integrations where internal parameters should not be exposed or treated as part of the llm context.

Changes

  • Adds exclude_args support to Tool, ToolManager, FastMCP, and OpenAPITool
  • Updates parameter filtering logic
  • Adds tests to verify tool registration and execution still function correctly with exclusions
  • Tests Passes Pre-commit hooks

deepak-stratforge avatar May 29 '25 10:05 deepak-stratforge

@deepak-stratforge I don't understand the motivation here.

If you want to have a function that has extra arguments and a tool with reduced arguments, the following pattern will always work:

from fastmcp import FastMCP

mcp = FastMCP()

def my_function(x: int, y: str, z: bool):
    ...
    
@mcp.tool()
def my_tool(x: int):
    return my_function(x=x, y='foo', z=True)

I'm not seeing a strong reason for the framework to provide runtime shortcuts to this, it will only make parsing code more difficult

jlowin avatar May 29 '25 13:05 jlowin

Hi @jlowin ,

Thanks for your feedback. I understand wrapper functions can work, but exclude_args simplifies injecting dynamic or runtime arguments like state or credentials, which can't be hardcoded. This is similar to how LangGraph uses InjectedState to hide arguments from the LLM.

In my case, handling state and memory with MCP tools was tough. Passing metadata in the Context object isn't always easy, especially with LangChain integrations their adapters made simple tool requests complicated. exclude_args is useful for enabling simple callbacks with many MCP agents, but I'm unsure about all possible use cases. To keep things safe, we can ensure only arguments with default values can be added to exclude_args.

If you feel this isn't needed, feel free to close the PR.

deepak-stratforge avatar May 29 '25 14:05 deepak-stratforge

@deepak-stratforge would you mind saying more about the use case? My concern is that using exclude_args without default arguments leads to a broken MCP server in a way that can't be determined until runtime (and would probably require FastMCP to implement new nonstandard guards against a variety of edge cases). On the other hand, exclude_args with default arguments seems to provide no functionality different from either using global variables/classes or the wrap method I showed above. After all, the default value is what's going to be used in the function. So while this very clearly would permit two suboptimal outcomes (broken servers and unexpectedly missing arguments), I'm still not sure what unique functionality it enables.

Maybe if you could share more about the objective you have - is the problem that Context is hard? or dependency injection in general?

jlowin avatar May 29 '25 18:05 jlowin

@jlowin it’s for cases where we need to inject sensitive data like user_id at runtime without exposing it to the LLM, like langchains InjectedState https://python.langchain.com/docs/concepts/tools/#injectedstate Context is great for sessions but not for injectiting runtime args. also i dont think global variables or wrapper function can handle per-request runtime injection.

@mcp.tool(exclude_args=["user_id"])
def get_user_details_and_greet_him(user_id: str = None) -> str:
    if user_id:
    	return greet_user_with_details(user_id)
    return greet_user()
 mcp_server_connection_lifespan = ....
 
@app(....)
 active_listener():
 	agent ---------- (mcp_server_conn)
	 1. list_tool
	 2. llm_makes_tool_call schema
	 3. add user_id to the tool call args
	 4. return the tool call to llm

here, user_id is excluded from LLM schema and injected at runtime. only args with defaults are allowed, so server crashes would mostly not happen. also, it’s optional and only might be needed for some internal operations. image

deepak-stratforge avatar May 30 '25 07:05 deepak-stratforge

https://python.langchain.com/docs/concepts/tools/#injectedtoolarg

deepak-stratforge avatar May 30 '25 07:05 deepak-stratforge

@deepak-stratforge @jlowin,

+1 for this PR This exclude_args feature looks very promising.

Our project, MCP Plexus (a multi-tenant, OAuth-enabled MCP server framework built on your wonderful fastmcp), would significantly benefit from this addition.

Currently, our framework uses decorators (like @requires_auth and @requires_api_key) that inject arguments (e.g., authenticated HTTP clients, API keys) into tool functions at runtime. Without exclude_args, these server-injected parameters, even with default values, would appear in the tool schema presented to the LLM, which is not ideal.

This PR would allow us to:

  1. Clean up LLM tool schemas: By explicitly excluding these framework-injected arguments, we can present a more accurate and concise interface to the LLM.
  2. Adopt a standardized approach: It provides a fastmcp-native mechanism for managing arguments that are handled server-side, rather than by the LLM.

The requirement for default values on excluded arguments is a good safeguard. We see this as a valuable enhancement for fastmcp and would find it very useful in MCP Plexus.

Great work!

Super-I-Tech avatar Jun 01 '25 16:06 Super-I-Tech

@deepak-stratforge , @Super-I-Tech thank you -- I think I understand better now. Lets merge this PR and then I may add the ability to do this as a type annotation (similar to the linked LangChain approach) which would preserve a little more safety.

jlowin avatar Jun 01 '25 20:06 jlowin

I think another other approach you can use for this is middleware + context variables, but those are kind of a pain for newcomers

Sillocan avatar Jun 03 '25 01:06 Sillocan