Skip to main content

This is a HTTP implementation of the MCP protocol

Project description

Simple HTTP MCP Server Implementation

This project provides a lightweight server implementation for the Model Context Protocol (MCP) over HTTP. It allows you to expose Python functions as tools and prompts that can be discovered and executed remotely via a JSON-RPC interface. It is intended to be used with a Starlette or FastAPI application (see demo).

The following badge corresponds to the example server for this project. Find it in the tests/app/ folder.

Simple HTTP Server MCP server

Features

  • MCP Protocol Compliant: Implements the MCP specification for tool and prompts discovery and execution. No support for notifications.
  • HTTP and STDIO Transport: Uses HTTP (POST requests) or STDIO for communication.
  • Async Support: Built on Starlette or FastAPI for asynchronous request handling.
  • Type-Safe: Leverages Pydantic for robust data validation and serialization.
  • Server State Management: Access shared state through the lifespan context using the get_state_key method.
  • Request Access: Access the incoming request object from your tools and prompts.

Server Architecture

The library provides a single MCPServer class that uses lifespan to manage shared state across the entire application lifecycle.

MCPServer

The MCPServer is designed to work with Starlette's lifespan system for managing shared server state.

Key Characteristics:

  • Lifespan Based: Uses Starlette's lifespan events to initialize and manage shared server state
  • Application-Level State: State persists across the entire application lifecycle, not per-request
  • Flexible: Can be used with any custom context class stored in the lifespan state

Example Usage:

import contextlib
from collections.abc import AsyncIterator
from typing import TypedDict
from dataclasses import dataclass, field
from starlette.applications import Starlette
from http_mcp.server import MCPServer

@dataclass
class Context:
    call_count: int = 0
    user_preferences: dict = field(default_factory=dict)

class State(TypedDict):
    context: Context

@contextlib.asynccontextmanager
async def lifespan(_app: Starlette) -> AsyncIterator[State]:
    yield {"context": Context()}

mcp_server = MCPServer(
    name="my-server",
    version="1.0.0",
    tools=my_tools,
    prompts=my_prompts
)

app = Starlette(lifespan=lifespan)
app.mount("/mcp", mcp_server.app)

Tools

Tools are the functions that can be called by the client.

Example:

  1. Define the arguments and output for the tools:
# app/tools/models.py
from pydantic import BaseModel, Field

class GreetInput(BaseModel):
    question: str = Field(description="The question to answer")

class GreetOutput(BaseModel):
    answer: str = Field(description="The answer to the question")

# Note: the description on Field will be passed when listing the tools.
# Having a description is optional, but it's recommended to provide one.
  1. Define the tools:
# app/tools/tools.py
from http_mcp.types import Arguments

from app.tools.models import GreetInput, GreetOutput

def greet(args: Arguments[GreetInput]) -> GreetOutput:
    return GreetOutput(answer=f"Hello, {args.inputs.question}!")
# app/tools/__init__.py

from http_mcp.types import Tool
from app.tools.models import GreetInput, GreetOutput
from app.tools.tools import greet

TOOLS = (
    Tool(
        func=greet,
        inputs=GreetInput,
        output=GreetOutput,
    ),
)

__all__ = ["TOOLS"]
  1. Instantiate the server:
# app/main.py
from starlette.applications import Starlette
from http_mcp.server import MCPServer
from app.tools import TOOLS

mcp_server = MCPServer(tools=TOOLS, name="test", version="1.0.0")

app = Starlette()
app.mount(
    "/mcp",
    mcp_server.app,
)

Server State Management

The server uses Starlette's lifespan system to manage shared state across the entire application lifecycle. State is initialized when the application starts and persists until it shuts down. Context is accessed through the get_state_key method on the Arguments object.

Example:

  1. Define a context class:
from dataclasses import dataclass, field

# app/context.py

@dataclass
class Context:
    called_tools: list[str] = field(default_factory=list)

    def get_called_tools(self) -> list[str]:
        return self.called_tools

    def add_called_tool(self, tool_name: str) -> None:
        self.called_tools.append(tool_name)
  1. Set up the application with lifespan:
import contextlib
from collections.abc import AsyncIterator
from typing import TypedDict
from starlette.applications import Starlette
from app.context import Context
from http_mcp.server import MCPServer

class State(TypedDict):
    context: Context

@contextlib.asynccontextmanager
async def lifespan(_app: Starlette) -> AsyncIterator[State]:
    yield {"context": Context(called_tools=[])}

mcp_server = MCPServer(
    tools=TOOLS,
    name="test",
    version="1.0.0",
)

app = Starlette(lifespan=lifespan)
app.mount("/mcp", mcp_server.app)
  1. Access the context in your tools:
from pydantic import BaseModel, Field
from http_mcp.types import Arguments
from app.context import Context

class MyToolArguments(BaseModel):
    question: str = Field(description="The question to answer")

class MyToolOutput(BaseModel):
    answer: str = Field(description="The answer to the question")

async def my_tool(args: Arguments[MyToolArguments]) -> MyToolOutput:
    # Access the context from lifespan state
    context = args.get_state_key("context", Context)
    context.add_called_tool("my_tool")
    ...

    return MyToolOutput(answer=f"Hello, {args.inputs.question}!")

Request Access

You can access the incoming request object from your tools. The request object is passed to each tool call and can be used to access headers, cookies, and other request data (e.g. request.state, request.scope).

from pydantic import BaseModel, Field
from http_mcp.types import Arguments

class MyToolArguments(BaseModel):
    question: str = Field(description="The question to answer")

class MyToolOutput(BaseModel):
    answer: str = Field(description="The answer to the question")


async def my_tool(args: Arguments[MyToolArguments]) -> MyToolOutput:
    # Access the request
    auth_header = args.request.headers.get("Authorization")
    ...

    return MyToolOutput(answer=f"Hello, {args.inputs.question}!")

# Use MCPServer:
from http_mcp.server import MCPServer

mcp_server = MCPServer(
    name="my-server",
    version="1.0.0",
    tools=(my_tool,),
)

Prompts

You can add interactive templates that are invoked by user choice. Prompts now support lifespan state access, similar to tools.

  1. Define the arguments for the prompts:
from pydantic import BaseModel, Field

from http_mcp.mcp_types.content import TextContent
from http_mcp.mcp_types.prompts import PromptMessage
from http_mcp.types import Arguments, Prompt


class GetAdvice(BaseModel):
    topic: str = Field(description="The topic to get advice on")
    include_actionable_steps: bool = Field(
        description="Whether to include actionable steps in the advice", default=False
    )


def get_advice(args: Arguments[GetAdvice]) -> tuple[PromptMessage, ...]:
    """Get advice on a topic."""
    template = """
    You are a helpful assistant that can give advice on {topic}.
    """
    if args.inputs.include_actionable_steps:
        template += """
        The advice should include actionable steps.
        """
    return (
        PromptMessage(
            role="user",
            content=TextContent(
                text=template.format(topic=args.inputs.topic)
            ),
        ),
    )


PROMPTS = (
    Prompt(
        func=get_advice,
        arguments_type=GetAdvice,
    ),
)
  1. Using prompts with lifespan state:
from pydantic import BaseModel, Field
from http_mcp.mcp_types.content import TextContent
from http_mcp.mcp_types.prompts import PromptMessage
from http_mcp.types import Arguments, Prompt
from app.context import Context

class GetAdvice(BaseModel):
    topic: str = Field(description="The topic to get advice on")

def get_advice_with_context(args: Arguments[GetAdvice]) -> tuple[PromptMessage, ...]:
    """Get advice on a topic with context awareness."""
    # Access the context from lifespan state
    context = args.get_state_key("context", Context)
    called_tools = context.get_called_tools()
    template = """
    You are a helpful assistant that can give advice on {topic}.
    Previously called tools: {tools}
    """

    return (
        PromptMessage(
            role="user",
            content=TextContent(
                text=template.format(
                    topic=args.inputs.topic,
                    tools=", ".join(called_tools) if called_tools else "none"
                )
            )
        ),
    )

PROMPTS_WITH_CONTEXT = (
    Prompt(
        func=get_advice_with_context,
        arguments_type=GetAdvice,
    ),
)
  1. Instantiate the server:
from starlette.applications import Starlette

from app.prompts import PROMPTS
from http_mcp.server import MCPServer

app = Starlette()
mcp_server = MCPServer(tools=(), prompts=PROMPTS, name="test", version="1.0.0")

app.mount(
    "/mcp",
    mcp_server.app,
)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

http_mcp-0.4.2.tar.gz (477.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

http_mcp-0.4.2-py3-none-any.whl (18.1 kB view details)

Uploaded Python 3

File details

Details for the file http_mcp-0.4.2.tar.gz.

File metadata

  • Download URL: http_mcp-0.4.2.tar.gz
  • Upload date:
  • Size: 477.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.6.11

File hashes

Hashes for http_mcp-0.4.2.tar.gz
Algorithm Hash digest
SHA256 dd4bfeb00f883f019ad7e989bcdb4538f01aeeb18148114cde7b1ac2afa69ba1
MD5 965cb5aa59d0e8f73c11f35943e50164
BLAKE2b-256 b5cc71a50285bfc20d08876c10ecc0d8596669bd7c4b452c16b26677bc1145c2

See more details on using hashes here.

File details

Details for the file http_mcp-0.4.2-py3-none-any.whl.

File metadata

  • Download URL: http_mcp-0.4.2-py3-none-any.whl
  • Upload date:
  • Size: 18.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.6.11

File hashes

Hashes for http_mcp-0.4.2-py3-none-any.whl
Algorithm Hash digest
SHA256 4f3319f85bc11aa026db5a7ca5343973abbb9f9a503a50ce654d6691bc4fda5d
MD5 5f750ed5a2e265259dec145128824609
BLAKE2b-256 76f6c5c9070d568c3dcba3e7ac71104c8b412e3ea5727b4421e210b9e64b62dd

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page