Skip to content

Instantly share code, notes, and snippets.

@skannan-maf
Last active June 18, 2025 19:27
Show Gist options
  • Save skannan-maf/3f42108ca9e85850229f33bfbf58289d to your computer and use it in GitHub Desktop.
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!
# 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"]
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()
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 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
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
mcp[cli]
httpx
fastapi
fastmcp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment