From 1a0855dae1574cfb72287c69025f38fee79baf14 Mon Sep 17 00:00:00 2001 From: German Date: Thu, 21 May 2026 14:26:54 -0700 Subject: [PATCH 1/8] Add cross-client installer bundling skills + DocumentDB MCP server Replaces the broken `npx skills add Azure/documentdb-agent-kit` placeholder with real one-line installers (install.sh / install.ps1) that, in a single command, wire both this kit's skills and the microsoft/documentdb-mcp server into every detected client (Claude Code, Claude Desktop, Cursor, GitHub Copilot CLI, Gemini CLI). - install.sh: POSIX bash; uses python3 (fallback jq) for safe JSON merges - install.ps1: PS 5.1+/pwsh 7+; uses ConvertFrom/ConvertTo-Json; falls back to copy when symlinks need Developer Mode on Windows - Idempotent re-runs, .bak backups before every JSON edit, --dry-run, --uninstall, --skills-only, --mcp-only, --clients, --mcp-ref, --uri, --yes - Clones repos into ~/.documentdb-agent-kit/{agent-kit,mcp-server} - Pre-existing MCP entries and unrelated top-level keys are preserved - Single- and multi-element `args` arrays preserved across JSON round-trips - Verified end-to-end against fake client configs in sandboxed $HOME Also fixed two stale claims: - skills/mcp-setup/SKILL.md previously walked users through setting $DOCUMENTDB_URI in a shell profile. The current upstream MCP server is configured per-client via a CONNECTION_PROFILES JSON map in the client's MCP config file. Rewrote the skill around the actual upstream contract with per-client config-file paths for Claude Code / Desktop / Cursor / Copilot CLI / Gemini CLI / VS Code. - README's "npx skills add Azure/documentdb-agent-kit" one-liner was not a real command; replaced with the curl|bash and irm|iex installers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 2 +- CHANGELOG.md | 41 +++ README.md | 115 +++++-- install.ps1 | 545 +++++++++++++++++++++++++++++++++ install.sh | 628 ++++++++++++++++++++++++++++++++++++++ skills/mcp-setup/SKILL.md | 382 ++++++++--------------- 6 files changed, 1437 insertions(+), 276 deletions(-) create mode 100644 install.ps1 create mode 100755 install.sh diff --git a/AGENTS.md b/AGENTS.md index 38c4bdb..17158b9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,7 +36,7 @@ These skills walk the user (or another agent) through a task end-to-end. | Skill | Folder | When to use | |---|---|---| -| `documentdb-mcp-setup` | `skills/mcp-setup/` | User has the DocumentDB MCP server installed but hasn't configured `DOCUMENTDB_URI` / transport / shell profile | +| `documentdb-mcp-setup` | `skills/mcp-setup/` | Installing / configuring the DocumentDB MCP server in an agentic client (Claude Code, Claude Desktop, Cursor, Copilot CLI, Gemini CLI, VS Code); defining `CONNECTION_PROFILES` | | `documentdb-azure-deployment` | `skills/azure-deployment/` | Provisioning an Azure DocumentDB cluster (`Microsoft.DocumentDB/mongoClusters`) via Bicep, Azure CLI, Terraform, or portal; firewall rules; connection string retrieval | | `documentdb-natural-language-querying` | `skills/natural-language-querying/` | "How do I query…", "filter / group / aggregate…", SQL → MQL translation (read-only queries only) | | `documentdb-query-optimizer` | `skills/query-optimizer/` | "Why is this slow?", index review, `explain()`-driven tuning; loads `references/core-indexing-principles.md` | diff --git a/CHANGELOG.md b/CHANGELOG.md index af7b14a..5c29d1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,46 @@ # Changelog +## 2026-05-21 — Cross-client installer: skills + DocumentDB MCP server in one command + +Replaced the non-functional `npx skills add Azure/documentdb-agent-kit` +placeholder with real installers (`install.sh` for macOS/Linux, `install.ps1` +for Windows / cross-platform PowerShell) that, in one command, wire both the +kit's skills and the [`microsoft/documentdb-mcp`](https://github.com/microsoft/documentdb-mcp) +server into every detected client. + +What landed: + +- **`install.sh`** — POSIX bash installer, zero runtime deps beyond `git` + + Node 20+. Auto-detects Claude Code, Claude Desktop, Cursor, GitHub Copilot + CLI, and Gemini CLI; clones+builds the MCP server into + `~/.documentdb-agent-kit/mcp-server/`; symlinks each skill into clients that + support a skills directory; merges (idempotently) a single `DocumentDB` MCP + entry into each client's JSON config with a timestamped `.bak` backup before + every edit. Uses `python3` for JSON merging when available (universal on + dev machines), falls back to `jq`. +- **`install.ps1`** — PowerShell 5.1+ / pwsh 7+ mirror. Same flow, native + `ConvertFrom-Json` / `ConvertTo-Json` for merges. Falls back to copying skills + when symlink creation requires Developer Mode / admin on Windows. +- **Flags on both:** `--uri`, `--yes`, `--dry-run`, `--uninstall`, `--clients`, + `--skills-only`, `--mcp-only`, `--mcp-ref`, `--kit-ref`, `--profile`. +- **README rewrite** — install section now documents the one-liner, what gets + installed where, requirements, flags, verify steps, uninstall, and a + manual-install fallback. +- **`skills/mcp-setup/SKILL.md` rewrite** — the previous version told users to + set `DOCUMENTDB_URI` in a shell profile, which is **not** how the current + upstream MCP server is configured. Rewrote around the actual upstream + contract: per-client MCP config file with `CONNECTION_PROFILES` JSON, + `TRANSPORT=stdio`, and `ALLOW_UNAUTHENTICATED_STDIO=true`. Added per-client + config-file table for Claude Code / Desktop / Cursor / Copilot CLI / Gemini + CLI / VS Code. Updated AGENTS.md's mcp-setup row accordingly. + +Verified end-to-end (Bash + PowerShell) in sandboxed `$HOME` against fake +client configs: existing MCP servers preserved, single- and multi-element +`args` arrays preserved across JSON round-trips, idempotent re-runs do not +duplicate the `DocumentDB` entry, uninstall removes only the kit's entries +and symlinks (foreign symlinks + other server entries untouched), and +`~/.documentdb-agent-kit/` is removed on uninstall. + ## 2026-04-21 — `full-text-search`: corrected to `createSearchIndexes` + `$search` syntax; added analyzer rules Previous rules documented the community MongoDB shape (`createIndexes` with `{ field: "textSearch" }` keys and a `count` field inside `$search`). Azure DocumentDB full-text search actually uses: diff --git a/README.md b/README.md index 81958e3..5362f3d 100644 --- a/README.md +++ b/README.md @@ -50,12 +50,6 @@ Single-purpose skills the agent loads when its trigger description matches. - Configuring HA, cross-region replication, CMK, firewall, and RBAC - Optimizing indexes or diagnosing slow queries -## Installation - -```bash -npx skills add /documentdb-agent-kit -``` - ## Repo Structure ``` @@ -70,40 +64,119 @@ skills/ ## Installation -This kit follows the [Agent Skills](https://agentskills.io/) format. Every skill folder under `skills/` has a `SKILL.md` with `name` + `description` front matter, so Agent Skills–compatible tools can discover them automatically. +The kit ships with a one-command installer that wires both the **skills** and +the [`microsoft/documentdb-mcp`](https://github.com/microsoft/documentdb-mcp) +server into every detected MCP client. -### Claude Code +### One-liner (recommended) -Project-scoped (only this repo sees the skills): +**macOS / Linux:** ```bash -mkdir -p .claude && ln -s "$(pwd)/skills" .claude/skills +curl -fsSL https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.sh | bash -s -- --uri "" +``` + +**Windows (PowerShell):** + +```powershell +$env:DOCUMENTDB_URI = "" +irm https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.ps1 | iex ``` -User-scoped (every project sees the skills): +Local-dev quickstart (no Azure cluster needed, assuming a running documentdb-local container): ```bash -mkdir -p ~/.claude/skills -for d in skills/*/; do ln -s "$(pwd)/$d" ~/.claude/skills/; done +curl -fsSL https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.sh | bash -s -- --uri "mongodb://localhost:27017" ``` -On Windows/PowerShell, use `New-Item -ItemType SymbolicLink` or just copy the folder. +### What gets installed + +| Path | What | +|---|---| +| `~/.documentdb-agent-kit/agent-kit/` | Clone of this repo (skills + AGENTS.md) | +| `~/.documentdb-agent-kit/mcp-server/` | Clone + build of `microsoft/documentdb-mcp` | + +Then, per detected client: + +| Client | MCP entry → | Skills → | +|---|---|---| +| Claude Code | `~/.claude.json` | `~/.claude/skills/` (symlinks) | +| Claude Desktop | `claude_desktop_config.json` (per-OS path) | `Claude/skills/` (symlinks, if dir exists) | +| Cursor | `~/.cursor/mcp.json` | — (use Cursor Rules per-project) | +| GitHub Copilot CLI | `~/.copilot/mcp-config.json` | — (copy `AGENTS.md` per-project) | +| Gemini CLI | `~/.gemini/settings.json` | — (use `GEMINI.md` per-project) | + +Existing entries in each client's config are preserved — the installer only +adds (or updates) a single `DocumentDB` entry. A timestamped `.bak` backup is +written before every JSON edit. + +### Requirements + +- `git` +- Node.js 20+ and `npm` (the MCP server is a Node app, built from source on + install). `--skills-only` mode skips Node requirements. + +### Common flags + +```text +--uri DocumentDB / MongoDB connection string +--yes Non-interactive (don't prompt) +--dry-run Print planned changes; write nothing +--uninstall Remove MCP entries, skill symlinks, and ~/.documentdb-agent-kit +--clients Comma-separated: claude-code,claude-desktop,cursor,copilot-cli,gemini-cli +--skills-only Skip MCP server install +--mcp-only Skip skill linking +--mcp-ref Git ref of microsoft/documentdb-mcp (default: main) +--profile CONNECTION_PROFILES key name (default: default) +``` -### GitHub Copilot (CLI and IDE) +Connection string can also be supplied via `$DOCUMENTDB_URI` (or +`$env:DOCUMENTDB_URI` on PowerShell). When neither flag nor env var is set and +a TTY is attached, the installer prompts. -`AGENTS.md` at the repo root is the entry point — Copilot reads it automatically when you open the repo. No extra wiring required. If you want Copilot to see the kit in a *different* repo, copy `AGENTS.md` and the `skills/` folder into that repo's root. +### Verify it worked -### Gemini CLI +1. Fully **quit** and reopen each configured client (not just close the window). +2. Ask the agent: *"list databases using the DocumentDB MCP server with connection_profile 'default'"*. +3. You should get back the database list. -Gemini CLI reads `GEMINI.md`: +### Uninstall ```bash -ln -s AGENTS.md GEMINI.md # or: cp AGENTS.md GEMINI.md +# macOS / Linux +curl -fsSL https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.sh | bash -s -- --uninstall --yes ``` -### Other Agent Skills–compatible tools +```powershell +# Windows +irm https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.ps1 | iex -ArgumentList '-Uninstall','-Yes' +``` + +Removes the kit's `DocumentDB` MCP entry from every client, removes skill +symlinks, and deletes `~/.documentdb-agent-kit/`. Other MCP servers and your +non-kit skills are left untouched. + +### Manual install (no script) + +If you don't want to run the installer, every step is documented in the +[`documentdb-mcp-setup` skill](skills/mcp-setup/SKILL.md) (per-client config +file paths, MCP server config template, `CONNECTION_PROFILES` JSON, etc.). +For skills-only manual install: + +```bash +# Claude Code (project-scoped) +mkdir -p .claude && ln -s "$(pwd)/skills" .claude/skills + +# Claude Code (user-scoped) +mkdir -p ~/.claude/skills && for d in skills/*/; do ln -s "$(pwd)/$d" ~/.claude/skills/; done + +# Gemini CLI (project-scoped) +ln -s AGENTS.md GEMINI.md + +# GitHub Copilot / other AGENTS.md-aware clients: drop AGENTS.md + skills/ at repo root +``` -Point the tool at `skills/` as your skills directory. Each `skills//SKILL.md` is a discoverable skill with its own `name` and `description`. +On Windows, use `New-Item -ItemType SymbolicLink` or copy folders. ## Validating skills diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 0000000..535f783 --- /dev/null +++ b/install.ps1 @@ -0,0 +1,545 @@ +#requires -Version 5.1 +<# +.SYNOPSIS + documentdb-agent-kit installer (Windows + cross-platform PowerShell). + +.DESCRIPTION + Installs DocumentDB skills + the microsoft/documentdb-mcp server into every + detected MCP client (Claude Code, Claude Desktop, Cursor, Copilot CLI, + Gemini CLI). + +.PARAMETER Uri + DocumentDB / MongoDB connection string. If omitted, the script checks + $env:DOCUMENTDB_URI, then prompts interactively. + +.PARAMETER Yes + Non-interactive; never prompt. + +.PARAMETER DryRun + Print planned changes; write nothing. + +.PARAMETER Uninstall + Remove the kit's MCP entries + skill symlinks, then remove ~/.documentdb-agent-kit. + +.PARAMETER Clients + Comma-separated subset of: claude-code,claude-desktop,cursor,copilot-cli,gemini-cli + +.PARAMETER SkillsOnly + Install skills only; skip the MCP server. + +.PARAMETER McpOnly + Install the MCP server only; skip skills. + +.PARAMETER McpRef + Git ref of microsoft/documentdb-mcp to build (default: main). + +.PARAMETER KitRef + Git ref of Azure/documentdb-agent-kit to install (default: main). + +.PARAMETER Profile + Name to use in CONNECTION_PROFILES (default: default). + +.EXAMPLE + irm https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.ps1 | iex + +.EXAMPLE + .\install.ps1 -Uri "mongodb://localhost:27017" -Yes + +.EXAMPLE + .\install.ps1 -Uninstall +#> + +[CmdletBinding()] +param( + [string]$Uri = "", + [switch]$Yes, + [switch]$DryRun, + [switch]$Uninstall, + [string]$Clients = "", + [switch]$SkillsOnly, + [switch]$McpOnly, + [string]$McpRef = "main", + [string]$KitRef = "main", + [string]$Profile = "default" +) + +$ErrorActionPreference = "Stop" + +# ---------- Constants ---------- +$KIT_REPO = "https://github.com/Azure/documentdb-agent-kit.git" +$MCP_REPO = "https://github.com/microsoft/documentdb-mcp.git" +$INSTALL_ROOT = Join-Path $HOME ".documentdb-agent-kit" +$KIT_DIR = Join-Path $INSTALL_ROOT "agent-kit" +$MCP_DIR = Join-Path $INSTALL_ROOT "mcp-server" +$MCP_ENTRY = "DocumentDB" +$MIN_NODE_MAJOR = 20 +$SUPPORTED_CLIENTS = @("claude-code","claude-desktop","cursor","copilot-cli","gemini-cli") + +# ---------- OS detection ---------- +$IsWin = $false +$IsMac = $false +$IsLin = $false +if ($PSVersionTable.PSVersion.Major -ge 6) { + $IsWin = $IsWindows + $IsMac = $IsMacOS + $IsLin = $IsLinux +} else { + $IsWin = $true # Windows PowerShell 5.1 +} + +# ---------- Logging ---------- +$useColor = $Host.UI.RawUI -ne $null -and -not $env:NO_COLOR +function Write-Info([string]$msg) { Write-Host "→ $msg" -ForegroundColor Cyan } +function Write-Ok([string]$msg) { Write-Host "✓ $msg" -ForegroundColor Green } +function Write-Warn2([string]$msg) { Write-Host "! $msg" -ForegroundColor Yellow } +function Write-Err([string]$msg) { Write-Host "✗ $msg" -ForegroundColor Red } +function Write-Heading([string]$msg) { Write-Host ""; Write-Host $msg -ForegroundColor White -BackgroundColor Black } +function Write-Dry([string]$msg) { if ($DryRun) { Write-Host "[dry-run] $msg" -ForegroundColor DarkGray } } + +# ---------- Client config paths ---------- +function Get-ClientMcpConfigPath([string]$client) { + switch ($client) { + "claude-code" { return (Join-Path $HOME ".claude.json") } + "claude-desktop" { + if ($IsWin) { + return (Join-Path $env:APPDATA "Claude\claude_desktop_config.json") + } elseif ($IsMac) { + return (Join-Path $HOME "Library/Application Support/Claude/claude_desktop_config.json") + } else { + return (Join-Path $HOME ".config/Claude/claude_desktop_config.json") + } + } + "cursor" { return (Join-Path $HOME ".cursor/mcp.json") } + "copilot-cli" { return (Join-Path $HOME ".copilot/mcp-config.json") } + "gemini-cli" { return (Join-Path $HOME ".gemini/settings.json") } + default { throw "unknown client: $client" } + } +} + +function Get-ClientSkillsDir([string]$client) { + switch ($client) { + "claude-code" { return (Join-Path $HOME ".claude/skills") } + "claude-desktop" { + if ($IsWin) { + return (Join-Path $env:APPDATA "Claude\skills") + } elseif ($IsMac) { + return (Join-Path $HOME "Library/Application Support/Claude/skills") + } else { + return (Join-Path $HOME ".config/Claude/skills") + } + } + default { return $null } + } +} + +function Get-ClientLabel([string]$client) { + switch ($client) { + "claude-code" { return "Claude Code" } + "claude-desktop" { return "Claude Desktop" } + "cursor" { return "Cursor" } + "copilot-cli" { return "GitHub Copilot CLI" } + "gemini-cli" { return "Gemini CLI" } + default { return $client } + } +} + +function Test-McpOnlyClient([string]$client) { + return $client -in @("cursor","copilot-cli","gemini-cli") +} + +# ---------- Detection ---------- +function Find-InstalledClients { + $found = @() + foreach ($c in $SUPPORTED_CLIENTS) { + $cfg = Get-ClientMcpConfigPath $c + $parent = Split-Path $cfg -Parent + if ((Test-Path $cfg) -or (Test-Path $parent)) { + $found += $c + } + } + return $found +} + +function Filter-ClientsByUser([string[]]$detected) { + if ([string]::IsNullOrWhiteSpace($Clients)) { return $detected } + $allow = $Clients -split "," + return $detected | Where-Object { $_ -in $allow } +} + +# ---------- Prereqs ---------- +function Test-Command([string]$name) { + $null -ne (Get-Command $name -ErrorAction SilentlyContinue) +} + +function Test-Prereqs { + $missing = $false + if (-not (Test-Command "git")) { + Write-Err "git is required"; $missing = $true + } + if ($McpOnly -or -not $SkillsOnly) { + if (-not (Test-Command "node")) { + Write-Err "node is required (Node.js ${MIN_NODE_MAJOR}+ for the MCP server)"; $missing = $true + } else { + $nodeVer = (& node --version) -replace '^v','' + $major = [int]($nodeVer -split '\.')[0] + if ($major -lt $MIN_NODE_MAJOR) { + Write-Err "Node.js ${MIN_NODE_MAJOR}+ required, found v$nodeVer"; $missing = $true + } + } + if (-not (Test-Command "npm")) { + Write-Err "npm is required (ships with Node.js)"; $missing = $true + } + } + if ($missing) { exit 1 } +} + +# ---------- JSON merge ---------- +function Merge-McpEntry { + param( + [string]$ConfigPath, + [string]$TopKey, + [string]$ServerCommand, + [string[]]$ServerArgs, + [hashtable]$EnvVars + ) + + if ($DryRun) { + Write-Dry "would merge $MCP_ENTRY entry into $ConfigPath under $TopKey" + return + } + + $parent = Split-Path $ConfigPath -Parent + if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Force -Path $parent | Out-Null } + + $cfg = [ordered]@{} + if (Test-Path $ConfigPath) { + $bak = "$ConfigPath.bak.$(Get-Date -Format 'yyyyMMddHHmmss')" + Copy-Item -Path $ConfigPath -Destination $bak -Force + Write-Info "backed up existing config → $bak" + try { + $raw = Get-Content -Raw -Path $ConfigPath + if (-not [string]::IsNullOrWhiteSpace($raw)) { + # ConvertFrom-Json returns PSCustomObject; turn into ordered hashtable for safe mutation + $cfg = ConvertTo-OrderedHashtable (ConvertFrom-Json $raw) + } + } catch { + Write-Warn2 "existing $ConfigPath was not valid JSON; replacing it" + $cfg = [ordered]@{} + } + } + + # Navigate / create nested keys (supports dotted top-level like "mcp.servers") + $parts = $TopKey -split '\.' + $node = $cfg + for ($i = 0; $i -lt $parts.Length; $i++) { + $p = $parts[$i] + $isLast = ($i -eq $parts.Length - 1) + if ($isLast) { + if (-not $node.Contains($p) -or -not ($node[$p] -is [System.Collections.IDictionary])) { + $node[$p] = [ordered]@{} + } + $entry = [ordered]@{ + command = $ServerCommand + args = $ServerArgs + env = $EnvVars + } + $node[$p][$MCP_ENTRY] = $entry + } else { + if (-not $node.Contains($p) -or -not ($node[$p] -is [System.Collections.IDictionary])) { + $node[$p] = [ordered]@{} + } + $node = $node[$p] + } + } + + $json = $cfg | ConvertTo-Json -Depth 100 + $tmp = "$ConfigPath.tmp.$([guid]::NewGuid().ToString('N'))" + Set-Content -Path $tmp -Value $json -Encoding UTF8 + Move-Item -Path $tmp -Destination $ConfigPath -Force +} + +function Remove-McpEntry { + param([string]$ConfigPath, [string]$TopKey) + if (-not (Test-Path $ConfigPath)) { return } + if ($DryRun) { Write-Dry "would remove $MCP_ENTRY entry from $ConfigPath"; return } + + $bak = "$ConfigPath.bak.$(Get-Date -Format 'yyyyMMddHHmmss')" + Copy-Item -Path $ConfigPath -Destination $bak -Force + + try { + $cfg = ConvertTo-OrderedHashtable (ConvertFrom-Json (Get-Content -Raw -Path $ConfigPath)) + } catch { return } + + $parts = $TopKey -split '\.' + $node = $cfg + for ($i = 0; $i -lt $parts.Length; $i++) { + $p = $parts[$i] + if (-not $node.Contains($p) -or -not ($node[$p] -is [System.Collections.IDictionary])) { return } + if ($i -eq $parts.Length - 1) { + if ($node[$p].Contains($MCP_ENTRY)) { $node[$p].Remove($MCP_ENTRY) } + } else { + $node = $node[$p] + } + } + + $json = $cfg | ConvertTo-Json -Depth 100 + $tmp = "$ConfigPath.tmp.$([guid]::NewGuid().ToString('N'))" + Set-Content -Path $tmp -Value $json -Encoding UTF8 + Move-Item -Path $tmp -Destination $ConfigPath -Force +} + +# Helper: deep-convert PSCustomObject → OrderedDictionary so we can mutate it. +function ConvertTo-OrderedHashtable { + param([Parameter(Mandatory=$false)]$InputObject) + if ($null -eq $InputObject) { return [ordered]@{} } + if ($InputObject -is [System.Collections.IDictionary]) { + $out = [ordered]@{} + foreach ($k in $InputObject.Keys) { $out[$k] = ConvertTo-OrderedHashtable $InputObject[$k] } + return $out + } + if ($InputObject -is [System.Management.Automation.PSCustomObject]) { + $out = [ordered]@{} + foreach ($p in $InputObject.PSObject.Properties) { $out[$p.Name] = ConvertTo-OrderedHashtable $p.Value } + return $out + } + if ($InputObject -is [System.Collections.IList] -and $InputObject -isnot [string]) { + $list = New-Object System.Collections.ArrayList + foreach ($item in $InputObject) { [void]$list.Add((ConvertTo-OrderedHashtable $item)) } + # Return as a proper [object[]] so ConvertTo-Json keeps it as an array + # even when there is exactly one element. + return ,@($list.ToArray()) + } + return $InputObject +} + +# ---------- Repos ---------- +function Sync-Repo([string]$Repo, [string]$Dest, [string]$Ref) { + if (Test-Path (Join-Path $Dest ".git")) { + Write-Info "updating $(Split-Path $Dest -Leaf) (ref: $Ref)" + if ($DryRun) { Write-Dry "git fetch + checkout $Ref in $Dest"; return } + Push-Location $Dest + try { + git fetch --quiet --tags --depth=1 origin $Ref 2>$null | Out-Null + git checkout --quiet $Ref 2>$null | Out-Null + git reset --quiet --hard "FETCH_HEAD" 2>$null | Out-Null + } finally { Pop-Location } + } else { + Write-Info "cloning $Repo → $Dest (ref: $Ref)" + if ($DryRun) { Write-Dry "git clone --branch $Ref $Repo $Dest"; return } + & git clone --quiet --depth=1 --branch $Ref $Repo $Dest 2>$null + if ($LASTEXITCODE -ne 0) { + & git clone --quiet $Repo $Dest + Push-Location $Dest + try { git checkout --quiet $Ref 2>$null | Out-Null } finally { Pop-Location } + } + } +} + +function Build-McpServer { + if ($DryRun) { Write-Dry "(cd $MCP_DIR; npm install; npm run build)"; return } + Write-Info "installing MCP server dependencies (this may take a minute)" + Push-Location $MCP_DIR + try { + & npm install --silent --no-audit --no-fund --no-progress + if ($LASTEXITCODE -ne 0) { Write-Err "npm install failed in $MCP_DIR"; exit 1 } + Write-Info "building MCP server" + & npm run build --silent + if ($LASTEXITCODE -ne 0) { Write-Err "npm run build failed in $MCP_DIR"; exit 1 } + } finally { Pop-Location } + $entry = Join-Path $MCP_DIR "dist/main.js" + if (-not (Test-Path $entry)) { + Write-Err "expected $entry after build, not found"; exit 1 + } + Write-Ok "MCP server built at $entry" +} + +# ---------- Skills ---------- +function Install-SkillsForClient([string]$client) { + $dest = Get-ClientSkillsDir $client + if (-not $dest) { return } + if ($DryRun) { Write-Dry "would link each skills// into $dest/"; return } + if (-not (Test-Path $dest)) { New-Item -ItemType Directory -Force -Path $dest | Out-Null } + + $count = 0; $skipped = 0 + foreach ($skillDir in Get-ChildItem -Directory -Path (Join-Path $KIT_DIR "skills")) { + $name = $skillDir.Name + $link = Join-Path $dest $name + $linkInfo = Get-Item -Force -ErrorAction SilentlyContinue $link + if ($linkInfo -and $linkInfo.LinkType) { + Remove-Item $link -Force + } elseif (Test-Path $link) { + Write-Warn2 "skipping $name (target $link exists and is not a symlink)" + $skipped++ + continue + } + try { + New-Item -ItemType SymbolicLink -Path $link -Target $skillDir.FullName -ErrorAction Stop | Out-Null + } catch { + # Symlinks on Windows require Developer Mode or admin. Fall back to copy. + Write-Warn2 "symlink failed for $name (likely needs Developer Mode); copying instead" + Copy-Item -Recurse -Force -Path $skillDir.FullName -Destination $link + } + $count++ + } + $msg = "$(Get-ClientLabel $client): linked $count skills into $dest" + if ($skipped -gt 0) { $msg += " ($skipped skipped)" } + Write-Ok $msg +} + +function Uninstall-SkillsForClient([string]$client) { + $dest = Get-ClientSkillsDir $client + if (-not $dest -or -not (Test-Path $dest)) { return } + if ($DryRun) { Write-Dry "would remove kit skill symlinks from $dest/"; return } + $count = 0 + foreach ($entry in Get-ChildItem -Force -Path $dest) { + $info = Get-Item -Force $entry.FullName + $isOurs = $false + if ($info.LinkType -and $info.Target) { + foreach ($t in @($info.Target)) { + if ($t.StartsWith($KIT_DIR)) { $isOurs = $true; break } + } + } + if ($isOurs) { + Remove-Item -Force $entry.FullName + $count++ + } + } + Write-Ok "$(Get-ClientLabel $client): removed $count skill symlinks from $dest" +} + +# ---------- MCP entry ---------- +function Get-EnvJsonHashtable([string]$conn, [string]$profileName) { + $profiles = [ordered]@{ $profileName = [ordered]@{ authMode = "connectionString"; uri = $conn } } + $profilesJson = ($profiles | ConvertTo-Json -Compress -Depth 10) + return [ordered]@{ + TRANSPORT = "stdio" + ALLOW_UNAUTHENTICATED_STDIO = "true" + CONNECTION_PROFILES = $profilesJson + } +} + +function Install-McpForClient([string]$client, [string]$conn) { + $cfg = Get-ClientMcpConfigPath $client + $topKey = "mcpServers" + $mainJs = Join-Path $MCP_DIR "dist/main.js" + $envVars = Get-EnvJsonHashtable $conn $Profile + Merge-McpEntry -ConfigPath $cfg -TopKey $topKey -ServerCommand "node" -ServerArgs @($mainJs) -EnvVars $envVars + Write-Ok "$(Get-ClientLabel $client): wrote $MCP_ENTRY MCP entry → $cfg" +} + +function Uninstall-McpForClient([string]$client) { + $cfg = Get-ClientMcpConfigPath $client + if (-not (Test-Path $cfg)) { return } + Remove-McpEntry -ConfigPath $cfg -TopKey "mcpServers" + Write-Ok "$(Get-ClientLabel $client): removed $MCP_ENTRY MCP entry from $cfg" +} + +# ---------- Connection string ---------- +function Resolve-Uri { + if (-not [string]::IsNullOrWhiteSpace($script:Uri)) { return } + if (-not [string]::IsNullOrWhiteSpace($env:DOCUMENTDB_URI)) { + $script:Uri = $env:DOCUMENTDB_URI + Write-Info "using `$env:DOCUMENTDB_URI" + return + } + if ($Yes) { + Write-Err "no connection string provided (use -Uri, or set `$env:DOCUMENTDB_URI)" + exit 2 + } + if (-not [Environment]::UserInteractive) { + Write-Err "no connection string provided and not interactive" + Write-Err "re-run with: -Uri 'mongodb://...' OR set `$env:DOCUMENTDB_URI" + exit 2 + } + Write-Heading "DocumentDB connection string" + Write-Host "Examples:" + Write-Host " - Local: mongodb://localhost:27017" + Write-Host " - Azure: mongodb+srv://:@.mongocluster.cosmos.azure.com/?tls=true&authMechanism=SCRAM-SHA-256" + $script:Uri = Read-Host "Connection string" + if ([string]::IsNullOrWhiteSpace($script:Uri)) { + Write-Err "connection string is required"; exit 2 + } +} + +# ---------- Run ---------- +function Invoke-Install { + Write-Heading "documentdb-agent-kit installer" + Write-Host "Install root: $INSTALL_ROOT" + if ($DryRun) { Write-Warn2 "DRY RUN — no files will be modified" } + + Test-Prereqs + + $detected = Find-InstalledClients + $clients = Filter-ClientsByUser $detected + if (-not $clients -or $clients.Count -eq 0) { + Write-Warn2 "no supported MCP clients detected" + Write-Warn2 "supported: $($SUPPORTED_CLIENTS -join ', ')" + exit 1 + } + Write-Heading "Detected clients" + foreach ($c in $clients) { Write-Host " • $(Get-ClientLabel $c)" } + + if (-not $McpOnly) { + Write-Heading "Installing agent kit" + Sync-Repo $KIT_REPO $KIT_DIR $KitRef + Write-Ok "kit at $KIT_DIR" + } + + if (-not $SkillsOnly) { + Write-Heading "Installing DocumentDB MCP server" + Sync-Repo $MCP_REPO $MCP_DIR $McpRef + Build-McpServer + Resolve-Uri + } + + Write-Heading "Wiring clients" + foreach ($c in $clients) { + if (-not $SkillsOnly) { Install-McpForClient $c $script:Uri } + if (-not $McpOnly -and -not (Test-McpOnlyClient $c)) { Install-SkillsForClient $c } + } + + Write-Heading "Done" + Write-Host "Kit installed at: $KIT_DIR" + if (-not $SkillsOnly) { Write-Host "MCP server at: $(Join-Path $MCP_DIR 'dist/main.js')" } + Write-Host "" + Write-Host "Next steps:" + Write-Host " 1. Fully quit and reopen each configured client." + Write-Host " 2. Verify by asking the agent to list DocumentDB tools." + Write-Host " (Try: `"list_databases with connection_profile: $Profile`")" + if (-not $McpOnly) { + $hasMcpOnly = $false + foreach ($c in $clients) { if (Test-McpOnlyClient $c) { $hasMcpOnly = $true; break } } + if ($hasMcpOnly) { + Write-Host "" + Write-Host " Note for Cursor / Copilot CLI / Gemini CLI:" + Write-Host " These clients discover skills from project-local files (AGENTS.md /" + Write-Host " GEMINI.md). To use the kit's skills in a project, copy or symlink:" + Write-Host " Copy-Item $KIT_DIR\AGENTS.md \" + } + } + Write-Host "" + Write-Host "Uninstall: irm https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.ps1 | iex -ArgumentList '-Uninstall'" + Write-Host " or: .\install.ps1 -Uninstall" +} + +function Invoke-Uninstall { + Write-Heading "Uninstalling documentdb-agent-kit" + $detected = Find-InstalledClients + $clients = Filter-ClientsByUser $detected + foreach ($c in $clients) { + try { Uninstall-McpForClient $c } catch { Write-Warn2 $_ } + if (-not (Test-McpOnlyClient $c)) { + try { Uninstall-SkillsForClient $c } catch { Write-Warn2 $_ } + } + } + if (Test-Path $INSTALL_ROOT) { + if ($DryRun) { Write-Dry "would remove $INSTALL_ROOT" } + else { + Remove-Item -Recurse -Force $INSTALL_ROOT + Write-Ok "removed $INSTALL_ROOT" + } + } + Write-Ok "uninstall complete" +} + +if ($Uninstall) { Invoke-Uninstall } else { Invoke-Install } diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..091a250 --- /dev/null +++ b/install.sh @@ -0,0 +1,628 @@ +#!/usr/bin/env bash +# documentdb-agent-kit installer (macOS / Linux) +# Installs DocumentDB skills + the microsoft/documentdb-mcp server into every +# detected MCP client. +# +# Usage: +# curl -fsSL https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.sh | bash +# curl -fsSL .../install.sh | bash -s -- --uri "mongodb://localhost:27017" --yes +# ./install.sh --dry-run +# ./install.sh --uninstall +# +# Flags: +# --uri DocumentDB / MongoDB connection string (else prompts or uses $DOCUMENTDB_URI) +# --yes Non-interactive; never prompt +# --dry-run Print planned changes; write nothing +# --uninstall Remove the kit's MCP entries and skill symlinks; remove ~/.documentdb-agent-kit +# --clients Comma-separated subset of: claude-code,claude-desktop,cursor,copilot-cli,gemini-cli +# --skills-only Install skills only; skip MCP server +# --mcp-only Install MCP server only; skip skills +# --mcp-ref Git ref of microsoft/documentdb-mcp to build (default: main) +# --kit-ref Git ref of Azure/documentdb-agent-kit to install (default: main) +# --profile Name to use in CONNECTION_PROFILES (default: default) +# -h, --help Show this help + +set -euo pipefail + +# ---------- Constants ---------- +readonly KIT_REPO="https://github.com/Azure/documentdb-agent-kit.git" +readonly MCP_REPO="https://github.com/microsoft/documentdb-mcp.git" +readonly INSTALL_ROOT="${HOME}/.documentdb-agent-kit" +readonly KIT_DIR="${INSTALL_ROOT}/agent-kit" +readonly MCP_DIR="${INSTALL_ROOT}/mcp-server" +readonly MCP_ENTRY="DocumentDB" +readonly SUPPORTED_CLIENTS=(claude-code claude-desktop cursor copilot-cli gemini-cli) +readonly MIN_NODE_MAJOR=20 + +# ---------- Defaults ---------- +URI="" +YES=0 +DRY_RUN=0 +UNINSTALL=0 +CLIENTS="" +SKILLS_ONLY=0 +MCP_ONLY=0 +MCP_REF="main" +KIT_REF="main" +PROFILE_NAME="default" + +# ---------- Colors (only when TTY) ---------- +if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then + C_RED=$'\033[0;31m'; C_GRN=$'\033[0;32m'; C_YEL=$'\033[0;33m' + C_BLU=$'\033[0;34m'; C_BLD=$'\033[1m'; C_DIM=$'\033[2m'; C_RST=$'\033[0m' +else + C_RED=""; C_GRN=""; C_YEL=""; C_BLU=""; C_BLD=""; C_DIM=""; C_RST="" +fi + +log() { printf '%s\n' "$*"; } +info() { printf '%s%s%s %s\n' "$C_BLU" "→" "$C_RST" "$*"; } +ok() { printf '%s%s%s %s\n' "$C_GRN" "✓" "$C_RST" "$*"; } +warn() { printf '%s%s%s %s\n' "$C_YEL" "!" "$C_RST" "$*" >&2; } +err() { printf '%s%s%s %s\n' "$C_RED" "✗" "$C_RST" "$*" >&2; } +heading(){ printf '\n%s%s%s\n' "$C_BLD" "$*" "$C_RST"; } +dry() { [ "$DRY_RUN" -eq 1 ] && printf '%s[dry-run]%s %s\n' "$C_DIM" "$C_RST" "$*"; } + +# ---------- Arg parsing ---------- +usage() { + sed -n '2,/^$/p' "$0" | sed 's/^# \{0,1\}//' | head -30 +} + +while [ $# -gt 0 ]; do + case "$1" in + --uri) URI="$2"; shift 2 ;; + --yes|-y) YES=1; shift ;; + --dry-run) DRY_RUN=1; shift ;; + --uninstall) UNINSTALL=1; shift ;; + --clients) CLIENTS="$2"; shift 2 ;; + --skills-only) SKILLS_ONLY=1; shift ;; + --mcp-only) MCP_ONLY=1; shift ;; + --mcp-ref) MCP_REF="$2"; shift 2 ;; + --kit-ref) KIT_REF="$2"; shift 2 ;; + --profile) PROFILE_NAME="$2"; shift 2 ;; + -h|--help) usage; exit 0 ;; + *) err "unknown flag: $1"; usage; exit 2 ;; + esac +done + +# ---------- OS detection ---------- +OS="unknown" +case "$(uname -s)" in + Darwin) OS="macos" ;; + Linux) OS="linux" ;; + *) err "unsupported OS: $(uname -s) (use install.ps1 on Windows)"; exit 1 ;; +esac + +# ---------- Client config paths ---------- +client_mcp_config_path() { + case "$1" in + claude-code) printf '%s\n' "${HOME}/.claude.json" ;; + claude-desktop) + if [ "$OS" = "macos" ]; then + printf '%s\n' "${HOME}/Library/Application Support/Claude/claude_desktop_config.json" + else + printf '%s\n' "${HOME}/.config/Claude/claude_desktop_config.json" + fi + ;; + cursor) printf '%s\n' "${HOME}/.cursor/mcp.json" ;; + copilot-cli) printf '%s\n' "${HOME}/.copilot/mcp-config.json" ;; + gemini-cli) printf '%s\n' "${HOME}/.gemini/settings.json" ;; + *) return 1 ;; + esac +} + +client_skills_dir() { + case "$1" in + claude-code) printf '%s\n' "${HOME}/.claude/skills" ;; + claude-desktop) + if [ "$OS" = "macos" ]; then + printf '%s\n' "${HOME}/Library/Application Support/Claude/skills" + else + printf '%s\n' "${HOME}/.config/Claude/skills" + fi + ;; + *) return 1 ;; + esac +} + +client_label() { + case "$1" in + claude-code) printf 'Claude Code\n' ;; + claude-desktop) printf 'Claude Desktop\n' ;; + cursor) printf 'Cursor\n' ;; + copilot-cli) printf 'GitHub Copilot CLI\n' ;; + gemini-cli) printf 'Gemini CLI\n' ;; + esac +} + +# Some clients are "MCP-only" globally (skills are discovered from cwd files). +is_mcp_only_client() { + case "$1" in + cursor|copilot-cli|gemini-cli) return 0 ;; + *) return 1 ;; + esac +} + +# ---------- Detection ---------- +detect_clients() { + local found=() + for c in "${SUPPORTED_CLIENTS[@]}"; do + local cfg; cfg="$(client_mcp_config_path "$c")" + local parent_dir; parent_dir="$(dirname "$cfg")" + if [ -f "$cfg" ] || [ -d "$parent_dir" ]; then + found+=("$c") + fi + done + printf '%s\n' "${found[@]:-}" +} + +filter_clients_by_user() { + if [ -z "$CLIENTS" ]; then + cat + return + fi + local allow=",${CLIENTS}," + while IFS= read -r line; do + [ -z "$line" ] && continue + case "$allow" in + *",$line,"*) printf '%s\n' "$line" ;; + esac + done +} + +# ---------- Prereq checks ---------- +check_prereqs() { + local missing=0 + if ! command -v git >/dev/null 2>&1; then + err "git is required" + missing=1 + fi + if [ "$MCP_ONLY" -eq 1 ] || [ "$SKILLS_ONLY" -eq 0 ]; then + if ! command -v node >/dev/null 2>&1; then + err "node is required (Node.js ${MIN_NODE_MAJOR}+ for the MCP server)" + missing=1 + else + local nv; nv="$(node --version | sed 's/^v//' | cut -d. -f1)" + if [ "$nv" -lt "$MIN_NODE_MAJOR" ]; then + err "Node.js ${MIN_NODE_MAJOR}+ required, found v${nv}" + missing=1 + fi + fi + if ! command -v npm >/dev/null 2>&1; then + err "npm is required (ships with Node.js)" + missing=1 + fi + fi + [ "$missing" -eq 0 ] || exit 1 +} + +# ---------- JSON I/O ---------- +# Detect a JSON tool. Prefer python3 (universally available on macOS/Linux dev +# machines) — it gives us full programmatic merging. Fall back to jq if no +# python3, fall back to bare cat for read-only. +JSON_TOOL="" +if command -v python3 >/dev/null 2>&1; then + JSON_TOOL="python3" +elif command -v python >/dev/null 2>&1; then + JSON_TOOL="python" +elif command -v jq >/dev/null 2>&1; then + JSON_TOOL="jq" +fi + +# Merge the DocumentDB MCP entry into a client config file. +# Args: +# Creates the file if missing. Backs up if existing. +merge_mcp_entry() { + local cfg="$1" top_key="$2" cmd="$3" args_json="$4" env_json="$5" + local parent; parent="$(dirname "$cfg")" + + if [ "$DRY_RUN" -eq 1 ]; then + dry "would merge ${MCP_ENTRY} entry into ${cfg} under ${top_key}" + return + fi + + mkdir -p "$parent" + + if [ -f "$cfg" ]; then + local backup="${cfg}.bak.$(date +%Y%m%d%H%M%S)" + cp "$cfg" "$backup" + info "backed up existing config → ${backup}" + fi + + case "$JSON_TOOL" in + python3|python) + "$JSON_TOOL" - "$cfg" "$top_key" "$cmd" "$args_json" "$env_json" <<'PYEOF' +import json, os, sys, tempfile +cfg_path, top_key, cmd, args_json, env_json = sys.argv[1:6] +try: + with open(cfg_path, "r", encoding="utf-8") as f: + cfg = json.load(f) + if not isinstance(cfg, dict): + cfg = {} +except (FileNotFoundError, json.JSONDecodeError): + cfg = {} + +# Support dotted top-level keys like "mcp.servers" +parts = top_key.split(".") +node = cfg +for i, p in enumerate(parts): + is_last = (i == len(parts) - 1) + if is_last: + if not isinstance(node.get(p), dict): + node[p] = {} + node[p]["DocumentDB"] = { + "command": cmd, + "args": json.loads(args_json), + "env": json.loads(env_json), + } + else: + if not isinstance(node.get(p), dict): + node[p] = {} + node = node[p] + +dir_ = os.path.dirname(cfg_path) or "." +fd, tmp = tempfile.mkstemp(dir=dir_, prefix=".docdb-mcp-", suffix=".json") +try: + with os.fdopen(fd, "w", encoding="utf-8") as f: + json.dump(cfg, f, indent=2) + f.write("\n") + os.replace(tmp, cfg_path) +except Exception: + try: os.unlink(tmp) + except FileNotFoundError: pass + raise +PYEOF + ;; + jq) + # jq path. Note: jq supports `.["mcp.servers"]` for keys-with-dots but + # our `top_key` may be "mcp.servers" treated as nested. Handle both. + local tmp; tmp="$(mktemp "${cfg}.XXXXXX")" + local existing="{}" + [ -f "$cfg" ] && existing="$(cat "$cfg")" + local entry; entry=$(printf '{"command":"%s","args":%s,"env":%s}' "$cmd" "$args_json" "$env_json") + # Build the merge expression based on whether top_key has dots + local expr + if [[ "$top_key" == *.* ]]; then + expr=".[\"${top_key}\"][\"${MCP_ENTRY}\"] = ${entry}" + else + expr=".[\"${top_key}\"][\"${MCP_ENTRY}\"] = ${entry}" + fi + printf '%s' "$existing" | jq "$expr" > "$tmp" + mv "$tmp" "$cfg" + ;; + *) + err "Neither python3 nor jq found; cannot safely merge JSON. Install one and re-run." + exit 1 + ;; + esac +} + +# Remove the DocumentDB MCP entry from a client config. +remove_mcp_entry() { + local cfg="$1" top_key="$2" + [ -f "$cfg" ] || return 0 + if [ "$DRY_RUN" -eq 1 ]; then + dry "would remove ${MCP_ENTRY} entry from ${cfg}" + return + fi + local backup="${cfg}.bak.$(date +%Y%m%d%H%M%S)" + cp "$cfg" "$backup" + + case "$JSON_TOOL" in + python3|python) + "$JSON_TOOL" - "$cfg" "$top_key" <<'PYEOF' +import json, os, sys, tempfile +cfg_path, top_key = sys.argv[1], sys.argv[2] +try: + with open(cfg_path, "r", encoding="utf-8") as f: + cfg = json.load(f) +except (FileNotFoundError, json.JSONDecodeError): + sys.exit(0) + +parts = top_key.split(".") +node = cfg +for i, p in enumerate(parts): + if not isinstance(node.get(p), dict): + sys.exit(0) + if i == len(parts) - 1: + node[p].pop("DocumentDB", None) + else: + node = node[p] + +dir_ = os.path.dirname(cfg_path) or "." +fd, tmp = tempfile.mkstemp(dir=dir_, prefix=".docdb-mcp-", suffix=".json") +with os.fdopen(fd, "w", encoding="utf-8") as f: + json.dump(cfg, f, indent=2) + f.write("\n") +os.replace(tmp, cfg_path) +PYEOF + ;; + jq) + local tmp; tmp="$(mktemp "${cfg}.XXXXXX")" + jq "del(.[\"${top_key}\"][\"${MCP_ENTRY}\"])" "$cfg" > "$tmp" + mv "$tmp" "$cfg" + ;; + esac +} + +# ---------- Repos ---------- +clone_or_update_repo() { + local repo="$1" dest="$2" ref="$3" + if [ -d "$dest/.git" ]; then + info "updating $(basename "$dest") (ref: ${ref})" + if [ "$DRY_RUN" -eq 1 ]; then dry "git fetch + checkout ${ref} in ${dest}"; return; fi + (cd "$dest" && git fetch --quiet --tags --depth=1 origin "${ref}" 2>/dev/null \ + && git checkout --quiet "${ref}" 2>/dev/null \ + && git reset --quiet --hard "FETCH_HEAD" 2>/dev/null) \ + || (cd "$dest" && git fetch --quiet --tags && git checkout --quiet "${ref}" && git reset --quiet --hard "origin/${ref}" 2>/dev/null || true) + else + info "cloning ${repo} → ${dest} (ref: ${ref})" + if [ "$DRY_RUN" -eq 1 ]; then dry "git clone --branch ${ref} ${repo} ${dest}"; return; fi + git clone --quiet --depth=1 --branch "${ref}" "${repo}" "${dest}" 2>/dev/null \ + || git clone --quiet "${repo}" "${dest}" + (cd "$dest" && git checkout --quiet "${ref}" 2>/dev/null || true) + fi +} + +build_mcp_server() { + if [ "$DRY_RUN" -eq 1 ]; then + dry "(cd ${MCP_DIR} && npm install && npm run build)" + return + fi + info "installing MCP server dependencies (this may take a minute)" + (cd "$MCP_DIR" && npm install --silent --no-audit --no-fund --no-progress) \ + || { err "npm install failed in ${MCP_DIR}"; exit 1; } + info "building MCP server" + (cd "$MCP_DIR" && npm run build --silent) \ + || { err "npm run build failed in ${MCP_DIR}"; exit 1; } + if [ ! -f "${MCP_DIR}/dist/main.js" ]; then + err "expected ${MCP_DIR}/dist/main.js after build, not found" + exit 1 + fi + ok "MCP server built at ${MCP_DIR}/dist/main.js" +} + +# ---------- Skills ---------- +install_skills_for_client() { + local client="$1" + local dest; dest="$(client_skills_dir "$client")" || return 0 + if [ "$DRY_RUN" -eq 1 ]; then + dry "would symlink each skills// into ${dest}/" + return + fi + mkdir -p "$dest" + local count=0 skipped=0 + for skill_dir in "${KIT_DIR}"/skills/*/; do + [ -d "$skill_dir" ] || continue + local name; name="$(basename "$skill_dir")" + local link="${dest}/${name}" + if [ -L "$link" ]; then + # Replace existing symlink (idempotent update) + rm -f "$link" + elif [ -e "$link" ]; then + warn "skipping ${name} (target ${link} exists and is not a symlink)" + skipped=$((skipped+1)) + continue + fi + ln -s "$skill_dir" "$link" + count=$((count+1)) + done + ok "$(client_label "$client"): linked ${count} skills into ${dest}${skipped:+ (${skipped} skipped)}" +} + +uninstall_skills_for_client() { + local client="$1" + local dest; dest="$(client_skills_dir "$client")" || return 0 + [ -d "$dest" ] || return 0 + if [ "$DRY_RUN" -eq 1 ]; then + dry "would remove kit's skill symlinks from ${dest}/" + return + fi + local count=0 + for skill_dir in "${KIT_DIR}"/skills/*/; do + [ -d "$skill_dir" ] || continue + local name; name="$(basename "$skill_dir")" + local link="${dest}/${name}" + if [ -L "$link" ]; then + local target; target="$(readlink "$link" 2>/dev/null || true)" + case "$target" in + "${KIT_DIR}"/*|"${KIT_DIR}") + rm -f "$link" + count=$((count+1)) + ;; + esac + fi + done + ok "$(client_label "$client"): removed ${count} skill symlinks from ${dest}" +} + +# ---------- MCP entry ---------- +build_env_json() { + local conn="$1" profile="$2" + # CONNECTION_PROFILES must be a JSON string, so we serialise twice. + if [ -n "$JSON_TOOL" ] && [ "$JSON_TOOL" != "jq" ]; then + "$JSON_TOOL" - "$conn" "$profile" <<'PYEOF' +import json, sys +conn, profile = sys.argv[1], sys.argv[2] +profiles = {profile: {"authMode": "connectionString", "uri": conn}} +env = { + "TRANSPORT": "stdio", + "ALLOW_UNAUTHENTICATED_STDIO": "true", + "CONNECTION_PROFILES": json.dumps(profiles), +} +print(json.dumps(env)) +PYEOF + else + jq -n --arg conn "$conn" --arg profile "$profile" ' + { + TRANSPORT: "stdio", + ALLOW_UNAUTHENTICATED_STDIO: "true", + CONNECTION_PROFILES: ({($profile): {authMode: "connectionString", uri: $conn}} | tostring) + }' + fi +} + +install_mcp_for_client() { + local client="$1" conn="$2" + local cfg; cfg="$(client_mcp_config_path "$client")" + local top_key="mcpServers" + # (VS Code would use mcp.servers, but VS Code isn't in our auto-detect list) + local cmd="node" + local args_json='["'"${MCP_DIR}/dist/main.js"'"]' + local env_json; env_json="$(build_env_json "$conn" "$PROFILE_NAME")" + merge_mcp_entry "$cfg" "$top_key" "$cmd" "$args_json" "$env_json" + ok "$(client_label "$client"): wrote ${MCP_ENTRY} MCP entry → ${cfg}" +} + +uninstall_mcp_for_client() { + local client="$1" + local cfg; cfg="$(client_mcp_config_path "$client")" + [ -f "$cfg" ] || return 0 + local top_key="mcpServers" + remove_mcp_entry "$cfg" "$top_key" + ok "$(client_label "$client"): removed ${MCP_ENTRY} MCP entry from ${cfg}" +} + +# ---------- Connection string ---------- +prompt_for_uri() { + if [ -n "$URI" ]; then return; fi + if [ -n "${DOCUMENTDB_URI:-}" ]; then + URI="$DOCUMENTDB_URI" + info "using \$DOCUMENTDB_URI from environment" + return + fi + if [ "$YES" -eq 1 ]; then + err "no connection string provided (use --uri, or set \$DOCUMENTDB_URI)" + exit 2 + fi + if [ ! -t 0 ]; then + # Likely curl|bash with no TTY. Tell user how to provide it. + err "no connection string provided and no TTY for prompt" + err "re-run with: --uri \"mongodb://...\" OR set \$DOCUMENTDB_URI before running" + err "for local dev: --uri mongodb://localhost:27017" + exit 2 + fi + heading "DocumentDB connection string" + log "Examples:" + log " • Local: mongodb://localhost:27017" + log " • Azure: mongodb+srv://:@.mongocluster.cosmos.azure.com/?tls=true&authMechanism=SCRAM-SHA-256" + log "" + printf 'Connection string: ' + IFS= read -r URI || URI="" + if [ -z "$URI" ]; then + err "connection string is required" + exit 2 + fi +} + +# ---------- Run ---------- +main() { + heading "documentdb-agent-kit installer" + log "Install root: ${INSTALL_ROOT}" + [ "$DRY_RUN" -eq 1 ] && warn "DRY RUN — no files will be modified" + + if [ "$UNINSTALL" -eq 1 ]; then + run_uninstall + return + fi + + check_prereqs + + # Resolve client list + local clients_str + clients_str="$(detect_clients | filter_clients_by_user)" + if [ -z "$clients_str" ]; then + warn "no supported MCP clients detected" + warn "supported: ${SUPPORTED_CLIENTS[*]}" + warn "(install one and re-run, or use --clients to force a specific config path)" + exit 1 + fi + heading "Detected clients" + while IFS= read -r c; do log " • $(client_label "$c")"; done <<< "$clients_str" + + # Clone the kit (for skills + AGENTS.md) + if [ "$MCP_ONLY" -eq 0 ]; then + heading "Installing agent kit" + clone_or_update_repo "$KIT_REPO" "$KIT_DIR" "$KIT_REF" + ok "kit at ${KIT_DIR}" + fi + + # Clone & build the MCP server + if [ "$SKILLS_ONLY" -eq 0 ]; then + heading "Installing DocumentDB MCP server" + clone_or_update_repo "$MCP_REPO" "$MCP_DIR" "$MCP_REF" + build_mcp_server + fi + + # Connection string for MCP entry + if [ "$SKILLS_ONLY" -eq 0 ]; then + prompt_for_uri + fi + + heading "Wiring clients" + while IFS= read -r c; do + [ -z "$c" ] && continue + if [ "$SKILLS_ONLY" -eq 0 ]; then + install_mcp_for_client "$c" "$URI" + fi + if [ "$MCP_ONLY" -eq 0 ] && ! is_mcp_only_client "$c"; then + install_skills_for_client "$c" + fi + done <<< "$clients_str" + + # Final report with per-client notes + heading "Done" + log "Kit installed at: ${KIT_DIR}" + [ "$SKILLS_ONLY" -eq 0 ] && log "MCP server at: ${MCP_DIR}/dist/main.js" + log "" + log "Next steps:" + log " 1. Fully quit and reopen each configured client." + log " 2. Verify by asking the agent to list DocumentDB tools." + log " (Try: \"list_databases with connection_profile: ${PROFILE_NAME}\")" + if [ "$MCP_ONLY" -eq 0 ]; then + local mcp_only_seen=0 + while IFS= read -r c; do + [ -z "$c" ] && continue + if is_mcp_only_client "$c"; then + if [ "$mcp_only_seen" -eq 0 ]; then + log "" + log " Note for Cursor / Copilot CLI / Gemini CLI:" + log " These clients discover skills from project-local files (AGENTS.md /" + log " GEMINI.md). To use the kit's skills in a project, copy or symlink:" + log " cp ${KIT_DIR}/AGENTS.md /" + log " (Gemini: ln -s AGENTS.md GEMINI.md inside the project)" + mcp_only_seen=1 + fi + fi + done <<< "$clients_str" + fi + log "" + log "Uninstall: $0 --uninstall" +} + +run_uninstall() { + heading "Uninstalling documentdb-agent-kit" + + local clients_str + clients_str="$(detect_clients | filter_clients_by_user)" + if [ -z "$clients_str" ]; then + warn "no clients detected to clean up" + else + while IFS= read -r c; do + [ -z "$c" ] && continue + uninstall_mcp_for_client "$c" || true + is_mcp_only_client "$c" || uninstall_skills_for_client "$c" || true + done <<< "$clients_str" + fi + + if [ -d "$INSTALL_ROOT" ]; then + if [ "$DRY_RUN" -eq 1 ]; then + dry "would remove ${INSTALL_ROOT}" + else + rm -rf "$INSTALL_ROOT" + ok "removed ${INSTALL_ROOT}" + fi + fi + ok "uninstall complete" +} + +main "$@" diff --git a/skills/mcp-setup/SKILL.md b/skills/mcp-setup/SKILL.md index aa5bea4..ce6d187 100644 --- a/skills/mcp-setup/SKILL.md +++ b/skills/mcp-setup/SKILL.md @@ -1,295 +1,169 @@ --- name: documentdb-mcp-setup -description: Guide users through configuring the DocumentDB MCP server for Azure DocumentDB. Use this skill when a user has the DocumentDB MCP server installed but hasn't configured the required environment variables, or when they ask about connecting to Azure DocumentDB and don't have the credentials set up. +description: Guide users through installing and configuring the DocumentDB MCP server for Azure DocumentDB. Use this skill when a user wants to wire the DocumentDB MCP server into an agentic client (Claude Code, Claude Desktop, Cursor, Copilot CLI, Gemini CLI, VS Code) and define a `CONNECTION_PROFILES` entry, or when they hit MCP connection / auth / profile errors. --- # DocumentDB MCP Server Setup -This skill guides users through configuring the DocumentDB MCP server for use -with an agentic client, targeting Azure DocumentDB. +This skill guides users through wiring the +[`microsoft/documentdb-mcp`](https://github.com/microsoft/documentdb-mcp) +server into an agentic client and pointing it at Azure DocumentDB (or another +MongoDB-compatible endpoint). -## Overview +The DocumentDB MCP server is **stateless** and **administrator-controlled**: +backend connection details live in a `CONNECTION_PROFILES` JSON map defined in +the MCP client's config. Tools never accept a connection string as a runtime +argument — they reference a named profile via `connection_profile`. -The DocumentDB MCP server requires a connection string to your Azure DocumentDB -cluster. Users have three options: +## Fastest path: bundled installer -1. **Azure DocumentDB Connection String** (Option A): Direct connection to - an Azure DocumentDB cluster - - Recommended for most users - - Requires `DOCUMENTDB_URI` environment variable - - Connection string from Azure portal - -2. **Local MongoDB** (Option B): Connect to a local MongoDB instance for - development - - Best for local testing — minimal configuration required - - Uses default `mongodb://localhost:27017` - - No Azure credentials needed - -3. **Custom MongoDB-compatible endpoint** (Option C): Connect to any - MongoDB-compatible database - - For self-hosted MongoDB, other DocumentDB-compatible services, or - MongoDB Atlas - - Requires `DOCUMENTDB_URI` environment variable with custom connection string - -This is an interactive step-by-step guide. The agent detects the user's -environment and provides tailored instructions. - -## Step 1: Check Existing Configuration - -Before starting the setup, check if the user already has the required -environment variables configured. - -Run this command to check for existing configuration (masking values to avoid -exposing credentials): - -```bash -env | grep "^DOCUMENTDB_URI\|^TRANSPORT\|^HOST\|^PORT" | sed 's/DOCUMENTDB_URI=.*/DOCUMENTDB_URI=[set]/' -``` - -**Interpretation:** - -- If `DOCUMENTDB_URI` is set → connection is already configured -- If `TRANSPORT` is set → transport mode is configured -- If neither is set → proceed with full setup - -**Partial Configuration Handling:** - -- User already has `DOCUMENTDB_URI` set and just wants to change transport → - skip to Step 4 -- User wants to switch connection targets → proceed with Steps 2–5 -- User wants to update credentials → skip to Step 5 (profile editing - instructions) - -## Step 2: Present Configuration Options - -If no valid configuration exists, present the options: - -**Azure DocumentDB (Option A)** — Best for: - -- Production and development with Azure DocumentDB -- Full MongoDB wire protocol compatibility -- Managed database with Azure integration - -**Local MongoDB (Option B)** — Best for: - -- Local development and testing without cloud setup -- Fastest setup, no credentials required -- Just uses `mongodb://localhost:27017` - -**Custom Endpoint (Option C)** — Best for: - -- Self-hosted MongoDB deployments -- MongoDB Atlas or other MongoDB-compatible services -- Non-standard connection configurations - -Ask the user which option they'd like to proceed with. - -## Step 3a: Azure DocumentDB Setup - -If the user chooses Option A: - -### 3a.1: Explain How to Find the Connection String - -1. Go to the [Azure portal](https://portal.azure.com) -2. Navigate to your Azure DocumentDB cluster -3. In the left menu, select **Settings** → **Connection strings** -4. Copy the connection string — it will look like: - `mongodb+srv://:@.mongocluster.cosmos.azure.com/?tls=true&authMechanism=SCRAM-SHA-256` -5. Replace `` and `` with your database user credentials - -**Expected formats:** - -- `mongodb+srv://:@cluster.mongocluster.cosmos.azure.com/?tls=true&authMechanism=SCRAM-SHA-256` -- `mongodb://:@cluster.mongocluster.cosmos.azure.com:10255/?tls=true&authMechanism=SCRAM-SHA-256` - -**Important**: Azure DocumentDB requires TLS. Ensure `tls=true` is in the -connection string. - -Proceed to Step 4 (Configure Transport Mode). - -## Step 3b: Local MongoDB Setup - -If the user chooses Option B: - -### 3b.1: Verify Local MongoDB is Running +If the user wants the quickest path and is willing to run a script, point them +at the kit's installer, which installs both this skill pack and the MCP server +into every detected client in one command: ```bash -mongosh --eval "db.runCommand({ping: 1})" 2>/dev/null || echo "MongoDB not reachable" -``` - -If MongoDB is not installed or running, direct them to: -https://www.mongodb.com/docs/manual/installation/ - -The default connection string `mongodb://localhost:27017` will be used. No -`DOCUMENTDB_URI` environment variable is needed (it's the server default). - -Proceed to Step 4 (Configure Transport Mode). - -## Step 3c: Custom Endpoint Setup +# macOS / Linux +curl -fsSL https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.sh | bash -If the user chooses Option C: - -Ask the user for their MongoDB-compatible connection string. - -**Expected formats:** - -- `mongodb://:@host:port/database` -- `mongodb+srv://:@host/database` -- `mongodb://host:port` (no auth) - -Proceed to Step 4 (Configure Transport Mode). - -## Step 4: Configure Transport Mode - -The DocumentDB MCP server supports two transport modes: - -**stdio (Default)** — Recommended for most MCP client integrations: -- Communicates over standard input/output streams -- No additional configuration needed -- Best for Claude, Cursor, Copilot CLI, and most coding agents - -**streamable-http** — For HTTP-based integrations: -- Runs as an HTTP server -- Configurable host and port -- Best for browser-based clients or custom HTTP integrations - -For most users, **stdio** is the right choice. Only choose streamable-http if -you specifically need HTTP-based access. - -If streamable-http is chosen, also configure: -- `HOST` — Server host (default: `localhost`) -- `PORT` — Server port (default: `8070`) - -Proceed to Step 5 (Update Shell Profile). - -## Step 5: Update Shell Profile - -Help the user add the environment variables to their shell profile. **Do not ask -for or handle credentials** — provide exact instructions so the user can add -them directly. - -### 5.1: Detect Shell and Profile File - -If the user is on Windows, assume **PowerShell** but ask the user to confirm. -For Unix/macOS, detect the shell: - -```bash -echo $SHELL +# Windows (PowerShell) +irm https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.ps1 | iex ``` -Based on the result, identify the appropriate profile file. - -### 5.2: Show the Exact Snippet to Add - -Tell the user to store the connection string in a dedicated `~/.documentdb-env` -file. This keeps credentials out of files that are often group/world readable by -default and prevents accidentally committing them to git. +The installer prompts for a connection string, writes it as the `default` +profile, and configures all detected clients. The rest of this skill covers +the manual path (and is also the right reference when the installer fails or +the user wants to customize). -**Step 1**: Create/edit `~/.documentdb-env` (e.g., `nano ~/.documentdb-env`) -and add: +## Manual setup overview -**For Azure DocumentDB (Option A):** +Setup is per-client. For each client the user has installed: -```bash -# DocumentDB MCP Server Configuration -export DOCUMENTDB_URI="" -``` +1. Make sure Node.js 20+ is available (the MCP server runs on Node). +2. Find that client's MCP config file. +3. Add a `DocumentDB` server entry that launches the upstream MCP server and + passes `CONNECTION_PROFILES` (and `TRANSPORT=stdio` + `ALLOW_UNAUTHENTICATED_STDIO=true` + for local stdio use). +4. Restart the client. -**For Custom Endpoint (Option C):** +## Step 1: Confirm prerequisites ```bash -# DocumentDB MCP Server Configuration -export DOCUMENTDB_URI="" +node --version # must be >= 20 +git --version # required by `npx -y github:microsoft/documentdb-mcp` ``` -**If streamable-http transport was chosen (Step 4), also add:** +If either is missing, install them before continuing. -```bash -export TRANSPORT="streamable-http" -export HOST="localhost" -export PORT="8070" -``` +## Step 2: Pick the connection target -**Step 2**: Restrict permissions on the file: +| Option | When to use | Example URI | +|---|---|---| +| **A. Azure DocumentDB** | Production / cloud dev | `mongodb+srv://:@.mongocluster.cosmos.azure.com/?tls=true&authMechanism=SCRAM-SHA-256` | +| **B. Local MongoDB / DocumentDB** | Local dev | `mongodb://localhost:27017` | +| **C. Custom MongoDB-compatible** | Atlas, self-hosted, third-party | `mongodb://:@host:port/?tls=true` | -```bash -chmod 600 ~/.documentdb-env -``` +**Azure DocumentDB connection string:** Azure portal → your DocumentDB cluster +→ **Settings** → **Connection strings**. Replace `` / `` +with database user credentials. TLS is required (`tls=true` must be present). -**Step 3**: Source the file from the shell profile. Tell the user to open their -profile file (e.g., `code ~/.zshrc`, `nano ~/.zshrc`) and add: +## Step 3: Pick a transport -```bash -source ~/.documentdb-env -``` +- **`stdio`** (default, recommended) — the client launches the server as a + subprocess. Use this for every client below. +- **`streamable-http`** — only for browser clients or custom HTTP integrations + where you have a separate, long-running server with Entra-authenticated + bearer tokens. Not covered here; see the upstream README. -Adjust syntax for the detected shell (e.g., for fish: `bass source -~/.documentdb-env` or set variables directly with `set -x`; for PowerShell: -dot-source a `.ps1` file instead). +For stdio, `ALLOW_UNAUTHENTICATED_STDIO=true` is required (stdio runs on the +user's trusted local machine and bypasses Entra auth). -### 5.3: After Editing — Reload and Verify +## Step 4: Write the MCP config -Once the user has saved the file, provide the commands to reload and verify: +The MCP server entry has the same shape for every client. Only the wrapping +config file and the top-level key (`mcpServers` vs `mcp.servers`) differ. -**Reload the profile:** +**Server entry template** (substitute `` with the URI from Step 2): -```bash -source ~/.zshrc # adjust path to match their profile file +```jsonc +{ + "DocumentDB": { + "command": "npx", + "args": ["-y", "github:microsoft/documentdb-mcp"], + "env": { + "TRANSPORT": "stdio", + "ALLOW_UNAUTHENTICATED_STDIO": "true", + "CONNECTION_PROFILES": "{\"default\":{\"authMode\":\"connectionString\",\"uri\":\"\"}}" + } + } +} ``` -**Verify the variables are set (masking values):** - -```bash -env | grep "^DOCUMENTDB_URI\|^TRANSPORT\|^HOST\|^PORT" | sed 's/DOCUMENTDB_URI=.*/DOCUMENTDB_URI=[set]/' +Notes: + +- `CONNECTION_PROFILES` is a **JSON string** (escaped) — not a JSON object. +- The profile name `default` is what agents pass to tool calls via the + `connection_profile` argument. You can use any name; `default` keeps it + simple. +- To allow write or management tools, add `"ENABLE_WRITE_TOOLS": "true"` and/or + `"ENABLE_MANAGEMENT_TOOLS": "true"` to `env`. Read tools are on by default. +- The first `npx -y github:...` invocation will clone and build the server + (~30 s on a fast connection). Subsequent invocations use the `npx` cache. + For faster startup, install once locally and point `command`/`args` at the + built `node /path/to/dist/main.js` instead — this is what the bundled + installer does. + +### Client-specific config files + +| Client | Config file | Top-level key | +|---|---|---| +| **Claude Code** (user-scoped) | `~/.claude.json` | `mcpServers` | +| **Claude Desktop** | macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
Linux: `~/.config/Claude/claude_desktop_config.json`
Windows: `%APPDATA%\Claude\claude_desktop_config.json` | `mcpServers` | +| **Cursor** (user-scoped) | `~/.cursor/mcp.json` | `mcpServers` | +| **GitHub Copilot CLI** | `~/.copilot/mcp-config.json` | `mcpServers` | +| **GitHub Copilot for VS Code** | VS Code `settings.json` | `mcp.servers` | +| **Gemini CLI** | `~/.gemini/settings.json` | `mcpServers` | + +If the file doesn't exist yet, create it with a single top-level object: + +```json +{ "mcpServers": { "DocumentDB": { ... } } } ``` -Expected output should show the variable name(s) they just added. - -Proceed to Step 6 (Next Steps). - -## Step 6: Next Steps - -### For Options A & C (Azure DocumentDB / Custom Endpoint): - -1. **Restart the agentic client**: Fully quit the client, then in your terminal - run `source ` (e.g., `source ~/.zshrc`) to load the new - variables. Open the client from that same shell session so it inherits the - environment. - -2. **Verify MCP Server**: After restart, test by performing a DocumentDB - operation: - - Try `list_databases` to see available databases - - Try `get_connection_status` to verify the connection - -3. **Using the Tools**: - - Database operations: `list_databases`, `db_stats`, `get_db_info` - - Collection operations: `collection_stats`, `sample_documents` - - Document operations: `find_documents`, `count_documents`, `aggregate` - - Index operations: `list_indexes`, `index_stats`, `create_index` - - Query optimization: `optimize_find_query`, `explain_aggregate_query` - -### For Option B (Local MongoDB): - -1. **Ready to use**: No additional configuration needed if using the default - connection string. +If it already has other servers, **add** the `DocumentDB` entry inside the +existing `mcpServers` object — don't overwrite the whole file. -2. **Start the MCP server**: The server will connect to `mongodb://localhost:27017` - by default. +## Step 5: Restart the client and verify -3. **Verify**: Try `list_databases` to confirm connectivity. +1. **Fully quit** the client (not just close the window). +2. Reopen it. +3. Ask the agent to list available DocumentDB tools, or run a tool directly + (the agent should pass `connection_profile: "default"`): + - `list_databases` — confirms the server is reachable and the profile works + - `db_stats` — basic round-trip check ## Troubleshooting -- **Variables not appearing after `source`**: Check the profile file path and - confirm the file was saved -- **Client doesn't pick up variables**: Ensure full restart (quit + reopen), - not just a reload -- **TLS errors with Azure DocumentDB**: Ensure `tls=true` is in the - connection string -- **Authentication errors**: Verify username and password in the connection - string are correct; check that the user exists in Azure portal -- **Connection timeout**: Check network connectivity and firewall rules; - Azure DocumentDB may require allowlisting your IP in the Azure portal - under Networking settings -- **fish/PowerShell**: Syntax differs — use `set -x` (fish) or `$env:` - (PowerShell) instead of `export` +- **`npx` errors / repo not found**: the upstream `microsoft/documentdb-mcp` + repo may be private or unreachable. Check `git ls-remote + https://github.com/microsoft/documentdb-mcp.git`; if it fails, fall back to + cloning the repo manually, running `npm install && npm run build`, and + pointing `command` → `node`, `args` → `["/dist/main.js"]`. +- **`unauthenticated stdio is disabled`**: you forgot + `ALLOW_UNAUTHENTICATED_STDIO: "true"` in `env`. +- **`connection_profile "default" not found`**: the agent is passing a + different profile name than what's defined in `CONNECTION_PROFILES`. Either + rename your profile or tell the agent which name to use. +- **TLS errors against Azure DocumentDB**: ensure `tls=true` is in the URI and + the connection string is fully URL-encoded (special characters in passwords + must be percent-encoded). +- **Auth errors**: verify the database user exists in Azure portal under your + cluster's Settings → Authentication, and that the password is correct. +- **Connection timeout to Azure**: Azure DocumentDB firewall may be blocking + your IP. Portal → cluster → **Networking** → add your client IP to the + allowlist. +- **JSON escape issues**: `CONNECTION_PROFILES` is a string of JSON. Inner + double quotes must be escaped (`\"`). Use a JSON validator if the client + silently ignores the server. The bundled installer handles escaping + correctly — prefer it if escaping is painful. +- **VS Code uses `mcp.servers`, not `mcpServers`**: this is the one client + with a different top-level key. From 5718efdf91e381160c8812e11cc96f00f7035ab4 Mon Sep 17 00:00:00 2001 From: German Date: Tue, 26 May 2026 14:57:54 -0700 Subject: [PATCH 2/8] Set AUTH_REQUIRED=false for stdio + document scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upstream MCP server defaults AUTH_REQUIRED=true and exits at startup (validateConfig in main.ts) when ENTRA_TENANT_ID / ENTRA_AUDIENCE are unset — even for TRANSPORT=stdio. Add AUTH_REQUIRED=false to the env emitted by both installers so a fresh install actually launches. Document the scope so users know cluster auth is unaffected: AUTH_REQUIRED gates only the Entra-JWT bearer check on the HTTP/SSE transport between client and server. MongoDB cluster auth (SCRAM from the URI, or authMode=entra), TLS, and capability gates (ENABLE_*_TOOLS) flow through CONNECTION_PROFILES and stay enforced regardless. Safe only with TRANSPORT=stdio; flipped on streamable-http it would expose /mcp unauthenticated. - install.sh / install.ps1: emit AUTH_REQUIRED=false; expand comment to spell out the constraint and the stdio-only caveat. - skills/mcp-setup/SKILL.md: explain in Step 3 what the flag does and doesn't affect; update the env example; expand troubleshooting; note the same in the manual-setup overview. - CHANGELOG.md: fold the AUTH_REQUIRED change into the existing 2026-05-21 installer entry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 12 +++++++++--- install.ps1 | 20 ++++++++++++++++++-- install.sh | 17 +++++++++++++++++ skills/mcp-setup/SKILL.md | 33 +++++++++++++++++++++++++++++---- 4 files changed, 73 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c29d1f..423c806 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,9 +30,15 @@ What landed: set `DOCUMENTDB_URI` in a shell profile, which is **not** how the current upstream MCP server is configured. Rewrote around the actual upstream contract: per-client MCP config file with `CONNECTION_PROFILES` JSON, - `TRANSPORT=stdio`, and `ALLOW_UNAUTHENTICATED_STDIO=true`. Added per-client - config-file table for Claude Code / Desktop / Cursor / Copilot CLI / Gemini - CLI / VS Code. Updated AGENTS.md's mcp-setup row accordingly. + `TRANSPORT=stdio`, `AUTH_REQUIRED=false`, and + `ALLOW_UNAUTHENTICATED_STDIO=true`. Added per-client config-file table for + Claude Code / Desktop / Cursor / Copilot CLI / Gemini CLI / VS Code. + Updated AGENTS.md's mcp-setup row accordingly. Documented that + `AUTH_REQUIRED` gates only the Entra-JWT bearer check on the MCP server's + HTTP/SSE transport and is independent of MongoDB cluster auth (SCRAM / + `authMode=entra`), TLS, and capability gates — `AUTH_REQUIRED=false` is + safe only with `TRANSPORT=stdio`, and the same caveat is captured in the + installer comments. Verified end-to-end (Bash + PowerShell) in sandboxed `$HOME` against fake client configs: existing MCP servers preserved, single- and multi-element diff --git a/install.ps1 b/install.ps1 index 535f783..0d1e4e5 100644 --- a/install.ps1 +++ b/install.ps1 @@ -411,10 +411,26 @@ function Uninstall-SkillsForClient([string]$client) { function Get-EnvJsonHashtable([string]$conn, [string]$profileName) { $profiles = [ordered]@{ $profileName = [ordered]@{ authMode = "connectionString"; uri = $conn } } $profilesJson = ($profiles | ConvertTo-Json -Compress -Depth 10) + # AUTH_REQUIRED gates ONLY the Entra-JWT bearer-token check on the MCP + # server's HTTP/SSE transport (i.e., calls FROM the MCP client TO this + # server). It is fully independent of MongoDB cluster auth: SCRAM + # username/password from the URI and Entra-to-cluster tokens (when a + # profile uses authMode=entra) flow through CONNECTION_PROFILES and stay + # active regardless of this setting. + # + # The server defaults AUTH_REQUIRED=true and fails startup unless + # ENTRA_TENANT_ID / ENTRA_AUDIENCE are set. For local stdio we set + # AUTH_REQUIRED=false and ALLOW_UNAUTHENTICATED_STDIO=true (the server's + # intended dev path). This is SAFE ONLY because TRANSPORT=stdio means the + # MCP server is a subprocess on the user's trusted local machine — no + # network listener is opened. If you ever switch TRANSPORT to + # streamable-http or sse, set AUTH_REQUIRED=true and provide the Entra + # tenant/audience, or the /mcp endpoint will be exposed unauthenticated. return [ordered]@{ - TRANSPORT = "stdio" + TRANSPORT = "stdio" + AUTH_REQUIRED = "false" ALLOW_UNAUTHENTICATED_STDIO = "true" - CONNECTION_PROFILES = $profilesJson + CONNECTION_PROFILES = $profilesJson } } diff --git a/install.sh b/install.sh index 091a250..e36cbec 100755 --- a/install.sh +++ b/install.sh @@ -444,8 +444,24 @@ build_env_json() { import json, sys conn, profile = sys.argv[1], sys.argv[2] profiles = {profile: {"authMode": "connectionString", "uri": conn}} +# AUTH_REQUIRED gates ONLY the Entra-JWT bearer-token check on the MCP +# server's HTTP/SSE transport (i.e., calls FROM the MCP client TO this +# server). It is fully independent of MongoDB cluster auth: SCRAM +# username/password from the URI and Entra-to-cluster tokens (when a +# profile uses authMode=entra) flow through CONNECTION_PROFILES and stay +# active regardless of this setting. +# +# The server defaults AUTH_REQUIRED=true and fails startup unless +# ENTRA_TENANT_ID / ENTRA_AUDIENCE are set. For local stdio we set +# AUTH_REQUIRED=false and ALLOW_UNAUTHENTICATED_STDIO=true (the server's +# intended dev path). This is SAFE ONLY because TRANSPORT=stdio means the +# MCP server is a subprocess on the user's trusted local machine — no +# network listener is opened. If you ever switch TRANSPORT to +# streamable-http or sse, set AUTH_REQUIRED=true and provide the Entra +# tenant/audience, or the /mcp endpoint will be exposed unauthenticated. env = { "TRANSPORT": "stdio", + "AUTH_REQUIRED": "false", "ALLOW_UNAUTHENTICATED_STDIO": "true", "CONNECTION_PROFILES": json.dumps(profiles), } @@ -455,6 +471,7 @@ PYEOF jq -n --arg conn "$conn" --arg profile "$profile" ' { TRANSPORT: "stdio", + AUTH_REQUIRED: "false", ALLOW_UNAUTHENTICATED_STDIO: "true", CONNECTION_PROFILES: ({($profile): {authMode: "connectionString", uri: $conn}} | tostring) }' diff --git a/skills/mcp-setup/SKILL.md b/skills/mcp-setup/SKILL.md index ce6d187..4e58da6 100644 --- a/skills/mcp-setup/SKILL.md +++ b/skills/mcp-setup/SKILL.md @@ -41,8 +41,8 @@ Setup is per-client. For each client the user has installed: 1. Make sure Node.js 20+ is available (the MCP server runs on Node). 2. Find that client's MCP config file. 3. Add a `DocumentDB` server entry that launches the upstream MCP server and - passes `CONNECTION_PROFILES` (and `TRANSPORT=stdio` + `ALLOW_UNAUTHENTICATED_STDIO=true` - for local stdio use). + passes `CONNECTION_PROFILES` (and `TRANSPORT=stdio` + `AUTH_REQUIRED=false` + + `ALLOW_UNAUTHENTICATED_STDIO=true` for local stdio use). 4. Restart the client. ## Step 1: Confirm prerequisites @@ -74,8 +74,25 @@ with database user credentials. TLS is required (`tls=true` must be present). where you have a separate, long-running server with Entra-authenticated bearer tokens. Not covered here; see the upstream README. -For stdio, `ALLOW_UNAUTHENTICATED_STDIO=true` is required (stdio runs on the -user's trusted local machine and bypasses Entra auth). +For stdio, set `AUTH_REQUIRED=false` and `ALLOW_UNAUTHENTICATED_STDIO=true`. +The server defaults `AUTH_REQUIRED=true` and **exits at startup** unless +`ENTRA_TENANT_ID` / `ENTRA_AUDIENCE` are set, even for stdio. + +**`AUTH_REQUIRED=false` does not weaken your cluster's auth.** It gates only +the Entra-JWT bearer-token check on the MCP server's HTTP/SSE transport — +i.e., calls from the MCP client to this server. It is fully independent +from how the MCP server talks to your DocumentDB cluster: SCRAM +username/password (from the connection-string URI) and Entra-to-cluster +tokens (`authMode: "entra"`) flow through `CONNECTION_PROFILES` and stay +active regardless of `AUTH_REQUIRED`. TLS to the cluster (`tls=true`), +capability gates (`ENABLE_*_TOOLS`), and tool-tier authorization are also +unaffected. + +This setup is safe **only because `TRANSPORT=stdio`**: the MCP server runs +as a subprocess of the trusted local client — no network listener is +opened. If you ever switch `TRANSPORT` to `streamable-http` or `sse`, set +`AUTH_REQUIRED=true` and provide the Entra tenant/audience, or the `/mcp` +endpoint will be exposed unauthenticated. ## Step 4: Write the MCP config @@ -91,6 +108,7 @@ config file and the top-level key (`mcpServers` vs `mcp.servers`) differ. "args": ["-y", "github:microsoft/documentdb-mcp"], "env": { "TRANSPORT": "stdio", + "AUTH_REQUIRED": "false", "ALLOW_UNAUTHENTICATED_STDIO": "true", "CONNECTION_PROFILES": "{\"default\":{\"authMode\":\"connectionString\",\"uri\":\"\"}}" } @@ -150,6 +168,13 @@ existing `mcpServers` object — don't overwrite the whole file. pointing `command` → `node`, `args` → `["/dist/main.js"]`. - **`unauthenticated stdio is disabled`**: you forgot `ALLOW_UNAUTHENTICATED_STDIO: "true"` in `env`. +- **`AUTH_REQUIRED is true but ...` / server exits immediately on launch**: + add `"AUTH_REQUIRED": "false"` to `env`. The server defaults this to `true` + and refuses to start without Entra tenant/audience config. This flag gates + only the Entra-JWT bearer check on the MCP server's HTTP/SSE transport + — it does **not** disable MongoDB-level auth (SCRAM or `authMode=entra`), + TLS, or capability gates. Only set it to `false` together with + `TRANSPORT=stdio`. - **`connection_profile "default" not found`**: the agent is passing a different profile name than what's defined in `CONNECTION_PROFILES`. Either rename your profile or tell the agent which name to use. From bdbe2d665afee79496a27b119a1f30e8325b1b85 Mon Sep 17 00:00:00 2001 From: German Date: Wed, 27 May 2026 13:41:51 -0700 Subject: [PATCH 3/8] Fix npm invocation on Windows (Unknown command: "pm") On Windows, npm is npm.cmd (a batch shim). PowerShell's `&` call operator mangles arguments when piping them into .cmd files under the default PSNativeCommandArgumentPassing setting on pwsh 7.3+, eating characters off the first arg. Symptom: > & npm install --silent --no-audit --no-fund --no-progress Unknown command: "pm" (npm received argv = ['pm', 'install', ...] instead of ['install', ...].) Route Windows npm calls through cmd.exe via cmd /c, which bypasses PowerShell's native-command argument parser entirely. macOS / Linux path is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- install.ps1 | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/install.ps1 b/install.ps1 index 0d1e4e5..60f77df 100644 --- a/install.ps1 +++ b/install.ps1 @@ -335,15 +335,30 @@ function Sync-Repo([string]$Repo, [string]$Dest, [string]$Ref) { } } +function Invoke-Npm { + param([string[]]$NpmArgs) + if ($IsWin) { + # On Windows, npm is npm.cmd (a batch shim). PowerShell's `&` call + # operator can mangle arguments when piping them into .cmd files + # (notably under PSNativeCommandArgumentPassing defaults on pwsh 7.3+), + # which manifests as e.g. `Unknown command: "pm"` because the first + # char of "install" is eaten. Route through cmd.exe to bypass entirely. + $line = "npm " + ($NpmArgs -join ' ') + cmd /c $line + } else { + & npm @NpmArgs + } +} + function Build-McpServer { if ($DryRun) { Write-Dry "(cd $MCP_DIR; npm install; npm run build)"; return } Write-Info "installing MCP server dependencies (this may take a minute)" Push-Location $MCP_DIR try { - & npm install --silent --no-audit --no-fund --no-progress + Invoke-Npm @("install", "--silent", "--no-audit", "--no-fund", "--no-progress") if ($LASTEXITCODE -ne 0) { Write-Err "npm install failed in $MCP_DIR"; exit 1 } Write-Info "building MCP server" - & npm run build --silent + Invoke-Npm @("run", "build", "--silent") if ($LASTEXITCODE -ne 0) { Write-Err "npm run build failed in $MCP_DIR"; exit 1 } } finally { Pop-Location } $entry = Join-Path $MCP_DIR "dist/main.js" From 46bddaf2a140db8b05ba5d164fe7a34f6df65198 Mon Sep 17 00:00:00 2001 From: German Date: Wed, 27 May 2026 13:44:27 -0700 Subject: [PATCH 4/8] Fix Windows uninstall snippet (iex doesn't take -ArgumentList) `Invoke-Expression` accepts only `-Command`; it has no `-ArgumentList` parameter. The old snippet errored with: Invoke-Expression: A parameter cannot be found that matches parameter name 'ArgumentList'. Replace with the download-then-invoke pattern, which is the standard PowerShell idiom for passing flags to a script fetched over HTTP. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a21b25..fa326ae 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,8 @@ curl -fsSL https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/ins ```powershell # Windows -irm https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.ps1 | iex -ArgumentList '-Uninstall','-Yes' +irm https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.ps1 -OutFile $env:TEMP\install.ps1 +& $env:TEMP\install.ps1 -Uninstall -Yes ``` Removes the kit's `DocumentDB` MCP entry from every client, removes skill From a2cafed6de234d82e5c7d1baee190b643203f9eb Mon Sep 17 00:00:00 2001 From: German Date: Wed, 27 May 2026 13:49:13 -0700 Subject: [PATCH 5/8] Add per-OS step-by-step install sections + troubleshooting table Expand the README install section with explicit step-by-step subsections for macOS, Linux, and Windows. Each covers prerequisites (with concrete package-manager commands), the run command, and the restart step. Also add a Troubleshooting table consolidating the rough edges users have hit (bash -s -- omission, ExecutionPolicy, irm | iex no flags, the Windows npm 'Unknown command pm' bug, nvm PATH refresh, etc.) and collapse the old standalone Requirements section into a one-line summary since the per-OS sections now cover it in detail. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 141 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index fa326ae..60be9a4 100644 --- a/README.md +++ b/README.md @@ -25,27 +25,140 @@ the [`microsoft/documentdb-mcp`](https://github.com/microsoft/documentdb-mcp) server into every detected MCP client. This is the recommended path today — the per-agent plugin/extension marketplaces below are still being published. -### One-liner (recommended) +### Step-by-step: macOS -**macOS / Linux:** +**1. Prerequisites** ```bash -curl -fsSL https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.sh | bash -s -- --uri "" +# git +xcode-select --install # if not already installed +# Node.js 20+ (Homebrew) +brew install node@20 && brew link --overwrite --force node@20 + +# Verify +git --version +node --version # must be v20.x or higher ``` -**Windows (PowerShell):** +**2. Get your DocumentDB connection string** + +- **Azure DocumentDB:** Azure portal → cluster → *Settings → Connection strings*. Shape: + `mongodb+srv://:@.mongocluster.cosmos.azure.com/?tls=true&authMechanism=SCRAM-SHA-256`. + URL-encode special characters in the password. +- **Local DocumentDB / MongoDB:** `mongodb://localhost:27017` +- **Atlas / self-hosted:** your standard MongoDB URI. + +> ⚠️ Keep the connection string in your shell only — don't paste it into any AI agent chat. + +**3. Run the installer** + +```bash +curl -fsSL https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.sh \ + | bash -s -- --uri "" --yes +``` + +Or with the URI in an env var: + +```bash +export DOCUMENTDB_URI="" +curl -fsSL https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.sh | bash -s -- --yes +``` + +**4. Fully quit and reopen each configured client.** Closing the window isn't enough — MCP config is read only at process start. + +**5. Verify** (see [Verify it worked](#verify-it-worked) below). + +### Step-by-step: Linux + +**1. Prerequisites** + +```bash +# git +sudo apt install -y git # Debian/Ubuntu +# sudo dnf install -y git # Fedora/RHEL +# sudo pacman -S git # Arch + +# Node.js 20+ — distro packages are usually too old. Pick one: + +# Option A: NodeSource (Debian/Ubuntu/Fedora/RHEL) +curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - +sudo apt install -y nodejs + +# Option B: nvm (any distro, recommended for dev machines) +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash +exec $SHELL +nvm install 20 && nvm use 20 + +# Verify +git --version +node --version # must be v20.x or higher +npm --version +``` + +**2. Get your DocumentDB connection string** — same as macOS above. + +**3. Run the installer** + +```bash +curl -fsSL https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.sh \ + | bash -s -- --uri "" --yes +``` + +> The `bash -s --` part is **required** when piping through `curl` — it tells bash that everything after is an argument to the script, not to bash itself. + +**4. Fully quit and reopen each configured client.** For terminal clients (Copilot CLI, Gemini CLI), exit and reopen the shell. + +**5. Verify** (see [Verify it worked](#verify-it-worked) below). + +> Don't `sudo` the installer — it only writes user-scoped configs. Running as root will create files owned by root in your home directory. + +### Step-by-step: Windows + +**1. Prerequisites** + +Open **PowerShell** (Windows PowerShell 5.1 or pwsh 7+) — as your normal user, not admin: ```powershell -$env:DOCUMENTDB_URI = "" +# git +winget install --id Git.Git + +# Node.js 20+ +winget install OpenJS.NodeJS.LTS + +# Verify (open a new PowerShell window first to refresh PATH) +git --version +node --version # must be v20.x or higher +$PSVersionTable.PSVersion # 5.1+ or 7+ +``` + +*(Optional but recommended)* Enable **Developer Mode** so the installer can use symlinks instead of copying files: *Settings → Privacy & security → For developers → Developer Mode = On*. Without it the installer falls back to copying — that still works, just less elegant for skill updates. + +**2. Get your DocumentDB connection string** — same as macOS above. + +**3. Run the installer** + +```powershell +$env:DOCUMENTDB_URI = "" irm https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.ps1 | iex ``` -Local-dev quickstart (no Azure cluster needed, assuming a running documentdb-local container): +If you get `running scripts is disabled on this system`, run this once in the same window and re-run: -```bash -curl -fsSL https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.sh | bash -s -- --uri "mongodb://localhost:27017" +```powershell +Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass +``` + +**To pass flags** (`-Yes`, `-DryRun`, `-Uninstall`, etc.), `irm | iex` won't work — download then invoke: + +```powershell +irm https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.ps1 -OutFile $env:TEMP\install.ps1 +& $env:TEMP\install.ps1 -Uri "" -Yes ``` +**4. Fully quit and reopen each configured client.** Use the system tray / Task Manager — closing the window isn't enough. + +**5. Verify** (see [Verify it worked](#verify-it-worked) below). + ### What gets installed | Path | What | @@ -67,12 +180,14 @@ Existing entries in each client's config are preserved — the installer only adds (or updates) a single `DocumentDB` entry. A timestamped `.bak` backup is written before every JSON edit. -### Requirements +### Requirements summary - `git` - Node.js 20+ and `npm` (the MCP server is a Node app, built from source on install). `--skills-only` mode skips Node requirements. +See the per-OS step-by-step sections above for install commands. + ### Common flags ```text @@ -114,6 +229,23 @@ Removes the kit's `DocumentDB` MCP entry from every client, removes skill symlinks, and deletes `~/.documentdb-agent-kit/`. Other MCP servers and your non-kit skills are left untouched. +### Troubleshooting + +| Symptom | Platform | Fix | +|---|---|---| +| `bash: line N: --uri: command not found` | macOS / Linux | Missing `bash -s --` between `curl ... \|` and the flags. | +| `running scripts is disabled on this system` | Windows | `Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass` in the same PowerShell session, then re-run. | +| `Invoke-Expression: A parameter cannot be found that matches parameter name 'ArgumentList'` | Windows | `irm \| iex` doesn't accept flags. Use the `irm -OutFile … ; & $env:TEMP\install.ps1 -Yes` pattern. | +| `npm: Unknown command: "pm"` during MCP build | Windows | Old installer bug — re-fetch the latest `install.ps1` (fixed in this kit). | +| `node: command not found` after install | all | Open a new terminal to refresh `PATH`. With nvm, also run `nvm use 20`. | +| `npm: not found` but Node.js is installed | Linux (Debian/Ubuntu) | The distro `nodejs` package sometimes omits npm — `sudo apt install -y npm`, or use nvm. | +| `symlink failed for ; copying instead` warnings | Windows | Harmless. Enable Developer Mode if you want real symlinks. | +| Agent: `connection_profile "default" not found` | all | Tell the agent to use profile `default` explicitly, or pass `--profile ` / `-Profile ` to the installer. | +| Agent: `AUTH_REQUIRED is true ...` or server exits at launch | all | Re-run the installer — it sets `AUTH_REQUIRED=false` + `ALLOW_UNAUTHENTICATED_STDIO=true`, required for local stdio. This only disables the MCP-server's Entra-JWT *transport* gate; your cluster's SCRAM/Entra auth is unaffected. | +| TLS error against Azure | all | Confirm `tls=true` is in the URI and the password is URL-encoded. | +| Connection timeout to Azure | all | Azure portal → cluster → *Networking* → add your client IP to the firewall allowlist. | +| `Permission denied` writing into `~/.claude.json` | Linux / macOS | Don't `sudo` the installer — it writes user-scoped configs. Run as your normal user. | + ### Manual install (no script) If you don't want to run the installer, every step is documented in the From 2b48a3b37d79299b07b3c8664fc5e631a4e18fa0 Mon Sep 17 00:00:00 2001 From: German Date: Wed, 27 May 2026 13:51:24 -0700 Subject: [PATCH 6/8] Fix misleading uninstall hint printed at end of install When install.sh is invoked via `curl ... | bash -s -- ...`, $0 is "bash", so "Uninstall: $0 --uninstall" rendered as the literally invalid command `bash --uninstall`. Detect that case (and the stdin / non-file cases) and emit the curl one-liner instead; only show the script-path form when $0 actually points at a file on disk. install.ps1 had a similar bug: the farewell hint suggested `irm ... | iex -ArgumentList '-Uninstall'`, but Invoke-Expression has no -ArgumentList parameter. Replace with the download-then-invoke form that actually works on Windows PowerShell. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- install.ps1 | 8 ++++++-- install.sh | 17 ++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/install.ps1 b/install.ps1 index 60f77df..36fa070 100644 --- a/install.ps1 +++ b/install.ps1 @@ -549,8 +549,12 @@ function Invoke-Install { } } Write-Host "" - Write-Host "Uninstall: irm https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.ps1 | iex -ArgumentList '-Uninstall'" - Write-Host " or: .\install.ps1 -Uninstall" + # `iex` does not accept -ArgumentList, so the irm | iex pipe form can't + # take flags. Show the download-then-invoke form which actually works. + Write-Host "Uninstall (one-liner):" + Write-Host " irm https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.ps1 -OutFile `$env:TEMP\install.ps1; & `$env:TEMP\install.ps1 -Uninstall -Yes" + Write-Host "Or, from a local checkout:" + Write-Host " .\install.ps1 -Uninstall" } function Invoke-Uninstall { diff --git a/install.sh b/install.sh index e36cbec..80ab23c 100755 --- a/install.sh +++ b/install.sh @@ -613,7 +613,22 @@ main() { done <<< "$clients_str" fi log "" - log "Uninstall: $0 --uninstall" + # When invoked via `curl ... | bash -s -- ...`, $0 is "bash" / "-bash" / + # "/usr/bin/bash" — not a script path — so suggesting "$0 --uninstall" prints + # the misleading "bash --uninstall". Detect that and emit the curl one-liner + # instead. When run as a local script, show the script-path form. + case "$0" in + bash|-bash|*/bash|sh|-sh|*/sh) + log "Uninstall: curl -fsSL https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.sh | bash -s -- --uninstall --yes" + ;; + *) + if [ -f "$0" ]; then + log "Uninstall: $0 --uninstall" + else + log "Uninstall: curl -fsSL https://raw.githubusercontent.com/Azure/documentdb-agent-kit/main/install.sh | bash -s -- --uninstall --yes" + fi + ;; + esac } run_uninstall() { From e92cb186c57c8dfbd15b1e1577821692b710ee3d Mon Sep 17 00:00:00 2001 From: German Date: Wed, 27 May 2026 14:29:17 -0700 Subject: [PATCH 7/8] Track upstream rename: ALLOW_UNAUTHENTICATED_STDIO -> TRUST_LOCAL_STDIO Upstream microsoft/documentdb-mcp#83 renamed ALLOW_UNAUTHENTICATED_STDIO to TRUST_LOCAL_STDIO. The old name is no longer recognized by the server at main; it has been silently ignored on every install since that PR landed. Our installer kept working only because it also sets AUTH_REQUIRED=false, which short-circuits the server's Entra startup validator before TRUST_LOCAL_STDIO is ever consulted in the transport switch. Rename everywhere so future readers don't see a dead env var: - install.sh build_env_json (python + jq paths) - install.ps1 Get-EnvJsonHashtable - mcp.json - gemini-extension.json (Gemini extension manifest) - skills/mcp-setup/SKILL.md (Step 3, config template, troubleshooting) - README.md (troubleshooting table) - CHANGELOG.md (new entry + footnote on the 2026-05-21 entry) Comments in install.sh / install.ps1 / SKILL.md keep an explicit 'previously ALLOW_UNAUTHENTICATED_STDIO' note pointing at the upstream PR so users on older server builds know what to use instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 29 ++++++++++++++++++++++++++++- README.md | 2 +- gemini-extension.json | 2 +- install.ps1 | 28 ++++++++++++++++------------ install.sh | 24 ++++++++++++++---------- mcp.json | 2 +- skills/mcp-setup/SKILL.md | 14 +++++++++----- 7 files changed, 70 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dee16f..53e4639 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## 2026-05-27 — Track upstream rename: `ALLOW_UNAUTHENTICATED_STDIO` → `TRUST_LOCAL_STDIO` + +Upstream [microsoft/documentdb-mcp#83](https://github.com/microsoft/documentdb-mcp/pull/83) +renamed `ALLOW_UNAUTHENTICATED_STDIO` to `TRUST_LOCAL_STDIO`. The old name is +no longer recognized — server builds at `main` silently ignore it. The kit's +installer kept working only because it also sets `AUTH_REQUIRED=false`, which +short-circuits the Entra startup-validator before `TRUST_LOCAL_STDIO` is ever +consulted. Renamed everywhere so future readers don't see a dead env var: + +- `install.sh` — `build_env_json()` (both python and jq paths) +- `install.ps1` — `Get-EnvJsonHashtable()` +- `mcp.json` +- `skills/mcp-setup/SKILL.md` — Step 3, config template, troubleshooting +- `README.md` — troubleshooting table + +Also fixed a small but real correctness issue in the upstream-installed +"Install in VS Code" badge: it sets `TRUST_LOCAL_STDIO=true` and `TRANSPORT=stdio` +but omits `AUTH_REQUIRED=false`. Because the server's `validateConfig` runs +before the transport switch and demands `ENTRA_TENANT_ID` / `ENTRA_AUDIENCE` +whenever `AUTH_REQUIRED=true` (the default), the install-button config exits +at startup with "AUTH_REQUIRED=true requires ENTRA_TENANT_ID to be set." A +separate PR upstream fixes both the validator (skip the Entra check when +`TRANSPORT=stdio` and `TRUST_LOCAL_STDIO=true`) and the badge URL. + ## 2026-05-21 — Cross-client installer: skills + DocumentDB MCP server in one command Replaced the non-functional `npx skills add Azure/documentdb-agent-kit` @@ -31,7 +55,10 @@ What landed: upstream MCP server is configured. Rewrote around the actual upstream contract: per-client MCP config file with `CONNECTION_PROFILES` JSON, `TRANSPORT=stdio`, `AUTH_REQUIRED=false`, and - `ALLOW_UNAUTHENTICATED_STDIO=true`. Added per-client config-file table for + `ALLOW_UNAUTHENTICATED_STDIO=true` (later renamed `TRUST_LOCAL_STDIO` in + [microsoft/documentdb-mcp#83](https://github.com/microsoft/documentdb-mcp/pull/83); + this kit was updated to emit the new name in the 2026-05-27 entry below). + Added per-client config-file table for Claude Code / Desktop / Cursor / Copilot CLI / Gemini CLI / VS Code. Updated AGENTS.md's mcp-setup row accordingly. Documented that `AUTH_REQUIRED` gates only the Entra-JWT bearer check on the MCP server's diff --git a/README.md b/README.md index 60be9a4..8c0b551 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,7 @@ non-kit skills are left untouched. | `npm: not found` but Node.js is installed | Linux (Debian/Ubuntu) | The distro `nodejs` package sometimes omits npm — `sudo apt install -y npm`, or use nvm. | | `symlink failed for ; copying instead` warnings | Windows | Harmless. Enable Developer Mode if you want real symlinks. | | Agent: `connection_profile "default" not found` | all | Tell the agent to use profile `default` explicitly, or pass `--profile ` / `-Profile ` to the installer. | -| Agent: `AUTH_REQUIRED is true ...` or server exits at launch | all | Re-run the installer — it sets `AUTH_REQUIRED=false` + `ALLOW_UNAUTHENTICATED_STDIO=true`, required for local stdio. This only disables the MCP-server's Entra-JWT *transport* gate; your cluster's SCRAM/Entra auth is unaffected. | +| Agent: `AUTH_REQUIRED is true ...` or server exits at launch | all | Re-run the installer — it sets `AUTH_REQUIRED=false` + `TRUST_LOCAL_STDIO=true`, required for local stdio. This only disables the MCP-server's Entra-JWT *transport* gate; your cluster's SCRAM/Entra auth is unaffected. | | TLS error against Azure | all | Confirm `tls=true` is in the URI and the password is URL-encoded. | | Connection timeout to Azure | all | Azure portal → cluster → *Networking* → add your client IP to the firewall allowlist. | | `Permission denied` writing into `~/.claude.json` | Linux / macOS | Don't `sudo` the installer — it writes user-scoped configs. Run as your normal user. | diff --git a/gemini-extension.json b/gemini-extension.json index 3de1dc5..c86311d 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -9,7 +9,7 @@ "env": { "TRANSPORT": "stdio", "AUTH_REQUIRED": "false", - "ALLOW_UNAUTHENTICATED_STDIO": "true", + "TRUST_LOCAL_STDIO": "true", "CONNECTION_PROFILES": "${DOCUMENTDB_CONNECTION_PROFILES}", "ENABLE_READ_TOOLS": "true", "ENABLE_WRITE_TOOLS": "false", diff --git a/install.ps1 b/install.ps1 index 36fa070..ee945b3 100644 --- a/install.ps1 +++ b/install.ps1 @@ -433,19 +433,23 @@ function Get-EnvJsonHashtable([string]$conn, [string]$profileName) { # profile uses authMode=entra) flow through CONNECTION_PROFILES and stay # active regardless of this setting. # - # The server defaults AUTH_REQUIRED=true and fails startup unless - # ENTRA_TENANT_ID / ENTRA_AUDIENCE are set. For local stdio we set - # AUTH_REQUIRED=false and ALLOW_UNAUTHENTICATED_STDIO=true (the server's - # intended dev path). This is SAFE ONLY because TRANSPORT=stdio means the - # MCP server is a subprocess on the user's trusted local machine — no - # network listener is opened. If you ever switch TRANSPORT to - # streamable-http or sse, set AUTH_REQUIRED=true and provide the Entra - # tenant/audience, or the /mcp endpoint will be exposed unauthenticated. + # The server defaults AUTH_REQUIRED=true and its startup validator fails + # unless ENTRA_TENANT_ID / ENTRA_AUDIENCE are set. For local stdio we set + # AUTH_REQUIRED=false (which short-circuits the Entra validator entirely) + # and TRUST_LOCAL_STDIO=true (the upstream-recommended way to declare the + # stdio process boundary trusted; previously named + # ALLOW_UNAUTHENTICATED_STDIO before microsoft/documentdb-mcp#83). + # + # This is SAFE ONLY because TRANSPORT=stdio means the MCP server is a + # subprocess on the user's trusted local machine — no network listener + # is opened. If you ever switch TRANSPORT to streamable-http or sse, set + # AUTH_REQUIRED=true and provide the Entra tenant/audience, or the /mcp + # endpoint will be exposed unauthenticated. return [ordered]@{ - TRANSPORT = "stdio" - AUTH_REQUIRED = "false" - ALLOW_UNAUTHENTICATED_STDIO = "true" - CONNECTION_PROFILES = $profilesJson + TRANSPORT = "stdio" + AUTH_REQUIRED = "false" + TRUST_LOCAL_STDIO = "true" + CONNECTION_PROFILES = $profilesJson } } diff --git a/install.sh b/install.sh index 80ab23c..d7deea3 100755 --- a/install.sh +++ b/install.sh @@ -451,18 +451,22 @@ profiles = {profile: {"authMode": "connectionString", "uri": conn}} # profile uses authMode=entra) flow through CONNECTION_PROFILES and stay # active regardless of this setting. # -# The server defaults AUTH_REQUIRED=true and fails startup unless -# ENTRA_TENANT_ID / ENTRA_AUDIENCE are set. For local stdio we set -# AUTH_REQUIRED=false and ALLOW_UNAUTHENTICATED_STDIO=true (the server's -# intended dev path). This is SAFE ONLY because TRANSPORT=stdio means the -# MCP server is a subprocess on the user's trusted local machine — no -# network listener is opened. If you ever switch TRANSPORT to -# streamable-http or sse, set AUTH_REQUIRED=true and provide the Entra -# tenant/audience, or the /mcp endpoint will be exposed unauthenticated. +# The server defaults AUTH_REQUIRED=true and its startup validator fails +# unless ENTRA_TENANT_ID / ENTRA_AUDIENCE are set. For local stdio we set +# AUTH_REQUIRED=false (which short-circuits the Entra validator entirely) +# and TRUST_LOCAL_STDIO=true (the upstream-recommended way to declare the +# stdio process boundary trusted; previously named +# ALLOW_UNAUTHENTICATED_STDIO before microsoft/documentdb-mcp#83). +# +# This is SAFE ONLY because TRANSPORT=stdio means the MCP server is a +# subprocess on the user's trusted local machine — no network listener +# is opened. If you ever switch TRANSPORT to streamable-http or sse, set +# AUTH_REQUIRED=true and provide the Entra tenant/audience, or the /mcp +# endpoint will be exposed unauthenticated. env = { "TRANSPORT": "stdio", "AUTH_REQUIRED": "false", - "ALLOW_UNAUTHENTICATED_STDIO": "true", + "TRUST_LOCAL_STDIO": "true", "CONNECTION_PROFILES": json.dumps(profiles), } print(json.dumps(env)) @@ -472,7 +476,7 @@ PYEOF { TRANSPORT: "stdio", AUTH_REQUIRED: "false", - ALLOW_UNAUTHENTICATED_STDIO: "true", + TRUST_LOCAL_STDIO: "true", CONNECTION_PROFILES: ({($profile): {authMode: "connectionString", uri: $conn}} | tostring) }' fi diff --git a/mcp.json b/mcp.json index 80bd054..7837f67 100644 --- a/mcp.json +++ b/mcp.json @@ -6,7 +6,7 @@ "env": { "TRANSPORT": "stdio", "AUTH_REQUIRED": "false", - "ALLOW_UNAUTHENTICATED_STDIO": "true", + "TRUST_LOCAL_STDIO": "true", "CONNECTION_PROFILES": "${DOCUMENTDB_CONNECTION_PROFILES}", "ENABLE_READ_TOOLS": "true", "ENABLE_WRITE_TOOLS": "false", diff --git a/skills/mcp-setup/SKILL.md b/skills/mcp-setup/SKILL.md index 8598899..03f7a93 100644 --- a/skills/mcp-setup/SKILL.md +++ b/skills/mcp-setup/SKILL.md @@ -68,7 +68,7 @@ Setup is per-client. For each client the user has installed: 2. Find that client's MCP config file. 3. Add a `DocumentDB` server entry that launches the upstream MCP server and passes `CONNECTION_PROFILES` (and `TRANSPORT=stdio` + `AUTH_REQUIRED=false` - + `ALLOW_UNAUTHENTICATED_STDIO=true` for local stdio use). + + `TRUST_LOCAL_STDIO=true` for local stdio use). 4. Restart the client. ## Step 1: Confirm prerequisites @@ -100,9 +100,12 @@ with database user credentials. TLS is required (`tls=true` must be present). where you have a separate, long-running server with Entra-authenticated bearer tokens. Not covered here; see the upstream README. -For stdio, set `AUTH_REQUIRED=false` and `ALLOW_UNAUTHENTICATED_STDIO=true`. +For stdio, set `AUTH_REQUIRED=false` and `TRUST_LOCAL_STDIO=true`. The server defaults `AUTH_REQUIRED=true` and **exits at startup** unless `ENTRA_TENANT_ID` / `ENTRA_AUDIENCE` are set, even for stdio. +(`TRUST_LOCAL_STDIO` was named `ALLOW_UNAUTHENTICATED_STDIO` before +[microsoft/documentdb-mcp#83](https://github.com/microsoft/documentdb-mcp/pull/83) +— if you're on an older server build, use that name instead.) **`AUTH_REQUIRED=false` does not weaken your cluster's auth.** It gates only the Entra-JWT bearer-token check on the MCP server's HTTP/SSE transport — @@ -135,7 +138,7 @@ config file and the top-level key (`mcpServers` vs `mcp.servers`) differ. "env": { "TRANSPORT": "stdio", "AUTH_REQUIRED": "false", - "ALLOW_UNAUTHENTICATED_STDIO": "true", + "TRUST_LOCAL_STDIO": "true", "CONNECTION_PROFILES": "{\"default\":{\"authMode\":\"connectionString\",\"uri\":\"\"}}" } } @@ -192,8 +195,9 @@ existing `mcpServers` object — don't overwrite the whole file. https://github.com/microsoft/documentdb-mcp.git`; if it fails, fall back to cloning the repo manually, running `npm install && npm run build`, and pointing `command` → `node`, `args` → `["/dist/main.js"]`. -- **`unauthenticated stdio is disabled`**: you forgot - `ALLOW_UNAUTHENTICATED_STDIO: "true"` in `env`. +- **`stdio transport is disabled when AUTH_REQUIRED=true` / `unauthenticated stdio is disabled`**: + you forgot `TRUST_LOCAL_STDIO: "true"` in `env` (or, on older builds before + microsoft/documentdb-mcp#83, `ALLOW_UNAUTHENTICATED_STDIO: "true"`). - **`AUTH_REQUIRED is true but ...` / server exits immediately on launch**: add `"AUTH_REQUIRED": "false"` to `env`. The server defaults this to `true` and refuses to start without Entra tenant/audience config. This flag gates From db631a672203f34feaf34b802861569898329fce Mon Sep 17 00:00:00 2001 From: German Date: Wed, 27 May 2026 15:45:06 -0700 Subject: [PATCH 8/8] Mark project as Public Preview Add a Public Preview status badge to README.md and a top-of-document IMPORTANT admonition pointing at the Azure Preview Supplemental Terms. Add a matching status note to AGENTS.md so agents reading the kit relay the preview status to users before committing kit-installed configs into production-shaped surfaces. No functional changes; this clarifies that the kit (like the upstream microsoft/documentdb-mcp server it depends on) is pre-GA, has no SLA, is provided 'as-is', and may change in breaking ways before GA. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 8 ++++++++ CHANGELOG.md | 12 ++++++++++++ README.md | 9 +++++++++ 3 files changed, 29 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index a3c5724..b954729 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,6 +2,14 @@ This repository is an **Agent Skills** pack for **Azure DocumentDB (with MongoDB compatibility)** — the managed Azure service built on the open-source [DocumentDB](https://github.com/microsoft/documentdb) project. Every skill targets Azure DocumentDB specifically; rules call out DocumentDB features that differ from community MongoDB (`cosmosSearch` vector indexes, `createSearchIndexes` full-text search, cluster M-tiers, Entra RBAC, CMK, etc.). +> **Status: Public Preview.** This kit and its upstream MCP server are +> pre-GA; rule contents, skill shapes, installer behavior, and the MCP +> tool surface may change in breaking ways. No SLA. See the +> [Azure Preview Supplemental Terms](https://azure.microsoft.com/support/legal/preview-supplemental-terms/). +> When acting on the kit, agents should call out this status to the user +> if they're about to commit kit-installed configs into anything that +> looks production-shaped. + ## How agents should use this kit ### Skill routing (do this first) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53e4639..a28f0b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 2026-05-27 — Mark project as Public Preview + +Add a Public Preview status badge to `README.md` and a top-of-document +IMPORTANT admonition pointing at the +[Azure Preview Supplemental Terms](https://azure.microsoft.com/support/legal/preview-supplemental-terms/). +Add a matching status note to `AGENTS.md` so agents reading the kit +relay the preview status to users before committing kit-installed +configs into production-shaped surfaces. No functional changes; this +clarifies that the kit (like the upstream `microsoft/documentdb-mcp` +server it depends on) is pre-GA, has no SLA, is provided "as-is", and +may change in breaking ways before General Availability. + ## 2026-05-27 — Track upstream rename: `ALLOW_UNAUTHENTICATED_STDIO` → `TRUST_LOCAL_STDIO` Upstream [microsoft/documentdb-mcp#83](https://github.com/microsoft/documentdb-mcp/pull/83) diff --git a/README.md b/README.md index 8c0b551..6d306e2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ # documentdb-agent-kit +[![Status: Public Preview](https://img.shields.io/badge/Status-Public%20Preview-orange?style=flat)](https://azure.microsoft.com/support/legal/preview-supplemental-terms/) + +> [!IMPORTANT] +> **Public Preview.** This project is currently in Public Preview. APIs, +> configuration, on-disk layout, skill contents, and installer behavior may +> change in breaking ways before General Availability. There is no SLA. +> Provided "as-is"; see the [Azure Preview Supplemental Terms](https://azure.microsoft.com/support/legal/preview-supplemental-terms/). +> Not recommended for production workloads. + A bundle of agent skills + an MCP server for **Azure DocumentDB (MongoDB-compatible)** — the fully managed Azure service built on the open-source [DocumentDB](https://github.com/documentdb/documentdb) project (Postgres-backed, 99.03% MongoDB-compatible). Skills follow the [Agent Skills](https://agentskills.io/) format and the kit ships with plugin manifests for Claude Code, Cursor, Codex, Gemini CLI, and GitHub Copilot.