Skip to main content
Glama
ahmetacn

HoPe Chatbot MCP Server

by ahmetacn
README.md
# HoPe Chatbot MCP Server

Uses [FastMCP](https://gofastmcp.com/getting-started/welcome) v3.

For code organization reasons MCP functionality is split into logical modules,
MCP sub-servers.

Main MCP server can import/mount sub-domain MCP servers. (`mcp_server/app.py`)


# Development

Define your capabilities separated to composable MCP servers.

Define new MCP tools, resources, prompts...
for `<some>` domain
for `<some>` business capability
in `<some>` MCP sub-server
in `mcp_server/servers/<some>/mcp.py`.


## Expose MCP server capabilities

**Decide** how you want your MCP tools, resources, prompts... to be available:

- in 1 combined MCP server (all-in-one) 🌐
- as separate MCP server only 🔒🪪
- both ✨


### All-in-one MCP server

Include sub-domain MCP server in combined MCP server.

Follow these steps in `mcp_server/app.py`:

- Import your FastMCP app

    ```py
    from mcp_server.servers.some import some_mcp
    ```

- Find main MCP server defined.
  Add capabilities of your MCP server into main MCP server
  in startup/setup lifespan phase of main MCP server

    ```py
    async def setup(app: FastMCP):
        # Import or mount subservers

        app.mount(some_mcp)  # dynamic mount
        # await app.import_server(some_mcp)  # static copy


    @contextlib.asynccontextmanager
    async def lifespan(app: FastMCP):
        # Setup
        await setup(app)
        yield
        # Cleanup

    main_mcp = FastMCP(
        name="home-of-performance-mcp-server",
        lifespan=lifespan
    )

- Main combined MCP server contains capabilities of your MCP server at: `BASE_URL/mcp`.

    It should be true for main combined MCP server ran in all cases:

    - `mcp_server/app.py` ran as a module
        - FastMCP.run method
    - `mcp_server/app.py` ran with uvicorn
        - `FastMCP.http_app`
            - ASGI app of your MCP server mount to FastAPI app, uvicorn accessing FastAPI ASGI app
            - (uvicorn accessing your ASGI directly will work too)


### Separate MCP server

Expose separate sub-domain MCP server.

Follow these steps in `mcp_server/app.py`:

- Import your FastMCP app

    ```py
    from mcp_server.servers.some import some_mcp
    ```

- Create ASGI app from your FastMCP app
  
  (no need to redefine `path="/mcp"` kwarg)

    ```py
    some_mcp_app = some_mcp.http_app()
    ```

- Add lifespan of your FastMCP app to combined lifespan used by FastAPI app

    ```py
    @contextlib.asynccontextmanager
    async def combined_lifespan(app: FastAPI):
        """Combine lifespans for FastAPI app"""
        # Run all lifespans
        async with main_mcp_app.lifespan(app):
            async with some_mcp_app.lifespan(app):
                # Setup
                yield
                # Cleanup

    fastapi_app = FastAPI(
        title="Home of Performance MCP",
        lifespan=combined_lifespan
    )
    ```

- Mount ASGI app of your MCP server to FastAPI app under `/some` path

    ```py
    fastapi_app.mount("/some", some_mcp_app)

    ```

- find your separate MCP server at: `BASE_URL/some/mcp`


## Running locally

- Define environment variables in `.env` according to `.env.template` (do not commit secrets)
- Install required python packages from `requirements.txt`
- Start FastMCP app through `run` method

  `python -m mcp_server.app`

  or run FastAPI app with MCP server(s) mount into it through gunicorn, uvicorn...

  `source .venv/bin/activate && uvicorn src.app:app --host 0.0.0.0 --port 8000 --reload`