faraztools-mcp
OfficialClick on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@faraztools-mcpconvert notes.md to a PDF with the academic theme"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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.
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'sFastMCP, renamed. The import isfrom mcp.server import MCPServer.Transport options (
host,port, …) belong onmcp.run(), never on the constructor.
Tools
Tool | Description |
| Example tool — echoes the given text back. Copy this shape when adding a real one. |
| Convert Markdown into a styled PDF: headings, emphasis, links, lists, blockquotes, syntax-highlighted code, tables, rules and local images. Takes |
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 syncThat's it — uv sync creates .venv and installs everything from uv.lock.
Run
uv run faraztools-mcp
# or, equivalently
uv run python -m faraztools_mcpUnder 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-mcpSo the pieces to translate into each client's config format are always:
Field | Value |
command |
|
args |
|
Tip:
uv runre-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-mcpOr 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-mcpOr 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 |
|
Windows |
|
Linux |
|
{
"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-mcpcodex 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 |
|
Windows |
|
{
"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
Create
src/faraztools_mcp/tools/<name>.pyexposing aregister(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 argRegister the module in
src/faraztools_mcp/tools/__init__.py(import it, add it to_MODULES).Add
tests/test_<name>.pyusing 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.pyLicense
MIT © Faraz Mazhar
Available Tools
2 toolsechoA
Echo the given text back to the caller.
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| theme | No | default | |
| title | No | ||
| markdown | No | ||
| input_path | No | ||
| output_path | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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.
2 tool updates
v0.1.0- First observed
echo - First observed
markdown_to_pdf
TDQS
Scored across 2 tools
The two tools have completely different purposes: one echoes text, the other converts Markdown to PDF. There is no overlap or ambiguity between them.
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.
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.
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
Related MCP Connectors
Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.
The OpenRouter for tools. One MCP connection gives any AI agent 254 hosted tools, pay per call.
Exposes FEDLIN's public security scanners as agent-callable tools over Streamable HTTP.
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
Related MCP Servers
- AlicenseAqualityCmaintenanceExposes 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.3MIT
- AlicenseNot gradedqualityCmaintenanceEnables 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
- AlicenseNot gradedqualityCmaintenanceEnables AI hosts to call deterministic tools such as echo, add, reverse, read/write files, and a C++-implemented two_sum via the MCP stdio interface.21 npmApache 2.0
- AlicenseNot gradedqualityCmaintenanceEnables registering custom agent tools via a minimal JSON-RPC over stdio implementation, with zero external dependencies, to expose them to AI agents like Claude, Cursor, and Gemini.MIT