* fix(cli): declare @inquirer/core as direct dependency
selectOrInput.ts imports from @inquirer/core directly, but the package
was only resolvable as a transitive of @inquirer/prompts. Under pnpm's
isolated linker this fails with ERR_MODULE_NOT_FOUND at startup.
Fixes#2651
* chore: add changeset
* fix(cli): validate skill names to prevent path traversal on install
Adds boundary validation and containment checks so a remote SKILL.md
with a malicious name field (e.g. `name: ..`) cannot escape the skills
root during `ctx7 skills install`. Previously, the value flowed from
parseSkillFrontmatter to installSkillFiles unchecked, and the existing
traversal guard only verified files stayed inside the attacker-chosen
directory rather than the real skills root, enabling arbitrary file
writes outside `.claude/skills` (e.g. `.claude/settings.json` for
hook-driven RCE). symlinkSkill had the same trust issue and could
`rm(recursive: true)` arbitrary directories.
* chore: add changeset for skill name validation
* chore: soften changeset wording
Some LLM clients send `context7CompatibleLibraryID`/`userQuery` instead of
the canonical `libraryId`/`query` — likely echoing phrasing from the tool
descriptions — and trip Zod validation before the tool ever runs.
Hook `Transport.onmessage` (set before `server.connect()` so the SDK chains
its dispatch over it) and rewrite those two keys in place on `tools/call`
requests where the canonical key is absent. Published `tools/list` schemas
are unchanged; the rewrite is a server-side compatibility shim.
Listen for stdin end/close and SIGHUP and exit with code 0. Without these,
an idle undici keep-alive socket left over from a recent fetch keeps
libuv's event loop alive past stdin EOF, leaving an orphan node process
when the parent (e.g. Claude Code) is force-killed.
Fixes#2542
readJsonConfig only caught file-read errors, not JSON.parse errors.
During `ctx7 remove`, the detector iterates every agent's well-known
config path; an unparseable JSON file at any of them (e.g. a
hand-edited ~/.claude.json) crashed the command with an unhandled
SyntaxError before it could do anything.
Wrap the readJsonConfig call in hasMcpConfig with a try/catch that
logs a warning naming the path and parse error and skips that agent.
Keep readJsonConfig itself strict so write paths in setup and
uninstallMcp continue to surface failures via their existing handling.
* fix(mcp): stream tool-call responses so headers flush before 60s
The remote MCP server's StreamableHTTPServerTransport runs in JSON mode
(`enableJsonResponse: true`), which buffers the entire response and writes
status + headers + body together at the end of the tool call. Long-running
tools — notably `query-docs` with `researchMode: true` — routinely take
60–250s, during which no bytes are written to the wire.
MCP HTTP clients cap the underlying `fetch()` waiting for headers
(Claude Code: 60s, hardcoded in @modelcontextprotocol/sdk consumers),
independent of the higher-level per-tool timeout. Production curl with
`-w time_starttransfer` shows `start_transfer ≈ total ≈ 125s` for a
research call — the connection sits silent for the full duration before
flushing in one burst, well past any reasonable client `fetch` timeout.
Switch to SSE responses for POST tool calls. The SDK then returns the
HTTP response synchronously after parsing the request, headers flush in
ms, and the body streams while the tool runs. Same total wall time, but
clients see headers immediately and don't time out.
The existing NGINX-timeout comment above (about rejecting GETs) is about
the standalone GET SSE channel for server-initiated notifications and
still applies — GETs remain rejected. Per-request POST SSE responses are
bounded by the tool call and work fine on the existing ingress
(`proxy-buffering: off`, `proxy-read-timeout: 3600`).
Streamable HTTP requires clients to accept both `application/json` and
`text/event-stream` (SDK enforces 406 otherwise), so this is transparent
to compliant clients including Claude Code.
* remove comment
* chore: add changeset
* fix(mcp): emit progress notifications during researchMode query-docs
The SSE-streaming change in the previous commit only addresses clients
whose timeout fires when response *headers* don't arrive (e.g. Claude
Code's `wrapFetchWithTimeout`). Clients using the MCP SDK's default
`Protocol.request()` timer (`DEFAULT_REQUEST_TIMEOUT_MSEC = 60000`) hit
a wall-clock timeout that bytes flowing don't reset.
Emit `notifications/progress` every 20s while the upstream fetch is in
flight, gated on `researchMode: true` and the client supplying a
`progressToken` in `_meta`. Clients that pass an `onprogress` handler
have the SDK include `progressToken` automatically; on each notification
the SDK resets their JSON-RPC request timer (when they also opted into
`resetTimeoutOnProgress: true`), keeping 60–250s research runs alive.
Clients that don't include a `progressToken` see no notifications and no
behavior change. Fast `query-docs` calls are unaffected — the interval
only arms when `researchMode` is true.
* Merge branch 'master' of https://github.com/upstash/context7 into fix/mcp-stream-tool-responses
# Conflicts:
# packages/mcp/src/index.ts
* fix(mcp): restore researchMode and progress notifications on query-docs
Re-add the researchMode parameter to the query-docs input schema and
re-emit periodic notifications/progress while the upstream call is in
flight. Clients that opt into resetTimeoutOnProgress (e.g. opencode)
reset their per-request timer on each notification, which keeps the
long-running researchMode call alive past the SDK's default 60s
wall-clock timeout.
* fix(mcp): create a fresh McpServer per HTTP request
The HTTP transport is stateless (sessionIdGenerator: undefined), but the
handler shared one global McpServer across requests. McpServer extends
Protocol, which has a single _transport field. Each server.connect()
overwrites it, and any transport.close on any request fires the shared
Protocol's onclose, leaving _transport undefined for everyone else.
That meant a long-running researchMode call lost its transport every
time an unrelated short request (tool list refresh, init confirmation,
etc.) closed in the background, surfacing as "Not connected" on every
subsequent sendNotification and ultimately a -32001 timeout on the
client.
Switch to the per-request pattern from the SDK's
simpleStatelessStreamableHttp example: a createMcpServer factory builds
a fresh server, registers tools, and is closed alongside the transport
on res.on('close'). stdio mode keeps a single server, since stdio has
exactly one transport for the process lifetime.
* Merge remote-tracking branch 'origin/master' into fix/mcp-stream-tool-responses
# Conflicts:
# packages/mcp/src/index.ts
* chore(mcp): minimize PR diff against master
Drop the now-redundant changeset that duplicated the SSE-streaming note
already released in 2.2.3, restore comments inadvertently dropped during
the createMcpServer refactor, and rename the changeset file to reflect
what this PR actually changes.
Net diff vs master is now wrapping the existing setup in a
createMcpServer factory, calling it per HTTP request, and closing the
server alongside the transport (12 logical lines added, ignoring
indentation introduced by Prettier).
* fix(mcp): move client info capture to MCP server initialization for stdio mode
Hide the `researchMode` parameter from the MCP tool's input schema so
agents stop invoking it. The upstream `/api/v2/context` route still
accepts and serves the parameter; only the MCP-layer surface is
removed. Reasoning: several MCP clients hit per-request timeouts (60s
defaults in the SDK and in fetch-wrappers) on long-running research
calls in ways that can't all be solved server-side. Until the timeout
story is reliable across clients, agents shouldn't be able to call it
via MCP.
Updates:
- Drop the `researchMode` field from the `query-docs` input schema.
- Drop the workflow line in the tool description that referenced it.
- Stop forwarding `researchMode` to fetchLibraryContext; the field is
optional on ContextRequest, so omitting it is equivalent to false.
* fix(cli): support Windows backslash in skill installation path check
* chore(cli): add changeset for Windows path check fix
* style(cli): format Windows path guard
---------
Co-authored-by: Fahreddin Özcan <ozcanfahrettinn@gmail.com>
* fix(mcp): support NODE_EXTRA_CA_CERTS for enterprise MITM proxies
When NODE_EXTRA_CA_CERTS is set, reads the CA certificate file and
injects it into undici's global dispatcher. This fixes fetch failures
behind enterprise transparent SSL intercept proxies (Zscaler, etc.)
where the default TLS context does not trust the corporate CA.
The CA certs are also passed through when an explicit HTTPS_PROXY is
configured, so both proxy modes work with custom certificates.
Fixes#2268
This contribution was developed with AI assistance (Claude Code).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add changeset for NODE_EXTRA_CA_CERTS support
* fix(mcp): preserve default CAs with NODE_EXTRA_CA_CERTS
* chore: add changeset for CA trust fix
* chore: remove superseded changeset
* fix(mcp): lint test files with test tsconfig
* fix(mcp): use explicit test file path
* fix(mcp): support older Node TLS CA APIs
* test(mcp): run certificate test with vitest
---------
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(cli): add Gemini CLI support to setup command
Adds Gemini CLI as a supported agent in `ctx7 setup`. Configures MCP
server in `.gemini/settings.json` using `httpUrl` (Gemini's HTTP
streaming transport), appends rules to `GEMINI.md`, and installs skills
to `.gemini/skills/`. Also adds a permission fix tip when skill
installation fails with EACCES.
* chore: add changeset for Gemini CLI setup
* fix(cli): use GITHUB_TOKEN for skill downloads to avoid rate limits
Closes#2363
* chore: add changeset for gh ratelimit fix
* fix(cli): fallback to gh auth token for skill downloads
Supports private repos and users with gh CLI but no env vars set.
Closes#2369
* feat(cli): support installing skills from private/unindexed repos
When the Context7 backend doesn't know about a repo, the CLI now falls
back to fetching the repo tree from GitHub directly, parsing SKILL.md
frontmatter locally, and downloading skill files. Uses GitHub API status
codes to differentiate non-existent repos from repos without skills.
Closes#2369
* docs: add CLAUDE.md with Context7 integration guidelines
Add documentation for Claude Code integration including:
- Quick setup instructions
- Usage examples with popular libraries
- Auto-trigger rules for NestJS, Prisma, SvelteKit, etc.
- Best practices for library documentation queries
Closes#2272
* feat(mcp): add --version/-v flag to CLI
Add version flag support using Commander.js .version() method.
Uses existing SERVER_VERSION constant from package.json.
Fixes#2284
* chore: remove unrelated CLAUDE.md from PR
* chore: add changeset for mcp version flag
---------
Co-authored-by: liwenjun-dev <liwenjun.dev@gmail.com>
Co-authored-by: Fahreddin Özcan <ozcanfahrettinn@gmail.com>
* fix(setup): allow re-selecting already configured agents and overwrite existing MCP config
Previously, agents with existing Context7 MCP config were disabled in the
selection prompt with "(already configured)" and their config was never
updated. This made it impossible to reconfigure an agent (e.g. rotate API
key or fix a broken URL) without manually editing config files.
Now all agents are always selectable, existing config is overwritten with
fresh entries on re-setup, and TOML replacement correctly handles
sub-sections like http_headers.
* fix(setup): eliminate double file read and whitespace drift in TOML overwrite
Inline the server existence check into appendTomlServer to avoid reading
the file twice. Normalize whitespace around the replaced block so repeated
re-configurations do not accumulate blank lines.
* chore: add patch changeset for setup reconfiguration fix
* fix(setup): resolve all OpenCode config file variants for MCP setup
OpenCode supports 4 config file names (opencode.json, opencode.jsonc,
.opencode.json, .opencode.jsonc) but setup only checked opencode.json
and opencode.jsonc. Users with dotfile variants were never detected as
"already configured" and setup would create a duplicate opencode.json.
Changed mcp.projectPath/globalPath (single string) to
mcp.projectPaths/globalPaths (string[]) so agents can declare all valid
config paths. resolveMcpPath now takes the full candidates array and
returns the first existing file, falling back to the first entry.
Closes#2313
* chore: add patch changeset for OpenCode config variant fix
* refactor(setup): update rule template to be selective with examples
Based on eval benchmarks showing that selective rules with explicit
should/should-not examples achieve 98% recall with 0 false positives,
vs the previous broad rule.
Split RULE_CONTENT into MCP_RULE_CONTENT and CLI_RULE_CONTENT to
support both MCP and CLI setup modes with appropriate tool references.
* feat(setup): install CLI rule alongside skill in ctx7 setup --cli
Previously, CLI setup mode only installed the find-docs skill (66% trigger
rate). Now it also installs a rule file with ctx7 CLI instructions for each
selected agent, matching the MCP setup behavior.
Benchmarks show skill + rule achieves 96-98% trigger rate vs 66% skill-only.
* feat(find-docs): update skill description to improve trigger rate
Replace passive description with pushy version that explicitly names common
libraries and counters Claude's undertriggering tendency.
Eval results: 66% -> 98% clean recall, 72% -> 92% with-context recall.
* chore(setup): remove deprecated RULE_CONTENT export and alwaysApply frontmatter
alwaysApply is not a Claude Code feature (it's Cursor-specific). Claude Code
rules without paths frontmatter load unconditionally by default. Remove the
deprecated alias now that all callers use MCP_RULE_CONTENT or CLI_RULE_CONTENT.
* refactor(setup): fetch rules from GitHub, add alwaysApply for Cursor
- Rule source of truth in rules/context7-mcp.md and rules/context7-cli.md
- templates.ts fetches from GitHub raw (master then main)
- getRuleContent(mode, agent) adds alwaysApply frontmatter for Cursor
- CLI setup now logs rule install failures instead of silently swallowing
* refactor(setup): add Codex agent, remove needsAlwaysApply field, use shared installRule
- Add Codex agent config with AGENTS.md append support
- Remove needsAlwaysApply field (hardcoded cursor check in templates.ts)
- CLI setup uses shared installRule instead of inline CLI_RULE_PATHS
- installRule handles both file-based (Claude/Cursor/OpenCode) and
append-based (Codex/AGENTS.md) rule installation
* refactor(setup): unify CLI setup with MCP agent picker, group output by agent
- CLI setup now uses the same agent picker as MCP setup (no auto-detection)
- Output grouped by agent instead of repeating find-docs for each path
- Removed unused promptForInstallTargets/getTargetDirs imports
* fix(setup): add branch fallback URL for rule fetch, show error on rule failure
* fix(setup): OpenCode uses AGENTS.md, fix append logic for existing files
- OpenCode rule changed from .opencode/rules/ to AGENTS.md (append type)
since OpenCode reads AGENTS.md natively, not a rules directory
- Fixed append logic: proper spacing for empty/non-empty files, clean
idempotent replacement on re-run, preserves existing content
* test(setup): add tests for rule install, MCP config, and AGENTS.md append
* chore(setup): remove unused mergeInstructions and instructionsGlob
* fix: lint formatting, add changeset
* fix(setup): Codex global rule goes to ~/.codex/AGENTS.md
* fix(setup): align CLI output format with MCP output
* feat(setup): add --codex flag for MCP and CLI setup
* docs(rules): align trigger language with skill, use npx ctx7@latest in CLI rule
* docs(rules): prefer ctx7 over web search for library docs
* docs(find-docs): prefer skill over web search in description
* docs(rules): consolidate trigger language, add selection criteria
* docs(rules): add query quality guidance and auth env var to CLI rule
* cleanup
* fix: remove branch URL, add offline fallback matching rule files exactly
* fix(setup): support opencode.jsonc, strip JSON comments, fix detect path
- readJsonConfig now strips // and /* */ comments before parsing (JSONC support)
- resolveMcpPath checks for .jsonc variant when .json is specified
- OpenCode detect paths include both opencode.json and opencode.jsonc
- Fixed OpenCode project path from .opencode.json to opencode.json
* fix(setup): fix JSONC comment stripping to preserve URLs in strings
The regex-based comment stripper was matching // inside string values
(e.g., https:// URLs), corrupting the JSON. Replaced with a character-
level parser that skips string literals before stripping comments.
* fix(setup): Codex uses TOML config.toml, detect .opencode.json variant
- Codex MCP writes to ~/.codex/config.toml using [mcp_servers.context7]
TOML sections instead of JSON (matches Codex docs and add-mcp tool)
- OpenCode detection checks opencode.json, opencode.jsonc, and .opencode.json
- Added TOML server block writer with idempotent append
- stripJsonComments now handles URLs in strings correctly
* test(setup): add JSONC, TOML, and resolveMcpPath tests
12 new tests covering:
- JSONC: comment stripping preserves URLs, block comments, plain JSON
- resolveMcpPath: .jsonc resolution, .json fallback, non-json passthrough
- TOML: buildTomlServerBlock output, http_headers, readTomlServerExists,
appendTomlServer (empty file, preserve existing, idempotent)
* fix(setup): Codex MCP needs type=http, fix checkbox disabled item selection
- Codex TOML entry now includes type = "http" (required by Codex)
- Checkbox cursor starts on first non-disabled item
- Checkbox navigation skips disabled items
* fix: address PR review feedback on rules and skill description
- Surface libraryId format (/org/project) in step 2 where selection happens
- Suggest retrying with alternate names/queries instead of just punctuation
- MCP rule: explicitly require resolve-library-id unless /org/repo provided
- Skill description: explicitly warn against relying on training data for APIs
* fix: consistent /org/project format in MCP rule
* fix: revert opencode detection paths to original
* feat: enhance Context7 server configuration with website URL, description, and detailed usage instructions
* run format
* add changeset
* feat: align MCP server instructions with eval-proven trigger language
Based on invocation routing benchmarks (60 queries, clean + mid-session):
- Named specific libraries to counter confidence skip
- Added 'even when you think you know the answer' override
- Added 'prefer over web search' to catch WebSearch fallback
- Unified trigger paragraph to match client rule format
- Added explicit 'do not use for' negatives
---------
Co-authored-by: Fahreddin Özcan <ozcanfahrettinn@gmail.com>
* fix(cli): use ~/.agents/skills for global universal skill installs
The universal global path was ~/.config/agents/skills, but the
canonical standard (used by Vercel's skills CLI and OpenAI Codex)
is ~/.agents/skills. This aligns context7 with the broader ecosystem.
Fixes#2276
* chore: add changeset for global skills path fix
* fix(cli): remove shell:true from spawn in generate command
Removes shell:true from the child_process.spawn() call that opens
the user's editor. The editor path and preview file path are already
passed as separate arguments, so shell interpolation is unnecessary.
Using shell:true allows shell metacharacters in EDITOR or file paths
to be interpreted, which is a command injection vector.
Fixes#2240
This contribution was developed with AI assistance (Claude Code).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add changeset for shell:true removal
---------
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Fahreddin Özcan <ozcanfahrettinn@gmail.com>
* fix(cli): prevent directory traversal in skill file installation
installSkillFiles() used path.join() with unsanitized file paths from
GitHub API responses. A malicious skill repository could include files
with "../" sequences in their paths, allowing writes outside the
intended skill directory.
Changes:
- installer.ts: use path.resolve() and verify the resolved path stays
within the skill directory boundary before writing
- github.ts: reject file paths containing ".." at download time as an
additional defense-in-depth check
* chore: add changeset for directory traversal fix
* fix: format installer.ts to pass prettier lint
---------
Co-authored-by: Fahreddin Özcan <ozcanfahrettinn@gmail.com>
* fix(mcp): support NODE_EXTRA_CA_CERTS for enterprise MITM proxies
When NODE_EXTRA_CA_CERTS is set, reads the CA certificate file and
injects it into undici's global dispatcher. This fixes fetch failures
behind enterprise transparent SSL intercept proxies (Zscaler, etc.)
where the default TLS context does not trust the corporate CA.
The CA certs are also passed through when an explicit HTTPS_PROXY is
configured, so both proxy modes work with custom certificates.
Fixes#2268
This contribution was developed with AI assistance (Claude Code).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add changeset for NODE_EXTRA_CA_CERTS support
---------
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Fahreddin Özcan <ozcanfahrettinn@gmail.com>