1. Conduid
  2. AI
  3. Aiohttp MCP
MCP server · AI

Aiohttp MCP

Tools for building Model Context Protocol (MCP) servers on top of aiohttp

Unclaimed MIT ai
59Fair

Scored 3 days ago · breakdown

About Aiohttp MCP

Aiohttp MCP is an MCP server published by kulapard in the AI category: tools for building Model Context Protocol (MCP) servers on top of aiohttp.

The repository has 8 stars and 3 forks, with the last commit 3 days ago. Recent commit activity is the strongest signal that reported issues will get attention.

Install

From source
git clone https://github.com/kulapard/aiohttp-mcp

This server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.

Ask AI

✦

Ask AI about Aiohttp MCP

Powered by Claude · Grounded in docs

I know everything about Aiohttp MCP. Ask me about installation, configuration, usage, or troubleshooting.

Security checks

Static checks derived from the repository 4 days ago. Heuristics on the source, not an audit.

  • ✓README presentThe repository ships documentation.
  • ✓License declaredUsable under a stated license.
  • ✓Tests presentA test suite exists in the repository.
  • !Dependencies pinnedNo lockfile. Installs can pull different code over time.
  • ✓No dynamic code executionSource scan found no eval, dynamic Function, or shell execution.
  • !Scoped permissionsDoesn't declare a permission scope. Assume it can do anything its process can.

Releases

0.7.00.7.0 · 15 Dec 2025What's Changed Potential fix for code scanning alert no. 4: Workflow does not contain permissions by @kulapard in https://github.com/kulapard/aiohttp-mcp/pull/37 [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in…
0.6.20.6.2 · 2 Dec 2025What's Changed [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in https://github.com/kulapard/aiohttp-mcp/pull/31, https://github.com/kulapard/aiohttp-mcp/pull/32, https://github.com/kulapard/aiohttp-mcp/pull/34,…
0.6.10.6.1 · 5 Nov 2025What's Changed Improve CI workflow: add Python 3.14 support and weekly scheduled runs by @kulapard in https://github.com/kulapard/aiohttp-mcp/pull/28 upd: dependencies by @kulapard in https://github.com/kulapard/aiohttp-mcp/pull/29 Full…
0.6.00.6.0 · 3 Nov 2025What's Changed Add request context examples, tests, and tool title parameter by @kulapard in https://github.com/kulapard/aiohttp-mcp/pull/27 Full Changelog: https://github.com/kulapard/aiohttp-mcp/compare/0.5.0...0.6.0
0.5.00.5.0 · 3 Nov 2025What's Changed add streamable http support by @kulapard in https://github.com/kulapard/aiohttp-mcp/pull/13 streamable http tests by @kulapard in https://github.com/kulapard/aiohttp-mcp/pull/14 add support for configurable transport mode to…

README

aiohttp-mcp

GitHub Actions Workflow Status codecov pre-commit.ci status PyPI - Version PyPI Downloads PyPI - Python Version GitHub License

Tools for building Model Context Protocol (MCP) servers on top of aiohttp.

Implements the MCP protocol natively — no heavy SDK dependencies. Only 3 runtime dependencies: aiohttp, aiohttp-sse, pydantic.

Features

  • Native MCP protocol implementation (supports specs 2025-11-25, 2025-06-18, 2025-03-26)
  • Streamable HTTP transport with SSE streaming
  • Easy integration with aiohttp web applications
  • Tool, Resource, and Prompt support with decorator-based registration
  • Shared state via ctx.app and per-request data via ctx.request
  • Stateless by default, with optional stateful mode for server push and resumability
  • Event store support for resumability
  • Async-first design with full type hints
  • JSON response mode for non-streaming deployments

Installation

With uv package manager:

uv add aiohttp-mcp

Or with pip:

pip install aiohttp-mcp

Quick Start

Basic Server Setup

Create a simple MCP server with a custom tool:

import datetime
from zoneinfo import ZoneInfo

from aiohttp import web

from aiohttp_mcp import AiohttpMCP, build_mcp_app

# Initialize MCP
mcp = AiohttpMCP()


# Define a tool
@mcp.tool()
def get_time(timezone: str) -> str:
    """Get the current time in the specified timezone."""
    tz = ZoneInfo(timezone)
    return datetime.datetime.now(tz).isoformat()


# Create and run the application
app = build_mcp_app(mcp, path="/mcp")
web.run_app(app)

Using as a Sub-Application

You can also use aiohttp-mcp as a sub-application in your existing aiohttp server:

import datetime
from zoneinfo import ZoneInfo

from aiohttp import web

from aiohttp_mcp import AiohttpMCP, setup_mcp_subapp

mcp = AiohttpMCP()


# Define a tool
@mcp.tool()
def get_time(timezone: str) -> str:
    """Get the current time in the specified timezone."""
    tz = ZoneInfo(timezone)
    return datetime.datetime.now(tz).isoformat()


# Create your main application
app = web.Application()

# Add MCP as a sub-application
setup_mcp_subapp(app, mcp, prefix="/mcp")

web.run_app(app)

Stateful Mode & Resumability

By default, the server runs in stateless mode — each request creates a fresh transport, making it safe for load-balanced and multi-instance deployments. Tool notifications (ctx.info()) work inline via SSE POST responses.

For single-instance deployments that need server-initiated push (via GET SSE stream) or SSE resumability, enable stateful mode. Session state and events are stored in-process memory — this is not suitable for multi-instance deployments without sticky sessions.

from aiohttp_mcp import AiohttpMCP, InMemoryEventStore, build_mcp_app

# Stateful with resumability (single instance only)
# If client disconnects, it can reconnect with Last-Event-ID to replay missed events
mcp = AiohttpMCP(event_store=InMemoryEventStore())
app = build_mcp_app(mcp, path="/mcp", stateless=False)

Note: InMemoryEventStore is in-process only. For multi-instance stateful deployments, implement a custom EventStore backed by shared storage (e.g., Redis) and use sticky sessions.

Context Access

There are 3 ways to access the MCP context inside tools. All return the same Context object:

1. get_current_context() — module function

from aiohttp_mcp import get_current_context

@mcp.tool()
async def my_tool(query: str) -> str:
    ctx = get_current_context()
    user_id = ctx.request.headers.get("X-User-ID", "anonymous")
    await ctx.info(f"Query by {user_id}")
    return f"Result for {user_id}"

2. mcp.get_context() — instance method

@mcp.tool()
async def my_tool(query: str) -> str:
    ctx = mcp.get_context()
    user_id = ctx.request.headers.get("X-User-ID", "anonymous")
    return f"Result for {user_id}"

3. ctx: Context — parameter injection

Declare ctx: Context as a parameter — it's auto-injected and excluded from the tool's input schema:

from aiohttp_mcp import Context

@mcp.tool()
async def my_tool(query: str, ctx: Context) -> str:
    user_id = ctx.request.headers.get("X-User-ID", "anonymous")
    return f"Result for {user_id}"

Context capabilities:

  • ctx.request — aiohttp Request (headers, cookies, client IP)
  • ctx.app — aiohttp Application for shared state (ctx.app["db_pool"])
  • ctx.request_id — JSON-RPC request ID
  • await ctx.info(msg) / debug() / warning() / error() — send log to client
  • await ctx.report_progress(progress, total) — report progress
  • await ctx.read_resource(uri) — read a registered resource

Shared state via ctx.app:

from collections.abc import AsyncIterator

from aiohttp import web

from aiohttp_mcp import AiohttpMCP, build_mcp_app, get_current_context

mcp = AiohttpMCP()


@mcp.tool()
async def secure_query(sql: str) -> str:
    """Run a database query with auth validation."""
    ctx = get_current_context()
    db_pool = ctx.app["db_pool"]
    return await db_pool.query(sql)


async def startup(app: web.Application) -> AsyncIterator[None]:
    app["db_pool"] = await create_db_pool()
    yield
    await app["db_pool"].close()


app = build_mcp_app(mcp, path="/mcp")
app.cleanup_ctx.append(startup)

Resource Composition

Tools can read registered resources during execution via ctx.read_resource(uri), avoiding logic duplication:

from aiohttp_mcp import AiohttpMCP, get_current_context

mcp = AiohttpMCP()


@mcp.resource("config://{service}")
async def get_config(service: str) -> str:
    """Service configuration exposed as a resource."""
    return load_config(service)


@mcp.tool()
async def deploy(service: str) -> str:
    """Deploy a service using its registered config."""
    ctx = get_current_context()
    config = await ctx.read_resource(f"config://{service}")
    return f"Deployed {service} with {config}"

This calls back into the resource registry using the same URI that MCP clients use — tools only need the URI, not a direct reference to the resource function.

Structured Return Types

Tools can return Pydantic BaseModel or dataclass instances — they are automatically serialized to JSON and generate outputSchema in tools/list responses:

import dataclasses
from pydantic import BaseModel
from aiohttp_mcp import AiohttpMCP

mcp = AiohttpMCP()


@dataclasses.dataclass
class UserData:
    name: str
    email: str
    age: int = 25


class UserResult(BaseModel):
    id: str
    name: str
    email: str


@mcp.tool()
async def create_user(data: UserData) -> UserResult:
    """Create a new user."""
    # Input: dataclass validated from dict/JSON automatically
    # Output: BaseModel serialized to JSON, outputSchema auto-generated
    return UserResult(id="123", name=data.name, email=data.email)

Plain types (str, dict, list) continue to serialize as before. outputSchema is generated for any return type annotation — BaseModel and dataclass return values additionally get proper JSON serialization via Pydantic's TypeAdapter instead of str().

Client Example

Here's how to create a client that interacts with the MCP server using the mcp client library:

import asyncio

from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client


async def main():
    # Connect to the MCP server
    async with streamablehttp_client("http://localhost:8080/mcp") as (
        read_stream,
        write_stream,
        _,
    ):
        async with ClientSession(read_stream, write_stream) as session:
            # Initialize the session
            await session.initialize()

            # List available tools
            tools = await session.list_tools()
            print("Available tools:", [tool.name for tool in tools.tools])

            # Call a tool
            result = await session.call_tool("get_time", {"timezone": "UTC"})
            print("Current time in UTC:", result.content)


if __name__ == "__main__":
    asyncio.run(main())

More Examples

For more examples, check the examples directory.

Development

Setup Development Environment

  1. Clone the repository:
git clone https://github.com/kulapard/aiohttp-mcp.git
cd aiohttp-mcp
  1. Create and activate a virtual environment:
uv venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate
  1. Install development dependencies:
uv sync --all-extras

Running Tests

uv run pytest

Requirements

  • Python 3.11 or higher
  • aiohttp >= 3.9.0, < 4.0.0
  • aiohttp-sse >= 2.2.0, < 3.0.0
  • pydantic >= 2.0.0, < 3.0.0

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

README fetched from the repository 4 days ago. The original is authoritative.

Questions

About Aiohttp MCP

How do I install Aiohttp MCP?

No package has been published for this server, so install it from source: clone the repository and follow its README.

Is Aiohttp MCP safe to use with an AI agent?

Its trust score is 59 out of 100 (fair). It passes 4 of the 6 static security checks run so far; the failures are listed above. It has no ConduID identity yet, so agent calls to it are not receipted.

Is Aiohttp MCP still maintained?

Yes — the last commit was 3 days ago. The repository has 8 stars and 3 forks and 6 open issues.