Skip to main content
Glama
parsestream

ParseStream

Official
by parsestream
README.md
# ParseStream MCP Server

[![Add to Cursor](https://img.shields.io/badge/Cursor-Add%20ParseStream-0098FF?style=flat-square&logo=cursor&logoColor=white)](https://cursor.com/install-mcp?name=parsestream&config=eyJ1cmwiOiJodHRwczovL21jcC5wYXJzZXN0cmVhbS5jb20vbWNwIiwiaGVhZGVycyI6eyJBdXRob3JpemF0aW9uIjoiQmVhcmVyIHBzX2xpdmVfeHh4eHh4eHh4eHh4eHh4eCJ9fQ%3D%3D)
[![Add to VS Code](https://img.shields.io/badge/VS%20Code-Add%20ParseStream-007ACC?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=parsestream&config=%7B%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fmcp.parsestream.com%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20ps_live_xxxxxxxxxxxxxxxx%22%7D%7D)
[![License: MIT](https://img.shields.io/badge/License-MIT-1a7f37?style=flat-square)](LICENSE)

ParseStream watches Reddit, X, LinkedIn, Quora and Hacker News for mentions of your
brand, scores each one for relevance and buying intent, and writes reply drafts. This
server hands that to your AI assistant: ask what people said about you this week, which
of those sound like someone shopping for a tool, pull one up in full, and get a draft
back that you edit and send yourself.

Nothing here can post. There is no tool on this server that publishes to Reddit, X,
LinkedIn, Quora or Hacker News, or that contacts anyone.

It is a remote server — nothing to install or run locally, just a URL and an API key.

| | |
| --- | --- |
| Endpoint | `https://mcp.parsestream.com/mcp` (also reachable at `https://parsestream.com/api/mcp`) |
| Transport | Streamable HTTP, stateless — no session to keep alive |
| Auth | `Authorization: Bearer ps_live_...` |
| Scope | One brand per key, derived from the key itself |
| Rate limit | 100 requests / minute / key |
| History | Rolling 30-day window |

## Get an API key first

In ParseStream, go to **Brand Settings → API Keys** and create one. It starts with
`ps_live_` and is shown exactly once, so copy it straight into your client config.

A key is scoped to a single brand — the server never reads arguments to decide whose
data you get, only the key. To connect a second brand, mint a second key and add it as
a second MCP server under a different name. Revoking a key in Brand Settings cuts off
MCP access immediately.

It is the same key the [Mentions REST API](https://parsestream.com/help#api) uses, and
both share one budget: 100 requests per minute per key. Every JSON-RPC call counts,
including the `tools/list` your client fetches on connect. Over the limit you get a 429
with a `Retry-After` header.

## Install

Replace `ps_live_xxxxxxxxxxxxxxxx` with your own key in whichever block matches your
tool, then restart the client. Every one of these clients can read the key from an
environment variable instead of holding it in the file — see
[Connecting without exposing your key](#connecting-without-exposing-your-key), and read
it first if any of these files lives inside a repository.

### Claude Code

```bash
claude mcp add --transport http parsestream https://mcp.parsestream.com/mcp \
  --header "Authorization: Bearer ps_live_xxxxxxxxxxxxxxxx" \
  -s local
```

> [!WARNING]
> Keep `-s local`. The `-s project` scope writes your key in plaintext into `.mcp.json`
> at the repo root — a file meant to be committed and shared with your team. A live
> ParseStream key ends up in git history, and if the repo is public, in the hands of
> anyone who clones it. `-s local` (the default) stores it in your own user config
> outside the repository, scoped to this project; `-s user` does the same but makes the
> server available in every project. Neither puts anything in the working tree. If your
> team genuinely needs a shared `.mcp.json`, put the *variable* in it rather than the
> key — [below](#connecting-without-exposing-your-key).

Verify with `claude mcp list`, or just ask the assistant *"what ParseStream brand am I
connected to?"* — it should name your brand and list your tracked keywords.

Claude Code can also install this repo as a plugin, which sets the server up for you and
reads the key from an environment variable instead of a config file:

```
/plugin marketplace add parsestream/parsestream-mcp
/plugin install parsestream@parsestream
```

Then export `PARSESTREAM_API_KEY=ps_live_...` in your shell profile.

### Cursor

`~/.cursor/mcp.json` (or `.cursor/mcp.json` inside a project — same caveat as above, do
not commit it):

```json
{
  "mcpServers": {
    "parsestream": {
      "url": "https://mcp.parsestream.com/mcp",
      "headers": {
        "Authorization": "Bearer ps_live_xxxxxxxxxxxxxxxx"
      }
    }
  }
}
```

The **Add to Cursor** badge at the top writes this file for you, with the placeholder
key in it — open the entry in Cursor's MCP settings afterwards and paste your real one.

### VS Code (GitHub Copilot)

`.vscode/mcp.json` — note the top-level key is `servers`, not `mcpServers`. VS Code is
the odd one out here:

```json
{
  "servers": {
    "parsestream": {
      "type": "http",
      "url": "https://mcp.parsestream.com/mcp",
      "headers": {
        "Authorization": "Bearer ps_live_xxxxxxxxxxxxxxxx"
      }
    }
  }
}
```

`.vscode/mcp.json` is usually committed, so prefer VS Code's `inputs` mechanism, or your
user-level `mcp.json` (**MCP: Open User Configuration** in the command palette), rather
than pasting a live key into a tracked file.

### Codex CLI

`~/.codex/config.toml`. Codex can read the key from the environment, which keeps it out
of the config file entirely:

```toml
[mcp_servers.parsestream]
url = "https://mcp.parsestream.com/mcp"
bearer_token_env_var = "PARSESTREAM_API_KEY"
```

Then `export PARSESTREAM_API_KEY="ps_live_xxxxxxxxxxxxxxxx"` in your shell profile. If
you would rather inline it, use `http_headers` — note that the key is `http_headers`,
not `headers`:

```toml
[mcp_servers.parsestream]
url = "https://mcp.parsestream.com/mcp"
http_headers = { Authorization = "Bearer ps_live_xxxxxxxxxxxxxxxx" }
```

### Grok

There is no Grok connector directory to be listed in, but Grok accepts a bring-your-own
MCP server: at [grok.com/connectors](https://grok.com/connectors), add a custom
connector with the URL `https://mcp.parsestream.com/mcp` and an `Authorization` header of
`Bearer ps_live_...`.

Grok's terminal agent reads Claude Code's MCP configuration, so if you already ran the
`claude mcp add` command above, it is already connected there.

### Claude Desktop and claude.ai

Not yet. A listing in Anthropic's connector directory is planned but not available at
the time of writing, so there is no one-click install in the Claude apps. Claude Code
works today and is the fastest way to try this.

## Connecting without exposing your key

One rule: **the key belongs in your environment or in a file under your home directory,
never in a file you commit.** `~/.claude.json`, `~/.cursor/mcp.json` and
`~/.codex/config.toml` are outside every repository and holding a key there is fine.
`.mcp.json`, `.cursor/mcp.json` and `.vscode/mcp.json` are the project-scoped versions of
those files, and all three exist to be shared with a team — Claude Code's and VS Code's
docs tell you to commit them outright. That is where keys leak.

Every client below can read the key out of the environment instead. The syntax differs
per client, which is the entire reason this section is four snippets rather than one.
Export it once:

```bash
export PARSESTREAM_API_KEY="ps_live_xxxxxxxxxxxxxxxx"
```

### Codex CLI

Codex is the only one of the four that stores the *name* of the variable rather than a
value, so nothing key-shaped ever reaches the file:

```toml
[mcp_servers.parsestream]
url = "https://mcp.parsestream.com/mcp"
bearer_token_env_var = "PARSESTREAM_API_KEY"
```

Or, without editing the file:

```bash
codex mcp add parsestream --url https://mcp.parsestream.com/mcp \
  --bearer-token-env-var PARSESTREAM_API_KEY
```

The config file carries a variable name and a URL and is safe to commit as it stands.
This is the design to want; the other three are workarounds by comparison.

### Claude Code

`-s local` (the default) and `-s user` both write to `~/.claude.json`, in your home
directory. Nothing lands in the working tree and there is nothing further to do — the
`claude mcp add` command in the Install section above is already safe.

Reach for a variable when the entry has to be project-scoped and shared. Claude Code
expands `${VAR}` and `${VAR:-default}` inside `command`, `args`, `env`, `url` and
`headers`, so a committed `.mcp.json` can carry the reference:

```json
{
  "mcpServers": {
    "parsestream": {
      "type": "http",
      "url": "https://mcp.parsestream.com/mcp",
      "headers": {
        "Authorization": "Bearer ${PARSESTREAM_API_KEY}"
      }
    }
  }
}
```

This is exactly what the [plugin](#claude-code) ships, which is why the plugin needs no
config file of your own.

> [!IMPORTANT]
> **If the variable is unset, Claude Code does not fail — it sends the literal text.**
> The server recognises that and says so, naming the variable in the 401
> `error_description`:
>
> ```
> Unexpanded variable, not a bad key: your MCP client sent the reference
> `${PARSESTREAM_API_KEY}` instead of the value it stands for, which means
> PARSESTREAM_API_KEY is not set in the environment the client runs in. Export
> PARSESTREAM_API_KEY=ps_live_... in your shell profile, then fully restart the client
> so it inherits the value.
> ```
>
> An export that only runs for interactive shells, or a client launched from the desktop
> rather than a terminal, produces exactly this.

`${input:...}` prompts are a VS Code feature; Claude Code does not support them, and a
config using one will send that literal too.

### Cursor

Same idea, different spelling: Cursor wants `${env:NAME}`, not `${NAME}`. The files look
alike but are not interchangeable, and pasting a Claude Code config here sends the
placeholder verbatim.

```json
{
  "mcpServers": {
    "parsestream": {
      "url": "https://mcp.parsestream.com/mcp",
      "headers": {
        "Authorization": "Bearer ${env:PARSESTREAM_API_KEY}"
      }
    }
  }
}
```

Cursor expands it in `command`, `args`, `env`, `url` and `headers`. Keep this in
`~/.cursor/mcp.json` if the setup is just yours; the `${env:...}` form is what makes a
project-level `.cursor/mcp.json` safe to commit.

### VS Code (GitHub Copilot)

`.vscode/mcp.json` is meant to be committed, so a literal key there is the same mistake
as `-s project`. Use `${env:VAR}`, or point `envFile` at a gitignored `.env`:

```json
{
  "servers": {
    "parsestream": {
      "type": "http",
      "url": "https://mcp.parsestream.com/mcp",
      "headers": {
        "Authorization": "Bearer ${env:PARSESTREAM_API_KEY}"
      }
    }
  }
}
```

VS Code also has an `inputs` mechanism that prompts once and remembers the answer —
`{"type": "promptString", "id": "parsestream-key", "password": true}` at the top level,
referenced as `${input:parsestream-key}`. The documented examples put it in an `env`
block; there is no documented example of `${input:}` inside an HTTP `headers` block, so
treat that combination as untested and prefer `${env:...}` or `envFile` for this server.
VS Code describes what it keeps only as "securely stored" — it does not promise the OS
keychain.

### Secret managers

Both of these compose with the configs above: they populate the environment, the client
reads it, and no file changes.

- **1Password CLI** — hold `PARSESTREAM_API_KEY` as an `op://` reference and launch the
  client through it: `op run -- claude`. The secret exists only for that process.
  Resolving `op://` from inside Claude Code's own config is an open request, not
  something that works today.
- **direnv** — a gitignored `.envrc` containing
  `export PARSESTREAM_API_KEY=ps_live_...`, then `direnv allow`. The key is in the
  environment while you are in that directory and nowhere else.

### If you already committed a key

Revoke it. **Brand Settings → API Keys**, delete that key, mint a fresh one, update your
environment. Do that before anything else — rewriting git history does not un-leak a
secret, it only makes it harder to find in your own repo. Assume anything pushed to a
public remote was scraped within minutes.

Revoking is cheap by design: keys are scoped to one brand, you can hold ten active keys
per brand, and revocation cuts off both MCP and REST access immediately. There is no
reason to nurse a compromised key along.

### Planned: OAuth

The real fix is not having a key. OAuth support is on the roadmap — your client gets a
scoped token through a browser consent screen, nothing long-lived is written anywhere,
and this section becomes unnecessary. It is not shipped yet; until it is, the environment
variable is the answer.

## Tools

Seven tools. Only `draft_reply` costs anything.

| Tool | What it does | Access | Cost |
| --- | --- | --- | --- |
| `get_brand_context` | The full workspace: brand profile and competitors, every tracked keyword with its ID and the platforms it is monitored on, plus the credit balance and whether drafting is available | read | free |
| `search_mentions` | Searches mentions across all five platforms. Filter by timeframe, platform, intent, relevance score, keyword, subreddit, mention type or status. Up to 30 per page | read | free |
| `get_mention` | One mention in full: untruncated body, parent post title, upvotes, subreddit size, and whether a draft is already waiting for it | read | free |
| `draft_reply` | Generates a reply draft for one mention and saves it to the Drafts queue. Choose the voice and length, or use the brand's saved defaults | writes a draft | **5 credits** |
| `mark_mention_replied` | Sets or clears the replied flag on one mention — the same toggle the web app shows | writes a flag | free |
| `highlight_mention` | Stars or unstars one mention, so a shortlist survives past the end of the chat | writes a flag | free |
| `get_brand_details` | Brand name, description and website, and nothing else. One indexed row, back in milliseconds | read | free |

Two notes on that table.

`get_brand_details` looks like it belongs next to `get_brand_context` and is listed last
instead. That is deliberate: `tools/list` is served in a pinned order so the response is
byte-identical between requests and clients can cache the tool block. New tools get
appended, never inserted, because inserting one invalidates every client's cache from
that entry on.

The two flag-writing tools only edit ParseStream's own record of a mention. They do not
touch the post or comment on the platform, and they do not notify anyone.

## How it fits together

A real session looks like this. The assistant picks the tools; you talk normally.

> **You:** anything worth replying to this week?

1. **`get_brand_details`** — who am I working for. Name, description, website, in
   milliseconds.
2. **`search_mentions`** with `timeframe: "7d"`, `intents: ["BUY_INTENT",
   "PRODUCT_QUESTION"]`, `relevanceMin: 4`, `statuses: ["new"]` — the two intent tags
   that mean someone is actively looking for a solution, confident keyword matches only,
   nothing already handled.
3. **`get_mention`** on the two that look best. Search gives you enough to triage;
   upvotes, the engagement-opportunity flag and "is there already a draft for this" only
   come back here.
4. **`draft_reply`** with `approach: "value_first"` and `replyLength: "short"`. Costs 5
   credits, takes 5–20 seconds, and returns the text.
5. **You** open [parsestream.com/replies](https://parsestream.com/replies), read the
   draft, edit it, post it yourself.
6. **`mark_mention_replied`** — and the thread stops showing up in `statuses: ["new"]`.

Ask for `get_brand_context` before a batch of drafts and the assistant will tell you the
exact balance, the reset date, and whether the account can draft at all.

### Things that will trip you up

- **Two clocks.** `timeframe`/`from`/`to` cut the window on *detection* time, but results
  are ordered by *publish* time. A mention posted a week ago and detected an hour ago
  falls inside a `1d` window and sorts onto the *last* page. So a `from` cursor is only
  safe if you page a window to exhaustion (`pagination.hasNext` false) before advancing
  it.
- **Empty means "all".** Every list filter — `platforms`, `intents`, `statuses`,
  `keywordIds`, `subreddits`, `mentionTypes` — means "no narrowing" when omitted or
  empty, never "none".
- **`intents` excludes untagged mentions.** The six tags are `BUY_INTENT`,
  `PRODUCT_QUESTION`, `COMPETITOR_COMPLAINT`, `PAIN_POINT`, `TESTIMONIAL` and
  `PROMOTIONAL`. Intent tagging is opt-in per account, so any non-empty `intents` list
  drops mentions the tagger never classified — including a list naming all six, which
  means "tagged with anything", not "no filter".
- **`statuses: ["new"]` means untouched.** It excludes highlighted mentions as well as
  replied ones, so starring a mention takes it out of your new queue.
- **`keywordIds` fails quietly.** Unrecognised IDs are dropped from the filter rather
  than rejected, so a typo silently narrows the search instead of erroring.
- **30 days, 30 per page.** History is capped at 30 days; asking for older returns a
  clear error rather than empty results. A larger `perPage` is clamped to 30, not
  rejected.

## What this server cannot do

Worth stating plainly, because assistants sometimes claim otherwise:

- **It never posts.** No tool publishes to Reddit, X, LinkedIn, Quora or Hacker News.
  The most it can do is write a draft into your Drafts queue.
- **It never contacts anyone.** No DMs, no emails, no notifications to mention authors.
- **It only ever touches your own brand's records.** The brand comes from the API key,
  never from a tool argument, so cross-tenant access is impossible by construction. A
  mention belonging to another brand reports as not found, exactly like an ID that never
  existed.
- **It deletes nothing.** The only writes are one reply draft and two boolean flags on
  your own mention rows.
- **`draft_reply` returns a draft, not a reply.** A human opens
  [parsestream.com/replies](https://parsestream.com/replies), reads it, edits it and
  sends it. If your assistant says it posted something, it did not.

Reads work on any plan. `draft_reply` needs an active subscription or trial and at least
5 credits — the same gate as the **Suggested reply** button in the app.

## Troubleshooting

**No tools show up.** Nearly always the header. It must be exactly
`Authorization: Bearer ps_live_...`, including the word `Bearer`.

**401 Unauthorized.** Read the `error_description`. If your config references an
environment variable that was never substituted, the server says so and names the
variable — set it and restart the client; see
[Connecting without exposing your key](#connecting-without-exposing-your-key). Any other
401 means the key really is mistyped or revoked, so mint a fresh one under Brand
Settings → API Keys.

**Searches come back empty.** Ask for brand context first — the keyword may be paused,
or not monitored on the platform you asked about. `get_brand_context` returns paused
keywords rather than hiding them, precisely so this is answerable.

**Drafting is refused.** No active subscription, or the balance is under 5 credits.
`plan.canDraftReplies` from `get_brand_context` answers this in one field. Reading keeps
working either way.

**429 Too Many Requests.** 100 per minute per key, shared with the REST API, and every
JSON-RPC call counts. Wait out the `Retry-After`, or use a separate key per client.

To take your client out of the picture entirely, call the server directly. Streamable
HTTP needs both content types in `Accept`:

```bash
curl -sS -X POST https://mcp.parsestream.com/mcp \
  -H "Authorization: Bearer ps_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

Drop the `Authorization` header and you should get a 401 whose `error_description` tells
you exactly what the server wanted — which is a quick way to confirm you are reaching
ParseStream and not a proxy.

## Links

- [parsestream.com](https://parsestream.com)
- [Full MCP documentation](https://parsestream.com/help#mcp)
- [Mentions REST API](https://parsestream.com/help#api)
- Support: [support@parsestream.com](mailto:support@parsestream.com)

## License

MIT — see [LICENSE](LICENSE).