Skip to main content
Glama
nsmith
by nsmith

Markpost

An MCP server that takes Markdown content and syndicates it to Twitter/X, Threads, and a static blog on S3. Designed to be used by AI agents like Claude Code — write a post in Markdown, and Markpost handles the formatting and publishing for each platform.

What it does

  • Formats per platform — Strips Markdown to plain text for social media, renders full HTML for blog

  • Auto-splits into threads — Long content is split at --- separators or sentence boundaries, respecting each platform's character limit (280 for Twitter, 500 for Threads)

  • Previews before publishing — Dry-run tool shows exactly how content will be formatted and split

  • Syndicates everywhere at once — Publish to all platforms with a single tool call, or pick specific ones

Related MCP server: Markdown-To-Notion

Tools

Tool

Description

publish_post

Format and publish Markdown to one or more platforms

preview_post

Preview formatting and thread splits without publishing

ping

Health check

Quick start

1. Install

git clone <this-repo> && cd markpost
uv venv && uv pip install -e ".[dev]"

2. Configure API keys

Copy the example config and fill in your credentials:

mkdir -p ~/.markpost
cp config.example.toml ~/.markpost/config.toml

Edit ~/.markpost/config.toml:

[twitter]
consumer_key = "your-consumer-key"
consumer_secret = "your-consumer-secret"
access_token = "your-access-token"
access_token_secret = "your-access-token-secret"

[threads]
access_token = "your-long-lived-access-token"
user_id = "your-threads-user-id"

[blog]
s3_bucket = "my-blog-bucket"
s3_prefix = "posts/"
base_url = "https://blog.example.com"

[blog.aws]
region = "us-east-1"

You can also set a custom config path via the MARKPOST_CONFIG environment variable.

3. Connect to an MCP client

See the sections below for your specific client.

Running the server

stdio mode (default)

stdio is the standard transport for local MCP clients. The server reads JSON-RPC messages from stdin and writes responses to stdout.

uv run python src/markpost/server.py

Or using the FastMCP CLI:

uv run fastmcp run src/markpost/server.py

HTTP mode (remote / SSE)

For remote access or web-based clients, run in HTTP mode:

uv run fastmcp run src/markpost/server.py --transport http --host 0.0.0.0 --port 9000

The server will be available at http://localhost:9000. Clients connect via the Streamable HTTP transport.

Client setup

Claude Code

Register the server with the claude CLI:

claude mcp add markpost -- uv run --directory /path/to/markpost fastmcp run src/markpost/server.py

Verify it's registered:

claude mcp list

The publish_post and preview_post tools will now be available in Claude Code sessions.

Claude Desktop

Add to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):

{
  "mcpServers": {
    "markpost": {
      "command": "uv",
      "args": [
        "run",
        "--directory", "/path/to/markpost",
        "fastmcp", "run", "src/markpost/server.py"
      ]
    }
  }
}

Restart Claude Desktop after saving.

Other MCP clients (HTTP)

Start the server in HTTP mode (see above), then point your client at http://localhost:9000.

Getting API keys

Twitter/X

  1. Go to the Twitter Developer Portal

  2. Create a Project and App

  3. Set app permissions to Read and Write

  4. Generate your Consumer Key (API Key), Consumer Secret (API Secret), Access Token, and Access Token Secret

  5. If you changed permissions after generating tokens, regenerate them

The free tier allows 1,500 tweets/month.

Threads

  1. Go to the Meta Developer Dashboard

  2. Create a new app and add the Threads API product

  3. Your Threads account must be public

  4. Complete the OAuth flow to get an access token:

    • Authorize at https://threads.net/oauth/authorize?client_id={app_id}&redirect_uri={uri}&scope=threads_basic,threads_content_publish&response_type=code

    • Exchange the code for a short-lived token at https://graph.threads.net/oauth/access_token

    • Exchange for a long-lived token (60 days) at https://graph.threads.net/access_token?grant_type=th_exchange_token&client_secret={secret}&access_token={short_token}

  5. Get your user ID: GET https://graph.threads.net/v1.0/me?access_token={token}

S3 Blog

  1. Create an S3 bucket configured for static website hosting

  2. Configure your AWS credentials via the standard methods (~/.aws/credentials, environment variables, or IAM role)

  3. Set s3_bucket, s3_prefix, and base_url in your config

The base_url should be the public URL where your blog is served (e.g., your CloudFront distribution or S3 website endpoint).

Thread splitting

Long content is automatically split into threads. You control splits two ways:

Explicit separators — Use --- in your Markdown to force a split:

Here is the first tweet in my thread.

---

And here is the second one.

Auto-splitting — If any section exceeds the platform's character limit, it's split at sentence boundaries. If a single sentence is too long, it falls back to word boundaries.

The preview_post tool lets you see exactly how content will be split before publishing.

Development

# Install dev dependencies
uv pip install -e ".[dev]"

# Run tests
uv run pytest tests/ -v

# Run a specific test file
uv run pytest tests/test_formatter.py -v

Project structure

src/markpost/
  server.py              # FastMCP server — ping, publish_post, preview_post
  config.py              # TOML config loading
  formatter.py           # markdown_to_plain, split_into_thread, markdown_to_html
  publishers/
    twitter.py           # Twitter/X via tweepy
    threads.py           # Threads via httpx (async)
    blog.py              # S3 upload via boto3

License

Apache 2.0

Available Tools

3 tools
pingB

Health check tool.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

B3.1/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are present, so the description carries full responsibility for behavioral disclosure. It only states that the tool performs a health check, but does not explain what it returns, whether it makes network calls, what success/failure looks like, or any side effects. This leaves the agent with incomplete expectations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise: 'Health check tool.' It is front-loaded and contains no filler. While it could include a bit more context without harming conciseness, for a zero-parameter tool this brief phrase is appropriately sized and easy to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple health check tool with no parameters, the description provides the essential purpose, but it lacks details about expected output or behavior. The presence of an output schema helps, but the description alone does not tell the agent what a healthy or unhealthy result looks like. It is minimally adequate but not complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, and the schema is trivially fully covered. With no parameters, the baseline is 4, and there is nothing the description needs to add about parameter meaning. The description adds no parameter details, but none are required.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states 'Health check tool,' which clearly identifies the tool's function as a health/connectivity check. It is distinct from the sibling tools publish_post and preview_post, which involve different resources. The phrasing is a noun phrase rather than verb+resource, but it is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided about when to use this tool versus alternatives. The description does not mention contexts such as pre-flight checks, monitoring, or verifying service availability. The sibling tools are clearly different, but no explicit usage direction is provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

preview_postA

Preview how content will be formatted for each platform.

Returns the formatted text and thread splits without actually publishing. Use this to verify formatting before calling publish_post.

ParametersJSON Schema
NameRequiredDescriptionDefault
titleNoPost title (for blog preview)
contentYesMarkdown-formatted content to preview
platformsNoPlatforms to preview for: 'twitter', 'threads', 'blog'. Defaults to all.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full responsibility for behavioral disclosure. It does well by stating the tool is non-publishing and returns formatted text plus thread splits. It could mention whether any changes or side effects occur, but 'without actually publishing' covers the most important behavioral trait.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact, front-loaded with the core purpose, and adds only relevant operational details. Every sentence contributes either the scope, return behavior, or when to use the tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given an output schema exists (context signal) and full parameter schema coverage, the description is largely complete. It explains the purpose, return content, non-publishing nature, and recommended usage. Minor gaps like platform-specific behavior details are covered by the output schema or are unnecessary for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents all three parameters. The description adds context about content being markdown and platforms being selectable, but largely recaps what the schema states. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the tool's job: preview content formatting per platform. It explicitly contrasts with publishing via 'without actually publishing' and is distinct from sibling publish_post, making the tool's purpose unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Direct usage guidance is provided: 'Use this to verify formatting before calling publish_post.' This explicitly names the alternative and the appropriate sequence, telling an agent when to choose this tool rather than publish_post.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

publish_postA

Publish Markdown content to social media and/or a static blog.

Formats content appropriately for each platform:

  • Twitter/X: Converts to plain text, auto-splits into threads at 280 chars

  • Threads: Converts to plain text, auto-splits into threads at 500 chars

  • Blog: Renders full HTML and uploads to S3

ParametersJSON Schema
NameRequiredDescriptionDefault
slugNoURL slug for blog post (e.g. 'my-first-post')
titleNoPost title (used for blog HTML <title>)
contentYesMarkdown-formatted content to publish
platformsNoPlatforms to publish to: 'twitter', 'threads', 'blog'. Defaults to all.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral burden and does disclose meaningful traits: plain-text conversion, character-based auto-threading for Twitter/X and Threads, and HTML rendering with S3 upload for blog. It does not mention error/auth/rollback behavior, but the main platform-specific behavior is transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact, front-loaded with the core action, and uses a scannable bullet list for platform-specific details. There is no filler or repetition of schema fields.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a multi-platform publish tool with an output schema present, the description covers the key transformations and destinations well. The main completeness gap is the absence of any explicit note about previewing alternatives or side effects such as irreversible public posting, though these are partly covered by other dimensions.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, and the description adds real value beyond the schema by explaining the platform-specific consequences of the platforms parameter (280/500-char thread splitting, S3 upload). The slug and title parameters remain covered by schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise verb+resource ('Publish Markdown content to social media and/or a static blog') and then enumerates platform-specific output behaviors, so an agent immediately knows what the tool does. It does not explicitly name or contrast the sibling preview_post, but the publish-vs-preview distinction is clear from the wording.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is given on when to use this tool versus the sibling preview_post, and there are no conditions, exclusions, or caveats about when publishing would be inappropriate. The usage context must be inferred entirely from the word 'publish.'

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. Dates show when Glama detected each change.

  1. 3 tool updatesv0.1.0
    • First observedping
    • First observedpreview_post
    • First observedpublish_post

TDQS

A3.8/5.0
Disambiguation5/5

publish_post and preview_post are cleanly separated as actual publishing vs. dry-run preview, and ping is an obvious health check. No two tools overlap in purpose.

Naming Consistency4/5

publish_post and preview_post follow a consistent verb_noun pattern, but ping breaks that pattern. The naming is still readable and predictable overall.

Tool Count5/5

Three tools is a tight, well-scoped set: health check, preview, and publish. Each tool earns its place with no redundancy.

Completeness4/5

The preview-then-publish workflow covers the server's stated purpose well. Minor gaps like listing platforms or managing existing posts exist, but they fall outside the core publish/preview flow.

Maintenance

ActivityInactive
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nsmith/markpost'

If you have feedback or need assistance with the MCP directory API, please join our Discord server