blog-mcp
Click 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., "@blog-mcpshow me the 5 most recent posts"
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.
blog-mcp
MCP server exposing three tools against the posts table on db.w3hc.org:
Tool | Description |
| List recent posts, optionally filtered by slug prefix |
| Fetch the most recent post whose slug matches a prefix |
| Insert or update a post by slug |
No raw SQL, no delete: the surface area is intentionally limited to what's safe to expose over the internet.
Architecture
Claude --HTTPS--> reverse proxy (TLS) --HTTP, localhost--> this server --> PostgresThe server binds to 127.0.0.1 only. It is never directly reachable from the
internet — whatever already terminates TLS on the VPS (nginx, Caddy, ...) is
the sole public entry point. Auth is a single bearer token, checked with a
constant-time comparison. Transport is MCP's stateless Streamable HTTP: every
request builds a fresh in-memory server, handles the call, and tears it down.
Related MCP server: WordPress MCP
Local development
pnpm install
cp .env.example .envFill in .env:
DATABASE_URL— already-issued credentials, just append?sslmode=verify-full. Do not addsslrootcert=system: that's a libpq-only value (works withpsql) and makes node-postgres try to read a literal file namedsystem. Plainsslmode=verify-fullgives correct full chain + hostname verification under Node, using its bundled CA store.MCP_BEARER_TOKEN— generate withopenssl rand -hex 32.
pnpm start/pnpm dev load .env via Node's built-in --env-file flag
(Node 20.6+), so no dotenv dependency is needed — but .env must exist in
the working directory or the process exits immediately with a missing
variable error.
Run it:
pnpm build && pnpm start
# or, with reload on change:
pnpm devRunning the tests
pnpm testRuns the full suite via Node's built-in test runner (node:test, through
tsx --test). No database connection or network access is required — pg's
pool.query is mocked in test/db.test.ts and test/tools.test.ts.
Testing with curl
TOKEN=... # from .env
# Handshake
curl -s -X POST http://127.0.0.1:3939/mcp \
-H "Authorization: Bearer $TOKEN" \
-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-test","version":"1.0"}}}'
# List tools
curl -s -X POST http://127.0.0.1:3939/mcp \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
# Call a tool
curl -s -X POST http://127.0.0.1:3939/mcp \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"posts_list","arguments":{"limit":3}}}'Testing with MCP Inspector
npx @modelcontextprotocol/inspectorPoint it at http://127.0.0.1:3939/mcp, transport "Streamable HTTP", and set
the Authorization header to Bearer <your token> in its auth settings.
Deployment (Infomaniak Ubuntu VPS)
Ship the code. On the VPS:
sudo mkdir -p /opt/blog-mcp sudo useradd --system --home /opt/blog-mcp --shell /usr/sbin/nologin blog-mcpCopy the repo there (git clone, rsync, or CI), then:
cd /opt/blog-mcp pnpm install --prod=false # devDependencies needed for the build step pnpm build pnpm prune --prod # drop devDependencies after buildingWrite the real
.envat/opt/blog-mcp/.env(copy.env.example, fill in the realDATABASE_URLand a freshly generatedMCP_BEARER_TOKEN). Lock it down:sudo chown blog-mcp:blog-mcp /opt/blog-mcp/.env sudo chmod 600 /opt/blog-mcp/.env sudo chown -R blog-mcp:blog-mcp /opt/blog-mcpInstall the systemd unit:
sudo cp deploy/blog-mcp.service /etc/systemd/system/blog-mcp.service sudo systemctl daemon-reload sudo systemctl enable --now blog-mcp sudo systemctl status blog-mcpReverse proxy. Example nginx server block terminating TLS and forwarding to the local port (adjust the domain/cert paths to whatever already manages TLS on this VPS, e.g. certbot):
server { listen 443 ssl http2; server_name blog.mcp.w3hc.org; ssl_certificate /etc/letsencrypt/live/blog.mcp.w3hc.org/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/blog.mcp.w3hc.org/privkey.pem; location /mcp { proxy_pass http://127.0.0.1:3939/mcp; proxy_set_header Host $host; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_read_timeout 300s; } }Because
proxy_set_header Host $host;forwards the public hostname, setALLOWED_HOSTS=blog.mcp.w3hc.org(pluslocalhost,127.0.0.1,[::1]for local testing) in.env— otherwise the SDK's DNS-rebinding protection will 403 every proxied request. Restart the service after changing.env:sudo systemctl restart blog-mcpRegister the connector with your MCP client. The server is a standard MCP endpoint (Streamable HTTP, bearer auth) — it isn't tied to any one provider. See Connecting a client below for Claude, OpenAI, Mistral, and Qwen.
Connecting a client
This server speaks plain MCP over Streamable HTTP with a bearer token — no
Claude-specific behavior anywhere in src/. Any MCP-compatible client can
call it; the config shape just differs per provider.
Claude
Claude Desktop, Claude.ai, and Claude Code all support custom connectors:
add one with URL https://blog.mcp.w3hc.org/mcp and the bearer token from
.env.
OpenAI
The Responses API has a native mcp tool type:
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-5",
input="List the 5 most recent posts",
tools=[{
"type": "mcp",
"server_label": "blog-mcp",
"server_url": "https://blog.mcp.w3hc.org/mcp",
"headers": {"Authorization": "Bearer <MCP_BEARER_TOKEN>"},
"require_approval": "never",
}],
)Mistral
In Mistral Studio: Connectors → Add Connector → Custom MCP Connector,
then set the URL to https://blog.mcp.w3hc.org/mcp and add a static header
Authorization: Bearer <MCP_BEARER_TOKEN>. Mistral requires the token to
be entered in Studio itself — it doesn't support passing it
programmatically per request.
Qwen
Qwen Code supports remote MCP
servers over HTTP. In .qwen/settings.json:
{
"mcpServers": {
"blog-mcp": {
"httpUrl": "https://blog.mcp.w3hc.org/mcp",
"headers": { "Authorization": "Bearer <MCP_BEARER_TOKEN>" }
}
}
}or via the CLI:
qwen mcp add --transport http blog-mcp https://blog.mcp.w3hc.org/mcp \
--header "Authorization: Bearer <MCP_BEARER_TOKEN>"DeepSeek
DeepSeek's API (chat completions and Responses API) has no native remote-MCP
tool as of this writing — its Responses API explicitly ignores the mcp
tool type. To use this server from DeepSeek, put an MCP-aware host in
between: an agent framework (e.g. LangChain's MCP adapter) or a coding CLI
that owns the MCP connection while delegating generation to
deepseek-chat/deepseek-reasoner as the backend model. There's no direct
provider-to-server config to hand you here — check back as DeepSeek's API
evolves.
Operations
Logs:
journalctl -u blog-mcp -f. Each tool call logs one line (tool=posts_list result=ok duration=12ms, orresult=error:<category>with aref=<id>for unexpected errors, matching the id the client sees). Rejected tokens log at most one401line per minute.Startup check: the server runs
select 1 from posts limit 1before listening and exits with code 1 if it fails, logging a one-line reason with the password redacted, e.g.Database check failed (auth): password authentication failed for user "website". A bad.envtherefore shows up as a failed unit rather than as silently failing tool calls.Health:
curl -s 127.0.0.1:3939/healthon the VPS returns200 {"db":"ok"}or503 {"db":"error","reason":"auth|unreachable|timeout"}. It needs no token, and nginx only forwards/mcp, so it isn't public. It goes through the sameALLOWED_HOSTScheck as/mcp, so keep127.0.0.1in that list (or pass-H "Host: blog.mcp.w3hc.org").Restart:
sudo systemctl restart blog-mcpToken rotation: generate a new one (
openssl rand -hex 32), update.env, restart, update the connector config in Claude. No fixed cadence is enforced; rotate every few months or after any suspected exposure.Resource footprint: idle ~40-80 MB resident memory, negligible CPU. Load is a handful of requests per day, millisecond-scale CPU cost each — far lighter than the nightly
pg_dumpall/restic backup jobs already running on this box.
Security notes
Never add a raw-SQL or delete tool. If a new use case needs one, write a new narrow, purpose-built tool instead of widening an existing one.
The
websitePostgres role is reused as-is — no elevated grants, samepg_hba/connection-limit restrictions as everything else using it.The process never binds to a public interface; only the reverse proxy is internet-facing.
License
GPL-3.0
Contact
Julien Béranger (GitHub)
Element: @julienbrg:matrix.org
Farcaster: julien-
Telegram: @julienbrg
This server cannot be deployed
Maintenance
Related MCP Connectors
Create, edit, organize, publish, and configure JustBlogged blogs from MCP clients.
Read-only access to MojaLab.com blog content: search and read posts, pages and tags as Markdown.
Publish Markdown from any agent: POST Markdown, get a gorgeous shareable URL.
Durable, versioned memory for AI assistants. Remote memory needs a Pastepile subscription.
Related MCP Servers
- AlicenseNot gradedqualityFmaintenanceEnables interaction with WordPress sites through the REST API, supporting content management for posts, pages, users, plugins, and custom post types with Application Password authentication.1,842 npm2MIT
- AlicenseNot gradedqualityDmaintenanceEnables WordPress content management via the Model Context Protocol, allowing creation, update, search, and retrieval of posts, as well as fetching block type schemas for generating block-based content.54 npm10ISC

Postoria MCP Serverofficial
AlicenseAqualityCmaintenanceEnables MCP-compatible clients to create, schedule, queue, inspect, and delete Postoria posts through the Postoria Public API.1418 npm1MIT- FlicenseNot gradedqualityCmaintenanceMCP wrapper over the dustinedwards.info operator publish API, exposing five tools for managing blog posts (list, get, save, sync, delete) while containing no policy of its own.-