Skip to content
grepvine.aiJoin preview

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.

Add the server for all your projects:

Terminal window
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:

.mcp.json
{
"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:

.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"]
}
}

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.

  1. Open Customize > Connectors and click Add custom connector. On a Team or Enterprise plan, an Owner adds it under Organization settings > Connectors instead.
  2. Enter https://api.grepvine.ai/mcp as the URL.
  3. For Authentication, choose No sign-in.
  4. Under Request headers, choose authorization and enter Bearer gvk_..., including Bearer and 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.

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:

claude_desktop_config.json
{
"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.

LangChain’s MCP adapters turn the server’s tools into LangChain tools that a DeepAgents agent can call:

Terminal window
pip install deepagents langchain-mcp-adapters

This 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 asyncio
import os
from deepagents import create_deep_agent
from 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 ainvoke or astream. 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 out create_index and put_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.

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:

Terminal window
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.

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.

The tools are sized for a model’s context and use simpler names than the API:

  • Indexes are one argument, index: "acme/support". list_indexes and create_index take organization instead.
  • search options are literal, ignore_case, whole_word (literal mode only), multiline and filter, which uses the filter syntax. lines_before and lines_after go 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 with get_document.
  • Pages default to 100 results for search and 50 for the listings. While has_next is true, call the tool again with page set to next_page and every other argument unchanged. A page can be empty while more results follow.
  • get_document reads from offset and returns next_offset until it reaches the end. For a binary document it returns is_text: false and no content; download it with gv get.
  • put_documents takes metadata in the typed form, up to 64 fields and 4 KiB per document. An optional idempotency_key of 16 to 128 characters makes a retry with the same documents safe. Use gv upload for larger imports.

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.

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.