Skip to main content
Glama
farazmazhar

faraztools-mcp

Official
by farazmazhar

faraztools-mcp

A personal MCP server — the small utilities I reach for on a regular basis, exposed as tools any MCP-capable AI client can call.

Python uv MCP Python SDK protocol Ruff License

It runs locally over stdio: the host application launches it as a subprocess and speaks MCP over the process's stdin/stdout. No port, no daemon, no network surface.


Table of contents


Related MCP server: mini-mcp-demo

What this is

A home for the one-off scripts and helpers that keep getting rewritten. Instead of copying a snippet around, each utility becomes a tool with a name, a description, and a typed input schema — all of which the SDK derives from a plain, type-hinted Python function.

Written against v2 of the official MCP Python SDK. Two notes for anyone reading the code:

  • The high-level server class is MCPServer — v1's FastMCP, renamed. The import is from mcp.server import MCPServer.

  • Transport options (host, port, …) belong on mcp.run(), never on the constructor.

Tools

Tool

Description

echo

Example tool — echoes the given text back. Copy this shape when adding a real one.

markdown_to_pdf

Convert Markdown into a styled PDF: headings, emphasis, links, lists, blockquotes, syntax-highlighted code, tables, rules and local images. Takes markdown text or an input_path, writes to output_path, and accepts a theme preset (default, compact, academic) or a JSON theme file overriding keys such as margin_cm, accent, font_size_pt and heading_numbering.

This table grows as tools are added. See Adding a tool.

PDF rendering is done by Typst via the typst wheel — no system binaries, no headless browser, no LaTeX.

Requirements

  • uv — dependency and environment management

  • Python 3.12+

Quick start

git clone <this-repo> faraztools-mcp
cd faraztools-mcp
uv sync

That's it — uv sync creates .venv and installs everything from uv.lock.

Run

uv run faraztools-mcp
# or, equivalently
uv run python -m faraztools_mcp

Under stdio the server prints nothing and waits on stdin for a host to connect. A silent terminal means it is working, not hung.

Development

uv run pytest                 # tests (in-memory, no subprocess)
uv run ruff format .          # format
uv run ruff check . --fix     # lint
uv run pyright                # type check

uv run mcp dev src/faraztools_mcp/server.py --with-editable .   # MCP Inspector (needs npx)

--with-editable . is required for mcp dev: the Inspector runs the server in its own isolated environment and needs this package importable there.

Conventions and the full tool-adding recipe live in AGENTS.md.


Connect an AI client

The launch command

Every client below launches the same command. Clone this repo somewhere permanent and replace /path/to/faraztools-mcp with that location:

uv run --directory /path/to/faraztools-mcp faraztools-mcp

So the pieces to translate into each client's config format are always:

Field

Value

command

uv

args

run, --directory, /path/to/faraztools-mcp, faraztools-mcp

Tip: uv run re-syncs the environment on each launch (it's a no-op once in sync), so you never manage a virtualenv by hand. Use an absolute path — hosts don't launch the server from this repo.

Command Code

CLI:

cmd mcp add faraztools -- uv run --directory /path/to/faraztools-mcp faraztools-mcp

Or drop this in .mcp.json at your project root (cmd mcp add-json accepts the same object):

{
  "mcpServers": {
    "faraztools": {
      "transport": "stdio",
      "command": "uv",
      "args": ["run", "--directory", "/path/to/faraztools-mcp", "faraztools-mcp"]
    }
  }
}

Verify with /mcp in a session. Tools appear as mcp__faraztools__<tool>.

Claude Code

CLI (one line, -- separates the server command):

claude mcp add faraztools -- uv run --directory /path/to/faraztools-mcp faraztools-mcp

Or commit .mcp.json at your project root to share it:

{
  "mcpServers": {
    "faraztools": {
      "type": "stdio",
      "command": "uv",
      "args": ["run", "--directory", "/path/to/faraztools-mcp", "faraztools-mcp"]
    }
  }
}

Add --scope user to make it available across all projects. Check it with claude mcp list, or /mcp inside a session.

Claude Desktop

Edit claude_desktop_config.json:

OS

Path

macOS

~/Library/Application Support/Claude/claude_desktop_config.json

Windows

%APPDATA%\Claude\claude_desktop_config.json

Linux

~/.config/Claude/claude_desktop_config.json

{
  "mcpServers": {
    "faraztools": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/faraztools-mcp", "faraztools-mcp"]
    }
  }
}

Restart Claude Desktop afterwards. The uv binary must be resolvable — if not, use its full path (which uv).

Cursor

Project config at .cursor/mcp.json, or global at ~/.cursor/mcp.json:

{
  "mcpServers": {
    "faraztools": {
      "type": "stdio",
      "command": "uv",
      "args": ["run", "--directory", "/path/to/faraztools-mcp", "faraztools-mcp"]
    }
  }
}

Enable it under Settings → MCP, then check the MCP Logs output channel if it doesn't connect.

VS Code (GitHub Copilot)

Workspace config at .vscode/mcp.json — note VS Code uses a servers key, not mcpServers:

{
  "servers": {
    "faraztools": {
      "type": "stdio",
      "command": "uv",
      "args": ["run", "--directory", "/path/to/faraztools-mcp", "faraztools-mcp"]
    }
  }
}

Approve the trust prompt on first start. For a machine-wide setup, run MCP: Open User Configuration instead.

Codex

Add to ~/.codex/config.toml (or .codex/config.toml for a single trusted project):

[mcp_servers.faraztools]
command = "uv"
args = ["run", "--directory", "/path/to/faraztools-mcp", "faraztools-mcp"]

Or via the CLI:

codex mcp add faraztools -- uv run --directory /path/to/faraztools-mcp faraztools-mcp

codex mcp list shows configured servers; /mcp lists them in the TUI.

OpenCode

Add to opencode.json (project) or ~/.config/opencode/opencode.json (global). OpenCode's command is a single array holding the command and its arguments:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "faraztools": {
      "type": "local",
      "command": ["uv", "run", "--directory", "/path/to/faraztools-mcp", "faraztools-mcp"],
      "enabled": true
    }
  }
}

Windsurf

Edit mcp_config.json (open it from the Cascade panel's ...Open MCP config file):

OS

Path

macOS / Linux

~/.config/devin/mcp_config.json

Windows

%APPDATA%\devin\mcp_config.json

{
  "mcpServers": {
    "faraztools": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/faraztools-mcp", "faraztools-mcp"]
    }
  }
}

Zed

Add to your Zed settings.json — Zed's key is context_servers:

{
  "context_servers": {
    "faraztools": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/faraztools-mcp", "faraztools-mcp"]
    }
  }
}

Or use Settings → AI → MCP Servers → Add Server → Add Local Server.

Any other MCP client

If a client supports stdio MCP servers, it needs only the command and args from the launch command. Most use an mcpServers object with command + args; a few differ (VS Code uses servers, Zed uses context_servers, OpenCode uses mcp with an array command). Point the client at the launch command and it works.


Adding a tool

  1. Create src/faraztools_mcp/tools/<name>.py exposing a register(mcp) function:

    from mcp.server import MCPServer
    
    
    def register(mcp: MCPServer) -> None:
        @mcp.tool()
        def my_tool(arg: str) -> str:
            """One sentence describing what the model should use this for."""
            return arg
  2. Register the module in src/faraztools_mcp/tools/__init__.py (import it, add it to _MODULES).

  3. Add tests/test_<name>.py using the in-memory client pattern.

The docstring is the description the model sees and the type hints are the input schema — write both deliberately. Full details in AGENTS.md.

Layout

src/faraztools_mcp/
├── __init__.py        # re-exports `mcp`
├── server.py          # builds MCPServer, registers tools, `main()`
├── __main__.py        # `python -m faraztools_mcp`
├── md_to_pdf/         # Markdown -> PDF (Typst): theme, converter, renderer, template
└── tools/
    ├── __init__.py    # `_MODULES` registry + `register_all(mcp)`
    ├── echo.py        # example tool module
    └── md_to_pdf.py   # the markdown_to_pdf tool
tests/
├── conftest.py        # anyio_backend fixture
├── test_echo.py
└── test_md_to_pdf.py

License

MIT © Faraz Mazhar

Available Tools

2 tools
echoA

Echo the given text back to the caller.

ParametersJSON Schema
NameRequiredDescriptionDefault
textYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.1/5.0
Behavior4/5

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

With no annotations provided, the description carries the full behavioral burden. It clearly states the behavior: text is echoed back to the caller. It doesn't elaborate on edge cases like empty strings or exact formatting, but the operation is trivial and the description adequately discloses the core behavior.

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, front-loaded sentence with no wasted words. It conveys the purpose and behavior efficiently, making it an excellent model of conciseness.

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?

The tool has one required parameter, a trivial operation, and an output schema (not shown). The description gives all necessary calling context: provide text, receive the same text back. Nothing essential is missing, and the complexity is so low that additional detail would be unnecessary.

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 0%, so the description must compensate. It adds minimal meaning by referring to 'the given text', which essentially restates the parameter name. For a single, self-explanatory string parameter this is adequate, but it does not provide examples, constraints, or additional context beyond the schema.

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 'Echo the given text back to the caller' uses a specific verb ('echo') and identifies the resource ('the given text'). It is immediately clear what the tool does and can be distinguished from the sibling tool markdown_to_pdf simply by purpose, even without an explicit comparative mention.

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?

The description implies usage: if an agent needs to pass text through unchanged, this is the tool. However, it provides no explicit when-to-use guidance, no exclusions, and no mention of alternatives such as markdown_to_pdf. The intended use is self-evident but not articulated.

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

markdown_to_pdfA

Convert Markdown into a styled PDF file and return the path written.

Pass the document either as markdown text or as input_path pointing at a .md file (exactly one of the two). output_path is where the PDF is written.

theme is a preset name ("default", "compact", "academic") or a path to a JSON file overriding theme keys such as margin_cm, accent, font_size_pt or heading_numbering. title adds a centred title block above the content.

Supports headings, emphasis, links, lists, blockquotes, fenced code, tables, rules and local images (resolved relative to the input file). Remote image URLs are not fetched.

ParametersJSON Schema
NameRequiredDescriptionDefault
themeNodefault
titleNo
markdownNo
input_pathNo
output_pathYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior4/5

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

With no annotations, the description carries the behavioral burden. It discloses side effects (writing to output_path), input selection, theme overrides, supported Markdown features, and a key limitation (remote image URLs not fetched). It is slightly incomplete on overwrite and failure behavior, but strong overall.

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 front-loaded with purpose, then organized into input modes, theming, and feature support. Each sentence carries useful information without filler or redundancy.

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 five-parameter tool with an output schema and no annotation support, the description fully covers input constraints, parameter semantics, supported features, and limitations. The output schema can handle return details, so the note about returning the written path is sufficient.

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?

Schema coverage is 0%, so the description must explain all parameters. It covers every one: markdown vs input_path, output_path, theme presets and key overrides, and title. This adds essential meaning that the bare schema cannot provide.

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 first sentence states a specific action ('Convert Markdown into a styled PDF file') and result ('return the path written'), which clearly differentiates it from the echo sibling. No ambiguity about what the tool does.

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 gives concrete invocation rules: exactly one of markdown or input_path must be supplied, and output_path is required. It does not explicitly contrast this tool against alias echo, but it provides sufficient context for correct use.

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

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv0.1.0
    • First observedecho
    • First observedmarkdown_to_pdf

TDQS

A4.1/5.0

Scored across 2 tools

Disambiguation5/5

The two tools have completely different purposes: one echoes text, the other converts Markdown to PDF. There is no overlap or ambiguity between them.

Naming Consistency4/5

Both names are lowercase and readable, and 'markdown_to_pdf' uses snake_case while 'echo' is a simple verb. Though the pattern is not perfectly uniform, the names are clear and conventional.

Tool Count3/5

With only two tools, the server is on the thin side. The 'faraztools' name suggests a broader toolkit, but for such a minimal set, the count is borderline acceptable.

Completeness2/5

The tools are unrelated and the domain is unclear—there is no cohesive surface to assess. Beyond echo and Markdown-to-PDF conversion, many common utilities are missing relative to the generic server name.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Exposes a verified tool registry (calculator, sandboxed file read, web fetch) over MCP stdio, enabling any MCP-capable client to reuse the same tools from the inspectable ReAct loop.
    3
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables local tool calling over Model Context Protocol via stdio, providing deterministic tools such as calc.add, text.word_count, and text.summarize_naive after JSON-RPC handshake and discovery.
    MIT