Skip to main content
Glama
farazmazhar

faraztools-mcp

Official
by farazmazhar
README.md
<div align="center">

# faraztools-mcp

**A personal [MCP](https://modelcontextprotocol.io) server — the small utilities I reach for on a regular basis, exposed as tools any MCP-capable AI client can call.**

[![Python](https://img.shields.io/badge/python-3.12%2B-3776AB?logo=python&logoColor=white)](https://www.python.org/)
[![uv](https://img.shields.io/badge/managed%20by-uv-DE5FE9?logo=astral&logoColor=white)](https://docs.astral.sh/uv/)
[![MCP Python SDK](https://img.shields.io/badge/MCP%20Python%20SDK-v2-1f6feb)](https://py.sdk.modelcontextprotocol.io/)
[![protocol](https://img.shields.io/badge/protocol-2026--07--28-8A2BE2)](https://modelcontextprotocol.io/specification/2026-07-28)
[![Ruff](https://img.shields.io/badge/lint-ruff-261230?logo=ruff&logoColor=white)](https://docs.astral.sh/ruff/)
[![License](https://img.shields.io/badge/license-MIT-2ea44f)](LICENSE)

</div>

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

- [What this is](#what-this-is)
- [Tools](#tools)
- [Requirements](#requirements)
- [Quick start](#quick-start)
- [Run](#run)
- [Development](#development)
- [Connect an AI client](#connect-an-ai-client)
  - [The launch command](#the-launch-command)
  - [Command Code](#command-code)
  - [Claude Code](#claude-code)
  - [Claude Desktop](#claude-desktop)
  - [Cursor](#cursor)
  - [VS Code (GitHub Copilot)](#vs-code-github-copilot)
  - [Codex](#codex)
  - [OpenCode](#opencode)
  - [Windsurf](#windsurf)
  - [Zed](#zed)
  - [Any other MCP client](#any-other-mcp-client)
- [Adding a tool](#adding-a-tool)
- [Layout](#layout)
- [License](#license)

---

## 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](#adding-a-tool).

PDF rendering is done by [Typst](https://typst.app/) via the `typst` wheel — no system
binaries, no headless browser, no LaTeX.

## Requirements

- [**uv**](https://docs.astral.sh/uv/) — dependency and environment management
- **Python 3.12+**

## Quick start

```bash
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

```bash
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

```bash
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](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:

```bash
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:

```bash
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):

```json
{
  "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):

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

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

```json
{
  "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` |

```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`:

```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`:

```json
{
  "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):

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

Or via the CLI:

```bash
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:

```json
{
  "$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` |

```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`:

```json
{
  "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](#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:

   ```python
   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](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](LICENSE) © Faraz Mazhar

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