Skip to main content
Glama
devaloi

mcpserve-py

by devaloi
README.md
# mcpserve-py

![CI](https://github.com/devaloi/mcpserve-py/actions/workflows/ci.yml/badge.svg)
![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)
![License: MIT](https://img.shields.io/badge/license-MIT-green)

A **Model Context Protocol (MCP)** server built in Python — exposes database query tools and document resources over JSON-RPC 2.0 stdio transport, enabling AI assistants to interact with SQLite databases and markdown documents.

## What is MCP?

The [Model Context Protocol](https://modelcontextprotocol.io/) is an open standard for connecting AI assistants to external tools and data sources. MCP servers expose **tools** (functions the AI can call) and **resources** (data the AI can read) over a JSON-RPC 2.0 transport.

This server implements the MCP protocol from scratch using raw JSON-RPC 2.0 over stdio — no SDK dependency required.

## Features

- šŸ”§ **8 tools** — database queries, document CRUD, search, date/time
- šŸ“„ **Resource providers** — documents and database schemas as readable resources
- šŸ›”ļø **SQL injection protection** — only SELECT queries allowed, with regex validation
- šŸ“ **YAML frontmatter** — documents stored as markdown with structured metadata
- šŸ”Œ **Stdio transport** — line-delimited JSON-RPC 2.0 over stdin/stdout
- ⚔ **Zero SDK dependency** — hand-rolled MCP protocol implementation
- āœ… **Well-tested** — 113 tests covering protocol, tools, resources, and integration

## Quick Start

```bash
# Clone and install
git clone https://github.com/devaloi/mcpserve-py.git
cd mcpserve-py
pip install -e ".[dev]"

# Run the server
python -m mcpserve_py

# Run tests
python -m pytest -v
```

### Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `MCPSERVE_DATA_DIR` | `data` | Directory for documents and data |
| `MCPSERVE_DB_PATH` | `data/mcpserve.db` | Path to SQLite database |
| `MCPSERVE_LOG_LEVEL` | `INFO` | Log level (DEBUG, INFO, WARNING, ERROR) |

## Claude Desktop Configuration

Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "mcpserve-py": {
      "command": "python",
      "args": ["-m", "mcpserve_py"],
      "env": {
        "MCPSERVE_DATA_DIR": "./data",
        "MCPSERVE_DB_PATH": "./data/mcpserve.db"
      }
    }
  }
}
```

## Tools

| Tool | Description | Parameters |
|------|-------------|------------|
| `query_database` | Execute read-only SQL query | `sql: str`, `params?: list` |
| `list_tables` | List all tables in database | — |
| `describe_table` | Get table schema | `table: str` |
| `create_document` | Create a markdown document | `title: str`, `content: str`, `tags?: list[str]` |
| `read_document` | Read document by title | `title: str` |
| `list_documents` | List all documents | `tag?: str` |
| `search_documents` | Full-text search across documents | `query: str` |
| `get_datetime` | Current date/time | `timezone?: str` |

## Resources

| URI Pattern | Description | MIME Type |
|-------------|-------------|-----------|
| `docs:///{title}` | Document content | `text/markdown` |
| `db:///schema` | Full database schema | `text/plain` |
| `db:///tables/{name}` | Single table schema | `text/plain` |

## Architecture

```
src/mcpserve_py/
ā”œā”€ā”€ __main__.py          # Entry point: python -m mcpserve_py
ā”œā”€ā”€ server.py            # MCP server: receive → dispatch → respond
ā”œā”€ā”€ protocol.py          # JSON-RPC 2.0 types and encoding
ā”œā”€ā”€ transport.py         # Stdio transport (line-delimited JSON)
ā”œā”€ā”€ config.py            # Pydantic settings
ā”œā”€ā”€ tools/
│   ā”œā”€ā”€ registry.py      # Tool registry
│   ā”œā”€ā”€ database.py      # SQLite tools (query, list_tables, describe)
│   ā”œā”€ā”€ documents.py     # Document tools (CRUD + search)
│   └── system.py        # System tools (get_datetime)
└── resources/
    ā”œā”€ā”€ provider.py      # Resource provider interface + registry
    ā”œā”€ā”€ documents.py     # Document resource provider
    └── database.py      # Database schema resource provider
```

### Design Decisions

- **No MCP SDK** — The protocol is implemented directly using JSON-RPC 2.0 dataclasses. This demonstrates deep understanding of the protocol rather than SDK usage.
- **Synchronous** — Stdio is inherently sequential; async adds complexity without benefit here.
- **Pydantic Settings** — Configuration via environment variables with type validation and `.env` file support.
- **Tool registry pattern** — Tools register themselves with a central registry, keeping the server dispatch clean.
- **Read-only SQL** — Mutations are rejected via regex before reaching SQLite, preventing data corruption by AI assistants.
- **YAML frontmatter** — Documents use the same format as static site generators (Jekyll, Hugo), making them human-readable and tool-friendly.

## Development

```bash
# Install with dev dependencies
pip install -e ".[dev]"

# Run tests
make test

# Lint
make lint

# Type check
make typecheck

# Format
make format

# All checks
make all
```

## License

MIT

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md). PRs welcome — run `make all` before submitting.

TDQS

A3.5/5.0

Scored across 8 tools

Disambiguation4/5

Most tools target clearly distinct resources and actions, so an agent can pick correctly with little hesitation. The only mild overlap is between list_documents (tag filter) and search_documents (content/title query), which descriptions do disentangle.

Naming Consistency5/5

Every tool follows a clean verb_noun snake_case pattern: query_database, list_tables, describe_table, create_document, read_document, list_documents, search_documents, get_datetime. No deviations or mixed conventions.

Tool Count5/5

Eight tools is well-scoped for a lightweight multi-purpose server covering database introspection, document management, and a datetime utility. Each tool earns its place with no redundancy.

Completeness3/5

Document handling lacks update and delete operations, and the database surface is read-only with no insert/update/delete, so lifecycle coverage is partial. The lone get_datetime tool also sits outside either domain, leaving a somewhat heterogeneous surface.