tamim-mcp-server
by TamimLikhon
README.md
# img2pdf
> Built by [Tamim](https://tamimlikhon.me) · [@tamimbuilds](https://x.com/tamimbuilds)
A local MCP (Model Context Protocol) server that converts images to PDF and PDFs to images. Supports
**any AI agent** via two transport modes:
- **Streamable HTTP** — for Cursor, Claude Code, and any HTTP-capable MCP client
- **stdio** — for Claude Desktop, VS Code MCP, Cline, Roo Code, Windsurf, Continue, and any stdio-spawning agent
## What's inside
| Tool | Description |
| ------------------------- | ----------------------------------------------------------- |
| `convert_images_to_pdf` | Convert local files, URLs, or base64 data into a single PDF |
| `convert_pdf_to_images` | Extract individual images from a PDF file |
| `get_server_time` | Sanity-check tool to confirm the connection is working |
## File structure
```
tamim-mcp-server/
├── src/
│ ├── server.ts # Fastify HTTP transport (Streamable HTTP)
│ ├── stdio-server.ts # stdio transport
│ ├── mcp.ts # createMcpServer() factory
│ ├── config.ts # env vars (port, token)
│ └── tools/
│ ├── index.ts # registerTools() - wires every tool in
│ ├── time.ts # get_server_time
│ └── imagesToPdf.ts # convert_images_to_pdf
├── .env.example
├── .gitignore
├── package.json
├── tsconfig.json
└── README.md
```
## 1. Install
```bash
npm install
cp .env.example .env
# open .env and set MCP_TOKEN (openssl rand -hex 24), or leave it blank for no auth
```
## 2. Run it
```bash
npm run dev # tsx watch, auto-reloads on save
# or, for a built version:
npm run build && npm start
```
You should see:
```
MCP server ready on http://127.0.0.1:8787/mcp
```
## 3. Test it before touching any client
```bash
curl -i -X POST http://127.0.0.1:8787/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
```
A `200 OK` with a `mcp-session-id` header back means the server is healthy.
Or use the official MCP Inspector for a UI instead of raw curl:
```bash
npx @modelcontextprotocol/inspector
# point it at http://127.0.0.1:8787/mcp
```
---
## 4. Connect any AI agent
### HTTP agents (server must already be running)
#### Cursor
`.cursor/mcp.json` (project root) or `~/.cursor/mcp.json` (global):
```json
{
"mcpServers": {
"tamim-local-mcp": {
"url": "http://127.0.0.1:8787/mcp",
"headers": { "Authorization": "Bearer YOUR_TOKEN_HERE" }
}
}
}
```
Omit the `headers` block entirely if you left `MCP_TOKEN` blank.
#### Claude Code
```bash
claude mcp add --transport http tamim-local-mcp http://127.0.0.1:8787/mcp \
--header "Authorization: Bearer YOUR_TOKEN_HERE"
```
---
### stdio agents (auto-spawned, no manual server start needed)
#### Claude Desktop
`claude_desktop_config.json`:
```json
{
"mcpServers": {
"tamim-local-mcp": {
"command": "npx",
"args": ["-y", "tamim-mcp-stdio"]
}
}
}
```
If you installed locally instead of publishing to npm:
```json
{
"mcpServers": {
"tamim-local-mcp": {
"command": "node",
"args": ["dist/stdio-server.js"],
"cwd": "/absolute/path/to/tamim-mcp-server"
}
}
}
```
#### VS Code / GitHub Copilot
`.vscode/mcp.json` (project root):
```json
{
"servers": {
"tamim-local-mcp": {
"command": "node",
"args": ["dist/stdio-server.js"],
"cwd": "${workspaceFolder}"
}
}
}
```
#### Cline / Roo Code
`~/.cline/mcp.json` or `~/.roo/mcp.json`:
```json
{
"mcpServers": {
"tamim-local-mcp": {
"command": "node",
"args": ["dist/stdio-server.js"],
"cwd": "/absolute/path/to/tamim-mcp-server"
}
}
}
```
#### Windsurf
`.windsurf/mcp.json`:
```json
{
"mcpServers": {
"tamim-local-mcp": {
"command": "node",
"args": ["dist/stdio-server.js"],
"cwd": "/absolute/path/to/tamim-mcp-server"
}
}
}
```
#### Continue
`.continue/config.yaml`:
```yaml
mcpServers:
- name: tamim-local-mcp
command: node
args:
- dist/stdio-server.js
cwd: /absolute/path/to/tamim-mcp-server
```
#### Generic stdio config (works with any MCP client)
```json
{
"command": "node",
"args": ["dist/stdio-server.js"],
"cwd": "/absolute/path/to/tamim-mcp-server"
}
```
---
## 5. Tool usage
### `convert_images_to_pdf`
Accepts images from three sources (at least one required):
```json
// Local file paths
{
"imagePaths": ["/path/to/photo1.jpg", "/path/to/photo2.png"],
"outputPath": "/path/to/output.pdf"
}
// URLs
{
"urls": ["https://example.com/image1.jpg", "https://example.com/image2.png"],
"outputPath": "/path/to/output.pdf"
}
// Base64 data
{
"base64Images": ["/9j/4AAQSkZJRg...", "iVBORw0KGgo..."],
"outputPath": "/path/to/output.pdf"
}
// Mix of all three
{
"imagePaths": ["/path/local.jpg"],
"urls": ["https://example.com/photo.png"],
"base64Images": ["iVBORw0KGgo..."],
"outputPath": "/path/to/output.pdf"
}
```
Options:
- `pageSize`: `"fit"` (default) — each page matches its image, or `"a4"` — centers on A4
- `marginPt`: margin in points when using A4 (default: 24)
### `convert_pdf_to_images`
Extracts individual pages from a PDF as images (JPEG or PNG). It creates a dedicated folder `[PDF_NAME]_to_images` within your specified output directory.
```json
{
"pdfPath": "/path/to/document.pdf",
"outputDir": "/path/to/downloads"
}
```
Options:
- `format`: `"jpeg"` (default) or `"png"`
- `scale`: Rendering scale factor (default: 2, where 1 = 72 DPI)
- `pageNumbers`: Array of specific 1-indexed pages to render (default: all pages)
- `quality`: JPEG quality 1-100 (default: 90)
### `get_server_time`
No parameters. Returns the current server time in ISO format.
---
## Keeping it running (HTTP mode)
HTTP-based MCP servers don't get auto-started by the client the way stdio
ones do - the server has to already be running when HTTP clients try
to connect. Options, easiest first:
- Leave `npm run dev` in a spare terminal tab
- `pm2 start dist/server.js --name mcp` to survive terminal closes and
auto-restart on crash
**stdio mode does not need this** — the agent spawns the process itself.
## Adding a new tool
1. Create `src/tools/yourTool.ts` exporting `registerYourTool(server)`
2. Call it from `src/tools/index.ts`
3. Keep the tool narrow - one job, a tight zod schema, a description that
tells the model exactly when to use it and what format inputs should be in
## Security notes
- The HTTP server binds to `127.0.0.1` only - not reachable from outside this
machine
- DNS rebinding protection is applied automatically by `@modelcontextprotocol/fastify`
- Set `MCP_TOKEN` to require a bearer token even for local HTTP calls
- stdio transport is inherently authenticated by process spawn — no token needed
- Keep total tools per server in the 6-10 range - Cursor caps out around 40
active tools across all connected servers combined
- [127.0.0.1:8787/mcp](http://127.0.0.1:8787/mcp)
- npx @modelcontextprotocol/inspector node dist/stdio-server.js
node dist/cli.js image.png
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues