Introduction
Hello! In our last lesson, we built a "gating" dependency, ensure_order_is_cancellable, to enforce a business rule on our cancel_order tool. We applied this dependency directly to the cancel_order endpoint, ensuring it runs only for that specific tool.
This brings us to a crucial architectural question: when should a pre-tool hook apply to just one tool, and when should it apply to all of them? Your experience with Rasa Pro likely highlights this distinction—some logic is specific to a single action, while other logic, like authentication, is universal.
In this lesson, we will explore this concept by differentiating between applying a dependency to a single FastAPI route versus applying it globally to all tools managed by an MCP server via AuthConfig. By the end, you'll be able to make informed decisions about where to place your pre-hook logic for maximum efficiency and security.
1. Understanding Dependency Scopes in FastAPI
Before we apply this to fastapi-mcp, it's essential to understand how FastAPI handles dependency scopes natively. A dependency can be applied at three main levels: the individual path operation, a router (a group of paths), or the entire application.
To get a solid overview of these scopes, please read the following article. It provides clear explanations and code examples for each level.
How to Implement Dependency Injection in FastAPI - freeCodeCamp
The article 'How to Implement Dependency Injection in FastAPI' from freeCodeCamp gives a great breakdown of dependency scopes. We'll focus on the section that explains the different levels at which you can apply dependencies.
Please read the section titled 'Dependency Scope', including its three subsections: 'Path Operation Level', 'Router Level', and 'Application Level'. This will give you the foundational knowledge of how FastAPI manages dependencies.
As a supplement, the following video offers a visual demonstration of applying dependencies at the path operation and global levels.
Fast API Tutorial, Part 25: Dependencies in path operation decorators, global dependencies
The video 'Fast API Tutorial, Part 25' by JVP Design provides a practical demonstration of path-level vs. global dependencies.
Watch from 05:11 to 11:21. The first part (until 08:50) shows how to add a dependency to the path operation decorator, which is an alternative to using Annotated. The second part (from 08:50 onwards) demonstrates setting up a global dependency on the main FastAPI app instance.
2. Scopes in the Context of fastapi-mcp
Now, let's translate this general FastAPI knowledge into our specific fastapi-mcp project. We have two primary ways to apply our pre-hooks:
A. Route-Specific Dependencies (For a Single Tool)
This is what we've been doing in the last two lessons. You apply a dependency directly to the FastAPI route that corresponds to your tool.
-
How:
- Function Signature (our current method): Using
Annotatedto inject the dependency and access its return value. This is ideal for validation that also transforms or passes data through.async def cancel_order(order_id: Annotated[str, Depends(ensure_order_is_cancellable)]): ... - Path Operation Decorator: Using the
dependenciesparameter in the decorator. This is best for "fire-and-forget" checks that don't need to return a value to the endpoint, like logging or simple permission checks.@app.post( "/orders/{order_id}/cancel", dependencies=[Depends(ensure_order_is_cancellable)], ... ) async def cancel_order(order_id: str): ...
- Function Signature (our current method): Using
-
When to Use: Use this for logic that is unique to a single tool. Our
ensure_order_is_cancellabledependency is a perfect example. It makes no sense to check if a customer profile is "cancellable," so this dependency belongs only to thecancel_ordertool.
B. Global Dependencies (For All Tools in an MCP Server)
This is for applying logic across an entire toolset. Instead of adding a dependency to every single route, fastapi-mcp lets you define it once in the AuthConfig.
You've already done this! The verify_auth dependency is a global dependency. It runs for every tool exposed by your MCP instance, ensuring no tool can be executed without a valid token.
To understand how fastapi-mcp handles this, let's look at its documentation.
Custom Auth Configuration | tadata-org/fastapi_mcp | Zread
The fastapi-mcp documentation explains its custom authentication and dependency configuration. We'll focus on the role of AuthConfig.
Read the subsections 'Custom Authentication Dependencies' and 'Authentication Configuration Options'. Notice how the dependencies parameter within AuthConfig is designed to run checks that raise HTTPException on failure, which is exactly how our verify_auth works.
-
How: By passing a list of
Dependsobjects to thedependenciesparameter ofAuthConfigwhen you instantiateFastApiMCP.mcp = FastApiMCP( app, # ... auth_config=AuthConfig( dependencies=[Depends(verify_auth)], # This applies to all tools ), ) -
When to Use: Use this for cross-cutting concerns that must apply to every tool call. Good examples include:
- Authentication: Is the user logged in? (Our
verify_authdoes this). - Authorization: Does the user have the basic role needed to use tools at all?
- Logging: Recording every tool execution for auditing.
- Billing/Rate-Limiting: Checking if the user has exceeded their API call quota.
- Authentication: Is the user logged in? (Our
3. Practical Exercise: Adding a Global Logging Dependency
Let's demonstrate the power of a global dependency by adding a simple logger that prints a message to your console every time any tool is called.
1. Create the Logging Dependency
In main.py, add a new, simple dependency function. It doesn't need to be async as it performs no I/O.
# main.py
# ... add this near your other dependencies
def log_tool_call(request: Request):
"""A simple dependency that logs which tool path was called."""
print(f"--- Tool call received for: {request.url.path} ---")
Note: We add request: Request as a parameter so FastAPI will pass the request object into our function, giving us access to useful information like the URL.
2. Apply it Globally
Now, modify your FastApiMCP instance to include this new dependency. It will run alongside verify_auth. FastAPI executes dependencies in the order they are listed.
# main.py
# ... update your FastApiMCP instance
mcp = FastApiMCP(
app,
name="Apxml API Services",
description="Tools for managing orders and customers.",
describe_all_responses=True,
describe_full_response_schema=True,
include_operations=[
"get_order_details",
"get_customer_profile",
"cancel_order",
],
auth_config=AuthConfig(
dependencies=[
Depends(verify_auth),
Depends(log_tool_call) # Add the new dependency here
],
),
)
3. Verify the Global Application
- Ensure your server is running (
uv run uvicorn main:app --reload). - Open the MCP Inspector, connect, and add your
Authorizationheader. - Execute the
get_order_detailstool withorder_id: ORD12345.- Check your terminal. You should see:
--- Tool call received for: /orders/ORD12345 ---
- Check your terminal. You should see:
- Now, execute the
get_customer_profiletool withcustomer_id: 101.- Check your terminal again. You should see:
--- Tool call received for: /customers/101 ---
- Check your terminal again. You should see:
- Finally, execute
cancel_orderwithorder_id: ORD999.- And again, in the terminal:
--- Tool call received for: /orders/ORD999/cancel ---
- And again, in the terminal:
As you can see, the log_tool_call dependency was executed for every tool without you having to add it to each endpoint individually.
Conclusion
In this lesson, you've learned the critical difference between applying dependencies at a route-specific versus a global scope within your fastapi-mcp server. This distinction is fundamental to building clean, secure, and maintainable AI toolsets.
Key Takeaways:
| Feature | Route-Specific Dependency | Global (AuthConfig) Dependency |
|---|---|---|
| Scope | A single tool (endpoint). | All tools within one FastApiMCP instance. |
| Implementation | Annotated[..., Depends(...)] in function signature or dependencies=[...] in the decorator. |
AuthConfig(dependencies=[...]) in FastApiMCP constructor. |
| Use Case | Logic unique to one tool, like specific input validation or business rules. | Cross-cutting concerns like authentication, logging, or basic authorization. |
| Example | ensure_order_is_cancellable (only for cancel_order). |
verify_auth and log_tool_call (for all tools). |
Choosing the right scope is an architectural decision. By correctly placing your validation and gating logic, you avoid repeating code and ensure that security checks are never accidentally forgotten.
Next Up: We have now completed our deep dive into pre-tool hooks. In the next module, we'll shift our focus to the other side of the tool call: post-tool patterns. You'll learn how to modify a tool's response before it goes to the model and, most excitingly, how to manage state between tool calls—a concept that directly mirrors the "slot setting" you're familiar with from Rasa.