Most "AI in your GTM stack" is a chat box bolted onto a dashboard, answering questions about data you would have found by clicking twice.
The useful version is the other way round: the assistant your team already works in (Claude, ChatGPT, Claude Code) gets the ability to read your live signal workspace as a tool, mid-conversation, alongside everything else it can do. Then "which competitor is bleeding customers fastest this quarter, and pull me the three strongest quotes" is one message, and the answer cites real rows from this morning's run.
Openpulse is an MCP server at /mcp. This post covers what it can do, how to connect it, why the tool surface is shaped the way it is, and what it deliberately refuses to do.
What you can ask, once it is connected
Concrete prompts, not capabilities:
"Which of our tracked competitors has the steepest increase in complaints over the last 30 days? Show me the top themes and the strongest quote for each."
"Pull the high-intent signals from the last week that mention pricing. Group them by the objection being raised and draft a one-paragraph response to each group for our FAQ."
"Our last three runs kept far fewer signals than usual. Look at the quality report and tell me which queries stopped producing."
"Read the last 40 signals on the Toronto dental listener and tell me which three practices to call first, and why."
That last one is the shape that matters. It is not a query but a judgement over a body of evidence, and it is exactly what an assistant is good at and a dashboard filter is not.
The assistant can also write back, in one narrow and deliberate way: it can rate a signal. So "read these twenty and thumbs-down anything that is a competitor's own marketing post" is a valid instruction, and those ratings are inlined into that listener's next classification prompts as calibration. The agent improves the filter it is reading through.
The eight tools
| Tool | Does | Role required |
|---|---|---|
list_workspaces | The workspaces you can access | member |
list_listeners / get_listener | What is being watched | member |
search_signals | Signals, filtered by listener, class, source, score, window | member |
get_rollup | Rivals, accounts or places, one row per entity | member |
list_runs | Recent runs and their status | member |
get_quality_report | Per-query and per-source yield | member |
rate_signal | Thumbs up/down, which calibrates the next pass | member |
trigger_run | Queue a run now | admin |
What is deliberately missing
There is no create, update or delete for listeners.
An agent that can create a listener can commit your workspace to recurring search and model spend on every schedule tick, forever, from a single misunderstood instruction. That is a real bill arriving from a sentence nobody read carefully. And no read tool needs that power to be useful: the entire value above is available from reading.
This is the same reasoning that puts trigger_run behind an admin role while everything else is member-level: a run spends money, so it is a different class of action from reading one.
Run cost is never exposed through any tool. It is an internal operating figure, and putting per-run spend in front of a customer's agent publishes unit economics that are nobody's business but ours. The dashboard's usage view answers the question a customer actually has (how much am I using) without answering the one they did not ask.
Connecting it
In the app: Settings → Connect AI, at the bottom of the sidebar. The dialog gives you the server URL and per-client steps for Claude, ChatGPT, Grok and the Claude Code CLI.
Two things in that dialog are worth calling out because they are the difference between "it works" and a support ticket that says "it doesn't work" with no further detail.
The URL is derived, never hardcoded. It is ${APP_URL}/mcp, and the two deployment shapes disagree about where that is: in production one container serves the API and dashboard on one origin, while in development the dashboard is on :5173 and the API on :4000. A constant would be right in exactly one of them and silently wrong in the other, and the failure mode is a connector that fails to authorise with nothing on screen naming the cause.
The dialog teaches you the first prompt, which is "List my Openpulse workspaces", and shows your current workspace id. That is not hand-holding. It is compensating for a real structural property of the protocol, explained below. Without it, a correctly connected client looks broken.
The dialog also states each vendor's plan requirement up front: ChatGPT custom connectors need a paid plan and the web app; Grok's need a paid tier. A free-plan user can otherwise follow every step correctly and still fail, with nothing on screen explaining why.
The Claude Code one-liner
claude mcp add --transport http openpulse https://openpulse.cloud/mcpThen /mcp in a session to complete the OAuth flow in your browser.
How the auth works, and why it is not a token you paste
This is the part most remote MCP servers get wrong, and the failure is invisible until a client tries to connect.
Clerk is the OAuth 2.1 authorization server. It runs the consent screen and mints the tokens. Openpulse is only a resource server: it verifies bearer tokens and serves data, and it issues nothing. There is no token endpoint anywhere in the codebase, which is what the MCP specification requires.
GET /.well-known/oauth-protected-resource/mcp # RFC 9728, who authorises us
GET /.well-known/oauth-authorization-server # the authorization server's metadata
POST /mcp # the JSON-RPC endpointBoth discovery documents are public and CORS-open by design: a client has to read them before it has a token, so gating them behind auth is a deadlock.
An unauthenticated call to /mcp returns a 401 carrying:
WWW-Authenticate: Bearer resource_metadata="https://openpulse.cloud/.well-known/oauth-protected-resource/mcp"Without that pointer, a client cannot discover where to authorise, and that is the most common way a remote MCP server ends up looking broken rather than merely unauthenticated. If you are building your own server, this header is the single highest-value thing to get right.
For you as a user, none of this is visible. You click connect, a consent screen appears, you approve, and the tools show up. No key to paste, nothing to rotate, and revoking access is a click in Clerk rather than a search for where you pasted a secret.
Why workspace_id is an argument, and why that is the security core
This is the one structural difference from the dashboard, and it is worth understanding because it explains a bit of friction you will otherwise find strange.
In the browser, your session token carries the active organisation as a claim the client cannot forge. The server just trusts it.
An OAuth access token identifies a user and says nothing about a workspace. So over MCP, workspace_id arrives as a tool argument chosen by a language model, which makes it completely untrusted input: the same category as a string typed by an anonymous user.
Every tool therefore resolves it through a membership check against Clerk before any query runs. Two consequences:
A workspace you do not belong to is reported as not available, never as forbidden. "Forbidden" confirms the id exists, which turns the tool surface into an enumeration oracle. "Not available" is the same answer for a workspace that does not exist and one you cannot see.
Membership is read from Clerk, not from our database, because Clerk is where it changes. A cached copy would keep saying yes for somebody removed from the workspace this morning, and access revocation that takes effect "eventually" is not access revocation.
The practical upshot: your agent's first call is always list_workspaces. It cannot guess an id, and it should not try. Hence the dialog teaching that first prompt.
What it is genuinely good at
Cross-referencing, which is where dashboards run out. "Which of the complaints about our competitor's onboarding overlap with the objections in our own high-intent signals?" is two search_signals calls and a judgement. There is no filter combination in any UI that answers it.
Reading the quality report like an operator. get_quality_report returns per-query and per-source yield. An agent reading it will notice that three of your fourteen queries have produced candidates but no signals for weeks, tell you which, and propose replacements, which is exactly the maintenance work that nobody does and that silently degrades every listening tool over six months.
Bulk triage with a reason. search_signals then rate_signal, twenty at a time, with the agent explaining its calls as it goes. Because ratings feed back into the classification prompt, a half-hour of this measurably improves the next week's precision. The feedback loop is the product; the agent is a faster way to close it.
Being where the work already is. In Claude Code, the signals are available in the same session as your codebase: "these five signals complain about our onboarding flow, find the relevant components" is one conversation instead of two tools and a copy-paste.
What it is not
It is not a replacement for webhooks. MCP is pull: an agent asks when a human is in the loop. If you want a signal to reach Slack at 3am without anyone asking, that is a webhook. The two are complementary and most workspaces that use one end up using both.
It is not an autonomous outbound agent. There is no send tool, in MCP or anywhere else in the product, and there will not be one. The product's claim is that it finds people worth talking to; it is not that it talks to them. The moment outreach can go out unread, signal quality stops mattering, because every reply is to something nobody checked.
It cannot configure anything. No listener creation, no plan changes, no webhook registration, no key minting. Read, rate, and, as an admin, trigger a run.
Latency is what it is. Tool calls hit the same paginated API the dashboard uses. Asking an agent to read four hundred signals means several round trips and it will feel like it. Narrow with filters first: classification, source, minScore and the window are all parameters.
An operator note, if you self-host
Skip this unless you run your own deployment.
Clerk ships with client registration off, so until you change it, discovery works and authorization fails, which is the worst possible combination, because /.well-known/… returns a valid document and every client fails only at the moment it tries to register.
clerk api /instance/oauth_application_settings -X PATCH \
--secret-key "$CLERK_SECRET_KEY" \
-d '{"client_id_metadata_documents_advertised": true,
"dynamic_oauth_client_registration": true}'Enable both, deliberately. The 2026-07-28 spec prefers Client ID Metadata Documents (the client's client_id is an HTTPS URL pointing at its own metadata, so no unauthenticated registration endpoint needs to exist) and deprecates Dynamic Client Registration. But deprecated is not removed, and most shipping MCP clients still register through DCR, so CIMD alone turns them away.
These settings are per instance. Configuring development says nothing about production, and the failure is silent in exactly the way described above. Verify against the deployed origin rather than against the settings you just wrote:
curl -s https://your-domain/.well-known/oauth-authorization-server \
| grep -o '"registration_endpoint"[^,]*'One more: MCP_RESOURCE_URL defaults to ${APP_URL}/mcp and is the audience tokens are bound to (RFC 8707). Point APP_URL at the wrong host and every token is rejected for the wrong audience, with no error message that says so.
Availability
MCP connectors are on every plan, including Go at $49/mo. It is not an enterprise upsell: an agent reading a workspace is a read, and gating reads behind a tier would make the cheap plan a demo rather than a product. Both plans open with a 14-day trial that takes a card up front.
The first prompt, once you are connected:
List my Openpulse workspaces.
See it on your own market
Paste your website, review the plan it proposes, and read what comes back tomorrow morning.
Questions about anything here? Email support@openpulse.cloud.