DEV Community

Omer Faruk Kolip
Omer Faruk Kolip

Posted on

I built an MCP server for Instagram — 16 tools, and everything the MCP publishing pipeline has quietly changed since my last one

A couple of months ago I wrote about shipping xtapdown-mcp — 14 X/Twitter tools as an MCP server. Since then I built the Instagram sibling — instapdown-mcp — 16 tools wrapping instapdown.com'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).

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.

If you're publishing your first MCP server: read the XTapDown post first, then this one. If you're publishing your second, this is the one to skim.


The setup — same shape as before

Same stack as xtapdown-mcp:

  • TypeScript + @modelcontextprotocol/sdk
  • stdio transport for local MCP clients
  • npm for install (npx -y instapdown-mcp)
  • zod schemas that double as the tool's input contract

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

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.

The Instagram-specific choice worth calling out: the downloader tools don't scrape. They call instapdown.com/api/download, which fronts a 4-layer extractor fallback chain (Meta CDN direct → RapidAPI social-scraper → Apify actor → community btch-downloader). When Meta rotates a GraphQL doc_id 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.

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


What actually changed since XTapDown

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

1. server.json grew a transport field

Old shape from a few months ago:

{
  "name": "io.github.farukkolip/xtapdown-mcp",
  "description": "...",
  "packages": [
    { "registryName": "npm", "name": "xtapdown-mcp", "version": "..." }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Current shape (schema 2025-12-11):

{
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
  "name": "io.github.farukkolip/instapdown-mcp",
  "description": "...",
  "version": "0.1.1",
  "packages": [
    {
      "registryType": "npm",
      "identifier": "instapdown-mcp",
      "version": "0.1.1",
      "transport": { "type": "stdio" }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Four renames + one required field, all now enforced at registry-submission time:

  • packages[0].registryNameregistryType
  • packages[0].nameidentifier
  • top-level version — now required (previously implicit from packages)
  • packages[0].transport — new required object, one of {stdio, streamable-http, sse}, stdio has no url, the other two do
  • description — hard cap at 100 chars, silently truncated for me last time, hard-rejected now

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

2. mcp-publisher device tokens are short-lived

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

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

3. Glama's scoring is layered now, not just "gated by a Dockerfile"

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

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

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

The two that quietly bit me:

glama.json schema evolved. The old shape ({"build": {"commands": [...]}}) still works for the Dockerfile builder but doesn't validate against the current $schema, so the checklist marks you red on "Has valid glama.json" even while the build passes. Current shape:

{
  "$schema": "https://glama.ai/mcp/schemas/server.json",
  "maintainers": ["your-github-handle"],
  "build": {
    "nodeVersion": "20",
    "buildSteps": ["npm ci", "npm run build"],
    "cmdArguments": ["mcp-proxy", "--", "node", "dist/index.js"]
  }
}
Enter fullscreen mode Exit fullscreen mode

The maintainers 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.

"Has related servers" needs a user-submitted related server. Glama's auto-populated "Related" grid (based on tag / category similarity) does not 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.

4. Maintenance score has a floor for new repos — even with everything green

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

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

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.

Practical takeaway: don't wait for Maintenance A before opening your awesome-mcp-servers 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.

5. PulseMCP no longer has a submission form

The old pulsemcp.com/submit was a real form. Today it's a static page that says:

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.

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

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


The publishing sequence I'd run today, if I were shipping shipment #3

  1. Write the server. Add node:test structured tests — the checklist under Tool Definition Quality now looks for structured test files, not just an ambient test script.
  2. Add a real Dockerfile and glama.json with the current schema and maintainers set. Don't wait until later.
  3. npm publish --access public first — that establishes the identifier before anything else references it.
  4. mcp-publisher login github and publish 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.
  5. Fork awesome-mcp-servers, 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.
  6. 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).
  7. 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").
  8. Comment on the awesome-mcp-servers PR with the final score screenshot and the maintenance-B caveat. Sit back.

Everything else — PulseMCP, mcp.so, cursor.directory — happens without you.


The bit that didn't need re-learning

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

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.


What's next

TikTok next — tiktapdown.comtiktapdown-mcp. The TapDown-family plan is to ship the four sister servers (X, Instagram, TikTok, plus one more I'm not announcing yet) with a shared publishing pipeline and a matching MCP Registry naming scheme (io.github.farukkolip/<site>-mcp). Once all four are up, I'll consolidate them into a single Product Hunt launch as "MCP standard for creator toolkits" rather than pinging PH four times.


Try instapdown-mcp

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.
Instapdown homepage screenshot: the Instagram creator toolkit landing page with a row of tool chips, a

Top comments (0)