Reorganize the Context7 tab navigation and make every section
collapsible. Mintlify only honors `expanded` on nested groups, so each
tab's sections now live one level down under a wrapper group.
Navigation:
- Split the flat Overview and How To groups into Get Started, Libraries
and Account
- Group library pages by Ownership and Configuration
- Merge the SDK and agentic tool trees into API and SDKs; Vercel AI SDK
is now a sibling of TypeScript
- Collapse all sections by default except Get Started
- Remove group icons
Content:
- Rename agentic-tools/overview.mdx to agentic-tools.mdx, retitle it
"Agentic Tools" and add a redirect from the old URL
- Drop the work-in-progress warning from the TypeScript SDK guide
No pages were added or removed.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(mcp): require auth on /mcp when client is a plugin
Plugin hosts such as Claude Code only start OAuth for servers that 401 at
connect time. Matching Exa MCP, ?client=claude-code-plugin (any client
value containing "plugin") now gates /mcp the same way /mcp/oauth does,
while anonymous access on the public URL is unchanged.
Co-authored-by: Enes Gules <enesgules@users.noreply.github.com>
* chore(mcp): drop the SDK OAuth-helpers TODO
The v2 helpers (bearerAuthChallengeResponse, oauthMetadataResponse) assume
Bearer-only OAuth on a fetch() handler. This server also accepts API keys,
mixes anonymous and required routes, returns JSON-RPC 401 bodies, and
proxies authorization-server metadata live, so they are not a drop-in.
Co-authored-by: Enes Gules <enesgules@users.noreply.github.com>
* docs: keep the plugin client auth gate out of user-facing docs
The ?client=claude-code-plugin gate stays in the server and Claude plugin
URL. OAuth docs continue to describe /mcp/oauth only.
Co-authored-by: Enes Gules <enesgules@users.noreply.github.com>
* simplify Claude plugin auth tracking
* separate plugin and client metrics
* extract plugin request detection
* simplify MCP request handling
* extract authentication policy
* use OAuth for Claude Code plugin
* support API key or OAuth in Claude plugin
* simplify Claude plugin authentication
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Enes Gules <enesgules@users.noreply.github.com>
Co-authored-by: Fahreddin Özcan <ozcanfahrettinn@gmail.com>
* feat(plugins): add Context7 plugin for OpenCode
Adds @upstash/context7-opencode, an OpenCode plugin that registers the
hosted Context7 MCP server, the context7-mcp skill, a docs-researcher
subagent, and the /context7-docs command through the config hook.
* docs(opencode): keep ctx7 setup as the primary install path
* refactor(opencode): drop the component toggles and tighten the option boundary
The skill/agent/command toggles were speculative: OpenCode dedupes skills by
name, so running ctx7 setup alongside the plugin is already safe, and a user
who wants a component gone can define it themselves, which the plugin never
overwrites. Removing them collapses resolveOptions into resolveApiKey and
deletes the duplicated defaulting between option parsing and config assembly.
Also assert the MCP endpoints as literals. The tests compared against the same
constants they exercised, so a wrong URL would have passed.
* refactor(opencode): drop tests and collapse the plugin into one file
config.ts existed to give the tests a seam. With the tests gone the split was
indirection with no consumer, so the constants, the config assembly, and the
plugin entry now live in src/index.ts and nothing but the default export is
public. Passing skillsDir through an ApplyInput object also stops being
necessary once it is a module constant.
* fix(opencode): run the MCP server over stdio and drop the slash command
Verified against a real OpenCode 1.18.11 session: the remote transport does
not work. OpenCode opens the optional GET SSE stream, mcp.context7.com answers
405, and OpenCode marks the server failed and registers no tools, so the model
never sees resolve-library-id and falls back to inventing bash calls. A control
project with a plain remote mcp block and no plugin fails the same way, so this
is not plugin-specific. Running the server over stdio connects and the model
calls the tools.
The bundled skill and the agent prompt also referenced the bare tool names.
OpenCode prefixes MCP tools with the server name, so they are now
context7_resolve-library-id and context7_query-docs.
Also removes the /context7-docs command. The skill already triggers on its own
and the subagent covers focused lookups.
* refactor(opencode): use the remote MCP server and drop the subagent
The plugin now adds two things: the hosted MCP server over the remote
transport, and the context7-mcp skill.
Known limitation: OpenCode opens the optional GET SSE stream on the endpoint
and mcp.context7.com answers 405, so OpenCode marks the server failed and
registers no tools. Reproduced on 1.18.11 and 1.18.16, and with a plain remote
mcp block and no plugin, so it is not plugin-specific. It resolves once
mcp.context7.com answers GET /mcp with a 200 SSE stream instead of 405.
* refactor(opencode): ship the canonical skill verbatim and drop the phantom peer dep
The bundled skill had been edited to hard-code OpenCode's context7_ tool
prefix. That divergence was unnecessary and counterproductive:
- It was fixing a misdiagnosis. The model only invented shell commands when
the MCP server had failed to connect and no context7 tools existed at all.
With the tools present, claude-haiku-4-5 calls context7_resolve-library-id
correctly from the bare-name canonical skill.
- It was defeated where it mattered. OpenCode dedupes skills by name and scans
~/.claude/skills first, so on any machine that ran ctx7 setup the bundled
copy is shadowed by the canonical one anyway.
- It was the only client copy to diverge in content. claude, copilot, codex
and cursor all ship skills/context7-mcp/SKILL.md verbatim, and there is no
tooling to keep copies in sync.
Also removes peerDependencies on @opencode-ai/plugin. The package is a
type-only import that the build erases, so the published dist has zero
references to it, and peerDependenciesMeta.optional made the declaration inert
anyway. The devDependency is what typechecking actually needs.
* refactor(opencode): strip commentary and inline the plugin function
Keeps only the two comments that stop someone breaking the plugin: why the
skills cast is needed, and why nothing but the default export may be exported.
Inlining the server function into the default export drops the Context7Plugin
binding and the Plugin type import, since satisfies PluginModule already types
the callback. 85 lines down to 56.
* docs(opencode): use the prefixed tool names consistently in the README
Root cause of the 2026-08-11 stream leak, reproduced deterministically: a
2025-era JSON-RPC batch carrying a request plus notifications/cancelled
for that same request never terminates. Per spec a cancelled request gets
no response, but the SDK's legacy stateless transport only closes the
POST's SSE stream once every request in it has been answered — so the
exchange hangs. The 15s keepalive heartbeats then kept the hung stream
"active" forever: no proxy idle timeout could fire, and only the
gateway's 1200s hard cap reaped it. v1 hung on the same batch but sent
no keepalives, so proxy idle timeouts self-healed it within the hour —
which is why the leak only became an outage with v2.
Fix: keepAliveMs: 0. No legitimate exchange here needs a heartbeat (the
tools are millisecond vector queries, p100 ~28s, zero requests over 30s
in 62.7M/day), so the only streams keepalives were keeping alive were
dead ones. Hung exchanges now go silent and the gateway reaps them at
streamIdleTimeout (300s, deployed in context7parser#655) instead of
accumulating for 1200s. This covers the whole class of silent hangs, not
just the cancellation shape.
Validated:
- cancel-batch hang emits 0 bytes over 35s (was: keepalive every 15s);
gateway idle-reap of silent streams was proven separately on a local
Envoy Gateway v1.8.1 (silent stream cut at the idle timeout,
heartbeating stream never)
- tools/call, [req,req] batches, modern-era requests all unchanged
- typecheck, eslint, prettier clean
The SDK accounting bug (cancelled requests should count as settled for
stream close) remains to be filed upstream.