Repository navigation
Description
Description
Current fastmcp server implementations, both high-level and low-level, present fundamental challenges for writing scalable and modular, multi-file applications.
The high-level fastmcp server relies on decorators like @mcp.tool(), @mcp.resource() etc. A prerequisite for these decorators is access to the main server instance (mcp). This design often forces developers to define all their primitives within a single monolithic file (e.g., server.py), which hinders modularity and makes it difficult to organize larger projects.
While the low-level server implementation allows for defining primitives in separate files, this approach introduces its own complexities. It requires manually importing the primitives for the list_<primitive> handlers and routing callbacks through a large if/else ladder or a match/case block (e.g call_tool handler). This is cumbersome and less elegant compared to the clean, decorator-based interface the high-level server provides.
There is no out of the box method or exposed interface to build large scale modularized mcp servers, and to add primitives from other files or sources where the mcp instance is not present involves patching the internals or utilizing some internal methods/managers manually.
This is why it's unergonomic
Usually mcp servers are often wrappers around existing systems to provide the system resources to a llm, hence for systems with extensive apis, could benefit from organization, ex: (users/ -> tools.py, resources.py etc, items/)
Maintainability, if there are a large number of developers working on a particular mcp server, smaller atomic and role specific files are easier to read, debug and maintain.
Consistency & Reusability, some primitives can have shared configs for a particular set of tools/resources like metadata, tags etc. These primitive groups can also be modularly reused based on versioning or application set features.
Here is how I envision how this might look like:
user_tools_manager = MCPPluginManager(name="user-tools")
def has_bot_user(ctx: MiddlewareContext[CallToolRequest]) -> bool:
return ctx.context.bot.user is not None
@user_tools_manager.register_tool
@user_tools_manager.check(has_bot_user) # predicate checks, can be control logic or access control
async def get_current_user(ctx: DiscordMCPContext) -> DiscordUser:
"""Get the current bot user."""
return DiscordUser.from_discord_user(ctx.bot.user)
@user_tools_manager.register_resource("resource://discord/user/{user_id}")
async def get_user_resource(ctx: DiscordMCPContext, user_id: str) -> DiscordUser:
"""Get a user resource by their ID."""
user = ctx.bot.get_user(int(user_id)) or await ctx.bot.fetch_user(int(user_id))
return DiscordUser.from_discord_user(user)
# Current completion/complete request handler requires a global handler, this could help with argument and callback specific handlers
@get_user_resource.autocomplete("user_id")
async def autocomplete_user_id(
ctx: DiscordMCPContext, ref: DiscordMCPResourceTemplate, query: str, context_args: dict[str, t.Any] | None = None
) -> list[str]:
"""Autocomplete user IDs."""
if not query:
return []
users = ctx.bot.users
return [str(user.id) for user in users if query.lower() in user.name.lower() or query in str(user.id)][:10]
@user_tools_manager.register_tool
@user_tools_manager.limit(RateLimitType.FIXED_WINDOW, rate=1, per=180) # possible ratelimit, or cooldown logic
async def get_latency(ctx: DiscordMCPContext) -> float:
"""Get the latency of the bot."""
latency = ctx.bot.latency * 1000
return latencyThese plugin managers or groups can be loaded and added to the respective primitive managers in the server when setting up the handlers.
This is working system we built when working on our own mcp server for discord, and the source code for the above can be found here, but the ability to do something similar natively out of the package would be a pretty nice option to have.
Plugin Manager- https://github.com/FallenDeity/discord-mcp/blob/master/src/discord_mcp/core/plugins/manager.py
Usage - https://github.com/FallenDeity/discord-mcp/blob/master/src/discord_mcp/core/discord_ext/user/tools.py
Server - https://github.com/FallenDeity/discord-mcp/blob/07dcc418aaa57cf22089c5db411e1c9c20063b77/src/discord_mcp/core/server/base.py#L819-L869
References
This already a common concept in quite a few popular frameworks like fastapi, django etc. which have the concept of routers which is largely used to create applications at scale
Activity
felixweinberger commentedon Oct 7, 2025
Ways to add features & enhancements like this have been discussed in a few forums, using this one as a place to consolidate discussion
Kludex commentedon Apr 5, 2026
I think it would be nice to have a namespace idea that we can build on top of.
But I agree that we should have something like "Router" is for Starlette.
HRV1527 commentedon Sep 4, 2026
@felixweinberger I want to work on this — planning to add an
MCPRouterclass mirroring FastAPI'sAPIRouter(register tools/resources/prompts on the router, thenmcp.include_router(router)to merge into the main MCPServer instance, with optional prefix support). Will open a PR referencing this issue once I have a working draft. Let me know if there's a preferred design direction before I start.maxisbey commentedon Sep 4, 2026
@HRV1527 I'd hold off on that. Something like this is quite a core design to the SDK and would be better left for maintainers of the repo.
Additionally, please have a read of our CONTRIBUTING guidelines before you spend much time on a PR as currently we don't have capacity to review community PRs very often, and as of right now PRs by people who aren't assigned to a given issue are auto-closed.
HRV1527 commentedon Sep 4, 2026
@maxisbey Thanks for the honest heads-up — it makes sense, and I appreciate you taking the time to explain the review capacity constraint rather than letting me sink hours into it. I'll hold off on this one.