Last active
June 18, 2025 19:27
-
-
Save skannan-maf/3f42108ca9e85850229f33bfbf58289d to your computer and use it in GitHub Desktop.
How to serve multiple MCP servers through a single app using different URL paths!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Use the official Python 3.10 image as the base image | |
FROM python:3.10 | |
# Set the working directory in the container | |
WORKDIR /app | |
# Install the "uv" package | |
RUN curl -Ls https://astral.sh/uv/install.sh | sh | |
# Initialize the mcp-server using "uv" | |
RUN /root/.local/bin/uv init mcp-server | |
# Change the working directory to mcp-server | |
WORKDIR /app/mcp-server | |
# Create venv | |
RUN /root/.local/bin/uv venv | |
# Activate venv | |
ENV VIRTUAL_ENV="/app/mcp-server/.venv" | |
ENV PATH="$VIRTUAL_ENV/bin:$PATH" | |
COPY requirements.txt . | |
# Install "mcp[cli]" and "httpx" packages using "uv" | |
RUN /root/.local/bin/uv add -r requirements.txt | |
# Copy source files to the mcp-server directory | |
COPY . . | |
# Expose the default port for the mcp-server | |
EXPOSE 8086 | |
# Default command to keep the container running | |
CMD ["python", "main.py"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from dotenv import load_dotenv | |
load_dotenv() | |
from mcp_singleton import MCPInstance | |
from starlette.applications import Starlette | |
from starlette.routing import Mount | |
import uvicorn | |
from starlette.routing import Mount, Router | |
USER_AGENT = 'multiple-mcp-servers-in-single-app-demo/0.0.1' | |
# | |
# Load all MCP tools | |
# NOTE: The "mcp" variable gets defined twice in the imports below but we wont use it. | |
# Instead, we will use the domain based Singleton class to retrieve them back in style | |
# | |
from mcp_math_server import * | |
from mcp_string_server import * | |
math_mcp = MCPInstance(domain='math').mcp | |
string_mcp = MCPInstance(domain='string').mcp | |
def main(): | |
app = Starlette( | |
routes=[ | |
Mount("/math", app=(math_mcp.sse_app())), | |
Mount("/string", app=(string_mcp.sse_app())), | |
] | |
) | |
uvicorn.run(app, host="0.0.0.0", port=8086) | |
if __name__ == "__main__": | |
main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from utils.mcp_singleton import MCPInstance | |
mcp = MCPInstance(domain='math').mcp | |
@mcp.tool() | |
def add(x, y): | |
''' | |
This function adds 2 of its arguments and returns the sum | |
Arguments x and y are expected to be numeric (either integer or floating point) | |
''' | |
return x+y | |
@mcp.tool() | |
def sub(x, y): | |
''' | |
This function subtracts the 2nd arg from first and returns the difference | |
Arguments x and y are expected to be numeric (either integer or floating point) | |
''' | |
return x-y | |
@mcp.tool() | |
def mul(x, y): | |
''' | |
This function multiplies both its arguments | |
Arguments x and y are expected to be numeric (either integer or floating point) | |
''' | |
return x*y | |
@mcp.tool() | |
def div(x, y): | |
''' | |
This function divides the first argument 'x' using the second argument 'y' | |
Arguments x and y are expected to be numeric (either integer or floating point) | |
''' | |
return x/y | |
@mcp.tool() | |
def idiv(x, y): | |
''' | |
This function divides the first argument 'x' using the second argument 'y' and returns the integer dividend | |
Arguments x and y are expected to be numeric (either integer or floating point) | |
''' | |
return x//y |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# This file provides an MCPInstance singleton object. | |
# This is a different type of Singleton because it allows a single object for each "domain" called in the constructor argument | |
# Just pass a "domain" argument while creating an instance and there will be a single MCPInstance object | |
# for that domain throughout your app. | |
# The MCPInstance object will have an "mcp" reference that can be mounted on a URL path in the app. | |
# This way, you can serve multiple MCP servers through a single server app using different URL paths | |
# | |
from mcp.server.fastmcp import FastMCP | |
class MCPInstance: | |
_domain_instance_map = {} # This is a class dictionary that holds 1 instance per domain | |
def __new__(cls, *args, **kwargs): | |
# 1 object for non-mcp-domains (ideally should never be used) | |
if 'domain' not in kwargs: | |
raise Exception("domain not specified. Please provide 'domain' in kwargs.") | |
domain = kwargs['domain'] | |
if domain in cls._domain_instance_map: | |
return cls._domain_instance_map[domain] | |
# New domain: Create object for first time | |
this_instance = super(MCPInstance, cls).__new__(cls) | |
this_instance._initialized = False | |
cls._domain_instance_map[domain] = this_instance | |
return this_instance | |
def __init__(self, domain): | |
if not self._initialized: | |
self.mcp = FastMCP(domain) # Earlier, we experimented with "root" argument. Havent characterized its behavior. For now, we are using the default. | |
self._initialized = True |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from mcp_singleton import MCPInstance | |
mcp = MCPInstance(domain='string').mcp | |
@mcp.tool() | |
def equal_nocase(x, y): | |
''' | |
This function compares 2 strings passed as arguments and returns True or False depending on whether they are same or not | |
Comparison is case insensitive | |
Arguments are expected to be strings | |
''' | |
return x.lower() == y.lower() | |
@mcp.tool() | |
def concat(x,y): | |
''' | |
Concats strings 'x' and 'y' together | |
Arguments expected to be strings | |
''' | |
return x+y |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
mcp[cli] | |
httpx | |
fastapi | |
fastmcp |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment