<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Omer Faruk Kolip</title>
    <description>The latest articles on DEV Community by Omer Faruk Kolip (@farukkolip).</description>
    <link>https://dev.to/farukkolip</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3864571%2F941974d5-d760-4b78-aa53-15c00fed1d21.JPG</url>
      <title>DEV Community: Omer Faruk Kolip</title>
      <link>https://dev.to/farukkolip</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/farukkolip"/>
    <language>en</language>
    <item>
      <title>I built an MCP server for X (Twitter) — 14 tools in ~600 lines, here's what I learned</title>
      <dc:creator>Omer Faruk Kolip</dc:creator>
      <pubDate>Tue, 14 Jul 2026 12:54:28 +0000</pubDate>
      <link>https://dev.to/farukkolip/i-built-an-mcp-server-for-x-twitter-14-tools-in-600-lines-heres-what-i-learned-15ca</link>
      <guid>https://dev.to/farukkolip/i-built-an-mcp-server-for-x-twitter-14-tools-in-600-lines-heres-what-i-learned-15ca</guid>
      <description>&lt;p&gt;I ship a free X (Twitter) creator toolkit at &lt;a href="https://xtapdown.com" rel="noopener noreferrer"&gt;xtapdown.com&lt;/a&gt; — 17 web tools for downloading tweets, generating hashtags, calculating engagement, splitting threads, that sort of thing. A few weeks ago I wrapped 14 of them as an MCP server and published it on npm, the Anthropic MCP Registry, Glama, and got it merged into &lt;code&gt;awesome-mcp-servers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The whole thing is ~600 lines of TypeScript. Here's what I learned along the way, and everything that actually mattered vs. the stuff I over-thought.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why MCP over just having a REST API
&lt;/h2&gt;

&lt;p&gt;I already had a REST API powering the web tools. I could have documented it and said "call these endpoints." Nobody would have.&lt;/p&gt;

&lt;p&gt;The problem with a REST API for AI use cases: the LLM has to &lt;em&gt;know&lt;/em&gt; your API exists. If nobody adds &lt;code&gt;xtapdown.com&lt;/code&gt; to their system prompt or context, it might as well not exist. MCP inverts that — the client discovers your tools once, and from then on, whenever the LLM decides "I need to download a tweet," your tool is right there in its tool list.&lt;/p&gt;

&lt;p&gt;I built the web tools first, spent months writing content around them, and got approximately zero traffic from AI assistants. Two weeks after publishing the MCP server, it had 500+ npm downloads. Different distribution channel, different mechanics.&lt;/p&gt;

&lt;p&gt;TL;DR: MCP is a distribution primitive for tools that AI assistants should call directly. If your product's core use case fits into a single LLM turn ("download this tweet", "give me hashtags for AI"), MCP is a much better fit than a REST API you have to onboard people onto.&lt;/p&gt;




&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;Nothing exotic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; + &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;stdio transport&lt;/strong&gt; for local Claude Desktop / Cursor / Cline use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streamable HTTP transport&lt;/strong&gt; for remote clients&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;npm&lt;/strong&gt; as the primary distribution channel (&lt;code&gt;npx -y xtapdown-mcp&lt;/code&gt; runs it with zero setup)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool handlers are pure functions. No database, no session state, no auth (the underlying data endpoints are public). This keeps the whole thing small enough that anyone can read the source and audit it before running it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// One tool handler, roughly:&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;download_tweet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X/Twitter post URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;include_media&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;include_media&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tweet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchTweet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;formatTweet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;include_media&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fourteen of these, each 20-40 lines. That's the whole MCP surface area.&lt;/p&gt;




&lt;h2&gt;
  
  
  The syndication endpoint trick
&lt;/h2&gt;

&lt;p&gt;The single most useful thing I want to share, because it took me a day of digging to find:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;X has a public JSON endpoint that returns full tweet data, no auth, no rate limits I've hit.&lt;/strong&gt; It's the endpoint the embed widget uses. It's been stable for years. You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full text with entities&lt;/li&gt;
&lt;li&gt;All attached media (video with variant URLs, images with source resolutions, GIFs as MP4)&lt;/li&gt;
&lt;li&gt;Author info&lt;/li&gt;
&lt;li&gt;Engagement stats (likes, retweets, replies, bookmarks, quotes)&lt;/li&gt;
&lt;li&gt;Reply-to metadata for thread walking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use it for &lt;code&gt;download_tweet&lt;/code&gt;, &lt;code&gt;get_tweet_screenshot_url&lt;/code&gt;, and thread reconstruction. In three months of production use across two sites, I've seen exactly one rate limit — during a viral link storm from a big Reddit thread.&lt;/p&gt;

&lt;p&gt;Search for &lt;code&gt;cdn.syndication.twimg.com/tweet-result&lt;/code&gt;. That's your endpoint. Handle the CORS on your own server, cache the response, done.&lt;/p&gt;

&lt;p&gt;Caveat: this is a public endpoint but it's not a documented API. It could change. Have a fallback plan (or at least monitoring) if you build something serious on top of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Publishing — this is where MCP gets weird
&lt;/h2&gt;

&lt;p&gt;Building the server is 10% of the effort. Getting it discoverable is 90%. There are five separate channels and each has its own quirks:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. npm
&lt;/h3&gt;

&lt;p&gt;Straightforward. Publish with &lt;code&gt;npm publish --access public&lt;/code&gt;. The package name matters — mine is &lt;code&gt;xtapdown-mcp&lt;/code&gt; which matches the site (&lt;code&gt;xtapdown.com&lt;/code&gt;). Symmetry helps discoverability.&lt;/p&gt;

&lt;p&gt;Add to &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"io.github.farukkolip/xtapdown-mcp"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the canonical ID the Anthropic MCP Registry uses. Reverse-DNS style, tied to your GitHub org/user. Get this right on first publish because it's a pain to change.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Anthropic MCP Registry
&lt;/h3&gt;

&lt;p&gt;Official registry from Anthropic. Uses a Go CLI called &lt;code&gt;mcp-publisher&lt;/code&gt; (get it from the &lt;a href="https://github.com/modelcontextprotocol/registry/releases" rel="noopener noreferrer"&gt;modelcontextprotocol/registry releases&lt;/a&gt;, not npm — I lost 20 minutes to that).&lt;/p&gt;

&lt;p&gt;You need a &lt;code&gt;server.json&lt;/code&gt; in your repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"io.github.farukkolip/xtapdown-mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"14 X (Twitter) creator tools as an MCP server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"repository"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/farukkolip/xtapdown-mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"github"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"packages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"registryName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xtapdown-mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mcp-publisher login github
mcp-publisher publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validation is strict. Description over 100 chars → rejected. Schema URL wrong → rejected. Repository URL missing &lt;code&gt;.source&lt;/code&gt; → rejected. Get all three right the first time.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Glama
&lt;/h3&gt;

&lt;p&gt;Glama auto-discovers MCP servers from GitHub and builds a Docker image to test them. If the build fails, they mark the badge red and the score is &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two gotchas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;They auto-detect pnpm if &lt;code&gt;pnpm-lock.yaml&lt;/code&gt; exists&lt;/strong&gt; even if your project uses npm. Add a &lt;code&gt;glama.json&lt;/code&gt; at repo root overriding the build:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"commands"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"npm ci"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run build"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The badge requires a LICENSE file&lt;/strong&gt;. Not just &lt;code&gt;"license": "MIT"&lt;/code&gt; in package.json — the actual &lt;code&gt;LICENSE&lt;/code&gt; file at repo root. Add one and their scanner picks it up on the next refresh.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Got both right → A/A/A score, verified badge.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. awesome-mcp-servers
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;punkpeye/awesome-mcp-servers&lt;/code&gt; repo. One-line PR adding your server in the alphabetically correct spot in the right section. Include the Glama badge (they require it now via a GitHub Actions bot).&lt;/p&gt;

&lt;p&gt;My PR got merged in a couple of days. Merge rate for real projects with good descriptions is high — the maintainer is friendly.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. PulseMCP + mcp.so + others
&lt;/h3&gt;

&lt;p&gt;These are third-party directories. Most of them auto-sync from the Anthropic Registry, so publishing there automatically populates the rest. Only PulseMCP needed a manual form for me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip Smithery.&lt;/strong&gt; I know it's on every "how to publish MCP" guide, but the current publishing flow is awkward and the audience-per-effort ratio is bad compared to the other five channels.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing this thing
&lt;/h2&gt;

&lt;p&gt;For local dev, add to &lt;code&gt;claude_desktop_config.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"xtapdown"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xtapdown-mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Claude Desktop. In a new chat, ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Download this tweet for me: &lt;a href="https://x.com/AnthropicAI/status/.." rel="noopener noreferrer"&gt;https://x.com/AnthropicAI/status/..&lt;/a&gt;."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If everything works, Claude will call &lt;code&gt;download_tweet&lt;/code&gt; and hand you back the media URLs and the tweet text.&lt;/p&gt;

&lt;p&gt;For CI, the MCP SDK ships a &lt;code&gt;runInMemory&lt;/code&gt; test harness. Roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;InMemoryTransport&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@modelcontextprotocol/sdk/inMemory.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;clientTransport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;serverTransport&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;InMemoryTransport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createLinkedPair&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serverTransport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.0.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientTransport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;callTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;download_tweet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://x.com/AnthropicAI/status/1234...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Faster than spinning up a real client, catches most breakage.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do this first, not last:&lt;/strong&gt; decide your &lt;code&gt;mcpName&lt;/code&gt; (the reverse-DNS ID) before you publish anywhere. It has to match across npm, Anthropic Registry, and Glama. Changing it later means republishing under a new identifier and losing whatever discoverability the old one built.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't overthink tool naming.&lt;/strong&gt; I spent hours agonizing over &lt;code&gt;download_tweet&lt;/code&gt; vs &lt;code&gt;get_tweet&lt;/code&gt; vs &lt;code&gt;fetch_tweet_media&lt;/code&gt;. In practice the LLM picks the right tool from the description regardless of the name, as long as the name isn't actively misleading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ship the CI test harness on day one.&lt;/strong&gt; I skipped it initially, then had to add it after a silent regression broke &lt;code&gt;get_x_trends&lt;/code&gt; for a week. Nobody complained because nobody was watching, but I noticed a drop in npm downloads and traced it back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distribution beats tool count.&lt;/strong&gt; I have 14 tools. Half of them are probably never called. But 14 sounds better in the readme than 6, and once someone's installed the server, adding a rarely-used tool costs nothing.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;I'm building the same pattern for TikTok (&lt;a href="https://tiktapdown.com" rel="noopener noreferrer"&gt;tiktapdown.com&lt;/a&gt; → &lt;code&gt;tiktapdown-mcp&lt;/code&gt;) and Instagram is queued. The goal is a small family of per-platform MCP servers that share nothing but a common publishing pipeline and Docker base image.&lt;/p&gt;

&lt;p&gt;If you're building your own MCP server, the fastest way to sanity-check the design is to install one that already works (mine or someone else's) and see what the tool calls actually look like from the LLM side. That gave me the biggest "oh, so &lt;em&gt;that's&lt;/em&gt; how this fits together" moment early on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;npx -y xtapdown-mcp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Config for Claude Desktop:&lt;/strong&gt; &lt;a href="https://xtapdown.com" rel="noopener noreferrer"&gt;full instructions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/farukkolip/xtapdown-mcp" rel="noopener noreferrer"&gt;github.com/farukkolip/xtapdown-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/xtapdown-mcp" rel="noopener noreferrer"&gt;xtapdown-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The web tools it wraps:&lt;/strong&gt; &lt;a href="https://xtapdown.com" rel="noopener noreferrer"&gt;xtapdown.com&lt;/a&gt; — free, no signup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy to answer any technical questions in the comments — especially about the syndication endpoint approach or any of the distribution channels above. If you're stuck on a publishing quirk I ran into, I probably have the fix in a scratch file somewhere.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
