Skip to main content
Glama
conorbronsdon

Social PostLint-MCP

postlint-mcp

Check a social post against a platform's real character limit before it ships. X, Bluesky, LinkedIn, Threads, Mastodon, Discord. Pure compute — no API, no auth, no network.

npm version License: Apache 2.0 Node Glama score Podcast X

Recorded from docs/demo.tape with vhs. The posts and counts come from scripts/fixtures.mjs, which the regression tests import too.


An MCP server that answers one question: does this post fit?

A language model cannot count characters by inspection, and on these platforms neither can you. The limits are not what they look like. X bills every URL at 23 characters through t.co whether the link is 12 characters or 200. Bluesky counts extended grapheme clusters, so a four-person family emoji is 1 and not 11. Mastodon charges nothing for the domain on a remote mention. Getting any of that wrong shows up as a rejected post, or a truncated one, at publish time.

Counting is what a tool call is for. The model cannot do it by inspection, and a deterministic function can do it exactly.

Why this exists. Two posts went out of a podcast promo workflow over the limit. A Bluesky post shipped at 302 against 300, with the line "Under 300 graphemes. Audit clean." sitting directly beneath it. An X post was drafted at 308 against 280 and would have been rejected on launch morning. Both were invisible to eyeballing, because in both cases the count was a claim and not a measurement. Both are regression tests in this repo.

Tools

Tool

What it returns

check_post

Verdict for one platform: counted length, the limit, headroom, and what drove the count

check_post_all

One row per platform, with the breakdown attached only to the rows that fail

platform_limits

Each platform's limit, its counting unit, why that unit is not a character count, and the source

Responses are small on purpose. check_post_all omits the arithmetic on passing rows because agents pay tokens per response.

Related MCP server: MCP Character Tools

How each platform counts

Platform

Limit

Unit

The part that surprises people

Source

x

280

weighted characters

Every URL costs exactly 23. CJK, Hangul, and emoji cost 2 each; Latin, Greek, Cyrillic, Hebrew, and Arabic cost 1. An emoji sequence is one unit of 2, not 2 per code point.

twitter-text v3 config

x_premium

25,000

weighted characters

Same weighting, higher ceiling.

X help center

bluesky

300

graphemes

Flags, ZWJ emoji, skin-tone modifiers, and combining accents each count as 1. URLs count in full. A second cap of 3,000 UTF-8 bytes can bind first on ZWJ-heavy text.

atproto lexicon

linkedin

3,000

characters

The 3,000 is generous; the fold is the real constraint. The feed collapses the post behind "see more" after a few lines.

LinkedIn help

threads

500

characters

The September 2025 change added a 10,000-character attachment. The post body is still 500.

Meta newsroom

mastodon

500

graphemes

URLs cost 23, as on X. On @user@example.social only @user counts. The limit is per-instance and plenty of servers run higher.

Mastodon API docs

discord

2,000

characters

4,000 with Nitro. Embeds have a separate 6,000 total.

Discord support

Every number above traces to a published source. Widely repeated figures that no primary source states — the Facebook post limit, the YouTube community post limit, Reddit's title cap, Instagram's organic caption cap — are deliberately absent. A limit that cannot be defended makes a passing check worth nothing.

Setup

Published on npm. The config blocks below use npx, which fetches it on first run; no clone required.

git clone https://github.com/conorbronsdon/postlint-mcp.git
cd postlint-mcp
npm install
npm run build

Claude Code

Add to your .mcp.json:

{
  "mcpServers": {
    "postlint": {
      "command": "node",
      "args": ["/absolute/path/to/postlint-mcp/dist/index.js"]
    }
  }
}

Claude Desktop

Same block, in claude_desktop_config.json.

Codex

Add to ~/.codex/config.toml:

[mcp_servers.postlint]
command = "npx"
args = ["-y", "@conorbronsdon/postlint-mcp"]

No token, no environment variables, no network access. Once the package is published, npx -y @conorbronsdon/postlint-mcp replaces the node invocation everywhere above.

Verify

Ask your assistant: "Check this post for X and Bluesky," and paste something with a couple of links in it.

A worked example

The X post that started this, run through check_post with platform: "x":

{
  "platform": "x",
  "limit": 280,
  "unit": "weighted characters",
  "length": 308,
  "over": true,
  "remaining": -28,
  "drivers": [
    "3 URLs counted as 23 each = 69",
    "239 other characters counted as 1 each"
  ],
  "warnings": []
}

The drivers line is the useful part. 69 of the budget went to links before a word was written, which tells you to move two of them into a reply rather than trimming prose.

The same post through check_post_all:

{
  "fits": ["x_premium", "linkedin", "threads", "mastodon", "discord"],
  "over": ["x", "bluesky"],
  "rows": [
    { "platform": "x", "length": 308, "limit": 280, "over": true, "drivers": ["3 URLs counted as 23 each = 69", "239 other characters counted as 1 each"] },
    { "platform": "bluesky", "length": 330, "limit": 300, "over": true, "drivers": ["3 URLs counted in full = 91 (Bluesky does not shorten links)", "239 other graphemes"] },
    { "platform": "mastodon", "length": 308, "limit": 500, "over": false, "remaining": 192 }
  ]
}

One post, three different lengths — 308, 330, and 308 again — from the same 330 characters of text. That gap is the whole reason this exists.

Draft placeholders

Drafts carry link placeholders, and [URL] is five characters while a real link is not. A post measured with the placeholder in place and posted with the link filled in is a post measured wrong; one draft came in at 264 that way and posted at 282.

So [URL], [LINK], [YOUTUBE URL], [SUBSTACK URL], and similar are priced as a real link (a 28-character YouTube short link, the shortest thing normally posted) and the response carries a warning saying the count is a floor.

What it does not do

  • It does not post anything. There is no write path, no credential, and no network call of any kind. That last one is enforced rather than asserted: a test replaces fetch, XMLHttpRequest, and WebSocket with throws and drives every tool, so a call added later fails CI instead of quietly making this sentence false.

  • It does not check an instance's actual limit. Mastodon servers configure their own; this reports the 500 default and tells you to read configuration.statuses.max_characters from the target server yourself.

  • It does not truncate. A truncate_to helper was considered and left out. Cutting a post at a character offset splits URLs, breaks grapheme clusters, and lands mid-sentence, and cutting it at a "safe" boundary silently drops whichever clause happened to be last. Either way the tool would be deciding what the post says. It reports the number and leaves the edit to you.

  • It does not detect every URL a platform would. Links with a scheme and www.-prefixed hosts always match. A bare domain matches only on a common TLD (src/count.ts holds the list), where the real twitter-text implementation carries the full IANA registry. Write https:// in front of a link and the count is exact.

  • It does not count media, polls, quote posts, or link cards. Those have their own rules and this measures text.

  • It does not know about content warnings. On Mastodon a CW counts toward the same 500. This checks the body alone.

  • It does not carry limits it cannot source. See the platform table.

Development

npm install
npm run build
npm test

Tests make no network calls, because the server makes none. The two historical over-limit posts are regression fixtures in src/__tests__/lint.test.ts, alongside grapheme cases for ZWJ family emoji, regional-indicator flags, skin-tone modifiers, combining accents, and CJK.

Contributing

Issues and pull requests are welcome. A new platform needs three things: the limit, the unit it is measured in, and a published source. A new counting rule needs a test that fails without it. Numbers repeated by third parties are not sources.

About

Built and maintained by Conor Bronsdon. I host the Chain of Thought podcast, which covers AI infrastructure, developer tools, and how practitioners actually use this stuff. I built this after shipping two over-limit posts in a workflow that was supposed to catch them.

Companion tools:

  • op3-mcp: podcast analytics through OP3 — downloads, geography, apps, per-episode breakdowns.

  • podcastindex-mcp: the Podcast Index MCP server, search by person or topic, trending shows, feed health.

  • substack-mcp: read posts and manage drafts on Substack, safe for agent workflows.

  • Transistor-MCP: the Transistor.fm MCP server. Episodes, transcripts, download counts.

  • ai-tools-for-creators: a curated list of AI skills and MCP servers for people who ship ideas for a living.

More at chainofthought.show and on X.


Disclaimer

This is an independent personal project, not affiliated with, sponsored by, or endorsed by any company. All views expressed are my own.

License

Apache-2.0

Available Tools

3 tools
check_postA

Check a social post against one platform's real character limit and report whether it fits. Returns the counted length, the limit, and what drove the count (URLs billed at a fixed width, non-Latin characters billed double, emoji sequences collapsed to one grapheme). Use this before publishing anything with a hard limit — the counting rules are per-platform and are not text.length.

ParametersJSON Schema
NameRequiredDescriptionDefault
textYesThe post text, exactly as it will be published. Leading and trailing whitespace is trimmed, as the platforms do. `[URL]`-style placeholders are priced as a real link.
platformYesWhich platform's rules to apply: x, x_premium, bluesky, linkedin, threads, mastodon, discord.

TDQS

A4.4/5.0
Behavior5/5

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

With no annotations, the description carries the full burden and delivers: it reveals non-obvious counting rules (URLs at fixed width, non-Latin characters double, emoji as one grapheme) and warns that results differ from text.length. This gives the agent valuable insight into the tool's behavior beyond basic function.

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?

Three sentences, no fluff. The most important action is first, followed by return value details and a practical usage tip. Every sentence contributes.

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?

The description covers the tool's purpose, return value, counting rules, and usage context. It lacks exact output structure, but with no output schema, the high-level return description is adequate for an agent to understand what to expect. It's complete enough for most scenarios.

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?

The schema already covers 100% of parameters with meaningful descriptions (trimming, placeholder pricing, platform enum). The tool description adds little parameter-specific info beyond the schema, so the baseline of 3 applies.

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 opens with a specific verb and resource: 'Check a social post against one platform's real character limit and report whether it fits.' It clearly states the core function and differentiates itself from siblings by emphasizing 'one platform' (vs check_post_all) and the idea of limits (vs platform_limits).

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

Usage Guidelines4/5

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

It gives an explicit usage invitation: 'Use this before publishing anything with a hard limit.' However, it does not explicitly name alternatives or state when not to use it, though the sibling tool names (check_post_all, platform_limits) imply some distinctions.

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

check_post_allA

Check one post against every platform at once and get a row per platform: counted length, limit, and whether it fits. Use this when deciding where a draft can go as-is. The per-platform arithmetic is included only for the platforms it fails, to keep the response small — call check_post for a single platform's full breakdown.

ParametersJSON Schema
NameRequiredDescriptionDefault
textYesThe post text, exactly as it will be published. Leading and trailing whitespace is trimmed, as the platforms do. `[URL]`-style placeholders are priced as a real link.

TDQS

A4.5/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. It discloses a nontrivial behavior: 'per-platform arithmetic is included only for the platforms it fails, to keep the response small.' It does not cover auth, rate limits, or error cases, but the special response-size behavior is valuable and specific.

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?

Three tight sentences, front-loaded with the primary purpose, followed by a usage trigger and a behavioral caveat with an alternative. Every sentence earns its place and no filler exists.

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

Completeness5/5

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

For a single-parameter tool with no output schema, the description explains the purpose, the output row concept, the compact-response behavior, and the sibling alternative. It is sufficiently complete for an agent to select and invoke this tool correctly.

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% and the text parameter already has rich schema description (trimming, URL placeholders). The tool description itself adds only contextual meaning ('counted length, limit') rather than new parameter-level details, so the 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 opens with a specific verb+resource: 'Check one post against every platform at once' and clearly states the output shape ('a row per platform: counted length, limit, and whether it fits'). It distinguishes itself from the sibling check_post by noting that check_post gives a single platform's full breakdown.

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?

It explicitly says when to use this tool: 'when deciding where a draft can go as-is.' It also provides an alternative by directing users to 'call check_post for a single platform's full breakdown,' making the choice between siblings clear.

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

platform_limitsA

List the platforms this server knows, with each one's limit, the unit it is measured in, why that unit is not a plain character count, and the source the number came from. Use it to explain a result or to see what is covered.

ParametersJSON Schema
NameRequiredDescriptionDefault
platformNoLimit the response to one platform. Omit for all of them.

TDQS

A4.2/5.0
Behavior4/5

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

With no annotations provided, the description carries the burden of behavioral disclosure. It clearly states that the tool lists platforms with their limits, units, explanatory rationale, and sources, and frames it as a read-only informational operation. It does not explicitly mention side effects or authentication, but for a simple listing tool, this is adequate.

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 two sentences of appropriate length. The first sentence front-loads the action and output contents, while the second sentence adds practical usage guidance. Every clause contributes value, and there is no redundancy or filler.

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?

The tool is simple: one optional parameter, no output schema, and modest complexity. The description enumerates the output fields (platform, limit, unit, reason, source) and provides a use case. It could be slightly more explicit about the response format, but it is sufficiently complete for an agent to select and invoke the tool correctly.

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?

The input schema provides 100% coverage: the only parameter, 'platform', is fully described with an enum of allowed values and a clear description ('Limit the response to one platform. Omit for all of them.'). The description adds no additional parameter-level detail, but none is needed given the schema's completeness.

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 opens with the specific verb 'List' and clearly identifies the resource: platforms known to the server, along with their limits, units, rationale for non-character-count units, and data sources. This distinguishes it from sibling tools like check_post and check_post_all, which focus on post-length validation rather than enumerating platform limits.

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

Usage Guidelines4/5

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

The description explicitly states when to use the tool: 'Use it to explain a result or to see what is covered.' This provides clear context. However, it does not explicitly mention when not to use it or alternative tools, so it stops short of a full 5.

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.

  1. 3 tool updatesv0.1.0
    • First observedcheck_post
    • First observedcheck_post_all
    • First observedplatform_limits

TDQS

A4.4/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a distinct purpose: check_post for a single platform's detailed breakdown, check_post_all for a summary across platforms, and platform_limits for reference data. There is no ambiguity between them, as the scope and output format are clearly differentiated.

Naming Consistency4/5

The main actions follow a verb_noun pattern: check_post and check_post_all. The third tool, platform_limits, is a noun phrase rather than an action, but it fits the resource-based naming convention and is still intuitive. Minor deviation, but overall the naming is predictable and readable.

Tool Count5/5

Three tools perfectly cover the server's narrow scope of checking social post limits. Each tool is essential and non-redundant, and the count feels neither too thin nor excessive for a linting utility.

Completeness4/5

The set covers the full workflow: check one platform, check all platforms, and query platform metadata. The only minor gap is the lack of a tool to add or modify platform definitions, but for a linting server this is a reasonable boundary and not a practical dead end.

Maintenance

ActivityMaintained
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Provides 14+ character-level text analysis tools that give LLMs the ability to accurately count letters, analyze individual characters, and work with text at the character level—overcoming tokenization limitations.
    14
    13
    3
    MIT
  • F
    license
    A
    quality
    D
    maintenance
    Provides tools for accurate Twitter/X post character counting, validation, and optimization using official counting methods. It enables users to extract entities like URLs and hashtags to ensure content fits within platform constraints.
    4
    1
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Accurately counts characters, bytes, and manuscript paper based on Unicode grapheme clusters, and provides deterministic feedback to help AI meet exact length limits for self-introductions or school records.
    -