Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 12

Route vs. Global Dependency Application

Recommended resources

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:

    1. Function Signature (our current method): Using Annotated to 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)]):
          ...
      
    2. Path Operation Decorator: Using the dependencies parameter 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):
          ...
      
  • When to Use: Use this for logic that is unique to a single tool. Our ensure_order_is_cancellable dependency is a perfect example. It makes no sense to check if a customer profile is "cancellable," so this dependency belongs only to the cancel_order tool.

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 Depends objects to the dependencies parameter of AuthConfig when you instantiate FastApiMCP.

    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_auth does 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.

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

  1. Ensure your server is running (uv run uvicorn main:app --reload).
  2. Open the MCP Inspector, connect, and add your Authorization header.
  3. Execute the get_order_details tool with order_id: ORD12345.
    • Check your terminal. You should see: --- Tool call received for: /orders/ORD12345 ---
  4. Now, execute the get_customer_profile tool with customer_id: 101.
    • Check your terminal again. You should see: --- Tool call received for: /customers/101 ---
  5. Finally, execute cancel_order with order_id: ORD999.
    • And again, in the terminal: --- Tool call received for: /orders/ORD999/cancel ---

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.