Skip to content

Add upstash setup to connect AI agents to Upstash - #23

Merged
CahidArda merged 7 commits into
mainfrom
setup-command
Oct 5, 2026
Merged

CahidArda merged 7 commits into
mainfrom
setup-command

Conversation

@CahidArda

Copy link
Copy Markdown
Collaborator

Upstash's answer to npx ctx7 setup: one command that wires the Upstash MCP server and skills into the user's agents.

npx @upstash/cli setup

What it installs

Agent Method
Claude Code claude plugin marketplace add upstash/skills + claude plugin install upstash@upstash (user or project scope)
Codex codex plugin marketplace add upstash/skills + codex plugin add upstash@upstash
Gemini CLI gemini extensions install https://github.com/upstash/skills --consent
Cursor Local plugin in ~/.cursor/plugins/local/upstash (no plugin CLI, and not in the Cursor marketplace yet)
VS Code, Copilot CLI, OpenCode Remote MCP (https://mcp.upstash.com/mcp) merged into the agent's config + the combined upstash skill

Plugin agents fall back to MCP + skill when the plugin can't be installed (CLI not on PATH, install error), --project is used for a user-only plugin, or --auth api-key is set (the plugins are OAuth-only). Reruns refresh the marketplace and update the plugin.

Flags

--claude --codex --cursor --gemini --vscode --copilot --opencode, --mode auto|plugin|mcp, --auth oauth|api-key (api-key uses the existing resolveAuth: flags, env, or upstash login; header is Authorization: Bearer email:key), -p/--project, -y/--yes, --ref, --dry-run, --json. With no agent flags it prompts (TTY) or uses detected agents (non-TTY / -y).

Differences from ctx7 setup

  • ctx7 never installs plugins; this does, since upstash/skills already ships Claude/Codex/Cursor/Gemini manifests that bundle the MCP.
  • No rules files — the combined skill already tells the agent when to prefer MCP tools.
  • No new dependencies (the CLI only has commander + dotenv). Skills come from one codeload.github.com tarball download, parsed with a ~60-line tar reader; no GitHub API rate limits.
  • Warns when a manual upstash MCP entry and the plugin would both load the server.

Tested

  • 15 unit tests (tests/unit/setup.test.ts): synthetic tarball, fake agent-CLI runner, temp HOME.
  • End-to-end in a sandbox with the real claude 2.1.263 and codex 0.153.4 CLIs: plugin installs are idempotent and show up in claude plugin list / ~/.codex/config.toml; TOML and JSON merges keep other servers; project scope writes .claude/settings.json with the plugin enabled.
  • Not tested: Gemini CLI install (not in the sandbox) and Cursor actually loading the local plugin — worth a manual check on a Mac before merging.

Follow-ups

  • Switch Cursor to the marketplace install once the listing is accepted.
  • Document upstash setup in the upstash-cli skill and the docs "Install by agent" page.
  • upstash setup --remove, and an API-key option for plugins (e.g. a headersHelper like context7's Claude plugin).

Installs the upstash/skills plugin (MCP server + skills) through each agent's
own CLI where plugins are supported (Claude Code, Codex, Gemini CLI), as a
local plugin for Cursor, and otherwise writes the remote MCP server into the
agent's config and installs the combined `upstash` skill (VS Code, Copilot
CLI, OpenCode). Falls back to MCP + skill when a plugin can't be installed.

Supports --project, --mode auto|plugin|mcp, --auth oauth|api-key, --dry-run,
--json, and agent detection. No new dependencies: skills come from the
upstash/skills tarball, parsed with a small tar reader.
@linear-code

linear-code Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

DX-3074

DX-3075

claude added 2 commits October 1, 2026 12:08
Replaces the numbered readline prompt with an arrow-key flow when run in a
terminal: pick scope, multi-select agents (detected ones pre-checked,
already-connected ones marked), pick OAuth or API key (prompting for
credentials when none are saved), confirm a plan, then a spinner per agent
with its steps. Flags still answer questions up front; -y, --json and
non-TTY runs keep the existing plain output unchanged.

@clack/prompts is pinned to ~1.0.1, the last line that runs on Node 18.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LWUubWwYWp1dZTK1S9Ek95
Every agent entry and plugin installer now cites the documentation page its
config paths, entry shape and CLI commands come from. Where the docs are
silent (where .claude.json lives under CLAUDE_CONFIG_DIR, the dotted
OpenCode file names) the comment says so and what the code relies on
instead. Comments only; no behavior change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LWUubWwYWp1dZTK1S9Ek95

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Project-scoped credentials, unsafe config handling, and ignored ref selection introduce security and correctness risks.

Review effort: Balanced
Findings: 4 High severity · 3 Medium severity · 1 Low severity

Open (8)
What changed in this PR

Adds upstash setup to connect supported AI agents through plugins or MCP plus skills.

Changes:

  • Adds agent detection, interactive setup, authentication, and fallbacks.
  • Adds configuration merging, plugin installation, and skill downloads.
  • Adds documentation and unit coverage.
File Description
src/​commands/​setup.ts Implements setup orchestration and CLI flags.
src/​setup/​agents.ts Defines agent-specific configuration.
src/​setup/​mcp-config.ts Merges MCP configuration files.
src/​setup/​plugins.ts Installs supported agent plugins.
src/​setup/​repo.ts Downloads and extracts skills.
src/​setup/​ui.ts Provides interactive terminal prompts.
src/​cli.ts Registers the setup command.
tests/​unit/​setup.test.ts Tests setup behavior and helpers.
README.md Documents agent setup.
package.json Adds prompt and color dependencies.
package-lock.json Locks new dependencies.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/commands/setup.ts Outdated
Comment thread src/setup/mcp-config.ts
Comment thread src/setup/mcp-config.ts
Comment thread src/setup/repo.ts
Comment thread src/commands/setup.ts Outdated
Comment thread src/setup/mcp-config.ts Outdated
Comment thread src/setup/mcp-config.ts Outdated
Comment thread package.json Outdated
Drop --auth api-key and the API-key prompts. Setup now never writes a
credential: every agent gets the bare server entry and signs in with OAuth
on first use. Rerunning setup replaces an `upstash` entry left by an older
API-key setup, so its header is removed too.

Also fixes the other review findings:
- Only a missing config file reads as empty; other read errors (EACCES,
  EISDIR) now stop the write instead of overwriting the file from {}.
- JSONC configs with trailing commas parse.
- TOML table matching resolves quoted keys, so [mcp_servers."upstash"] is
  replaced instead of declared a second time.
- Skill/plugin trees are validated and written to a staging directory
  before the old install is removed.
- --ref with a non-default branch uses MCP + skill for Claude Code and
  Codex, whose plugin installs cannot pin a ref, instead of silently
  installing the default branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LWUubWwYWp1dZTK1S9Ek95

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Ref validation, TOML merging, replacement safety, and PR description discrepancies remain unresolved.

Review effort: Balanced
Findings: 2 High severity · 1 Low severity

Open (3)
Resolved since last review (8)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Handle inline and dotted MCP tables before appending TOML sections

src/​setup/​mcp-config.ts:167

This only detects explicit table headers. A valid Codex config can instead contain mcp_servers.upstash = { url = "old" }; appending [mcp_servers.upstash] then declares the same table twice and makes the entire TOML config invalid. Handle dotted assignments/inline tables before appending, or use a TOML parser for the merge.

Comment thread src/commands/setup.ts
Comment thread src/setup/repo.ts Outdated
Comment thread tests/unit/setup.test.ts
claude added 2 commits October 2, 2026 14:24
- Remove --ref. Everything installs from the default branch of
  upstash/skills, so no user-supplied string reaches an agent CLI (Gemini's
  install runs through a shell on Windows) or the download URL.
- writeTree moves the previous install to a backup before renaming the
  staged tree into place, and restores it if that rename fails.
- Codex config: refuse to append [mcp_servers.upstash] when the server is
  already defined inline or with dotted keys (mcp_servers.upstash = {...},
  or upstash = {...} under [mcp_servers]), which would declare the table
  twice and break the file. The step fails with a message saying how to fix
  the file, and hasMcpEntry recognises those forms too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LWUubWwYWp1dZTK1S9Ek95
Resolves the package.json conflict by keeping both dependency sets:
@clack/prompts and picocolors from this branch, @upstash/blob and mime from
main. package-lock.json is main's lockfile with those two packages added by
npm install.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LWUubWwYWp1dZTK1S9Ek95

@alitariksahin alitariksahin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed revision d500d1d. Five P2 findings are attached inline.

Validation: all 134 unit tests, build, and typecheck passed locally. The TOML merge failures and ignored Claude update failure were reproduced separately. Live agent installation was not exercised.

Comment thread src/setup/mcp-config.ts Outdated
}
const kv = /^\s*([^=#\s][^=]*?)\s*=/.exec(line);
const keys = kv ? parseKeys(kv[1]!) : undefined;
if (keys && !startsWith(current, want) && startsWith([...current, ...keys], want)) return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Detect inline parent tables before appending

A valid Codex config such as mcp_servers = { other = { url = 'https://example.com/mcp' } } passes this guard because the key is an ancestor of mcp_servers.upstash. Setup then appends [mcp_servers.upstash], which illegally extends an inline TOML table. I reproduced writeMcpEntry returning success while Python's tomllib rejects the resulting file with Cannot declare ('mcp_servers', 'upstash') twice. Detect inline ancestors and refuse the write, or merge them safely.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0dda809. The TOML edit is still line-based, so comments and formatting are kept, but it now checks its own result: it parses the new file with smol-toml and compares it with the expected document before writing. Your mcp_servers = { other = {...} } case, and any other layout the line edit can't rewrite safely (the server defined inline or with dotted keys), now fails the step with a clear message and leaves the file untouched. This also replaces the hand-written inline/dotted detection. Test: "refuses layouts it cannot rewrite safely".

Comment thread src/setup/mcp-config.ts Outdated
if (definedOutsideTable(lines, table)) {
throw new Error(`${table} is defined inline or with dotted keys; replace it with a [${table}] table or remove it, then rerun`);
}
const start = lines.findIndex((l) => isHeader(l, table));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Ignore table-like text inside multiline strings

A [mcp_servers.upstash] line inside a valid multiline developer_instructions string matches this search. Replacement removes the remaining string content, including its closing delimiter, up to the next unrelated table header; the resulting config fails to parse with an unterminated-string error. I reproduced this starting with valid TOML. The multiline handling in definedOutsideTable also needs to apply to header discovery and replacement boundaries.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0dda809. Header detection now skips lines inside """ / ''' strings, so a [mcp_servers.upstash] line in developer_instructions is left alone and the real table is appended. If anything still changed a string, the parse-and-compare check would refuse the edit. Test: "ignores table-like lines inside multi-line strings".

Comment thread src/setup/mcp-config.ts Outdated
let end = start + 1;
while (end < lines.length) {
const line = lines[end]!;
if (isTableHeader(line) && !isSubTable(line, table)) break;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Remove Upstash subtables regardless of their position

Replacement stops at the next unrelated table. In a valid config ordered as [mcp_servers.upstash], [mcp_servers.other], then [mcp_servers.upstash.http_headers], the old Authorization header survives the migration to OAuth. I reproduced the resulting Upstash entry retaining Authorization = 'Bearer expired' after setup replaces its URL. Remove all matching descendant sections throughout the document, rather than only contiguous ones.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0dda809. Every [mcp_servers.upstash] and [mcp_servers.upstash.*] section is now removed wherever it sits, and the new block goes where the first one was. With your ordering (upstash, other, upstash.http_headers), the old Authorization header is gone afterwards. Test: "removes upstash sub-tables wherever they sit, and keeps TOML dates".

Comment thread src/setup/agents.ts Outdated
plugin: { kind: "codex", scopes: ["global"] },
mcp: {
format: "toml",
paths: (s) => [pick(s, join(".codex", "config.toml"), home(".codex", "config.toml"))],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Honor CODEX_HOME for user configuration

The MCP installer always writes ~/.codex/config.toml, even when Codex uses a custom CODEX_HOME. Thus setup --codex --mode mcp (or the automatic MCP fallback) reports success while writing a config the active agent never reads. Detection and isPluginInstalled have the same assumption. Resolve these user-level paths from CODEX_HOME, which defaults to ~/.codex, as described in the official configuration documentation.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0dda809. The Codex user config path, agent detection and the plugin-installed check all resolve from CODEX_HOME now, defaulting to ~/.codex (https://developers.openai.com/codex/config-advanced#config-and-state-locations). Project scope still uses .codex/config.toml. Test: "uses CODEX_HOME for Codex's user config and detection".

Comment thread src/setup/plugins.ts Outdated
`Plugin ${PLUGIN_ID} (${scope} scope)`,
);
// `install` reports success without upgrading an existing install.
if (result.ok && !ctx.dryRun) await ctx.run("claude", ["plugin", "update", PLUGIN_ID, "--scope", scope]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Report failed Claude plugin updates

The update result is discarded. On a rerun, plugin install can succeed without upgrading the existing plugin; if the subsequent update fails, setup still reports ok: true with every step done and no warning. I reproduced this with a runner returning success for marketplace/install commands and failure for plugin update. Record and report the update failure instead of returning the earlier install result.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0dda809. The update result is no longer discarded: a failed claude plugin update shows up as a failed "Update upstash@upstash" step with the CLI's error, and setup exits non-zero. The plugin is still installed, so setup doesn't fall back to MCP + skill, which would load the server twice. Test: "reports a failed Claude plugin update instead of claiming success".

…t failed Claude updates

Addresses the review on d500d1d:

- The Codex config is still edited line by line to keep comments and
  formatting, but the result is now parsed with smol-toml and compared with
  the expected document before it is written. Layouts the line edit can't
  rewrite safely, such as an inline parent table
  (mcp_servers = { other = {...} }) or the server defined inline or with
  dotted keys, now fail the step with a clear message instead of producing
  an invalid file. This replaces the hand-written inline/dotted detection.
- Header lines inside multi-line strings are no longer taken for tables.
- Every [mcp_servers.upstash] and [mcp_servers.upstash.*] section is removed
  wherever it sits, so a non-adjacent http_headers sub-table from an older
  API-key setup no longer survives.
- hasMcpEntry reads TOML configs with the parser too.
- Codex user config, detection and the plugin check use CODEX_HOME,
  defaulting to ~/.codex.
- A failed `claude plugin update` is reported as a failed step and a
  non-zero exit instead of a silent success; the plugin stays installed, so
  there is no MCP fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LWUubWwYWp1dZTK1S9Ek95
@CahidArda
CahidArda merged commit a5ec9b0 into main Oct 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants