groove-mcp
Groove MCP Server (read-only, AP research)
A Model Context Protocol (MCP) server for researching accounts-payable chatter in Groove HQ — look up a vendor's conversations, drill into messages, and get per-channel context.
How it works — data sources (important)
This fork does not read conversations over GraphQL. Live introspection with
a real admin token proved the v2 GraphQL conversation surface is not
accessible to our token: there are no conversations / conversation /
messages root query fields, and the Conversation / Message /
ConversationFilter types do not exist for this credential. Groove REST v1
(GET /v1/tickets) does return real data, so it is the only reachable path to
conversation data.
Domain | API used | Notes |
Conversations, messages | Groove REST v1 ( | Server-side filtered + fully paged. |
Contacts | Groove GraphQL v2 ( | Reachable for this token. |
Agents / channels / KB | Groove GraphQL v2 | May be unavailable depending on token scope; AP-only tokens may not see these. |
The GraphQL conversation/message queries that shipped upstream were dead
code and have been removed (the GraphQL client is retained only for the
contacts path). See docs/audit/assumption-inventory.md for the full trail.
Authentication uses an Authorization: Bearer <token> header — the token is
never placed in a URL/query string.
Installation
Get your GROOVE_API_TOKEN from
https://YOUR_SUBDOMAIN.groovehq.com/settings/developer/api.
Then add the server (local build):
npm install && npm run build
claude mcp add groove-mcp node ~/path/to/groovehq-mcp/dist/index.js -s user \
--env GROOVE_API_TOKEN=your_groove_api_token_hereGROOVE_API_URL (GraphQL endpoint, used only for the contacts path) defaults to
https://api.groovehq.com/v2/graphql. The REST v1 base is
https://api.groovehq.com/v1.
Available Tools
Conversations (REST v1)
listConversations — vendor/account conversation lookup, filtered server-side and paged to completion. Returns:
{ "pagination": { "total_count": 102, // Groove's true total for this query "returned": 102, // how many are in this response "complete": true, // returned === total_count (nothing hidden) "truncated": false, // true only if capped by maxResults "pages_fetched": 3, "per_page": 50, "note": "Complete: all 102 matching conversation(s) returned." }, "filtersApplied": { "customer": "artubing@tejastubular.com" }, "conversations": [ /* … */ ] }Always check
pagination.complete/total_count. Ifcompleteisfalse, the result is a partial set and must not be treated as exhaustive.Parameters (each pushed to Groove v1 — no client-side filtering):
customer— vendor email or Groove contact id (Groove v1customer). The correct way to get all of a vendor's conversations.contactId— back-compat alias forcustomer.state—unread | opened | closed | snoozed(v1state).assignee— assignee email/id (v1assignee).folder— folder id (v1folder).maxResults— explicit cap on total results. Omit to return every match.channelId,tagIds— not supported by Groove v1/tickets; if supplied they are not applied and are reported underunsupportedFilters(never silently approximated).
getConversation — one conversation by bare ticket id (
id, required).listMessages — messages for a conversation by bare ticket id (
conversationId, required; optionallimit).
Write operations (
createConversation,updateConversation,closeConversation,sendMessage,createNote,createContact,updateContact) are still registered from upstream but are out of scope for this read-only server and should be stripped in a follow-up; several are non-functional against this token.
Contacts (GraphQL v2)
listContacts —
search,limit,after.getContact —
id(required).
Agents / Knowledge Base (GraphQL v2, scope-dependent)
listAgents, getAgent, getAvailableAgents, searchKbArticles — may return authorization errors on AP-scoped tokens (surface not guaranteed).
Known constraint — invoice-number search is not yet built
The core AP use case is finding a conversation by invoice number. Groove v1
/tickets has no keyword/full-text search parameter. Invoice numbers live in
ticket titles (e.g. "Invoice 336205 from Tejas Tubular"), so search must be
done differently. See docs/audit/search-constraint.md for the options
(scope-by-customer-then-match-titles vs. a dedicated Groove search endpoint) —
by design this is documented, not implemented, pending a design decision.
Development
npm run build # tsc
npm run typecheck # tsc --noEmit
node scripts/smoke-pagination.test.mjs # e2e pagination smoke test (mocked network)Project Structure
├── src/
│ ├── index.ts # MCP server entry point + tool wiring
│ ├── rest-client.ts # Groove REST v1 client (conversations/tickets) — Bearer auth
│ ├── groove-client.ts # GraphQL v2 client wrapper (contacts path)
│ ├── tools/
│ │ ├── conversations.ts # REST v1: server-side filter + full pagination
│ │ ├── messages.ts # message tools
│ │ ├── contacts.ts # GraphQL v2
│ │ ├── channels.ts
│ │ └── agents.ts
│ ├── resources/kb-articles.ts
│ ├── types/groove.ts
│ └── utils/graphql-queries.ts # GraphQL (contacts + legacy write mutations)
├── scripts/
│ ├── introspection-gate.sh # Step 0 schema gate (needs a scoped token)
│ └── smoke-pagination.test.mjs # e2e pagination proof
├── docs/audit/ # assumption inventory, decisions, constraints
└── dist/ # compiled outputLicense
MIT (fork of christiangenco/groove-mcp).