OpenCode (the open-source terminal agent from SST, distinct from any similarly-named tools) takes a more explicit, config-file-first approach to MCP than Claude Code does. There's no mcp add subcommand ceremony — you declare servers directly in opencode.json, choosing between a local (stdio, spawns a subprocess) or remote (HTTP/SSE) server type. That explicitness is a double-edged sword: less magic, but also less hand-holding when something's misconfigured.
This topic covers getting Notion MCP running under OpenCode, the read/write patterns that work well from its agent loop, a full worked example turning a raw meeting transcript into a requirements page, and — honestly — where OpenCode's tool-calling still falls short of Claude Code's for this specific integration.
Installing and Connecting Notion MCP to OpenCode
OpenCode reads MCP configuration from opencode.json in the project root, or from ~/.config/opencode/config.json for global servers. The schema uses an mcp object keyed by server name, with a type field distinguishing local subprocess servers from remote HTTP ones.
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"notion": {
"type": "local",
"command": ["npx", "-y", "@notionhq/notion-mcp-server"],
"environment": {
"OPENAPI_MCP_HEADERS": "{\"Authorization\": \"Bearer ntn_your_token_here\", \"Notion-Version\": \"2022-06-28\"}"
},
"enabled": true
}
}
}
For the hosted remote server instead:
{
"mcp": {
"notion": {
"type": "remote",
"url": "https://mcp.notion.com/mcp",
"enabled": true
}
}
}
OpenCode's remote MCP support handles the OAuth handshake by opening a local callback listener and your default browser — it works, but expect it to be slightly less polished than Claude Code's equivalent flow (occasional need to re-authenticate after the CLI restarts, where Claude Code persists the session more reliably).
Because the token in the local config example above is inlined into environment, treat opencode.json as sensitive if you go this route — better practice is to reference an env var OpenCode inherits from the shell:
{
"mcp": {
"notion": {
"type": "local",
"command": ["npx", "-y", "@notionhq/notion-mcp-server"],
"environment": {
"OPENAPI_MCP_HEADERS": "{\"Authorization\": \"Bearer {env:NOTION_TOKEN}\", \"Notion-Version\": \"2022-06-28\"}"
}
}
}
}
export NOTION_TOKEN="ntn_your_internal_integration_secret_here"
opencode
Confirm the server loaded:
> /mcp
notion local connected (19 tools)
If it shows failed instead, the most common causes are: npx not resolvable in OpenCode's spawned shell (check PATH inheritance), a malformed OPENAPI_MCP_HEADERS JSON string (easy to break when hand-editing quotes), or a token that hasn't been shared with any pages yet — that last one won't fail the connection, but every tool call will 404.
Tips
- ValidateOPENAPI_MCP_HEADERSas JSON before saving — a single unescaped quote silently breaks the server with an unhelpful spawn error, not a clear parse error.
- Run/mcpright after any config change; OpenCode doesn't always hot-reload MCP servers mid-session, so a server edit sometimes needs a full restart to take effect.
- Keep alocalfallback configured even if you mainly useremote— CI runners without a browser can't complete OAuth, so scripted OpenCode runs need the token-based path.
Reading Pages and Writing Structured Content from OpenCode
Once connected, the read/write patterns are conceptually identical to Claude Code — same underlying Notion API, same tools — but OpenCode's agent loop tends to be more literal about following numbered instructions and less likely to infer intent from a vague prompt, so being explicit pays off more here.
opencode> Search Notion for the page titled "API Design Guidelines". Retrieve
its full content and summarize the section on pagination in 3 bullet points.
For writing, OpenCode handles simple, well-scoped block creation reliably:
opencode> Create a new page under the "Engineering Wiki" page called
"Rate Limiting Strategy — Draft". Add these blocks in order:
1. A heading_2 block: "Context"
2. A paragraph block summarizing that we're moving from fixed-window to
token-bucket rate limiting
3. A heading_2 block: "Implementation Notes"
4. A code block (language: go) with a basic token bucket struct
This produces a straightforward API-post-page call followed by API-patch-block-children with the four blocks. Where OpenCode starts to struggle is longer, multi-section documents generated in a single shot — it's more prone than Claude Code to flattening a nested structure (a toggle containing a list becomes a toggle containing a single run-on paragraph) when the instruction has more than roughly 5-6 structural elements. Breaking a big doc-generation task into two or three smaller append calls sidesteps this reliably.
opencode> First, create the page with just the title and an empty "Context"
heading. Confirm the page ID with me, then I'll ask you to append each
remaining section separately.
That extra confirmation step costs a little time but consistently produces cleaner block trees under OpenCode than a single large generation request.
Tips
- Keep single write requests to roughly 5 blocks or fewer for OpenCode; split larger documents into sequential append calls rather than one big generation.
- Ask OpenCode to echo back the page ID or URL after creating a page — it's a cheap sanity check before you build a multi-step workflow on top of an assumption about what got created.
- OpenCode respects explicit block-type names (heading_2,code,toggle) just as well as Claude Code does — the reliability gap is specifically about instruction density per call, not vocabulary.
Practical Example: Turning a Meeting Transcript into a Requirements Page
A realistic, recurring task: you have a raw transcript (from a planning call, a customer interview, a design review) and you want a clean requirements page in Notion, not a copy-paste of the transcript.
cat transcript.txt
opencode> Read transcript.txt. It's a raw meeting transcript from a planning
call about a new "saved searches" feature. Extract:
- The actual requirements being discussed (ignore small talk and tangents)
- Any explicit acceptance criteria mentioned
- Open questions that were raised but not resolved
- Who owns what, if stated
Then create a Notion page under the "Product Specs" database with:
- Title: "Saved Searches — Requirements"
- Status property: "Draft"
- Content structured as: Overview, Requirements (numbered list), Acceptance
Criteria (to_do blocks, unchecked), Open Questions (bulleted list)
Do not include verbatim transcript quotes except where a specific requirement
was stated in exact terms that matter (e.g. a numeric limit).
The "do not include verbatim quotes" instruction matters — without it, models tend to over-anchor on transcript phrasing, producing a page that reads like a lightly-cleaned chat log instead of an actual requirements doc. The distinction between extraction and transcription is worth stating explicitly every time.
Expected tool sequence: one API-post-database-query or API-retrieve-a-database call to confirm the "Product Specs" schema and its Status options, one API-post-page to create the row with properties set, then API-patch-block-children to append the structured body. Using to_do blocks (not plain bullets) for acceptance criteria is deliberate — it means the resulting checklist is directly actionable in Notion, not just descriptive text someone has to re-type as tasks later.
{
"object": "block",
"type": "to_do",
"to_do": {
"rich_text": [{ "type": "text", "text": { "content": "Support up to 20 saved searches per user" } }],
"checked": false
}
}
Tips
- Explicitly instruct "extract, don't transcribe" — it's the single biggest lever for turning a messy transcript into a page that reads like a real spec.
- Useto_doblocks for acceptance criteria instead of bullets whenever the criteria are genuinely actionable — it turns the page into a working checklist, not just a description.
- Have the agent flag ambiguous or contradictory statements from the transcript as explicit "Open Questions" rather than silently picking one interpretation — it surfaces disagreements the team actually needs to resolve.
Known Limitations for Notion MCP in OpenCode
Being straightforward about where this combination falls short:
- Tool-call chaining depth. Long chains (search → retrieve → transform → write → verify) are more prone to OpenCode dropping context or repeating a call unnecessarily compared to Claude Code, particularly on smaller backend models. If you're running OpenCode against a lighter local or open-weight model rather than a frontier one, expect more manual steering on multi-hop Notion tasks.
- Remote OAuth session stability. As mentioned above, the
mcp.notion.comremote connection under OpenCode occasionally requires re-authentication after a CLI restart in a way Claude Code's equivalent doesn't — not a dealbreaker, but plan for it in any workflow you intend to leave running unattended. - No built-in tool-output truncation guardrails. A wide database query or a page with a deep block tree can return a large payload; OpenCode doesn't automatically summarize or paginate this the way some other clients do, so a broad
API-post-database-queryagainst a database with hundreds of rows can eat a large chunk of context in one call. Filter aggressively. - Complex nested block generation. As covered above, deeply nested structures (toggles-within-toggles, tables with formatted cells) in a single write are less reliable than in Claude Code. Flattening the structure or splitting the write is the practical workaround, not a config fix.
- No native "dry run" or diff preview for writes. Unlike some git-integrated tools, there's no built-in way to preview a block tree before it's actually created — your only safety net is prompting for a plan-then-confirm pattern manually, as shown earlier.
None of these are blockers for real use — they're reasons to keep OpenCode tasks scoped and to verify output rather than assuming a large generation task landed correctly.
Tips
- If you're running OpenCode against a non-frontier backend model, keep Notion tasks to 1-2 tool calls per turn and verify results before chaining further steps.
- Always add explicit filters to database queries (Status,Assignee, a date range) rather than querying an entire database — it protects both your context budget and your rate limit.
- For anything structurally complex, plan for a follow-up "read back what you just created" verification step — it's cheap and catches flattened or malformed blocks before they sit unnoticed in a wiki.
Tips
OpenCode's explicit config model makes it a solid, transparent choice for Notion MCP — you can see exactly what's being spawned and with what credentials — but its agent loop needs more explicit, smaller-scoped instructions than Claude Code to get equally clean results on multi-block writes.
Tips
- Prefer thelocalserver type with a scoped, revocable integration token for anything you'll run unattended or in CI.
- Break document generation into smaller sequential writes rather than one large one — it's the single most effective mitigation for OpenCode's block-flattening tendency.
- Build the habit of a quick read-back verification after any non-trivial write — it costs one extra tool call and saves you from discovering a malformed page days later.