MCP server
Grepvine runs an MCP server at https://api.grepvine.ai/mcp. It uses the Streamable HTTP transport and authenticates each request with an API key in the Authorization header:
Authorization: Bearer gvk_...There is no OAuth sign-in. The tools run as the key’s user, with that user’s role in each organization, and the same limits and audit trail as the API. Create a key in the console, or run gv login, which saves one to ~/.grepvine/config.yaml.
Connect Claude Code.
Section titled “Connect Claude Code.”Add the server for all your projects:
claude mcp add --transport http --scope user grepvine https://api.grepvine.ai/mcp \ --header "Authorization: Bearer $GREPVINE_API_KEY"Your shell expands $GREPVINE_API_KEY before Claude Code sees it, so the key itself is saved in ~/.claude.json. Run /mcp in a session to check that grepvine is connected.
To share the server with a team, commit a .mcp.json to the repository instead. Claude Code expands ${GREPVINE_API_KEY} when it starts, so each developer uses their own key, and it asks each developer to approve the server once:
{ "mcpServers": { "grepvine": { "type": "http", "url": "https://api.grepvine.ai/mcp", "headers": { "Authorization": "Bearer ${GREPVINE_API_KEY}" } } }}Claude Code asks before each tool call. To let it search without asking and never write, add rules to .claude/settings.json:
{ "permissions": { "allow": [ "mcp__grepvine__list_indexes", "mcp__grepvine__get_index", "mcp__grepvine__search", "mcp__grepvine__list_documents", "mcp__grepvine__get_document" ], "deny": ["mcp__grepvine__create_index", "mcp__grepvine__put_documents"] }}Connect Claude Desktop.
Section titled “Connect Claude Desktop.”Claude Desktop connects either through a custom connector, which Anthropic’s servers call for you, or through a local bridge started from its configuration file.
Add a custom connector.
Section titled “Add a custom connector.”- Open Customize > Connectors and click Add custom connector. On a Team or Enterprise plan, an Owner adds it under Organization settings > Connectors instead.
- Enter
https://api.grepvine.ai/mcpas the URL. - For Authentication, choose No sign-in.
- Under Request headers, choose
authorizationand enterBearer gvk_..., includingBearerand the space.
Claude offers Request headers to some organizations only. If your dialog doesn’t have it, use the bridge below. On a Team or Enterprise plan, every member who connects uses the key the Owner entered, and so acts as that key’s user. Use a key whose user has the reader role, unless members need to write.
To keep Claude from writing, select the connector under Customize > Connectors and set create_index and put_documents to Blocked.
Run a local bridge.
Section titled “Run a local bridge.”mcp-remote is a third-party package that runs on your computer and relays Claude Desktop’s requests to the server. It needs Node.js 18 or later. Add it to claude_desktop_config.json, which is in ~/Library/Application Support/Claude/ on macOS and %APPDATA%\Claude\ on Windows:
{ "mcpServers": { "grepvine": { "command": "npx", "args": ["-y", "mcp-remote", "https://api.grepvine.ai/mcp", "--header", "Authorization:${AUTH_HEADER}"], "env": { "AUTH_HEADER": "Bearer gvk_..." } } }}mcp-remote reads the header value from AUTH_HEADER, which keeps the space after Bearer out of args. Claude Desktop on Windows doesn’t quote arguments that contain spaces. Quit and reopen Claude Desktop after editing the file.
Connect a DeepAgents agent.
Section titled “Connect a DeepAgents agent.”LangChain’s MCP adapters turn the server’s tools into LangChain tools that a DeepAgents agent can call:
pip install deepagents langchain-mcp-adaptersThis agent investigates customer incidents. A subagent does the searching, so raw results stay in the subagent’s context and the main agent receives only its summary:
import asyncioimport os
from deepagents import create_deep_agentfrom langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({ "grepvine": { "transport": "streamable_http", "url": "https://api.grepvine.ai/mcp", "headers": {"Authorization": f"Bearer {os.environ['GREPVINE_API_KEY']}"}, },})
async def main() -> None: tools = await client.get_tools() read_only = [tool for tool in tools if (tool.metadata or {}).get("readOnlyHint")] agent = create_deep_agent( model="anthropic:claude-sonnet-5-5", system_prompt=( "You investigate customer incidents at Acme. Delegate every search to the " "grepvine-researcher subagent, and cite ticket keys and file:line locations." ), subagents=[{ "name": "grepvine-researcher", "description": "Searches the acme/support tickets and the acme/monorepo code, and reports where something appears.", "system_prompt": ( "Search with Grepvine. Start with a literal phrase and a path filter, ask for at most " "50 results with 2 lines of context, and widen only if that finds too little. " "Reply with each key and line number and one sentence on what it shows." ), "tools": read_only, }], ) result = await agent.ainvoke({"messages": [{"role": "user", "content": ( "Customers say refunds have been stuck in pending since Tuesday. " "Find the tickets and the code that sets a refund to pending." )}]}) print(result["messages"][-1].content)
asyncio.run(main())- Run the agent with
ainvokeorastream. The adapted tools are async only. - Keep the agent read-only. The server marks its five read tools with
readOnlyHint, and the filter above leaves outcreate_indexandput_documents. - Let the model fix its own calls. A tool error, such as a filter that doesn’t parse, reaches the model as an error result with the message, and the run continues.
Connect another client.
Section titled “Connect another client.”Any MCP client that supports Streamable HTTP and custom headers needs the URL and the Authorization header above. To check a key without a client, list the tools with curl:
curl -sS https://api.grepvine.ai/mcp \ -H "Authorization: Bearer $GREPVINE_API_KEY" \ -H "Accept: application/json, text/event-stream" \ -H "Content-Type: application/json" \ -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'A 401 means the key is missing, mistyped or revoked.
Tools.
Section titled “Tools.”| Tool | What it does | Annotation |
|---|---|---|
list_indexes |
Lists the indexes you can access, with each one’s description, tags, state, document count and size. Without organization, it lists every organization’s indexes. |
Read-only |
get_index |
Reads one index’s description, tags, state, document count and size. | Read-only |
create_index |
Creates an empty index, optionally with a Markdown description. Needs the admin or owner role. |
Not read-only |
search |
Searches one index and returns each match with its key, line numbers and context lines. | Read-only |
put_documents |
Creates or replaces up to 256 text documents, with up to 2 MiB of content in total. Each document gets its own result. | Destructive |
list_documents |
Lists keys under a prefix, folded at a delimiter such as /. |
Read-only |
get_document |
Reads a document as text, 256 KiB at a time by default and up to 1 MiB. | Read-only |
When a client connects, the server also sends it instructions on addressing indexes, paging and patterns, so your system prompt doesn’t need to repeat them.
Arguments.
Section titled “Arguments.”The tools are sized for a model’s context and use simpler names than the API:
- Indexes are one argument,
index: "acme/support".list_indexesandcreate_indextakeorganizationinstead. searchoptions areliteral,ignore_case,whole_word(literal mode only),multilineandfilter, which uses the filter syntax.lines_beforeandlines_aftergo up to 10 each.- Match offsets count bytes from the start of the result’s
text, not from the start of the document. - Text over 2 KiB, in a result or a context line, is cut and marked
text_truncated. Read the rest withget_document. - Pages default to 100 results for
searchand 50 for the listings. Whilehas_nextis true, call the tool again withpageset tonext_pageand every other argument unchanged. A page can be empty while more results follow. get_documentreads fromoffsetand returnsnext_offsetuntil it reaches the end. For a binary document it returnsis_text: falseand no content; download it withgv get.put_documentstakes metadata in the typed form, up to 64 fields and 4 KiB per document. An optionalidempotency_keyof 16 to 128 characters makes a retry with the same documents safe. Usegv uploadfor larger imports.
Errors.
Section titled “Errors.”A failed call returns a tool error whose text is the API’s message, its code and the request ID:
filter byte 9: expected a value; quote a string, as in 'open' (invalid_filter; request 0192f4c7-5b1e-7a3d-9c2f-1e8b6a4d2f10)When the service is throttling your requests, the text ends with the wait, as in ; retry after 2s. The server doesn’t retry the call; the agent can call the tool again after that time.
Example: trace an incident.
Section titled “Example: trace an incident.”Asked in Claude Code to “find the tickets about refunds stuck in pending since Tuesday, and the code that sets a refund to pending”, an agent makes calls like these:
{"tool": "list_indexes"}{"tool": "search", "index": "acme/support", "pattern": "stuck in pending", "literal": true, "ignore_case": true, "filter": "opened_at >= (CURRENT_DATE - INTERVAL '7 days')", "amount": 50, "lines_after": 2}{"tool": "search", "index": "acme/monorepo", "pattern": "RefundStatusPending", "literal": true, "whole_word": true, "filter": "path STARTSWITH 'services/billing/'"}{"tool": "get_document", "index": "acme/monorepo", "key": "services/billing/refund.go"}The index descriptions returned by list_indexes tell it which index holds tickets and which holds code, and that tickets carry an opened_at timestamp it can filter on.