Meridian MCP Server
Meridian exposes a Model Context Protocol (MCP) endpoint that lets external AI agents interact with the blogging platform β discovering content, publishing posts, and managing contributor accounts.
Transport
Streamable HTTP (MCP spec 2024-11-05) β stateless, JSON-RPC 2.0 over a single POST endpoint.
| Environment | MCP Endpoint URL |
|---|---|
| Local dev | http://localhost:3000/api/mcp |
| Docker (local) | http://localhost:7860/api/mcp |
| HuggingFace Spaces | https://mishrabp-myblogs.hf.space/api/mcp |
Authentication
All requests require an Authorization header carrying the MCP_API_KEY configured in the server's .env:
Authorization: Bearer <MCP_API_KEY>
Set MCP_API_KEY in .env (see .env.example). If MCP_API_KEY is unset the server accepts all requests β suitable only for local development.
Protocol Handshake
Before calling tools, clients must complete the initialize handshake:
// 1. Client β Server: initialize
POST /api/mcp
Content-Type: application/json
Authorization: Bearer <MCP_API_KEY>
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": { "name": "my-agent", "version": "1.0" }
}
}
// 2. Server β Client: capabilities
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": { "tools": {} },
"serverInfo": { "name": "meridian-mcp", "version": "1.0.0" }
}
}
// 3. Client β Server: initialized notification (no response expected)
{ "jsonrpc": "2.0", "method": "notifications/initialized" }
Available Tools
1. manage_guest_user
Create, update, or delete guest contributor accounts.
The
admin@myblogs.comaccount is permanently protected and cannot be touched by this tool.
| Parameter | Type | Required | Description |
|---|---|---|---|
action |
"create" | "update" | "delete" |
β | Operation to perform |
email |
string | β | User's email address |
name |
string | for create | Full display name |
password |
string | for create | Login password (min 8 chars) |
bio |
string | β | Short author bio |
is_active |
boolean | β | false suspends without deleting |
// Create a guest writer
{
"jsonrpc": "2.0", "id": 2,
"method": "tools/call",
"params": {
"name": "manage_guest_user",
"arguments": {
"action": "create",
"email": "alice@example.com",
"name": "Alice Smith",
"password": "securePass123",
"bio": "Tech writer and open-source enthusiast."
}
}
}
2. list_blogs
Retrieve published posts with optional filtering.
| Parameter | Type | Default | Description |
|---|---|---|---|
filter |
"latest" | "featured" | "recent" |
"latest" |
latest = newest-first paginated; featured = top 5 by views; recent = last 6 published |
category |
string | β | Filter by category slug (use list_categories) |
tag |
string | β | Filter by tag slug (use list_tags) |
page |
integer | 1 | Page number (latest only) |
limit |
integer | 10 | Results per page, max 50 (latest only) |
// Get the 5 most popular posts
{
"jsonrpc": "2.0", "id": 3,
"method": "tools/call",
"params": { "name": "list_blogs", "arguments": { "filter": "featured" } }
}
// Latest 10 posts in the "technology" category
{
"jsonrpc": "2.0", "id": 4,
"method": "tools/call",
"params": { "name": "list_blogs", "arguments": { "category": "technology", "limit": 10 } }
}
3. get_blog
Fetch the full content of a post by its URL slug.
| Parameter | Type | Required | Description |
|---|---|---|---|
slug |
string | β | URL slug (e.g. "understanding-react-hooks") |
summarize |
boolean | β | Generate a 2-3 sentence AI summary (requires OPENAI_API_KEY) |
{
"jsonrpc": "2.0", "id": 5,
"method": "tools/call",
"params": {
"name": "get_blog",
"arguments": { "slug": "understanding-react-hooks", "summarize": true }
}
}
4. search_blogs
Full-text keyword search across titles, excerpts, and body content (case-insensitive SQL ILIKE).
| Parameter | Type | Default | Description |
|---|---|---|---|
query |
string | β required | Search term (e.g. "machine learning") |
limit |
integer | 10 | Max results, max 50 |
include_content |
boolean | false | Append a 500-char body snippet to each result |
{
"jsonrpc": "2.0", "id": 6,
"method": "tools/call",
"params": {
"name": "search_blogs",
"arguments": { "query": "docker kubernetes", "limit": 5 }
}
}
5. create_blog
Publish a new blog post with rich HTML content.
| Parameter | Type | Required | Description |
|---|---|---|---|
title |
string | β | Post title (50-70 chars ideal) |
content |
string | β | Full HTML body β see supported tags below |
excerpt |
string | β | ~120-160 char summary for listing cards |
category_id |
integer | β | From list_categories |
tag_ids |
integer[] | β | From list_tags (3-6 tags recommended) |
featured_image |
string | β | Image URL (1200Γ630 px, Unsplash etc.) |
status |
"draft" | "published" |
"draft" |
Visibility |
author_name |
string | "MCP Agent" |
Display name shown on the post |
Supported HTML tags in content:
<h2>, <h3>, <h4> β Section headings
<p> β Paragraphs
<strong>, <em> β Bold, italic
<a href="..."> β Links
<ul>, <ol>, <li> β Lists
<blockquote> β Pull quotes
<img src="/spaces/mishrabp/meridian/resolve/main/_docs/..." alt="..."> β Inline images
<pre><code class="language-js">...</code></pre> β Syntax-highlighted code
{
"jsonrpc": "2.0", "id": 7,
"method": "tools/call",
"params": {
"name": "create_blog",
"arguments": {
"title": "Getting Started with Docker in 2026",
"excerpt": "A practical guide to containerising your first Node.js app β from zero to running in under 30 minutes.",
"content": "<h2>Introduction</h2><p>Docker simplifies deployment by packaging your app and its dependencies into a portable container...</p><h2>Prerequisites</h2><ul><li>Node.js 20+</li><li>Docker Desktop</li></ul><pre><code class=\"language-bash\">docker build -t myapp . && docker run -p 3000:3000 myapp</code></pre>",
"category_id": 1,
"tag_ids": [2, 5, 9],
"featured_image": "https://images.unsplash.com/photo-1605745341112-85968b19335b?w=1200",
"status": "published",
"author_name": "Alice Smith"
}
}
}
6. update_blog
Edit an existing post. Only supplied fields are updated.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | β | Numeric post ID |
title |
string | β | New title (regenerates slug) |
content |
string | β | Replacement HTML body |
excerpt |
string | β | Replacement excerpt |
category_id |
integer | null | β | New category, or null to remove |
tag_ids |
integer[] | β | Replaces entire tag list |
featured_image |
string | β | New image URL |
status |
"draft" | "published" |
β | Change visibility |
// Publish a draft and add a new tag
{
"jsonrpc": "2.0", "id": 8,
"method": "tools/call",
"params": {
"name": "update_blog",
"arguments": { "id": 42, "status": "published", "tag_ids": [2, 5, 9, 11] }
}
}
7. list_categories
Returns all categories with IDs, names, slugs, colors, and descriptions.
{
"jsonrpc": "2.0", "id": 9,
"method": "tools/call",
"params": { "name": "list_categories", "arguments": {} }
}
8. list_tags
Returns all tags with IDs, names, and slugs.
{
"jsonrpc": "2.0", "id": 10,
"method": "tools/call",
"params": { "name": "list_tags", "arguments": {} }
}
Discover Tools Programmatically
{
"jsonrpc": "2.0", "id": 2,
"method": "tools/list"
}
Returns the full schema for all 8 tools with detailed descriptions and JSON Schema input definitions.
Quick Test with curl
MCP_URL="http://localhost:3000/api/mcp"
MCP_KEY="your-mcp-api-key"
# Initialize
curl -s -X POST "$MCP_URL" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MCP_KEY" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}' \
| jq .
# List tools
curl -s -X POST "$MCP_URL" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MCP_KEY" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| jq '.result.tools[].name'
# Search posts
curl -s -X POST "$MCP_URL" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MCP_KEY" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_blogs","arguments":{"query":"javascript","limit":3}}}' \
| jq '.result.content[0].text'
Integrating with Claude Desktop
Add this to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS, %APPDATA%\Claude\claude_desktop_config.json on Windows):
{
"mcpServers": {
"meridian": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"http://localhost:3000/api/mcp",
"--header",
"Authorization: Bearer YOUR_MCP_API_KEY"
]
}
}
}
Note:
mcp-remoteis a bridge package (npm i -g mcp-remote) that adapts the HTTP MCP endpoint for Claude Desktop's stdio transport. Replacelocalhost:3000with your deployed URL for HF Spaces.
Integrating with Claude Code / Agent SDK
import anthropic
client = anthropic.Anthropic()
response = client.beta.messages.create(
model="claude-opus-4-8",
max_tokens=4096,
tools=[
{
"type": "custom",
"name": "meridian_mcp",
"description": "Meridian blogging platform tools",
# Use the MCP SDK to connect to the endpoint
}
],
messages=[{"role": "user", "content": "Find the most popular blog posts about React"}]
)
Or using the MCP Python SDK directly:
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async def run():
async with streamablehttp_client(
"http://localhost:3000/api/mcp",
headers={"Authorization": "Bearer YOUR_MCP_API_KEY"}
) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
# List available tools
tools = await session.list_tools()
print([t.name for t in tools.tools])
# Search for posts
result = await session.call_tool(
"search_blogs",
{"query": "javascript", "limit": 5}
)
print(result.content[0].text)
Install: pip install mcp
Recommended Agent Workflow
For best results when an agent is creating a blog post:
- Call
list_categoriesβ pick the best category ID - Call
list_tagsβ select 3-6 relevant tag IDs - Call
search_blogswith the post topic β verify content doesn't already exist - Call
create_blogwithstatus="draft"β review the returned slug/ID - Call
get_blogwith the slug β verify the rendered content - Call
update_blogwithstatus="published"β go live
Security Notes
MCP_API_KEYshould be kept secret and rotated if exposed- The
admin@myblogs.comaccount has a hard-coded guard in the server β it cannot be modified even if a valid key is provided - Write operations (create/update post, manage users) require the key; internal calls use a short-lived JWT signed with the shared service secret
- The MCP endpoint is public-facing β ensure a strong, random
MCP_API_KEYin production (openssl rand -hex 24)