Skip to content

Trial Strategy: Multi-Server Plugin Discovery using @mcp.tool Decorators #230

@IanMayo

Description

@IanMayo

Trial Strategy: Multi-Server Plugin Discovery using @mcp.tool Decorators

Summary

Each plugin is implemented as a standalone FastMCP sub-server that defines one or more tools using the native @mcp.tool decorator.
At runtime, the main application walks the plugin folder tree, imports each sub-server, and composes them into a single aggregated FastMCP instance using main.import_server(...).

This approach keeps full FastMCP compatibility, preserves metadata supplied through decorator arguments, and supports modular composition for large tool libraries.

Design Goals

  • Allow each plugin to be fully self-contained, with its own FastMCP instance.
  • Support normal FastMCP decorator arguments (description, tags, etc.).
  • Automatically discover and import all plugin sub-servers at startup.
  • Preserve namespaces and prevent naming collisions through prefixes.
  • Keep the system compatible with manual or build-time registry generation if performance tuning becomes necessary later.

Example Folder Layout

toolvault/
  server.py
  plugins/
    geo/
      __init__.py
    filters/
      __init__.py
    acoustic/
      __init__.py

Example Plugin (plugins/geo/__init__.py)

from fastmcp import FastMCP

mcp = FastMCP(name="geo")

@mcp.tool(
    description="Set color to red for a given feature",
    tags=["geo", "style"]
)
def change_color_to_red(feature: dict) -> dict:
    f = dict(feature)
    f.setdefault("properties", {})["color"] = "red"
    return f

Each plugin can define multiple tools and resources on its own FastMCP instance.

Main Aggregating Server (server.py)

from fastmcp import FastMCP
import importlib, pkgutil

main = FastMCP(name="ToolVaultLike")

def import_plugins(root_pkg: str):
    """Walk a package tree and import any FastMCP subservers."""
    pkg = importlib.import_module(root_pkg)
    for m in pkgutil.walk_packages(pkg.__path__, prefix=f"{root_pkg}."):
        mod = importlib.import_module(m.name)
        sub = getattr(mod, "mcp", None)
        if sub:
            prefix = m.name.split(".")[-1]
            main.import_server(
                sub,
                prefix=prefix,
                on_duplicate_tools="error",
                on_duplicate_resources="ignore",
                on_duplicate_prompts="ignore",
            )

if __name__ == "__main__":
    import_plugins("plugins")
    main.run()

Behavior

  • Each discovered plugin is imported once.
  • All @mcp.tool metadata (e.g., description, tags) is preserved automatically.
  • Tool names are prefixed using their folder name, preventing collisions.
  • Tools, prompts, and resources are merged into the unified server.

Benefits

  • Full FastMCP compliance: Tools use the official @mcp.tool decorator and schema inference.
  • Zero coupling: Each plugin can be developed and tested as an independent server.
  • Automatic composition: No manual registry maintenance required during development.
  • Extensible: Registry generation or lazy-loading can be added later if startup becomes heavy.

Limitations

  • Every plugin module must be importable at startup (imports drive discovery).
  • Startup time scales with number of plugins; for large libraries, a build-time registry or lazy-load mechanism can later be introduced.
  • Duplicate tool names must be avoided or handled via prefix or duplicate-handling flags.

Future Enhancements

  • Registry generation: Build-time creation of a static import manifest for faster startup.
  • Lazy import: Generate lightweight proxy entries for large plugin sets.
  • Namespace management: Optionally group subservers by category or domain.
  • Hot-reload mode: File watcher to reload individual plugin servers without restarting the main process.

Acceptance Criteria

  • Each plugin defines its own FastMCP instance and uses @mcp.tool(...).
  • The main server dynamically imports all plugin subservers at startup.
  • All tool metadata is preserved and visible through /tools/list.
  • Duplicate names are prevented via prefixing.
  • The strategy runs successfully with multiple plugins in the plugins/ folder.

Labels: trial, architecture, fastmcp, plugin-system

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions