poma-grill-mcp — Grill MCP Server
poma-grill-mcp is POMA AI's Model Context Protocol server for the Grill context engine. It exposes seven tools covering the full Grill loop: ingest (sync, async, batch, resume), search, job status, and a self-describing grill_explain. Two implementations ship in the same repo — a stable Go binary and a Node/TypeScript package — both speaking the same tool surface against the same backend. POMA also hosts the server, so you can use Grill from any MCP client without any local install.
- Repo: github.com/poma-ai/poma-grill-mcp
- Hosted endpoint:
https://mcp.poma-ai.com/grill/v1 - Implementations: Go (stable, default; Homebrew +
go install+ Docker), Node/TypeScript (npx,npm install -g, build-from-source) - Transports: stdio, streamable HTTP, Docker (Go only)
- License: MPL-2.0
This server does not expose PrimeCut tools. For
primecut_*tools (raw chunk JSON,.pomaarchive bytes), usepoma-mcp. The two servers can run side-by-side in the same client config.
When to use this server
Pick poma-grill-mcp when:
- You want prompt-ready RAG context from Grill without writing a single line of glue code in your agent.
- You're ingesting large files and want the server to read them from disk via
file_pathinstead of inflating tool calls with base64. - You want zero-install access via POMA's hosted endpoint at
https://mcp.poma-ai.com/grill/v1. - You're building a backend service that uploads via HTTP — the Go binary has a dedicated
POST /ingest-uploadendpoint that accepts raw bytes or multipart with the same auth as MCP.
1. Get an API key
Sign up at console.poma-ai.com, create a project with product: "grill" (Create a project), and capture the project API key returned at creation. The key starts with poma_prod_gr_… — account-level POMA_API_KEY (prefix poma_acc_…) is rejected at the /grill/* boundary.
2. Install (skip for hosted)
If you're going to use the hosted endpoint, you don't need to install anything — jump to step 3, Option B.
Go binary
brew tap poma-ai/poma-grill-mcp
brew install poma
which poma-grill-mcpgo install github.com/poma-ai/poma-grill-mcp@latest
which poma-grill-mcpgit clone https://github.com/poma-ai/poma-grill-mcp
cd poma-grill-mcp/go
go build -o bin/poma-grill-mcp .
realpath bin/poma-grill-mcpNode / TypeScript
Requires Node 20+. Published as @poma-ai/poma-grill-mcp on npm.
# Most MCP clients can invoke npx directly — no separate install needed
npx -y @poma-ai/poma-grill-mcp -input -npm install -g @poma-ai/poma-grill-mcp
poma-grill-mcp -input -git clone https://github.com/poma-ai/poma-grill-mcp
cd poma-grill-mcp/node
npm install
npm run build
node dist/index.js -input -3. Add it to your agent
You have two options. Option B (hosted) is the easiest if your client supports type: "http" MCP servers.
Option A — local binary over stdio
Option A1: Go binary
{
"mcpServers": {
"poma-grill": {
"command": "/full/path/to/poma-grill-mcp",
"args": ["-input", "-"],
"env": { "POMA_API_KEY": "your-api-key" }
}
}
}{
"mcpServers": {
"poma-grill": {
"command": "/full/path/to/poma-grill-mcp",
"args": ["-input", "-"],
"env": { "POMA_API_KEY": "your-api-key" }
}
}
}{
"mcpServers": {
"poma-grill": {
"command": "/full/path/to/poma-grill-mcp",
"args": ["-input", "-"],
"env": { "POMA_API_KEY": "your-api-key" }
}
}
}Option A2: Node via npx (no install)
{
"mcpServers": {
"poma-grill": {
"command": "npx",
"args": ["-y", "@poma-ai/poma-grill-mcp", "-input", "-"],
"env": { "POMA_API_KEY": "your-api-key" }
}
}
}Option B — hosted HTTP endpoint (no install)
POMA hosts the server at https://mcp.poma-ai.com/grill/v1. Point your MCP client at it directly:
{
"mcpServers": {
"poma-grill": {
"type": "http",
"url": "https://mcp.poma-ai.com/grill/v1",
"headers": { "x-api-key": "your-api-key" }
}
}
}{
"mcpServers": {
"poma-grill": {
"url": "https://mcp.poma-ai.com/grill/v1",
"headers": { "x-api-key": "your-api-key" }
}
}
}The hosted server runs on POMA's infrastructure, so it cannot read paths on your laptop. Use
file_base64when calling it from a hosted client, or use a local binary (Option A) when you needfile_path.
After saving, restart the client. Sample prompt:
"Ingest
~/docs/contract.pdfinto POMA Grill using file_path, then search it for 'termination clause'."
Tools
poma-grill-mcp exposes seven tools covering the full Grill loop. Both Go and Node implementations expose the same surface.
| Tool | What it does |
|---|---|
grill_ingest | Starts ingest; returns job_id after upload. Does not wait for indexing to finish. |
grill_ingest_sync | Ingest + wait for terminal status. Returns job_id and the full events stream. |
grill_ingest_resume | Reconnect to an in-progress job's status stream and wait until terminal — without re-uploading. |
grill_ingest_batch | Upload up to 50 files with controlled concurrency. Returns job_ids as uploads complete. |
grill_jobs_status | Snapshot the status of up to 50 jobs in one call. No streaming. |
grill_search | Hybrid search returning prompt-ready context. Use doc_filter to scope to one doc. |
grill_explain | Returns a markdown explanation of how Grill works. No arguments, no auth — useful for self-documenting agents. |
doc_idvsjob_id: when an ingest reachesdone, the document'sdoc_idin Grill equals thejob_idreturned by ingest. Keep that value to feeddoc_filteron subsequent searches.
grill_ingest and grill_ingest_sync
Both tools accept the same arguments. Provide exactly one of file_path or file_base64.
| Argument | Type | Required | Description |
|---|---|---|---|
file_path | string | one-of | Path readable by the MCP server process (absolute or relative to its cwd). Best for large files; avoids giant JSON payloads. |
file_base64 | string | one-of | Standard base64 of the file bytes. Fine for small files. |
filename | string | no | Original basename (report.pdf). With file_path, defaults to the path basename; otherwise inferred from bytes. |
token | string | no | API key — overrides POMA_API_KEY for this call. |
Notes on file_path
- Works only when the server runs on the same machine as the file. The hosted MCP at
mcp.poma-ai.comcannot read laptop paths — usefile_base64from there, or run the server locally. GRILL_INGEST_ALLOWED_PREFIX(optional, env) — when set,file_pathmust resolve under that directory after symlinks are evaluated. Non-regular files are rejected. Use this in any environment where untrusted prompts can reach the server.GRILL_INGEST_MAX_BYTES(env, default 512 MiB) — caps the payload size. Set to0to disable the limit (use with care).
Picking grill_ingest vs grill_ingest_sync
| Use case | Tool |
|---|---|
| Agent will search immediately and needs the doc indexed first | grill_ingest_sync |
| Long ingest; agent will check back later; conversation context is precious | grill_ingest, then poll with grill_jobs_status or reconnect with grill_ingest_resume |
grill_ingest_resume
Reconnect to an already-running ingest job and wait for it to reach a terminal state. Use this when a previous grill_ingest returned a job_id (or the MCP connection dropped mid-ingest) and you don't want to re-upload the file.
| Argument | Type | Required | Description |
|---|---|---|---|
job_id | string | yes | Job ID from a prior ingest. |
token | string | no | API key. |
Returns the same job_id + events payload as grill_ingest_sync.
grill_ingest_batch
Upload up to 50 files in one tool call with controlled concurrency. Returns the job_ids as uploads complete — does not wait for indexing. Pair with grill_jobs_status to monitor.
| Argument | Type | Required | Description |
|---|---|---|---|
file_paths | array of string | yes | Up to 50 paths readable by the MCP server process. |
concurrency | integer | no | Upload concurrency. Default 5, max 10. Use 1 on free-tier accounts. |
token | string | no | API key. |
Returns { results, submitted_count, failed_count, quota_exceeded_count }. quota_exceeded entries (HTTP 403 from the queue) are retryable once running jobs finish.
grill_jobs_status
Get status snapshots for up to 50 jobs in a single call. Non-streaming — call on an interval if you want progress.
| Argument | Type | Required | Description |
|---|---|---|---|
job_ids | array of string | yes | Up to 50 job IDs to query. |
token | string | no | API key. |
Returns { results, pending_count, done_count, failed_count }. Each result has { job_id, status, is_terminal, error? }.
grill_search
Hybrid search across the project namespace. Result count is bounded server-side by relevance and a token budget — there is no top_k.
| Argument | Type | Required | Description |
|---|---|---|---|
query | string | yes | Natural-language search query. |
doc_filter | string | no | doc_id (= job_id from ingest) to restrict search to one document. |
exclude_doc_ids | array of string | no | Doc IDs to exclude from results (max 100). Useful in agent loops to avoid re-citing docs already shown. |
return_assets | boolean | no | Include asset references (figures, tables) in the context. |
return_page_images | boolean | no | Include page-image references. |
token | string | no | API key. |
Response (example):
{ "context": "<context>This is what is relevant […] inside your document.</context>" }The context field is the entire payload — drop it straight into your LLM prompt. See RetrievalContext format for the wrapper grammar (<doc>, <gap />, sandwich ordering, citations).
grill_explain
Self-documentation tool. Takes no arguments and requires no authentication — returns a markdown explanation of how Grill works (ingest, search, result format, how to get an API key). Useful for agents that want to introspect their own capabilities, and for "explain this server" probes.
Output shapes
grill_ingest_sync waits for a terminal status and returns events:
{
"job_id": "100c65a03a304aa343a1518aa79e8300-20260414T083549Z",
"events": [
{ "status": "queued" },
{ "status": "chunking" },
{ "status": "chunked" },
{ "status": "grilled" },
{ "doc_id": "xxxxxx-xxxxx-xxxxx-xxxxx" }
]
}grill_ingest (async variant) returns the same job_id immediately, without the trailing events.
On error, the MCP response sets isError: true and error describes the failure:
{ "error": "job failed: unsupported file type" }HTTP mode
Both Go and Node binaries support a long-lived HTTP server for custom integrations:
POMA_API_KEY=your-key poma-grill-mcp -http :8080 # Go
POMA_API_KEY=your-key node node/dist/index.js -http :8080 # Node- MCP endpoint:
POST http://localhost:8080/v1(standard streamable HTTP MCP). - Health check:
GET http://localhost:8080/health.
Large uploads — POST /ingest-upload (Go binary only)
The Go binary exposes a dedicated upload endpoint that bypasses MCP framing for big files. Same auth as MCP — x-api-key, Authorization: Bearer, or fall back to POMA_API_KEY on the server.
- Raw body: send file bytes as the body. Pass the basename via the
?filename=…query string or theX-Filenameheader. - Multipart:
Content-Type: multipart/form-datawith a part namedfile. - Response:
201 Createdwith{"job_id": "…"}. Size limits followGRILL_INGEST_MAX_BYTES.
curl -sS -X POST "http://localhost:8080/ingest-upload?filename=report.pdf" \
-H "x-api-key: $POMA_API_KEY" \
-H "Content-Type: application/octet-stream" \
--data-binary @./report.pdfThe returned job_id is the same one you'd pass to grill_search's doc_filter once indexing completes.
Docker (Go only)
# HTTP mode
docker run -e POMA_API_KEY=your-key -p 8080:8080 \
ghcr.io/poma-ai/poma-grill-mcp -http :8080
# Stdio (default entrypoint)
docker run -i -e POMA_API_KEY=your-key ghcr.io/poma-ai/poma-grill-mcpA Node Docker image is not currently published — use npx or npm install -g instead.
Flags and environment
| Flag | Default | Description |
|---|---|---|
-input <path|-> | — | Stdio mode: MCP on stdin (-) or a file path. |
-http <addr> | — | HTTP mode (e.g. :8080). Mutually exclusive with -input. |
Both Go and Node accept the same flags.
| Env var | Default | Description |
|---|---|---|
POMA_API_KEY | — | Grill project API key (prefix poma_prod_gr_…) used when no token is passed at call time. |
GRILL_INGEST_ALLOWED_PREFIX | unset | Restrict file_path to a directory tree (post-symlink). |
GRILL_INGEST_MAX_BYTES | 512 MiB | Max upload payload. 0 disables the limit. |
Tips for agent-driven workflows
- For large files, instruct the agent to use
file_path. Most clients default tofile_base64. A short system-prompt cue like "When the user gives a local path, preferfile_pathforgrill_ingest_sync" saves enormous amounts of tokens. - Capture
job_idafter ingest. The agent can then issue follow-upgrill_searchcalls scoped to that document withdoc_filter— this is the difference between "search my whole namespace" and "chat with this PDF" UX. - Combine ingest + search in one turn. With
grill_ingest_syncfollowed bygrill_search, the agent can answer a question about a freshly uploaded file in a single response. - Bulk ingest folders with
grill_ingest_batch+grill_jobs_status— submit everything once, poll status until terminal.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
file_path: not found | Hosted MCP is being asked to read a laptop path | Use file_base64, or run a local binary (Option A). |
payload too large | File exceeds GRILL_INGEST_MAX_BYTES | Raise the env, use /ingest-upload (Go HTTP mode), or POST directly to the REST API. |
forbidden / 401 on grill_* | API key is account-level (poma_acc_…) or belongs to a primecut project | Create a Grill project (projects) and use its project key (poma_prod_gr_…). |
file_path rejected with "outside allowed prefix" | GRILL_INGEST_ALLOWED_PREFIX is set | Move the file under the prefix, or unset the env if you trust the caller. |
Agent looks for primecut_* tools and finds nothing | poma-grill-mcp only ships grill_* tools | Install poma-mcp alongside this server. |
See also
poma-mcp— PrimeCut MCP server (primecut_*tools).- Grill quickstart
- Grill API reference
- RetrievalContext format