Skip to main content
Glama
AmirNaghibi

mcpserve

by AmirNaghibi
README.md
# mcpserve

Minimal, decorator-based framework for building [Model Context Protocol](https://modelcontextprotocol.io) (MCP) servers in Python.

**Zero required dependencies.** Define tools and resources with simple decorators, and mcpserve handles the JSON-RPC protocol, parameter inference, and stdio transport.

## Why?

MCP is the emerging standard for connecting AI assistants to external tools and data. But building an MCP server from scratch means implementing JSON-RPC 2.0 framing, capability negotiation, schema generation, and error handling. mcpserve handles all of that so you can focus on your tool logic.

- **Decorator-based** — `@server.tool()` and `@server.resource()` are all you need
- **Type inference** — parameter types and optionality are inferred from Python type hints
- **Async-ready** — supports both sync and async tool handlers
- **Zero dependencies** — stdlib only for the core library (asyncio + json)
- **MCP 2024-11-05** — implements the latest protocol version

## Architecture

```mermaid
graph LR
    subgraph "AI Assistant"
        A[LLM Client]
    end

    subgraph "mcpserve"
        B[Stdio Transport] --> C[JSON-RPC Router]
        C --> D[Method Dispatcher]
        D --> E[tools/list]
        D --> F[tools/call]
        D --> G[resources/list]
        D --> H[resources/read]
    end

    subgraph "Your Code"
        I["@server.tool()"]
        J["@server.resource()"]
    end

    A <-->|"stdin/stdout"| B
    F --> I
    H --> J
```

## Quick Start

```python
from mcpserve import Server

server = Server(name="my-tools", version="1.0.0")

@server.tool()
def add(a: int, b: int) -> str:
    """Add two numbers together."""
    return str(a + b)

@server.tool()
def search(query: str, limit: int = 10) -> str:
    """Search for documents matching a query."""
    # Your logic here
    return f"Found {limit} results for: {query}"

@server.resource(uri="status://health", name="Health Check")
def health():
    """Server health status."""
    return "ok"

if __name__ == "__main__":
    server.run()
```

That's it. Run your server, point an MCP client at it, and your tools are available to the AI.

## Installation

```bash
pip install mcpserve
```

Or from source:

```bash
git clone https://github.com/AmirNaghibi/mcpserve.git
cd mcpserve
pip install -e .
```

## Usage with Claude Desktop

Add to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "my-tools": {
      "command": "python",
      "args": ["/path/to/your/server.py"]
    }
  }
}
```

## Features

### Tool Registration

Parameters are automatically inferred from type hints:

```python
@server.tool()
def fetch_url(url: str, timeout: int = 30, follow_redirects: bool = True) -> str:
    """Fetch content from a URL."""
    # url: required string
    # timeout: optional integer, default 30
    # follow_redirects: optional boolean, default True
    ...
```

Supported types: `str`, `int`, `float`, `bool`, `list`, `dict`

### Async Tools

```python
@server.tool()
async def slow_operation(input: str) -> str:
    """An async tool that does something time-consuming."""
    await asyncio.sleep(1)
    return f"Processed: {input}"
```

### Resources

Expose data that the AI can read without calling a tool:

```python
@server.resource(uri="docs://readme", name="README", mime_type="text/markdown")
def readme():
    with open("README.md") as f:
        return f.read()
```

### Structured Results

Return rich results when plain text isn't enough:

```python
from mcpserve import ToolResult

@server.tool()
def query_db(sql: str) -> ToolResult:
    try:
        rows = db.execute(sql)
        return ToolResult.json(rows)
    except Exception as e:
        return ToolResult.error(f"Query failed: {e}")
```

### Custom Tool Names

```python
@server.tool(name="web_search", description="Search the internet")
def my_internal_function(query: str) -> str:
    ...
```

## Request Flow

```mermaid
sequenceDiagram
    participant Client as AI Client
    participant Transport as Stdio Transport
    participant Router as JSON-RPC Router
    participant Handler as Tool Handler

    Client->>Transport: {"jsonrpc":"2.0","id":1,"method":"initialize"}
    Transport->>Router: parse + validate
    Router-->>Transport: capabilities response
    Transport-->>Client: {"jsonrpc":"2.0","id":1,"result":{...}}

    Client->>Transport: {"method":"tools/call","params":{"name":"add","arguments":{"a":2,"b":3}}}
    Transport->>Router: parse
    Router->>Handler: dispatch(add, {a:2, b:3})
    Handler-->>Router: "5"
    Router-->>Transport: ToolResult
    Transport-->>Client: {"result":{"content":[{"type":"text","text":"5"}]}}
```

## API Reference

### `Server(name, version)`

Create a new MCP server.

### `@server.tool(name=None, description=None)`

Register a function as a tool. Name defaults to the function name, description defaults to the docstring.

### `@server.resource(uri, name=None, description=None, mime_type="text/plain")`

Register a function as a resource. The function is called when the client reads the resource URI.

### `ToolResult.text(str)` / `ToolResult.error(str)` / `ToolResult.json(data)`

Factory methods for creating tool results.

### `server.run()`

Start the server on stdio transport.

## Running Tests

```bash
pip install -e ".[dev]"
pytest tests/ -v
```

## License

MIT