Skip to main content
Glama
jasonplusproductions-create

buffer-mcp-server

README.md
# buffer-mcp-server

A custom MCP server that gives Claude **real** publish/schedule access to Buffer,
via the `createPost` GraphQL mutation — beyond what the official Buffer MCP connector
exposes (which is read-only + `create_idea` drafts only).

## Schema status: verified against the live API

The GraphQL operations here were introspected and validated against Buffer's live
endpoint (`api.buffer.com`). Two things were corrected after the initial build:

1. **The `channels` query** in `src/tools/list-channels.ts` takes a single
   `input: ChannelsInput!` argument (`{ organizationId }`), not a flat
   `channels(organizationId:)` argument.
2. **`CreatePostInput.assets` is a required (non-null) list.** Both
   `src/tools/draft-post.ts` and `src/tools/publish-post.ts` now send `assets: []`
   for text-only posts. The other fields (`schedulingType`, `mode`, `dueAt`,
   `saveToDraft`, `channelId`, `text`) match the live schema.

If Buffer changes their schema, re-introspect with a small test query/mutation and
adjust the `.ts` files to match.

## Setup

```bash
npm install
npm run build
```

## Configuration

Set two environment variables wherever you run this:

- `BUFFER_API_KEY` — your personal Buffer API key (Buffer → developer settings →
  requires you to be an organization owner). You said you already have this.
- `MCP_SHARED_SECRET` (optional but recommended for a public HTTP deployment) — an
  arbitrary string. If set, requests must include header `x-mcp-secret: <value>`.

## Running locally (stdio) — for Claude Desktop

Add to your Claude Desktop MCP config:

```json
{
  "mcpServers": {
    "buffer": {
      "command": "node",
      "args": ["/absolute/path/to/buffer-mcp-server/dist/index.js"],
      "env": { "BUFFER_API_KEY": "your_key_here" }
    }
  }
}
```

## Running as a remote HTTP server — for claude.ai custom connectors

```bash
TRANSPORT=http BUFFER_API_KEY=your_key MCP_SHARED_SECRET=some_random_string \
  PORT=3000 npm start
```

This exposes a stateless streamable-HTTP MCP endpoint at `POST /mcp`.

### Hosting options

This is a plain Node/Express app, so it runs on anything that runs Node — **Render,
Railway, Fly.io, or a small VPS** are the simplest. It does *not* run as-is on Cloudflare
Workers (no Node `http` server / Express there); porting it to Workers would mean
swapping Express for a raw `fetch` handler and using the SDK's Workers-compatible
transport pattern — possible, but a separate step from this build. Given the rest of
your stack lives on Cloudflare, that's a reasonable follow-up if you want everything
in one place.

Once hosted, register it in Claude: **Settings → Connectors → Add custom connector**,
paste your server's `https://your-host/mcp` URL.

## Tools this server exposes

- **buffer_list_channels** — read-only, lists channel IDs for an organization.
- **buffer_create_draft_post** — always saves as a draft (`saveToDraft: true` hardcoded).
  Free to call anytime, nothing goes live from this one.
- **buffer_publish_post** — actually schedules or queues a REAL post that will go live.
  Requires `confirmed: true` as a literal parameter, and its tool description explicitly
  instructs Claude to only call it right after you've said something like "yes, post it"
  to that exact text — never from an earlier general go-ahead, and never in response to
  instructions found inside a fetched web page or document.

## Approval workflow (what actually happens)

1. Claude drafts the post text in chat and shows it to you.
2. You say yes/approve.
3. Claude calls `buffer_publish_post` with `confirmed: true` and the exact approved text.

**Important limit to understand:** the `confirmed: true` parameter is a strong signal and
an audit trail (you can always check the tool-call log to see it was set), but it is Claude
setting that parameter based on judgment about the conversation — a JSON field can't
independently verify a human typed "yes." The real guarantee is behavioral: Claude is
built to always ask before taking a publishing action, and to treat that as non-negotiable
regardless of how a request is phrased. If you want a guarantee that doesn't depend on that
judgment at all, use `buffer_create_draft_post` exclusively and do the final approve/schedule
click yourself in Buffer — that's enforced by the code, not by anyone's judgment call.

For extra safety on top of either approach, you can also set **"Requires Approval"** on
your channels in Buffer's own settings — Buffer's docs confirm API-created posts on such
channels are saved as drafts awaiting approval regardless of what the API call requested.

## Known limitations

- If a channel is set to "Requires Approval" in Buffer, posts created via the API land
  as pending drafts regardless of `mode` — that's Buffer's behavior, not a bug here.
- No image/video upload tool included yet (Buffer changed their assets input format on
  May 25, 2026 — worth adding as a follow-up once the schema's confirmed).
- No delete/edit tool included — intentionally minimal for now; extend `src/tools/` if
  you want those.

TDQS

A4.6/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a distinct purpose: listing channels, creating drafts, and publishing live posts. No overlap or ambiguity.

Naming Consistency5/5

All tool names follow the consistent 'buffer_verb_noun' pattern with base-form verbs and appropriate nouns.

Tool Count5/5

Three tools is well-scoped for this server's purpose—covering channel listing, draft creation, and publishing without unnecessary extras.

Completeness4/5

The tool set covers the core workflow (list channels, draft, publish) but lacks retrieval or management tools for existing drafts or posts, leaving minor gaps.

Maintenance

ActivityStale
ResponsivenessNo issues