Skip to main content
Glama

ft_mcp

A Model Context Protocol (MCP) server implemented from scratch in Python, without an MCP SDK.

Python License: MIT Tests

MCP is an open, JSON-RPC 2.0 protocol that lets an AI host — such as Claude Desktop or the MCP Inspector — discover and invoke tools exposed by an external server over a standard transport. This project implements the server side of that protocol directly: the stdio framing, the JSON-RPC routing, the lifecycle handshake, schema validation, and the tool layer.

The implementation deliberately avoids an MCP SDK so the protocol mechanics stay explicit rather than hidden behind a library. The core (stdio transport) depends only on the Python standard library; aiohttp is needed only for the optional HTTP/SSE transport.

Scope

  • Transports: stdio (primary); Streamable HTTP + SSE (optional).

  • Lifecycle: initialize / notifications/initialized with protocol-version negotiation and capability advertisement.

  • Tools: registry, tools/list, tools/call, JSON Schema (draft 2020-12) validation. Two reference tools: get_system_time and calculate_file_hash.

  • Additional primitives: prompts/list, prompts/get, resources/list, resources/read.

  • Diagnostics: structured logging and per-method timing, written to stderr only.

Supported protocol versions: 2025-11-25 and 2025-03-26.

Related MCP server: Developer Toolbox MCP

Requirements

Python 3.10 or newer. The stdio server needs nothing else.

Installation

git clone https://github.com/alsaeed3/ft_mcp.git
cd ft_mcp

pip install -e .            # core (stdio) only
pip install -e ".[http]"    # add the optional HTTP/SSE transport
pip install -e ".[dev]"     # add pytest

Installation is optional; the server also runs directly from the source tree with python -m ft_mcp.

Usage

The server reads JSON-RPC messages from stdin, writes responses to stdout, and logs to stderr.

python -m ft_mcp

A full session — handshake, tool discovery, and a tool call — driven by hand:

printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25"}}' \
  '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
  '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_system_time","arguments":{}}}' \
  | python -m ft_mcp

Connecting a host

MCP Inspector:

npx @modelcontextprotocol/inspector python -m ft_mcp

Claude Desktop — add the server to claude_desktop_config.json:

{
  "mcpServers": {
    "ft_mcp": {
      "command": "python",
      "args": ["-m", "ft_mcp"],
      "cwd": "/absolute/path/to/ft_mcp"
    }
  }
}

Architecture

The server is organized into three layers, kept independent of one another so each can be reasoned about and tested on its own. The same router serves both transports unchanged.

                 ┌──────────────────────────────────────────────┐
   stdin  ─────▶ │  Transport            (ft_mcp/transport)      │
                 │  newline-delimited framing; flush-after-write │
   stdout ◀───── │  stdout carries protocol bytes only          │
                 └───────────────────────┬──────────────────────┘
                                         │ one UTF-8 line per message
                                         ▼
                 ┌──────────────────────────────────────────────┐
                 │  JSON-RPC 2.0 router  (ft_mcp/router)         │
                 │  parse → classify → dispatch                  │
                 │  lifecycle gate; error taxonomy               │
                 └───────────────────────┬──────────────────────┘
                                         │ validated params
                                         ▼
                 ┌──────────────────────────────────────────────┐
                 │  Business logic       (ft_mcp/tools)          │
                 │  tool registry, schemas, prompts, resources   │
                 └──────────────────────────────────────────────┘

Two constraints shape the transport layer. First, stdout is the protocol channel, so it carries nothing but newline-terminated JSON-RPC messages, flushed after each write; all diagnostics go to stderr. Second, stdin is read without blocking the event loop, so a long-running tool does not stall message intake.

A more detailed description — the lifecycle state machine, message classification, the error taxonomy, and the security model — is in docs/ARCHITECTURE.md.

Tools

Tool

Arguments

Description

get_system_time

(none)

Returns the current UTC time as an ISO 8601 string. Its schema sets additionalProperties: false so callers are not led to invent arguments.

calculate_file_hash

path (string), algorithm (md5 | sha1 | sha256)

Streams a file in 64 KiB chunks and returns its hex digest. The path is canonicalized and confined to an allowed root.

Each tool declares a JSON Schema (draft 2020-12) inputSchema. The router validates tools/call arguments against it and returns -32602 on a mismatch before the handler runs.

Error handling

The server distinguishes protocol errors from tool errors, because a host treats them differently.

Category

Example

Response

Protocol

truncated JSON

error -32700, id: null

Protocol

missing "jsonrpc":"2.0"

error -32600, id: null

Protocol

unknown method or tool

error -32601

Protocol

schema-invalid params

error -32602

Protocol

unexpected handler exception

error -32603

Tool logic

hashing a missing file

success result with isError: true and an error message

Tool failures are returned as successful responses carrying isError: true rather than JSON-RPC errors, so the model receives the message as feedback. Application-defined codes (for example the HTTP transport's invalid-session -32000) stay outside the reserved -32768..-32000 range.

Security

Tool inputs are treated as untrusted. calculate_file_hash resolves paths with os.path.realpath (so symlinks are followed to their target before the containment check), performs a boundary-aware check against an allowed root, and opens the file with O_NOFOLLOW to avoid a time-of-check/time-of-use swap. The HTTP transport binds to loopback, validates the Origin header against loopback for browser clients (DNS-rebinding protection), and echoes only the validated origin in CORS headers.

Optional HTTP/SSE transport

pip install -e ".[http]"
python -m ft_mcp --http --host 127.0.0.1 --port 8080

GET /mcp/sse opens an event stream and announces a POST endpoint; POST /mcp/messages accepts JSON-RPC and correlates each reply back onto the matching SSE session.

Testing

pip install -e ".[dev]"
python -m pytest -q

The suite in tests/test_protocol.py runs the server as a subprocess and asserts its responses at the protocol boundary — parse and invalid-request errors, the initialize handshake, tools/list and tools/call, tool errors surfacing as isError, stdout purity, and clean shutdown on EOF.

Project layout

ft_mcp/
├── __main__.py            # entry point and handler wiring (stdio / --http)
├── log.py                 # stderr-only logger
├── transport/
│   ├── stdio.py           # async stdin reader, flush-after-write writer
│   └── http.py            # optional HTTP + SSE transport
├── router/
│   ├── dispatch.py        # JSON-RPC parse, classify, dispatch, validation
│   ├── lifecycle.py       # session state machine
│   └── errors.py          # error codes and helper
└── tools/
    ├── registry.py        # tool dataclass and registry
    ├── system_time.py     # get_system_time
    ├── file_hash.py       # calculate_file_hash
    ├── prompts.py         # prompts and resources
    └── telemetry.py       # per-method timing → stderr

tests/test_protocol.py     # protocol compliance suite

License

MIT

Available Tools

2 tools
calculate_file_hashA

Calculate the hash of a file using the specified algorithm

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesPath to the file to hash
algorithmYesHashing algorithm to use

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries full burden. It states the action but does not disclose any behavioral traits such as file size limits, permissions, side effects, or potential errors. However, the operation is simple and non-destructive, so the gap is moderate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence with no redundancy or wasted words. It is concise and front-loaded with the essential action.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has only 2 required parameters and no output schema or nested objects, the description is largely complete. However, it could mention the return format (e.g., hex string) for full clarity. Still, it is adequate for selection and invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% with each parameter described. The description adds minimal extra meaning beyond the schema, only tying 'specified algorithm' to the parameter. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'calculate', the resource 'file', and the result 'hash', with the specified algorithm. It is specific and distinct from the sibling tool 'get_system_time'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit guidance on when to use or prerequisites, but the context is straightforward and the sibling tool is unrelated, so the purpose is self-explanatory.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_system_timeA

Get the current system time in ISO 8601 format

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, so the description carries the burden. It states the output format, which is adequate for a simple read operation. No additional behavioral traits like idempotency or side effects need disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Single sentence, no wasted words. Perfectly concise.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter tool with a simple output, the description is complete. No output schema is needed; the format is specified.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, and the schema coverage is 100%. The description adds no parameter info because none exist.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb (Get) and the resource (current system time) with format specification (ISO 8601). It distinguishes from the sibling 'calculate_file_hash' which is unrelated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies when to use (when needing current time in ISO 8601). No explicit when-not or alternatives, but given the sibling is unrelated, this is sufficient.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

TDQS

A3.9/5.0
Disambiguation5/5

The two tools, file hashing and system time, are in completely different domains with no overlap, making them easily distinguishable.

Naming Consistency5/5

Both tools follow a clear verb_noun pattern with snake_case, providing a consistent naming convention.

Tool Count2/5

With only two unrelated tools, the server feels thin and lacks a cohesive purpose; a more focused set would be appropriate.

Completeness2/5

Each domain has only a single tool, missing common operations (e.g., different hash algorithms or time zone variants), resulting in an incomplete surface.

Maintenance

ActivityStale
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • -
    license
    Not graded
    quality
    Not graded
    maintenance
    A basic MCP server built with FastMCP framework that provides example tools including message echoing and server information retrieval. Supports both stdio and HTTP transports for integration with various MCP clients.
  • A
    license
    Not graded
    quality
    C
    maintenance
    A minimal MCP server providing tools for time, calculations, sandboxed file operations, and safe URL fetching over stateless HTTP.
    16
    ISC

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/alsaeed3/ft_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server