meridian / _docs /MCP.md
GitHub Actions
Deploy to HuggingFace Spaces
8a428e5
|
Raw
History Blame Contribute Delete
12.1 kB

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.com account 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-remote is a bridge package (npm i -g mcp-remote) that adapts the HTTP MCP endpoint for Claude Desktop's stdio transport. Replace localhost:3000 with 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:

  1. Call list_categories β†’ pick the best category ID
  2. Call list_tags β†’ select 3-6 relevant tag IDs
  3. Call search_blogs with the post topic β†’ verify content doesn't already exist
  4. Call create_blog with status="draft" β†’ review the returned slug/ID
  5. Call get_blog with the slug β†’ verify the rendered content
  6. Call update_blog with status="published" β†’ go live

Security Notes

  • MCP_API_KEY should be kept secret and rotated if exposed
  • The admin@myblogs.com account 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_KEY in production (openssl rand -hex 24)