<?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 Instagram — 16 tools, and everything the MCP publishing pipeline has quietly changed since my last one</title>
      <dc:creator>Omer Faruk Kolip</dc:creator>
      <pubDate>Wed, 22 Jul 2026 12:50:18 +0000</pubDate>
      <link>https://dev.to/farukkolip/i-built-an-mcp-server-for-instagram-16-tools-and-everything-the-mcp-publishing-pipeline-has-26n8</link>
      <guid>https://dev.to/farukkolip/i-built-an-mcp-server-for-instagram-16-tools-and-everything-the-mcp-publishing-pipeline-has-26n8</guid>
      <description>&lt;p&gt;A couple of months ago I wrote about &lt;a href="https://dev.to/farukkolip/i-built-an-mcp-server-for-x-twitter"&gt;shipping xtapdown-mcp&lt;/a&gt; — 14 X/Twitter tools as an MCP server. Since then I built the Instagram sibling — &lt;a href="https://www.npmjs.com/package/instapdown-mcp" rel="noopener noreferrer"&gt;instapdown-mcp&lt;/a&gt; — 16 tools wrapping &lt;a href="https://instapdown.com" rel="noopener noreferrer"&gt;instapdown.com&lt;/a&gt;'s public surface (Reels/Story/carousel downloaders, engagement audit, live hashtag search, Reels hook library, best-time-to-post for 17 markets, 750-date 2026 content calendar).&lt;/p&gt;

&lt;p&gt;I thought I was just going to run the same playbook again. I wasn't. Between the two shipments, the MCP publishing pipeline changed in half a dozen small-but-annoying ways that would have burned an entire afternoon if I hadn't just seen them fresh. This post is that unlock — everything I'd do differently on shipment #2, and every schema/registry/scorer edge case that's landed since the first one.&lt;/p&gt;

&lt;p&gt;If you're publishing your first MCP server: read the &lt;a href="https://dev.to/farukkolip/i-built-an-mcp-server-for-x-twitter"&gt;XTapDown post&lt;/a&gt; first, then this one. If you're publishing your second, this is the one to skim.&lt;/p&gt;




&lt;h2&gt;
  
  
  The setup — same shape as before
&lt;/h2&gt;

&lt;p&gt;Same stack as xtapdown-mcp:&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 MCP clients&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;npm&lt;/strong&gt; for install (&lt;code&gt;npx -y instapdown-mcp&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;zod&lt;/strong&gt; schemas that double as the tool's input contract&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool handlers are pure functions calling &lt;code&gt;instapdown.com/api/*&lt;/code&gt;. No auth, no per-user state — everything the MCP surfaces is public data that the web tools already return.&lt;/p&gt;

&lt;p&gt;16 tools, roughly a page of code each. The whole server, including the embedded static datasets (Buffer 2026 best-time data for 17 markets, 25 curated hashtag seed niches, the 2026 content calendar with per-market playbooks) is ~900 lines of TypeScript.&lt;/p&gt;

&lt;p&gt;The Instagram-specific choice worth calling out: the downloader tools don't scrape. They call &lt;code&gt;instapdown.com/api/download&lt;/code&gt;, which fronts a 4-layer extractor fallback chain (Meta CDN direct → RapidAPI social-scraper → Apify actor → community &lt;code&gt;btch-downloader&lt;/code&gt;). When Meta rotates a GraphQL &lt;code&gt;doc_id&lt;/code&gt; and the primary extractor fails, the same call transparently falls through to the next layer. From the MCP client's perspective it just… works. From my perspective it means one broken extractor doesn't turn into a broken Claude Desktop experience overnight.&lt;/p&gt;

&lt;p&gt;Same syndication-endpoint spirit as X, different plumbing: put a real fallback chain on the server side, keep the MCP layer thin.&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually changed since XTapDown
&lt;/h2&gt;

&lt;p&gt;This is the "shipment #2 delta" — the parts that would have blindsided me if I'd shipped this straight off the old playbook.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;server.json&lt;/code&gt; grew a &lt;code&gt;transport&lt;/code&gt; field
&lt;/h3&gt;

&lt;p&gt;Old shape from a few months ago:&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;"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;"..."&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;Current shape (schema &lt;code&gt;2025-12-11&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;"$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/instapdown-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;"..."&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;"0.1.1"&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;"registryType"&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;"identifier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"instapdown-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;"0.1.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"transport"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stdio"&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;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;Four renames + one required field, all now enforced at registry-submission time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;packages[0].registryName&lt;/code&gt; → &lt;strong&gt;&lt;code&gt;registryType&lt;/code&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;packages[0].name&lt;/code&gt; → &lt;strong&gt;&lt;code&gt;identifier&lt;/code&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;top-level &lt;code&gt;version&lt;/code&gt; — now required (previously implicit from packages)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;packages[0].transport&lt;/code&gt; — new required object, one of &lt;code&gt;{stdio, streamable-http, sse}&lt;/code&gt;, &lt;code&gt;stdio&lt;/code&gt; has no url, the other two do&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt; — hard cap at &lt;strong&gt;100 chars&lt;/strong&gt;, silently truncated for me last time, hard-rejected now&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of these are wrong, &lt;code&gt;mcp-publisher publish&lt;/code&gt; returns a 422 with a JSON blob listing all offenders at once. Fix them in one go rather than round-tripping.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;mcp-publisher&lt;/code&gt; device tokens are short-lived
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;mcp-publisher login github&lt;/code&gt; gives you a device code that expires in ~30 seconds if you don't paste it into the GitHub &lt;code&gt;/login/device&lt;/code&gt; page fast enough. The retry path is confusing — if you re-run &lt;code&gt;login&lt;/code&gt;, you get a new code, but the &lt;em&gt;token&lt;/em&gt; from a successful login lives ~10 minutes.&lt;/p&gt;

&lt;p&gt;If your &lt;code&gt;publish&lt;/code&gt; step takes longer than that (e.g. because you got a validation error and spent a minute fixing it), you'll hit &lt;code&gt;401 Invalid or expired Registry JWT token&lt;/code&gt; and have to &lt;code&gt;login github&lt;/code&gt; again. Fine once you know it — burned me twice before I recognised the pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Glama's scoring is &lt;em&gt;layered&lt;/em&gt; now, not just "gated by a Dockerfile"
&lt;/h3&gt;

&lt;p&gt;Old model: Glama built your Docker image, ran a couple of checks, gave you an A/B/C score.&lt;/p&gt;

&lt;p&gt;New model: &lt;strong&gt;10-item Server Quality Checklist&lt;/strong&gt;. Every red X locks the score at whatever it currently is. The full list, from what I hit on instapdown-mcp:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Has a Glama release&lt;/li&gt;
&lt;li&gt;Server Coherence (tool descriptions consistent with the server description)&lt;/li&gt;
&lt;li&gt;Tool Definition Quality (zod → JSON schema round-trip is clean)&lt;/li&gt;
&lt;li&gt;Maintenance (weighted signal — commits + releases + issue triage history + CI + vuln alerts)&lt;/li&gt;
&lt;li&gt;Has a permissive license&lt;/li&gt;
&lt;li&gt;Has README&lt;/li&gt;
&lt;li&gt;Active usage (someone has actually called a tool via Glama's sandboxed MCP Inspector)&lt;/li&gt;
&lt;li&gt;Has valid glama.json (see below)&lt;/li&gt;
&lt;li&gt;Author verified&lt;/li&gt;
&lt;li&gt;Has related servers (user-linked, not the auto-suggested "related" grid)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The two that quietly bit me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;glama.json&lt;/code&gt; schema evolved.&lt;/strong&gt; The old shape (&lt;code&gt;{"build": {"commands": [...]}}&lt;/code&gt;) still works for the Dockerfile builder but doesn't validate against the current &lt;code&gt;$schema&lt;/code&gt;, so the checklist marks you red on "Has valid glama.json" even while the build passes. Current shape:&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://glama.ai/mcp/schemas/server.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;"maintainers"&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;"your-github-handle"&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;"nodeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"buildSteps"&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="nl"&gt;"cmdArguments"&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;"mcp-proxy"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dist/index.js"&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;The &lt;code&gt;maintainers&lt;/code&gt; array is what lights up the "Maintainer" cell on the Maintenance score card. Without it, that cell shows a dash even after you claim ownership via GitHub OAuth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Has related servers" needs a &lt;em&gt;user-submitted&lt;/em&gt; related server.&lt;/strong&gt; Glama's auto-populated "Related" grid (based on tag / category similarity) does &lt;strong&gt;not&lt;/strong&gt; count. You have to open the Related Servers tab → Suggest Server → link to something you know is genuinely related. In my case: xtapdown-mcp as the TapDown sibling. That flipped the last red X to green.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Maintenance score has a floor for new repos — even with everything green
&lt;/h3&gt;

&lt;p&gt;Even with all 10 checklist items green, a 1-day-old server tops out at Maintenance = &lt;strong&gt;B&lt;/strong&gt;. Glama's weighted signal factors in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Number of stable releases in 12mo (I had 1)&lt;/li&gt;
&lt;li&gt;Issue triage history (I had 0 issues)&lt;/li&gt;
&lt;li&gt;Response time SLA verified against real interactions (nothing to verify yet)&lt;/li&gt;
&lt;li&gt;Contributor count / commit cadence over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these can be forced. They fill in over weeks. xtapdown-mcp is A/A/A now, but that took months of activity — not something I did on shipment day.&lt;/p&gt;

&lt;p&gt;Practical takeaway: don't wait for Maintenance A before opening your &lt;code&gt;awesome-mcp-servers&lt;/code&gt; PR. Ship at A/A/B, note in the PR comment that the maintenance signal is time-based, and let the maintainer merge on the strength of the other nine green checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. PulseMCP no longer has a submission form
&lt;/h3&gt;

&lt;p&gt;The old &lt;code&gt;pulsemcp.com/submit&lt;/code&gt; was a real form. Today it's a static page that says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We ingest entries from the Official MCP Registry daily and process them weekly. If it has been a week since you published there, or want to make other adjustments to your listing, please email us.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So it's now genuinely just "publish to the Anthropic Registry and wait." Same for &lt;code&gt;mcp.so&lt;/code&gt;, &lt;code&gt;cursor.directory/mcp&lt;/code&gt;, and a couple of the smaller directories — Anthropic Registry has become the source of truth that everything else syncs from.&lt;/p&gt;

&lt;p&gt;Net effect: the article's original "5 channels" list collapses to &lt;strong&gt;3 things you actually do manually&lt;/strong&gt;: npm publish, Anthropic Registry publish, awesome-mcp-servers PR. Everything else auto-syncs.&lt;/p&gt;




&lt;h2&gt;
  
  
  The publishing sequence I'd run today, if I were shipping shipment #3
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Write the server. Add &lt;code&gt;node:test&lt;/code&gt; structured tests — the checklist under &lt;strong&gt;Tool Definition Quality&lt;/strong&gt; now looks for structured test files, not just an ambient &lt;code&gt;test&lt;/code&gt; script.&lt;/li&gt;
&lt;li&gt;Add a real &lt;code&gt;Dockerfile&lt;/code&gt; and &lt;code&gt;glama.json&lt;/code&gt; with the current schema and &lt;code&gt;maintainers&lt;/code&gt; set. Don't wait until later.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm publish --access public&lt;/code&gt; first — that establishes the identifier before anything else references it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mcp-publisher login github&lt;/code&gt; and &lt;code&gt;publish&lt;/code&gt; in quick succession, one session, before the JWT expires. If validation fails, fix and re-run login, don't try to bluff the same token through.&lt;/li&gt;
&lt;li&gt;Fork &lt;code&gt;awesome-mcp-servers&lt;/code&gt;, sync the fork if it's stale (mine was 719 commits behind), edit the README on the web, open the PR. Include the Glama score card badge markup so the maintainer's bot doesn't flag "missing badge" even if the score is stubbing to A/A/C for the first few hours.&lt;/li&gt;
&lt;li&gt;Submit to Glama once. Then wait. Then click Sync Server on the Repository tab once the daily crawl misses your latest push (it will — Glama's daily sync is calendar-day, not push-triggered).&lt;/li&gt;
&lt;li&gt;Fill the last-mile checklist items — Categories on the Profile tab, xtapdown-mcp-equivalent Related Server, one tool call via the MCP Inspector's Try in Browser (that's what registers "Active usage").&lt;/li&gt;
&lt;li&gt;Comment on the awesome-mcp-servers PR with the final score screenshot and the maintenance-B caveat. Sit back.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything else — PulseMCP, mcp.so, cursor.directory — happens without you.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bit that didn't need re-learning
&lt;/h2&gt;

&lt;p&gt;The MCP SDK itself. &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt; is stable, &lt;code&gt;InMemoryTransport&lt;/code&gt; still makes CI tests trivial, &lt;code&gt;server.tool(name, description, zodShape, handler)&lt;/code&gt; still reads exactly like the docs say. If anything, the ergonomics improved — the SDK's &lt;code&gt;callTool&lt;/code&gt; result now types cleanly through &lt;code&gt;unknown&lt;/code&gt; without needing the manual cast I used the first time.&lt;/p&gt;

&lt;p&gt;The publish process for shipment #2 took about 90 minutes of my time end-to-end, most of which was learning the deltas above. The code itself — 16 tools wrapping a REST API I already had — took closer to 3 hours. If you have a REST API and an audience of AI-assistant users, that ratio (3:1 for code:publishing) is probably the least misleading estimate I've seen for this kind of project.&lt;/p&gt;




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

&lt;p&gt;YouTube next. &lt;a href="https://www.npmjs.com/package/tiktapdown-mcp" rel="noopener noreferrer"&gt;tiktapdown-mcp&lt;/a&gt; is already live — same publishing pipeline as this one, I just never wrote it up separately because nothing new about the process had surfaced by that point. That's part of why this post covers the deltas from xtapdown-mcp directly.&lt;/p&gt;

&lt;p&gt;The TapDown-family plan is a five-site set (TikTok, X, Instagram, YouTube, Pinterest) with a shared publishing pipeline and a matching MCP Registry naming scheme (&lt;code&gt;io.github.farukkolip/&amp;lt;site&amp;gt;-mcp&lt;/code&gt;). Three of five are live; &lt;a href="https://youtapdown.com" rel="noopener noreferrer"&gt;YouTapDown&lt;/a&gt; and &lt;a href="https://pintapdown.com" rel="noopener noreferrer"&gt;PinTapdown&lt;/a&gt; are queued. Once all five are up, I'll consolidate them into a single Product Hunt launch as "MCP standard for creator toolkits" rather than pinging PH five times.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try instapdown-mcp
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;npx -y instapdown-mcp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Desktop config:&lt;/strong&gt; &lt;a href="https://instapdown.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/instapdown-mcp" rel="noopener noreferrer"&gt;github.com/farukkolip/instapdown-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/instapdown-mcp" rel="noopener noreferrer"&gt;instapdown-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Glama:&lt;/strong&gt; &lt;a href="https://glama.ai/mcp/servers/farukkolip/instapdown-mcp" rel="noopener noreferrer"&gt;glama.ai/mcp/servers/farukkolip/instapdown-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://instapdown.com" rel="noopener noreferrer"&gt;instapdown.com&lt;/a&gt; — free, no signup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you ran into any of the schema/scoring changes above shipping your own MCP server this month, drop a comment — I want to know what other quiet migrations are out there so shipment #3 goes cleaner.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F265mp8gvnw28gbt02n9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F265mp8gvnw28gbt02n9s.png" alt="Instapdown homepage screenshot: the Instagram creator toolkit landing page with a row of tool chips, a " width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>claude</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
    <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>
