Skip to content

Add sign-in challenges and the authenticate tool to aitools mcp serve - #7553

Draft
simonfaltum wants to merge 55 commits into
aitools-mcp/07-loginfrom
aitools-mcp/08-challenge
Draft

simonfaltum wants to merge 55 commits into
aitools-mcp/07-loginfrom
aitools-mcp/08-challenge

Conversation

@simonfaltum

@simonfaltum simonfaltum commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

This pull request is part of a stack that adds the hidden databricks aitools mcp command group, on top of #7551. A service that needs provider sign-in rejects tools/list until the user signs in, so the agent has no tool to start it. The proxy now offers one authenticate tool that runs the sign-in.

Changes

  • A challenge (JSON-RPC -32006, a -32042 sign-in URL on the profile's host, or an error result naming that URL) makes tools/list return only authenticate. A 401 is a challenge only with such a body; any other 401 gets the databricks auth login hint.
  • A challenged tools/call names authenticate and the login command. If tools/list still passes, authenticate is added to its first page until a call succeeds or the sign-in completes.
  • authenticate runs login's sign-in, sends progress naming the page, and sends tools/list_changed when signed in. A timeout names the URL, the last failed check and the login command.
  • The proxy never deletes a credential. If the service challenges again after "Already signed in", authenticate answers with the mcp logout command to run first.
  • The page opens with no stdin (stdin carries the protocol) and stays open when the request is cancelled, through a new libs/browser.OpenOutputToStderrWithoutStdin.
  • A server with no install record reads the service at start. A failed read starts it with sign-in handling on; an auth failure or a rejected token exits 2 with the login hint. Other token failures fail only their request.
  • The known-service lookup ignores case, so mcp list labels upper-case names too.

Why

Discovery fails before sign-in, so the agent can only start a sign-in through a tool that exists before it. A rejected Databricks token is not fixed by a provider sign-in, so it gets the login hint instead.

Decisions to review

  • No automatic recovery when a credential reads as active but the service keeps rejecting it. Deleting and re-creating it would also delete working credentials, for example while the gateway catches up with a sign-in just made in a terminal. Cost: a stale provider grant needs one mcp logout.
  • A bare 401, or one with a Databricks auth error body, is an auth failure. Cost: a gateway that answers a missing provider sign-in with a bare 401 gets the login hint.
  • The sign-in client authenticates through the refresh guard and is built only for servers that may need sign-in.
  • A page is not reopened after a cancelled attempt that showed it, so a retry does not open a second tab.

Tests

  • challenge_test.go covers the challenge bodies, 401s, which answers end the sign-in state, call-only challenges, the logout answer, page reopening, queued and cancelled calls, and progress. mcp_serve_test.go covers the guard on sign-in requests, the startup read (rejected token, signal) and the opener's context. libs/browser covers the no-stdin opener.
  • Acceptance serve-authenticate-tool runs the stdio flow with BROWSER=none.
  • Ran -race on the touched packages, gotestsum with BROWSER unset and BROWSER=none, ./task test-exp-aitools, the cmd/aitools acceptance tests, GOOS=windows go vet, ./task lint-q, ./task fmt-q, ./task checks.

Not tested: a live -32042 challenge, and a real browser.

This pull request and its description were written by Isaac.

@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Commit: 6b415917f | Workflow run

Regression test report

Selected 15 changed tests (limit=20, 0 not selected)

15 tests: regression=15 unreleased=0 coverage=0 failing=0
test branch main (8e70c8b) latest (v1.26.0)
TestAccept/cmd/aitools/mcp/add-agent-not-installed ✅ ❌ ❌
TestAccept/cmd/aitools/mcp/add-default-config ✅ ❌ ❌
TestAccept/cmd/aitools/mcp/add-text ✅ ❌ ❌
TestAccept/cmd/aitools/mcp/add-two-agents ✅ ❌ ❌
TestAccept/cmd/aitools/mcp/add-two-profiles ✅ ❌ ❌
TestAccept/cmd/aitools/mcp/add-unparsable-config ✅ ❌ ❌
TestAccept/cmd/aitools/mcp/drift-profile-missing ✅ ❌ ❌
TestAccept/cmd/aitools/mcp/hidden ✅ ❌ ❌
TestAccept/cmd/aitools/mcp/list-and-remove ✅ ❌ ❌
TestAccept/cmd/aitools/mcp/list-check ✅ ❌ ❌
TestAccept/cmd/aitools/mcp/login-flips-active ✅ ❌ ❌
TestAccept/cmd/aitools/mcp/repair-and-warnings ✅ ❌ ❌
TestAccept/cmd/aitools/mcp/serve-authenticate-tool ✅ ❌ ❌
TestAccept/cmd/aitools/mcp/serve-binding-errors ✅ ❌ ❌
TestAccept/cmd/aitools/mcp/serve-passthrough ✅ ❌ ❌
TestAccept/cmd/aitools/mcp/add-agent-not-installed: branch ✅ | main (8e70c8b) ❌ | latest (v1.26.0) ❌

main (8e70c8b):

acceptance_test.go:1374: Diff:
        --- cmd/aitools/mcp/add-agent-not-installed/output.txt
        +++ /tmp/TestAcceptcmdaitoolsmcpadd-agent-not-installed463041316/002/output.txt
        @@ -2,41 +2,58 @@
         === An agent named with --agents that is not installed is refused; the installed agent still gets the server, and the command exits non-zero
         
         >>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp --agents claude-code,antigravity --output json
        -{
        -  "profile": "logfood",
        -  "host": "[DATABRICKS_URL]",
        -  "scope": "global",
        -  "servers": [
        -    {
        -      "name": "genie_one_mcp-logfood",
        -      "service": "system.ai.genie_one_mcp",
        -      "sign_in_required": false,
        -      "agents": [
        -        {
        -          "name": "claude-code",
        -          "status": "installed",
        -          "path": "[TEST_TMP_DIR]/home/.claude.json"
        -        },
        -        {
        -          "name": "antigravity",
        -          "status": "failed",
        -          "error_category": "UNKNOWN_AGENT",
        -          "message": "Antigravity is not installed; install it first or leave it out of --agents"
        -        }
        -      ]
        -    }
        -  ],
        -  "error_category": "TYPE_UNSPECIFIED"
        -}
        +Error: unknown flag: --names
         
        +Usage:
        +  databricks aitools [command]
        +
        +Available Commands:
        +  install     Install Databricks skills and plugins for coding agents
        +  list        List installed skills and plugins
        +  uninstall   Uninstall Databricks skills and plugins
        +  update      Update installed skills and plugins
        +  version     Show installed skills and plugins version
        +
        +Flags:
        +  -h, --help   help for aitools
        +
        +Global Flags:
        +      --debug            enable debug logging
        +  -o, --output type      output type: text or json (default text)
        +  -p, --profile string   ~/.databrickscfg profile
        +  -t, --target string    bundle target to use (if applicable)
        +
        +Use "databricks aitools [command] --help" for more information about a command.
        +
        +
         === Nothing was created for Antigravity
         
         >>> ls -A home
         .claude
        -.claude.json
        -.databricks
         
         === With no installed agent named, the command is refused before any request, and the text output says what to do
         
         >>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp --agents antigravity
        -Error: Antigravity is not installed; install it first or leave it out of --agents
        +Error: unknown flag: --names
        +
        +Usage:
        +  databricks aitools [command]
        +
        +Available Commands:
        +  install     Install Databricks skills and plugins for coding agents
        +  list        List installed skills and plugins
        +  uninstall   Uninstall Databricks skills and plugins
        +  update      Update installed skills and plugins
        +  version     Show installed skills and plugins version
        +
        +Flags:
        +  -h, --help   help for aitools
        +
        +Global Flags:
        +      --debug            enable debug logging
        +  -o, --output type      output type: text or json (default text)
        +  -p, --profile string   ~/.databrickscfg profile
        +  -t, --target string    bundle target to use (if applicable)
        +
        +Use "databricks aitools [command] --help" for more information about a command.
        +
        
    acceptance_test.go:1152: 
        LOG.create-genie_one_mcp-logfood: {
        LOG.create-genie_one_mcp-logfood:   "comment": "Ask questions about your data",
        LOG.create-genie_one_mcp-logfood:   "created_by": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "effective_owner": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "metastore_id": "7a0ca28a-321c-875b-7a0c-a28a321c885f",
        LOG.create-genie_one_mcp-logfood:   "name": "mcp-services/system.ai.genie_one_mcp",
        LOG.create-genie_one_mcp-logfood:   "updated_by": "tester@databricks.com"
        LOG.create-genie_one_mcp-logfood: }

latest (v1.26.0):

acceptance_test.go:1374: Diff:
        --- cmd/aitools/mcp/add-agent-not-installed/output.txt
        +++ /tmp/TestAcceptcmdaitoolsmcpadd-agent-not-installed1497910035/002/output.txt
        @@ -2,41 +2,58 @@
         === An agent named with --agents that is not installed is refused; the installed agent still gets the server, and the command exits non-zero
         
         >>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp --agents claude-code,antigravity --output json
        -{
        -  "profile": "logfood",
        -  "host": "[DATABRICKS_URL]",
        -  "scope": "global",
        -  "servers": [
        -    {
        -      "name": "genie_one_mcp-logfood",
        -      "service": "system.ai.genie_one_mcp",
        -      "sign_in_required": false,
        -      "agents": [
        -        {
        -          "name": "claude-code",
        -          "status": "installed",
        -          "path": "[TEST_TMP_DIR]/home/.claude.json"
        -        },
        -        {
        -          "name": "antigravity",
        -          "status": "failed",
        -          "error_category": "UNKNOWN_AGENT",
        -          "message": "Antigravity is not installed; install it first or leave it out of --agents"
        -        }
        -      ]
        -    }
        -  ],
        -  "error_category": "TYPE_UNSPECIFIED"
        -}
        +Error: unknown flag: --names
         
        +Usage:
        +  databricks aitools [command]
        +
        +Available Commands:
        +  install     Install Databricks skills and plugins for coding agents
        +  list        List installed skills and plugins
        +  uninstall   Uninstall Databricks skills and plugins
        +  update      Update installed skills and plugins
        +  version     Show installed skills and plugins version
        +
        +Flags:
        +  -h, --help   help for aitools
        +
        +Global Flags:
        +      --debug            enable debug logging
        +  -o, --output type      output type: text or json (default text)
        +  -p, --profile string   ~/.databrickscfg profile
        +  -t, --target string    bundle target to use (if applicable)
        +
        +Use "databricks aitools [command] --help" for more information about a command.
        +
        +
         === Nothing was created for Antigravity
         
         >>> ls -A home
         .claude
        -.claude.json
        -.databricks
         
         === With no installed agent named, the command is refused before any request, and the text output says what to do
         
         >>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp --agents antigravity
        -Error: Antigravity is not installed; install it first or leave it out of --agents
        +Error: unknown flag: --names
        +
        +Usage:
        +  databricks aitools [command]
        +
        +Available Commands:
        +  install     Install Databricks skills and plugins for coding agents
        +  list        List installed skills and plugins
        +  uninstall   Uninstall Databricks skills and plugins
        +  update      Update installed skills and plugins
        +  version     Show installed skills and plugins version
        +
        +Flags:
        +  -h, --help   help for aitools
        +
        +Global Flags:
        +      --debug            enable debug logging
        +  -o, --output type      output type: text or json (default text)
        +  -p, --profile string   ~/.databrickscfg profile
        +  -t, --target string    bundle target to use (if applicable)
        +
        +Use "databricks aitools [command] --help" for more information about a command.
        +
        
    acceptance_test.go:1152: 
        LOG.create-genie_one_mcp-logfood: {
        LOG.create-genie_one_mcp-logfood:   "comment": "Ask questions about your data",
        LOG.create-genie_one_mcp-logfood:   "created_by": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "effective_owner": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "metastore_id": "7a0ca291-e785-3936-7a0c-a291e78539a4",
        LOG.create-genie_one_mcp-logfood:   "name": "mcp-services/system.ai.genie_one_mcp",
        LOG.create-genie_one_mcp-logfood:   "updated_by": "tester@databricks.com"
        LOG.create-genie_one_mcp-logfood: }
TestAccept/cmd/aitools/mcp/add-default-config: branch ✅ | main (8e70c8b) ❌ | latest (v1.26.0) ❌

main (8e70c8b):

acceptance_test.go:1374: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1374
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1115
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:660
        	Error:      	Not equal: 
        	            	expected: "\n=== Without DATABRICKS_CONFIG_FILE the server is added for the default config file\n\n>>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp --agents claude-code\nProfile logfood ([DATABRICKS_URL])\nAgents (named with --agents): claude-code\n\ngenie_one_mcp-logfood  system.ai.genie_one_mcp\n  claude-code  installed  [TEST_TMP_DIR]/home/.claude.json\n\n=== Claude Code config has no pinned variables for the server\n\n>>> gron.py home/.claude.json\njson.mcpServers.genie_one_mcp-logfood.env = {};\n\n=== State holds the binding with no config file\n\n>>> jq .mcp.servers[] | {name, profile, config_file} home/.databricks/aitools/skills/.state.json\n{\n  \"name\": \"genie_one_mcp-logfood\",\n  \"profile\": \"logfood\",\n  \"config_file\": \"\"\n}\n"
        	            	actual  : "\n=== Without DATABRICKS_CONFIG_FILE the server is added for the default config file\n\n>>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp --agents claude-code\nError: unknown flag: --names\n\nUsage:\n  databricks aitools [command]\n\nAvailable Commands:\n  install     Install Databricks skills and plugins for coding agents\n  list        List installed skills and plugins\n  uninstall   Uninstall Databricks skills and plugins\n  update      Update installed skills and plugins\n  version     Show installed skills and plugins version\n\nFlags:\n  -h, --help   help for aitools\n\nGlobal Flags:\n      --debug            enable debug logging\n  -o, --output type      output type: text or json (default text)\n  -p, --profile string   ~/.databrickscfg profile\n  -t, --target string    bundle target to use (if applicable)\n\nUse \"databricks aitools [command] --help\" for more information about a command.\n\n\nExit code: 1\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -4,21 +4,27 @@
        	            	 >>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp --agents claude-code
        	            	-Profile logfood ([DATABRICKS_URL])
        	            	-Agents (named with --agents): claude-code
        	            	+Error: unknown flag: --names
        	            	 
        	            	-genie_one_mcp-logfood  system.ai.genie_one_mcp
        	            	-  claude-code  installed  [TEST_TMP_DIR]/home/.claude.json
        	            	+Usage:
        	            	+  databricks aitools [command]
        	            	 
        	            	-=== Claude Code config has no pinned variables for the server
        	            	+Available Commands:
        	            	+  install     Install Databricks skills and plugins for coding agents
        	            	+  list        List installed skills and plugins
        	            	+  uninstall   Uninstall Databricks skills and plugins
        	            	+  update      Update installed skills and plugins
        	            	+  version     Show installed skills and plugins version
        	            	 
        	            	->>> gron.py home/.claude.json
        	            	-json.mcpServers.genie_one_mcp-logfood.env = {};
        	            	+Flags:
        	            	+  -h, --help   help for aitools
        	            	 
        	            	-=== State holds the binding with no config file
        	            	+Global Flags:
        	            	+      --debug            enable debug logging
        	            	+  -o, --output type      output type: text or json (default text)
        	            	+  -p, --profile string   ~/.databrickscfg profile
        	            	+  -t, --target string    bundle target to use (if applicable)
        	            	 
        	            	->>> jq .mcp.servers[] | {name, profile, config_file} home/.databricks/aitools/skills/.state.json
        	            	-{
        	            	-  "name": "genie_one_mcp-logfood",
        	            	-  "profile": "logfood",
        	            	-  "config_file": ""
        	            	-}
        	            	+Use "databricks aitools [command] --help" for more information about a command.
        	            	 
        	            	+
        	            	+Exit code: 1
        	            	+
        	Test:       	TestAccept/cmd/aitools/mcp/add-default-config
        	Messages:   	cmd/aitools/mcp/add-default-config/output.txt vs /tmp/TestAcceptcmdaitoolsmcpadd-default-config3955100321/002/output.txt
    acceptance_test.go:1152: 
        LOG.create-genie_one_mcp-logfood: {
        LOG.create-genie_one_mcp-logfood:   "comment": "Ask questions about your data",
        LOG.create-genie_one_mcp-logfood:   "created_by": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "effective_owner": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "metastore_id": "7a0ca291-52d1-2178-7a0c-a29152d122c2",
        LOG.create-genie_one_mcp-logfood:   "name": "mcp-services/system.ai.genie_one_mcp",
        LOG.create-genie_one_mcp-logfood:   "updated_by": "tester@databricks.com"
        LOG.create-genie_one_mcp-logfood: }

latest (v1.26.0):

acceptance_test.go:1374: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1374
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1115
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:660
        	Error:      	Not equal: 
        	            	expected: "\n=== Without DATABRICKS_CONFIG_FILE the server is added for the default config file\n\n>>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp --agents claude-code\nProfile logfood ([DATABRICKS_URL])\nAgents (named with --agents): claude-code\n\ngenie_one_mcp-logfood  system.ai.genie_one_mcp\n  claude-code  installed  [TEST_TMP_DIR]/home/.claude.json\n\n=== Claude Code config has no pinned variables for the server\n\n>>> gron.py home/.claude.json\njson.mcpServers.genie_one_mcp-logfood.env = {};\n\n=== State holds the binding with no config file\n\n>>> jq .mcp.servers[] | {name, profile, config_file} home/.databricks/aitools/skills/.state.json\n{\n  \"name\": \"genie_one_mcp-logfood\",\n  \"profile\": \"logfood\",\n  \"config_file\": \"\"\n}\n"
        	            	actual  : "\n=== Without DATABRICKS_CONFIG_FILE the server is added for the default config file\n\n>>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp --agents claude-code\nError: unknown flag: --names\n\nUsage:\n  databricks aitools [command]\n\nAvailable Commands:\n  install     Install Databricks skills and plugins for coding agents\n  list        List installed skills and plugins\n  uninstall   Uninstall Databricks skills and plugins\n  update      Update installed skills and plugins\n  version     Show installed skills and plugins version\n\nFlags:\n  -h, --help   help for aitools\n\nGlobal Flags:\n      --debug            enable debug logging\n  -o, --output type      output type: text or json (default text)\n  -p, --profile string   ~/.databrickscfg profile\n  -t, --target string    bundle target to use (if applicable)\n\nUse \"databricks aitools [command] --help\" for more information about a command.\n\n\nExit code: 1\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -4,21 +4,27 @@
        	            	 >>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp --agents claude-code
        	            	-Profile logfood ([DATABRICKS_URL])
        	            	-Agents (named with --agents): claude-code
        	            	+Error: unknown flag: --names
        	            	 
        	            	-genie_one_mcp-logfood  system.ai.genie_one_mcp
        	            	-  claude-code  installed  [TEST_TMP_DIR]/home/.claude.json
        	            	+Usage:
        	            	+  databricks aitools [command]
        	            	 
        	            	-=== Claude Code config has no pinned variables for the server
        	            	+Available Commands:
        	            	+  install     Install Databricks skills and plugins for coding agents
        	            	+  list        List installed skills and plugins
        	            	+  uninstall   Uninstall Databricks skills and plugins
        	            	+  update      Update installed skills and plugins
        	            	+  version     Show installed skills and plugins version
        	            	 
        	            	->>> gron.py home/.claude.json
        	            	-json.mcpServers.genie_one_mcp-logfood.env = {};
        	            	+Flags:
        	            	+  -h, --help   help for aitools
        	            	 
        	            	-=== State holds the binding with no config file
        	            	+Global Flags:
        	            	+      --debug            enable debug logging
        	            	+  -o, --output type      output type: text or json (default text)
        	            	+  -p, --profile string   ~/.databrickscfg profile
        	            	+  -t, --target string    bundle target to use (if applicable)
        	            	 
        	            	->>> jq .mcp.servers[] | {name, profile, config_file} home/.databricks/aitools/skills/.state.json
        	            	-{
        	            	-  "name": "genie_one_mcp-logfood",
        	            	-  "profile": "logfood",
        	            	-  "config_file": ""
        	            	-}
        	            	+Use "databricks aitools [command] --help" for more information about a command.
        	            	 
        	            	+
        	            	+Exit code: 1
        	            	+
        	Test:       	TestAccept/cmd/aitools/mcp/add-default-config
        	Messages:   	cmd/aitools/mcp/add-default-config/output.txt vs /tmp/TestAcceptcmdaitoolsmcpadd-default-config2820940326/002/output.txt
    acceptance_test.go:1152: 
        LOG.create-genie_one_mcp-logfood: {
        LOG.create-genie_one_mcp-logfood:   "comment": "Ask questions about your data",
        LOG.create-genie_one_mcp-logfood:   "created_by": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "effective_owner": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "metastore_id": "7a0ca299-1682-6892-7a0c-a2991682691e",
        LOG.create-genie_one_mcp-logfood:   "name": "mcp-services/system.ai.genie_one_mcp",
        LOG.create-genie_one_mcp-logfood:   "updated_by": "tester@databricks.com"
        LOG.create-genie_one_mcp-logfood: }
TestAccept/cmd/aitools/mcp/add-text: branch ✅ | main (8e70c8b) ❌ | latest (v1.26.0) ❌

main (8e70c8b):

acceptance_test.go:1374: Diff:
        --- cmd/aitools/mcp/add-text/output.txt
        +++ /tmp/TestAcceptcmdaitoolsmcpadd-text2808151695/002/output.txt
        @@ -2,73 +2,28 @@
         === Text output: Cursor approval hint, and the sign-in step for github
         
         >>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp,system.ai.github --agents claude-code,cursor
        -Profile logfood ([DATABRICKS_URL])
        -Agents (named with --agents): claude-code, cursor
        +Error: unknown flag: --names
         
        -genie_one_mcp-logfood  system.ai.genie_one_mcp
        -  claude-code  installed  [TEST_TMP_DIR]/home/.claude.json
        -  cursor       installed  [TEST_TMP_DIR]/home/.cursor/mcp.json
        -Cursor may ask you to approve genie_one_mcp-logfood. Run: cursor-agent mcp enable genie_one_mcp-logfood (the Cursor docs call the CLI agent), or approve it in Cursor's MCP settings.
        +Usage:
        +  databricks aitools [command]
         
        -github-logfood  system.ai.github
        -  claude-code  installed  [TEST_TMP_DIR]/home/.claude.json
        -  cursor       installed  [TEST_TMP_DIR]/home/.cursor/mcp.json
        -system.ai.github needs sign-in. Run: DATABRICKS_CONFIG_FILE=[TEST_TMP_DIR]/.databrickscfg databricks aitools mcp login system.ai.github --profile logfood
        -Cursor may ask you to approve github-logfood. Run: cursor-agent mcp enable github-logfood (the Cursor docs call the CLI agent), or approve it in Cursor's MCP settings.
        +Available Commands:
        +  install     Install Databricks skills and plugins for coding agents
        +  list        List installed skills and plugins
        +  uninstall   Uninstall Databricks skills and plugins
        +  update      Update installed skills and plugins
        +  version     Show installed skills and plugins version
         
        -=== Running it again re-asserts the entries, and nothing is written twice
        +Flags:
        +  -h, --help   help for aitools
         
        ->>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp,system.ai.github --agents claude-code,cursor
        -Profile logfood ([DATABRICKS_URL])
        -Agents (named with --agents): claude-code, cursor
        +Global Flags:
        +      --debug            enable debug logging
        +  -o, --output type      output type: text or json (default text)
        +  -p, --profile string   ~/.databrickscfg profile
        +  -t, --target string    bundle target to use (if applicable)
         
        -genie_one_mcp-logfood  system.ai.genie_one_mcp
        -  claude-code  already installed, re-asserted  [TEST_TMP_DIR]/home/.claude.json
        -  cursor       already installed, re-asserted  [TEST_TMP_DIR]/home/.cursor/mcp.json
        +Use "databricks aitools [command] --help" for more information about a command.
         
        -github-logfood  system.ai.github
        -  claude-code  already installed, re-asserted  [TEST_TMP_DIR]/home/.claude.json
        -  cursor       already installed, re-asserted  [TEST_TMP_DIR]/home/.cursor/mcp.json
        -system.ai.github needs sign-in. Run: DATABRICKS_CONFIG_FILE=[TEST_TMP_DIR]/.databrickscfg databricks aitools mcp login system.ai.github --profile logfood
         
        -=== Cursor config
        -
        ->>> cat home/.cursor/mcp.json
        -{
        -  "mcpServers": {
        -    "genie_one_mcp-logfood": {
        -      "args": [
        -        "aitools",
        -        "mcp",
        -        "serve",
        -        "--profile",
        -        "logfood",
        -        "--service",
        -        "system.ai.genie_one_mcp",
        -        "--agent",
        -        "cursor"
        -      ],
        -      "command": "[CLI]",
        -      "env": {
        -        "DATABRICKS_CONFIG_FILE": "[TEST_TMP_DIR]/.databrickscfg"
        -      }
        -    },
        -    "github-logfood": {
        -      "args": [
        -        "aitools",
        -        "mcp",
        -        "serve",
        -        "--profile",
        -        "logfood",
        -        "--service",
        -        "system.ai.github",
        -        "--agent",
        -        "cursor"
        -      ],
        -      "command": "[CLI]",
        -      "env": {
        -        "DATABRICKS_CONFIG_FILE": "[TEST_TMP_DIR]/.databrickscfg"
        -      }
        -    }
        -  }
        -}
        +Exit code: 1
        
    acceptance_test.go:1152: 
        LOG.create-genie_one_mcp-logfood: {
        LOG.create-genie_one_mcp-logfood:   "comment": "Ask questions about your data",
        LOG.create-genie_one_mcp-logfood:   "created_by": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "effective_owner": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "metastore_id": "7a0ca291-50ae-dcd1-7a0c-a29150aede11",
        LOG.create-genie_one_mcp-logfood:   "name": "mcp-services/system.ai.genie_one_mcp",
        LOG.create-genie_one_mcp-logfood:   "updated_by": "tester@databricks.com"
        LOG.create-genie_one_mcp-logfood: }
    acceptance_test.go:1152: 
        LOG.create-github-logfood: {
        LOG.create-github-logfood:   "comment": "Repositories, issues, pull requests",
        LOG.create-github-logfood:   "config": {
        LOG.create-github-logfood:     "source_connection": {
        LOG.create-github-logfood:       "name": "connections/github",
        LOG.create-github-logfood:       "options": {
        LOG.create-github-logfood:         "oauth_provider": "OAUTH_PROVIDER_GITHUB_MCP"
        LOG.create-github-logfood:       }
        LOG.create-github-logfood:     }
        LOG.create-github-logfood:   },
        LOG.create-github-logfood:   "created_by": "tester@databricks.com",
        LOG.create-github-logfood:   "effective_owner": "tester@databricks.com",
        LOG.create-github-logfood:   "metastore_id": "7a0ca291-52e3-d2be-7a0c-a29152e3d336",
        LOG.create-github-logfood:   "name": "mcp-services/system.ai.github",
        LOG.create-github-logfood:   "updated_by": "tester@databricks.com"
        LOG.create-github-logfood: }

latest (v1.26.0):

acceptance_test.go:1374: Diff:
        --- cmd/aitools/mcp/add-text/output.txt
        +++ /tmp/TestAcceptcmdaitoolsmcpadd-text3952582834/002/output.txt
        @@ -2,73 +2,28 @@
         === Text output: Cursor approval hint, and the sign-in step for github
         
         >>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp,system.ai.github --agents claude-code,cursor
        -Profile logfood ([DATABRICKS_URL])
        -Agents (named with --agents): claude-code, cursor
        +Error: unknown flag: --names
         
        -genie_one_mcp-logfood  system.ai.genie_one_mcp
        -  claude-code  installed  [TEST_TMP_DIR]/home/.claude.json
        -  cursor       installed  [TEST_TMP_DIR]/home/.cursor/mcp.json
        -Cursor may ask you to approve genie_one_mcp-logfood. Run: cursor-agent mcp enable genie_one_mcp-logfood (the Cursor docs call the CLI agent), or approve it in Cursor's MCP settings.
        +Usage:
        +  databricks aitools [command]
         
        -github-logfood  system.ai.github
        -  claude-code  installed  [TEST_TMP_DIR]/home/.claude.json
        -  cursor       installed  [TEST_TMP_DIR]/home/.cursor/mcp.json
        -system.ai.github needs sign-in. Run: DATABRICKS_CONFIG_FILE=[TEST_TMP_DIR]/.databrickscfg databricks aitools mcp login system.ai.github --profile logfood
        -Cursor may ask you to approve github-logfood. Run: cursor-agent mcp enable github-logfood (the Cursor docs call the CLI agent), or approve it in Cursor's MCP settings.
        +Available Commands:
        +  install     Install Databricks skills and plugins for coding agents
        +  list        List installed skills and plugins
        +  uninstall   Uninstall Databricks skills and plugins
        +  update      Update installed skills and plugins
        +  version     Show installed skills and plugins version
         
        -=== Running it again re-asserts the entries, and nothing is written twice
        +Flags:
        +  -h, --help   help for aitools
         
        ->>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp,system.ai.github --agents claude-code,cursor
        -Profile logfood ([DATABRICKS_URL])
        -Agents (named with --agents): claude-code, cursor
        +Global Flags:
        +      --debug            enable debug logging
        +  -o, --output type      output type: text or json (default text)
        +  -p, --profile string   ~/.databrickscfg profile
        +  -t, --target string    bundle target to use (if applicable)
         
        -genie_one_mcp-logfood  system.ai.genie_one_mcp
        -  claude-code  already installed, re-asserted  [TEST_TMP_DIR]/home/.claude.json
        -  cursor       already installed, re-asserted  [TEST_TMP_DIR]/home/.cursor/mcp.json
        +Use "databricks aitools [command] --help" for more information about a command.
         
        -github-logfood  system.ai.github
        -  claude-code  already installed, re-asserted  [TEST_TMP_DIR]/home/.claude.json
        -  cursor       already installed, re-asserted  [TEST_TMP_DIR]/home/.cursor/mcp.json
        -system.ai.github needs sign-in. Run: DATABRICKS_CONFIG_FILE=[TEST_TMP_DIR]/.databrickscfg databricks aitools mcp login system.ai.github --profile logfood
         
        -=== Cursor config
        -
        ->>> cat home/.cursor/mcp.json
        -{
        -  "mcpServers": {
        -    "genie_one_mcp-logfood": {
        -      "args": [
        -        "aitools",
        -        "mcp",
        -        "serve",
        -        "--profile",
        -        "logfood",
        -        "--service",
        -        "system.ai.genie_one_mcp",
        -        "--agent",
        -        "cursor"
        -      ],
        -      "command": "[CLI]",
        -      "env": {
        -        "DATABRICKS_CONFIG_FILE": "[TEST_TMP_DIR]/.databrickscfg"
        -      }
        -    },
        -    "github-logfood": {
        -      "args": [
        -        "aitools",
        -        "mcp",
        -        "serve",
        -        "--profile",
        -        "logfood",
        -        "--service",
        -        "system.ai.github",
        -        "--agent",
        -        "cursor"
        -      ],
        -      "command": "[CLI]",
        -      "env": {
        -        "DATABRICKS_CONFIG_FILE": "[TEST_TMP_DIR]/.databrickscfg"
        -      }
        -    }
        -  }
        -}
        +Exit code: 1
        
    acceptance_test.go:1152: 
        LOG.create-genie_one_mcp-logfood: {
        LOG.create-genie_one_mcp-logfood:   "comment": "Ask questions about your data",
        LOG.create-genie_one_mcp-logfood:   "created_by": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "effective_owner": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "metastore_id": "7a0ca299-15c2-dfee-7a0c-a29915c2e0de",
        LOG.create-genie_one_mcp-logfood:   "name": "mcp-services/system.ai.genie_one_mcp",
        LOG.create-genie_one_mcp-logfood:   "updated_by": "tester@databricks.com"
        LOG.create-genie_one_mcp-logfood: }
    acceptance_test.go:1152: 
        LOG.create-github-logfood: {
        LOG.create-github-logfood:   "comment": "Repositories, issues, pull requests",
        LOG.create-github-logfood:   "config": {
        LOG.create-github-logfood:     "source_connection": {
        LOG.create-github-logfood:       "name": "connections/github",
        LOG.create-github-logfood:       "options": {
        LOG.create-github-logfood:         "oauth_provider": "OAUTH_PROVIDER_GITHUB_MCP"
        LOG.create-github-logfood:       }
        LOG.create-github-logfood:     }
        LOG.create-github-logfood:   },
        LOG.create-github-logfood:   "created_by": "tester@databricks.com",
        LOG.create-github-logfood:   "effective_owner": "tester@databricks.com",
        LOG.create-github-logfood:   "metastore_id": "7a0ca299-1768-abf2-7a0c-a2991768ace2",
        LOG.create-github-logfood:   "name": "mcp-services/system.ai.github",
        LOG.create-github-logfood:   "updated_by": "tester@databricks.com"
        LOG.create-github-logfood: }
FAIL
TestAccept/cmd/aitools/mcp/add-two-agents: branch ✅ | main (8e70c8b) ❌ | latest (v1.26.0) ❌

main (8e70c8b):

acceptance_test.go:1374: Diff:
        --- cmd/aitools/mcp/add-two-agents/output.txt
        +++ /tmp/TestAcceptcmdaitoolsmcpadd-two-agents1011546460/002/output.txt
        @@ -2,157 +2,28 @@
         === Two services for two agents, as JSON
         
         >>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp,system.ai.github --agents claude-code,codex --output json
        -{
        -  "profile": "logfood",
        -  "host": "[DATABRICKS_URL]",
        -  "scope": "global",
        -  "servers": [
        -    {
        -      "name": "genie_one_mcp-logfood",
        -      "service": "system.ai.genie_one_mcp",
        -      "sign_in_required": false,
        -      "agents": [
        -        {
        -          "name": "claude-code",
        -          "status": "installed",
        -          "path": "[TEST_TMP_DIR]/home/.claude.json"
        -        },
        -        {
        -          "name": "codex",
        -          "status": "installed",
        -          "path": "[TEST_TMP_DIR]/home/.codex/config.toml"
        -        }
        -      ]
        -    },
        -    {
        -      "name": "github-logfood",
        -      "service": "system.ai.github",
        -      "sign_in_required": true,
        -      "agents": [
        -        {
        -          "name": "claude-code",
        -          "status": "installed",
        -          "path": "[TEST_TMP_DIR]/home/.claude.json"
        -        },
        -        {
        -          "name": "codex",
        -          "status": "installed",
        -          "path": "[TEST_TMP_DIR]/home/.codex/config.toml"
        -        }
        -      ]
        -    }
        -  ],
        -  "error_category": "TYPE_UNSPECIFIED"
        -}
        +Error: unknown flag: --names
         
        -=== Claude Code config keeps the entries it already had and gets ours
        +Usage:
        +  databricks aitools [command]
         
        ->>> cat home/.claude.json
        -{
        -  "mcpServers": {
        -    "my-server": {
        -      "type": "stdio",
        -      "command": "my-server",
        -      "args": []
        -    },
        -    "genie_one_mcp-logfood": {
        -      "args": [
        -        "aitools",
        -        "mcp",
        -        "serve",
        -        "--profile",
        -        "logfood",
        -        "--service",
        -        "system.ai.genie_one_mcp",
        -        "--agent",
        -        "claude-code"
        -      ],
        -      "command": "[CLI]",
        -      "env": {
        -        "DATABRICKS_CONFIG_FILE": "[TEST_TMP_DIR]/.databrickscfg"
        -      },
        -      "type": "stdio"
        -    },
        -    "github-logfood": {
        -      "args": [
        -        "aitools",
        -        "mcp",
        -        "serve",
        -        "--profile",
        -        "logfood",
        -        "--service",
        -        "system.ai.github",
        -        "--agent",
        -        "claude-code"
        -      ],
        -      "command": "[CLI]",
        -      "env": {
        -        "DATABRICKS_CONFIG_FILE": "[TEST_TMP_DIR]/.databrickscfg"
        -      },
        -      "type": "stdio"
        -    }
        -  }
        -}
        +Available Commands:
        +  install     Install Databricks skills and plugins for coding agents
        +  list        List installed skills and plugins
        +  uninstall   Uninstall Databricks skills and plugins
        +  update      Update installed skills and plugins
        +  version     Show installed skills and plugins version
         
        -=== Codex config
        +Flags:
        +  -h, --help   help for aitools
         
        ->>> cat home/.codex/config.toml
        -[mcp_servers.genie_one_mcp-logfood]
        -args = ["aitools", "mcp", "serve", "--profile", "logfood", "--service", "system.ai.genie_one_mcp", "--agent", "codex"]
        -command = "[CLI]"
        -[mcp_servers.genie_one_mcp-logfood.env]
        -DATABRICKS_CONFIG_FILE = "[TEST_TMP_DIR]/.databrickscfg"
        +Global Flags:
        +      --debug            enable debug logging
        +  -o, --output type      output type: text or json (default text)
        +  -p, --profile string   ~/.databrickscfg profile
        +  -t, --target string    bundle target to use (if applicable)
         
        -[mcp_servers.github-logfood]
        -args = ["aitools", "mcp", "serve", "--profile", "logfood", "--service", "system.ai.github", "--agent", "codex"]
        -command = "[CLI]"
        -[mcp_servers.github-logfood.env]
        -DATABRICKS_CONFIG_FILE = "[TEST_TMP_DIR]/.databrickscfg"
        +Use "databricks aitools [command] --help" for more information about a command.
         
        -=== State records the binding, without the time and CLI version
         
        ->>> jq .mcp | del(.servers[].installed_at, .servers[].cli_version) home/.databricks/aitools/skills/.state.json
        -{
        -  "servers": [
        -    {
        -      "name": "genie_one_mcp-logfood",
        -      "service": "system.ai.genie_one_mcp",
        -      "profile": "logfood",
        -      "host": "[DATABRICKS_URL]",
        -      "workspace_id": "[WORKSPACE_ID]",
        -      "sign_in_required": false,
        -      "command": "[CLI]",
        -      "config_file": "[TEST_TMP_DIR]/.databrickscfg",
        -      "agents": {
        -        "claude-code": {
        -          "path": "[TEST_TMP_DIR]/home/.claude.json",
        -          "scope": "user"
        -        },
        -        "codex": {
        -          "path": "[TEST_TMP_DIR]/home/.codex/config.toml",
        -          "scope": "user"
        -        }
        -      }
        -    },
        -    {
        -      "name": "github-logfood",
        -      "service": "system.ai.github",
        -      "profile": "logfood",
        -      "host": "[DATABRICKS_URL]",
        -      "workspace_id": "[WORKSPACE_ID]",
        -      "sign_in_required": true,
        -      "command": "[CLI]",
        -      "config_file": "[TEST_TMP_DIR]/.databrickscfg",
        -      "agents": {
        -        "claude-code": {
        -          "path": "[TEST_TMP_DIR]/home/.claude.json",
        -          "scope": "user"
        -        },
        -        "codex": {
        -          "path": "[TEST_TMP_DIR]/home/.codex/config.toml",
        -          "scope": "user"
        -        }
        -      }
        -    }
        -  ]
        -}
        +Exit code: 1
        
    acceptance_test.go:1152: 
        LOG.create-genie_one_mcp-logfood: {
        LOG.create-genie_one_mcp-logfood:   "comment": "Ask questions about your data",
        LOG.create-genie_one_mcp-logfood:   "created_by": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "effective_owner": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "metastore_id": "7a0ca291-484e-ce6e-7a0c-a291484ecf54",
        LOG.create-genie_one_mcp-logfood:   "name": "mcp-services/system.ai.genie_one_mcp",
        LOG.create-genie_one_mcp-logfood:   "updated_by": "tester@databricks.com"
        LOG.create-genie_one_mcp-logfood: }
    acceptance_test.go:1152: 
        LOG.create-github-logfood: {
        LOG.create-github-logfood:   "comment": "Repositories, issues, pull requests",
        LOG.create-github-logfood:   "config": {
        LOG.create-github-logfood:     "source_connection": {
        LOG.create-github-logfood:       "name": "connections/github",
        LOG.create-github-logfood:       "options": {
        LOG.create-github-logfood:         "oauth_provider": "OAUTH_PROVIDER_GITHUB_MCP"
        LOG.create-github-logfood:       }
        LOG.create-github-logfood:     }
        LOG.create-github-logfood:   },
... (6 more lines truncated, raise DATABRICKS_CLI_TEST_MAX_LOG to see more)

latest (v1.26.0):

acceptance_test.go:1374: Diff:
        --- cmd/aitools/mcp/add-two-agents/output.txt
        +++ /tmp/TestAcceptcmdaitoolsmcpadd-two-agents3904054431/002/output.txt
        @@ -2,157 +2,28 @@
         === Two services for two agents, as JSON
         
         >>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp,system.ai.github --agents claude-code,codex --output json
        -{
        -  "profile": "logfood",
        -  "host": "[DATABRICKS_URL]",
        -  "scope": "global",
        -  "servers": [
        -    {
        -      "name": "genie_one_mcp-logfood",
        -      "service": "system.ai.genie_one_mcp",
        -      "sign_in_required": false,
        -      "agents": [
        -        {
        -          "name": "claude-code",
        -          "status": "installed",
        -          "path": "[TEST_TMP_DIR]/home/.claude.json"
        -        },
        -        {
        -          "name": "codex",
        -          "status": "installed",
        -          "path": "[TEST_TMP_DIR]/home/.codex/config.toml"
        -        }
        -      ]
        -    },
        -    {
        -      "name": "github-logfood",
        -      "service": "system.ai.github",
        -      "sign_in_required": true,
        -      "agents": [
        -        {
        -          "name": "claude-code",
        -          "status": "installed",
        -          "path": "[TEST_TMP_DIR]/home/.claude.json"
        -        },
        -        {
        -          "name": "codex",
        -          "status": "installed",
        -          "path": "[TEST_TMP_DIR]/home/.codex/config.toml"
        -        }
        -      ]
        -    }
        -  ],
        -  "error_category": "TYPE_UNSPECIFIED"
        -}
        +Error: unknown flag: --names
         
        -=== Claude Code config keeps the entries it already had and gets ours
        +Usage:
        +  databricks aitools [command]
         
        ->>> cat home/.claude.json
        -{
        -  "mcpServers": {
        -    "my-server": {
        -      "type": "stdio",
        -      "command": "my-server",
        -      "args": []
        -    },
        -    "genie_one_mcp-logfood": {
        -      "args": [
        -        "aitools",
        -        "mcp",
        -        "serve",
        -        "--profile",
        -        "logfood",
        -        "--service",
        -        "system.ai.genie_one_mcp",
        -        "--agent",
        -        "claude-code"
        -      ],
        -      "command": "[CLI]",
        -      "env": {
        -        "DATABRICKS_CONFIG_FILE": "[TEST_TMP_DIR]/.databrickscfg"
        -      },
        -      "type": "stdio"
        -    },
        -    "github-logfood": {
        -      "args": [
        -        "aitools",
        -        "mcp",
        -        "serve",
        -        "--profile",
        -        "logfood",
        -        "--service",
        -        "system.ai.github",
        -        "--agent",
        -        "claude-code"
        -      ],
        -      "command": "[CLI]",
        -      "env": {
        -        "DATABRICKS_CONFIG_FILE": "[TEST_TMP_DIR]/.databrickscfg"
        -      },
        -      "type": "stdio"
        -    }
        -  }
        -}
        +Available Commands:
        +  install     Install Databricks skills and plugins for coding agents
        +  list        List installed skills and plugins
        +  uninstall   Uninstall Databricks skills and plugins
        +  update      Update installed skills and plugins
        +  version     Show installed skills and plugins version
         
        -=== Codex config
        +Flags:
        +  -h, --help   help for aitools
         
        ->>> cat home/.codex/config.toml
        -[mcp_servers.genie_one_mcp-logfood]
        -args = ["aitools", "mcp", "serve", "--profile", "logfood", "--service", "system.ai.genie_one_mcp", "--agent", "codex"]
        -command = "[CLI]"
        -[mcp_servers.genie_one_mcp-logfood.env]
        -DATABRICKS_CONFIG_FILE = "[TEST_TMP_DIR]/.databrickscfg"
        +Global Flags:
        +      --debug            enable debug logging
        +  -o, --output type      output type: text or json (default text)
        +  -p, --profile string   ~/.databrickscfg profile
        +  -t, --target string    bundle target to use (if applicable)
         
        -[mcp_servers.github-logfood]
        -args = ["aitools", "mcp", "serve", "--profile", "logfood", "--service", "system.ai.github", "--agent", "codex"]
        -command = "[CLI]"
        -[mcp_servers.github-logfood.env]
        -DATABRICKS_CONFIG_FILE = "[TEST_TMP_DIR]/.databrickscfg"
        +Use "databricks aitools [command] --help" for more information about a command.
         
        -=== State records the binding, without the time and CLI version
         
        ->>> jq .mcp | del(.servers[].installed_at, .servers[].cli_version) home/.databricks/aitools/skills/.state.json
        -{
        -  "servers": [
        -    {
        -      "name": "genie_one_mcp-logfood",
        -      "service": "system.ai.genie_one_mcp",
        -      "profile": "logfood",
        -      "host": "[DATABRICKS_URL]",
        -      "workspace_id": "[WORKSPACE_ID]",
        -      "sign_in_required": false,
        -      "command": "[CLI]",
        -      "config_file": "[TEST_TMP_DIR]/.databrickscfg",
        -      "agents": {
        -        "claude-code": {
        -          "path": "[TEST_TMP_DIR]/home/.claude.json",
        -          "scope": "user"
        -        },
        -        "codex": {
        -          "path": "[TEST_TMP_DIR]/home/.codex/config.toml",
        -          "scope": "user"
        -        }
        -      }
        -    },
        -    {
        -      "name": "github-logfood",
        -      "service": "system.ai.github",
        -      "profile": "logfood",
        -      "host": "[DATABRICKS_URL]",
        -      "workspace_id": "[WORKSPACE_ID]",
        -      "sign_in_required": true,
        -      "command": "[CLI]",
        -      "config_file": "[TEST_TMP_DIR]/.databrickscfg",
        -      "agents": {
        -        "claude-code": {
        -          "path": "[TEST_TMP_DIR]/home/.claude.json",
        -          "scope": "user"
        -        },
        -        "codex": {
        -          "path": "[TEST_TMP_DIR]/home/.codex/config.toml",
        -          "scope": "user"
        -        }
        -      }
        -    }
        -  ]
        -}
        +Exit code: 1
        
    acceptance_test.go:1152: 
        LOG.create-genie_one_mcp-logfood: {
        LOG.create-genie_one_mcp-logfood:   "comment": "Ask questions about your data",
        LOG.create-genie_one_mcp-logfood:   "created_by": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "effective_owner": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "metastore_id": "7a0ca299-0d79-287a-7a0c-a2990d792957",
        LOG.create-genie_one_mcp-logfood:   "name": "mcp-services/system.ai.genie_one_mcp",
        LOG.create-genie_one_mcp-logfood:   "updated_by": "tester@databricks.com"
        LOG.create-genie_one_mcp-logfood: }
    acceptance_test.go:1152: 
        LOG.create-github-logfood: {
        LOG.create-github-logfood:   "comment": "Repositories, issues, pull requests",
        LOG.create-github-logfood:   "config": {
        LOG.create-github-logfood:     "source_connection": {
        LOG.create-github-logfood:       "name": "connections/github",
        LOG.create-github-logfood:       "options": {
        LOG.create-github-logfood:         "oauth_provider": "OAUTH_PROVIDER_GITHUB_MCP"
        LOG.create-github-logfood:       }
        LOG.create-github-logfood:     }
        LOG.create-github-logfood:   },
... (6 more lines truncated, raise DATABRICKS_CLI_TEST_MAX_LOG to see more)
TestAccept/cmd/aitools/mcp/add-two-profiles: branch ✅ | main (8e70c8b) ❌ | latest (v1.26.0) ❌

main (8e70c8b):

acceptance_test.go:1374: Diff:
        --- cmd/aitools/mcp/add-two-profiles/output.txt
        +++ /tmp/TestAcceptcmdaitoolsmcpadd-two-profiles2806770181/002/output.txt
        @@ -2,52 +2,28 @@
         === The same service for a second profile adds an entry and renames nothing
         
         >>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp --agents claude-code
        -Profile logfood ([DATABRICKS_URL])
        -Agents (named with --agents): claude-code
        +Error: unknown flag: --names
         
        -genie_one_mcp-logfood  system.ai.genie_one_mcp
        -  claude-code  installed  [TEST_TMP_DIR]/home/.claude.json
        +Usage:
        +  databricks aitools [command]
         
        ->>> [CLI] aitools mcp add --profile dogfood --names system.ai.genie_one_mcp --agents claude-code --output json
        -{
        -  "profile": "dogfood",
        -  "servers": [
        -    {
        -      "name": "genie_one_mcp-dogfood",
        -      "agents": [
        -        {
        -          "name": "claude-code",
        -          "status": "installed"
        -        }
        -      ]
        -    }
        -  ]
        -}
        +Available Commands:
        +  install     Install Databricks skills and plugins for coding agents
        +  list        List installed skills and plugins
        +  uninstall   Uninstall Databricks skills and plugins
        +  update      Update installed skills and plugins
        +  version     Show installed skills and plugins version
         
        -=== Both entries are in the Claude Code config
        +Flags:
        +  -h, --help   help for aitools
         
        ->>> jq .mcpServers | keys home/.claude.json
        -[
        -  "genie_one_mcp-dogfood",
        -  "genie_one_mcp-logfood"
        -]
        +Global Flags:
        +      --debug            enable debug logging
        +  -o, --output type      output type: text or json (default text)
        +  -p, --profile string   ~/.databrickscfg profile
        +  -t, --target string    bundle target to use (if applicable)
         
        -=== State holds one entry per profile
        +Use "databricks aitools [command] --help" for more information about a command.
         
        ->>> jq .mcp | [.servers[] | {name, profile, agents: (.agents | keys)}] home/.databricks/aitools/skills/.state.json
        -[
        -  {
        -    "name": "genie_one_mcp-logfood",
        -    "profile": "logfood",
        -    "agents": [
        -      "claude-code"
        -    ]
        -  },
        -  {
        -    "name": "genie_one_mcp-dogfood",
        -    "profile": "dogfood",
        -    "agents": [
        -      "claude-code"
        -    ]
        -  }
        -]
        +
        +Exit code: 1
        
    acceptance_test.go:1152: 
        LOG.create-genie_one_mcp-dogfood: {
        LOG.create-genie_one_mcp-dogfood:   "comment": "Ask questions about your data",
        LOG.create-genie_one_mcp-dogfood:   "created_by": "tester@databricks.com",
        LOG.create-genie_one_mcp-dogfood:   "effective_owner": "tester@databricks.com",
        LOG.create-genie_one_mcp-dogfood:   "metastore_id": "7a0ca291-4922-72d3-7a0c-a291492273ec",
        LOG.create-genie_one_mcp-dogfood:   "name": "mcp-services/system.ai.genie_one_mcp",
        LOG.create-genie_one_mcp-dogfood:   "updated_by": "tester@databricks.com"
        LOG.create-genie_one_mcp-dogfood: }
    acceptance_test.go:1152: 
        LOG.create-genie_one_mcp-logfood: {
        LOG.create-genie_one_mcp-logfood:   "comment": "Ask questions about your data",
        LOG.create-genie_one_mcp-logfood:   "created_by": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "effective_owner": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "metastore_id": "7a0ca291-46a6-5b9c-7a0c-a29146a65cb4",
        LOG.create-genie_one_mcp-logfood:   "name": "mcp-services/system.ai.genie_one_mcp",
        LOG.create-genie_one_mcp-logfood:   "updated_by": "tester@databricks.com"
        LOG.create-genie_one_mcp-logfood: }

latest (v1.26.0):

acceptance_test.go:1374: Diff:
        --- cmd/aitools/mcp/add-two-profiles/output.txt
        +++ /tmp/TestAcceptcmdaitoolsmcpadd-two-profiles2304736118/002/output.txt
        @@ -2,52 +2,28 @@
         === The same service for a second profile adds an entry and renames nothing
         
         >>> [CLI] aitools mcp add --profile logfood --names system.ai.genie_one_mcp --agents claude-code
        -Profile logfood ([DATABRICKS_URL])
        -Agents (named with --agents): claude-code
        +Error: unknown flag: --names
         
        -genie_one_mcp-logfood  system.ai.genie_one_mcp
        -  claude-code  installed  [TEST_TMP_DIR]/home/.claude.json
        +Usage:
        +  databricks aitools [command]
         
        ->>> [CLI] aitools mcp add --profile dogfood --names system.ai.genie_one_mcp --agents claude-code --output json
        -{
        -  "profile": "dogfood",
        -  "servers": [
        -    {
        -      "name": "genie_one_mcp-dogfood",
        -      "agents": [
        -        {
        -          "name": "claude-code",
        -          "status": "installed"
        -        }
        -      ]
        -    }
        -  ]
        -}
        +Available Commands:
        +  install     Install Databricks skills and plugins for coding agents
        +  list        List installed skills and plugins
        +  uninstall   Uninstall Databricks skills and plugins
        +  update      Update installed skills and plugins
        +  version     Show installed skills and plugins version
         
        -=== Both entries are in the Claude Code config
        +Flags:
        +  -h, --help   help for aitools
         
        ->>> jq .mcpServers | keys home/.claude.json
        -[
        -  "genie_one_mcp-dogfood",
        -  "genie_one_mcp-logfood"
        -]
        +Global Flags:
        +      --debug            enable debug logging
        +  -o, --output type      output type: text or json (default text)
        +  -p, --profile string   ~/.databrickscfg profile
        +  -t, --target string    bundle target to use (if applicable)
         
        -=== State holds one entry per profile
        +Use "databricks aitools [command] --help" for more information about a command.
         
        ->>> jq .mcp | [.servers[] | {name, profile, agents: (.agents | keys)}] home/.databricks/aitools/skills/.state.json
        -[
        -  {
        -    "name": "genie_one_mcp-logfood",
        -    "profile": "logfood",
        -    "agents": [
        -      "claude-code"
        -    ]
        -  },
        -  {
        -    "name": "genie_one_mcp-dogfood",
        -    "profile": "dogfood",
        -    "agents": [
        -      "claude-code"
        -    ]
        -  }
        -]
        +
        +Exit code: 1
        
    acceptance_test.go:1152: 
        LOG.create-genie_one_mcp-dogfood: {
        LOG.create-genie_one_mcp-dogfood:   "comment": "Ask questions about your data",
        LOG.create-genie_one_mcp-dogfood:   "created_by": "tester@databricks.com",
        LOG.create-genie_one_mcp-dogfood:   "effective_owner": "tester@databricks.com",
        LOG.create-genie_one_mcp-dogfood:   "metastore_id": "7a0ca299-0bac-d8b2-7a0c-a2990bacda43",
        LOG.create-genie_one_mcp-dogfood:   "name": "mcp-services/system.ai.genie_one_mcp",
        LOG.create-genie_one_mcp-dogfood:   "updated_by": "tester@databricks.com"
        LOG.create-genie_one_mcp-dogfood: }
    acceptance_test.go:1152: 
        LOG.create-genie_one_mcp-logfood: {
        LOG.create-genie_one_mcp-logfood:   "comment": "Ask questions about your data",
        LOG.create-genie_one_mcp-logfood:   "created_by": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "effective_owner": "tester@databricks.com",
        LOG.create-genie_one_mcp-logfood:   "metastore_id": "7a0ca299-099a-9f29-7a0c-a299099aa023",
        LOG.create-genie_one_mcp-logfood:   "name": "mcp-services/system.ai.genie_one_mcp",
        LOG.create-genie_one_mcp-logfood:   "updated_by": "tester@databricks.com"
        LOG.create-genie_one_mcp-logfood: }

10 more failure details omitted, see the full report in the run.

@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Integration test report

Commit: 6b41591

Run: 43449427248

Env 💚​RECOVERED ✅​pass 🙈​skip Time
💚​ aws linux-2core-8gb 1 270 30 12:00
💚​ aws-windows-latest-4core-16gb 1 272 28 8:11
💚​ azure linux-2core-8gb 1 269 30 10:11
💚​ azure-windows-latest-4core-16gb 1 271 28 8:49
💚​ gcp linux-2core-8gb 1 270 30 11:38
💚​ gcp-windows-latest-4core-16gb 1 272 28 6:42
Test Name aws linux-2core-8gb aws-windows-latest-4core-16gb azure linux-2core-8gb azure-windows-latest-4core-16gb gcp linux-2core-8gb gcp-windows-latest-4core-16gb
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
Top 8 slowest tests (at least 2 minutes):
duration env testname
3:47 azure-windows-latest-4core-16gb TestFilerWorkspaceFilesExtensionsDelete
3:39 aws-windows-latest-4core-16gb TestAccept
3:35 gcp-windows-latest-4core-16gb TestAccept
3:34 azure-windows-latest-4core-16gb TestAccept
3:04 azure linux-2core-8gb TestFilerWorkspaceFilesExtensionsReadDir
2:34 aws linux-2core-8gb TestFilerRecursiveDelete/workspace_files
2:07 gcp linux-2core-8gb TestFilerWorkspaceFilesExtensionsRead
2:06 gcp linux-2core-8gb TestImportDir

simonfaltum and others added 18 commits November 13, 2026 14:50
For a service that needs provider sign-in, the gateway rejects tools/list
until the user signs in, so an agent sees no tools. The proxy now answers
a challenged tools/list with one authenticate tool. Calling it runs the
sign-in and asks the agent to re-list. A challenged tools/call names the
login command and asks the agent to re-list, without opening a browser.
The sign-in page opens at most once per session, and again only after a
timed-out attempt. Other services remain a pure passthrough.

Co-authored-by: Isaac <no-reply@databricks.com>
The serve notice for a server with no install record no longer ends with a period, so the golden
changes only in those two lines.

Co-authored-by: Isaac <no-reply@databricks.com>
The proxy now accepts an upstream answer only when its id is the id of the request. Two challenge
tests answered a request with a fixture that carried another request's id, so the answer was
reported as a failed upstream response. Give each request in those tests its own fixture.

Co-authored-by: Isaac <no-reply@databricks.com>
The sign-in now names the profile on stderr before the page, as `login` does, because the serve
command passes the profile to the shared sign-in.

Co-authored-by: Isaac <no-reply@databricks.com>
The authenticate tool runs the same sign-in that `login` runs. The shared helper now also reports whether the caller was
already signed in and whether the sign-in page was shown, so the proxy no longer reads the credential before the sign-in
reads it again. The sign-in timeout carries its wait and the failure of its last poll, and the workspace ID is resolved
once per session. The shared sign-in helper also prints its progress on stderr.

Refactor only, apart from the stderr progress the helper adds to the tool's output.

Co-authored-by: Isaac <no-reply@databricks.com>
Git Bash on Windows converts a leading slash in the path argument of $CLI, so the calls made by the new
serve acceptance scripts need the variable like the other scripts do.

Co-authored-by: Isaac <no-reply@databricks.com>
…ints

The sign-in hints of the authenticate tool wrote the login command by hand. The command now builds it with the same
helper as add and login, so a server pinned to a config file names that file, and a profile name with a space is quoted.

Co-authored-by: Isaac <no-reply@databricks.com>
A cancelled request has no response in the MCP spec, and the client has stopped waiting for one. The sign-in of a
cancelled authenticate call returned a context error that was written as a failed sign-in.

Co-authored-by: Isaac <no-reply@databricks.com>
The authenticate tool opened the sign-in page once per session and again only after a timeout, so a call that
completed, failed, or was refused before the page showed left later calls waiting on a page that was never opened.
The page is now held back only while a cancelled attempt that showed it may still be open in the user's browser.

Co-authored-by: Isaac <no-reply@databricks.com>
Before a challenge the proxy has no tool to offer, so a tool named authenticate that the upstream lists is the upstream's
and is forwarded. The proxy answers the call once the session has been challenged.

Co-authored-by: Isaac <no-reply@databricks.com>
Binding and Unity Catalog compare service names without case, but a sign-in URL with a different case from the
configured service name was not recognized, and the known-service table was looked up by a case-sensitive leaf. The
URL text check also missed a sign-in URL that follows an opening bracket or quote. Discovery uses the same lookup, so a
service listed in another case gets the same description.

Co-authored-by: Isaac <no-reply@databricks.com>
…d-in answer

An authenticate call answered "Already signed in" sent tools/list_changed, so a service that still challenged the
caller, for example after its grant was revoked, started a loop of re-lists, challenges and the same answer. The answer
no longer asks the agent to list again, since nothing changed. A challenge that follows it makes the next authenticate
delete the caller's credential first, so the sign-in waits for a new one.

Co-authored-by: Isaac <no-reply@databricks.com>
…startup read

The sign-in client authenticated outside the guard that stops the proxy on a token refresh failure, so such a failure
during authenticate reported the raw error without the login hint and left the proxy running. A failed sign-in or
startup call is now followed by one authentication through the guard. The startup read of a server with no install
record is bounded as the list --check checks are, so it does not wait for the SDK's retry budget, and its auth failure
exits 2 with the login hint, as the other credential failures do.

Co-authored-by: Isaac <no-reply@databricks.com>
An authenticate call waits up to five minutes, longer than the request timeout of common MCP clients, and sent nothing
until it answered. A call that carries a progress token now gets a notifications/progress message on each poll of the
sign-in. The sign-in helper takes its options as the mcp package's, so the poll callback reaches it.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
When the credential is already active, for example from a sign-in in a
terminal, the agent's cached tool list still shows only authenticate, so
the answer is followed by tools/list_changed. A challenge after that
answer still makes the next authenticate start a new sign-in, so the two
cannot loop.

Co-authored-by: Isaac <no-reply@databricks.com>
The URL elicitation and HTTP 401 challenge modes of the fake were used only by the fake's own tests, and the
proxy's tests cover -32042 and 401 with their own handlers. The fake's credential check now matches the service
name without case, as its service lookup does.

Co-authored-by: Isaac <no-reply@databricks.com>
simonfaltum and others added 28 commits November 13, 2026 14:50
A bare 401, or one whose body is a Databricks authentication error, means the Databricks token was rejected, which a provider
sign-in does not fix. Such an answer now gets the login hint, as the refresh guard words it, and no authenticate tool. A
401 with a sign-in body (JSON-RPC -32006, or a sign-in URL elicitation) is still a challenge. Before, a rejected token
after an "Already signed in" answer restarted the provider sign-in and deleted a valid credential.

Co-authored-by: Isaac <no-reply@databricks.com>
…redential

A server with no install record used to exit when its startup read of the service failed, even for a service that never
needs sign-in. The read now fails open: the serve starts with the sign-in handling on, which acts only on a challenge that
names the service, and one line on stderr says the read failed. A failure of the credential still exits 2 with the login
hint.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
A token failure that is not a rejection now fails only its request. The tests that expect the proxy to stop use a
rejected token, and the two proxy tests that keep serving use a transient failure with the bridge's guard.

Co-authored-by: Isaac <no-reply@databricks.com>
… proxy

A token failure that is not a rejection fails only the sign-in's request, with the transient message, and the guard
does not stop the proxy.

Co-authored-by: Isaac <no-reply@databricks.com>
The sign-in hooks saw every upstream message of an exchange, including
event-stream notifications, and counted any 2xx answer as success. A
progress notification or a JSON-RPC error on HTTP 200 could therefore end
the "Already signed in" state or the authenticate tool. Only the response
to the request is read now, and only a response with a result and no error
counts as success.

Co-authored-by: Isaac <no-reply@databricks.com>
A challenge after "Already signed in" asks for a restart of the sign-in.
A success from the service before the next authenticate left that restart
pending, so a much later, unrelated challenge could delete a working
credential. A success now ends the restart too.

Co-authored-by: Isaac <no-reply@databricks.com>
A challenged tools/call tells the agent to call authenticate, but the
agent's next tools/list could pass unchallenged, which removed the tool
from the list and left the agent without it. While the last challenge came
from a call, a passing list now carries the authenticate tool, until a
sign-in ends or a call passes.

Co-authored-by: Isaac <no-reply@databricks.com>
A poll of a sign-in can fire after the client cancelled the call. Its
progress notification was still written for the cancelled request. The
progress is now checked against the call's context before it is sent.

Co-authored-by: Isaac <no-reply@databricks.com>
The progress of a sign-in named the page only when the attempt did not
open it, so a client that hides stderr, or a browser that did not open,
never saw where to finish until the wait timed out. The page is now named
on every poll.

Co-authored-by: Isaac <no-reply@databricks.com>
The proxy rebuilt the timeout text that the sign-in already returns, from
fields that only it used. The timeout now carries the last poll's failure
alone, and the proxy answers with the error's text.

Co-authored-by: Isaac <no-reply@databricks.com>
A package-level regexp compiles at init. The pattern now follows the
package's other patterns and compiles on first use.

Co-authored-by: Isaac <no-reply@databricks.com>
signIn overwrote the poll and timeout of the options it was given, so the
fields a caller set for them were silently ignored. Callers now pass the
profile, the page opener and the poll callback, and signIn builds the
sign-in options itself.

Co-authored-by: Isaac <no-reply@databricks.com>
The sign-in of the authenticate tool returned the errors of the credential
delete, the workspace ID read and the wait after a restart as they came.
Each names the profile and the login command now, as the login command
does. A restart that removes the caller's sign-in says so on the sign-in's
stderr before the page.

The wait for a restart's delete is bounded as a whole. Its credential reads
could hold it for the SDK's retry budget, which is minutes.

Co-authored-by: Isaac <no-reply@databricks.com>
A signal during the startup read printed a read failure and served the
proxy on a cancelled context. The start now ends as a shutdown does. The
sign-in client was built and its profile resolved again on every serve,
even for a server whose record says it needs no sign-in, so it is built
only when the start reads the service or the record says it needs sign-in.

Co-authored-by: Isaac <no-reply@databricks.com>
The passthrough scenario created its service twice, and the authenticate
scenario sent its setup output to /dev/null. The duplicate create is gone,
and the setup commands write to LOG files. The retry waits still discard
their output, since it repeats the captured stdout on every poll.

Co-authored-by: Isaac <no-reply@databricks.com>
resultResponse lived beside the sign-in code and built its own envelope,
while errorResponse had a second one. Both are in messages.go over one
response type, with the version as a constant.

Co-authored-by: Isaac <no-reply@databricks.com>
The two tests checked the same stop with the same server and guard, one
per call. They are one table test now.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
…p read

A rejected token cancels serve's context, so the startup read returned the cancellation and serve exited 0 with no
line. The refresh failure is now checked first, and the auth failure is returned ahead of the cancellation.

Co-authored-by: Isaac <no-reply@databricks.com>
Only the responses to tools/list and tools/call are decoded for a challenge or a success, and an HTTP 401 is still read.
A response whose result or error has a shape the checks do not read is neither, so it passes through instead of failing
the request.

Co-authored-by: Isaac <no-reply@databricks.com>
A tools/list that passes after a challenged tool call no longer ends the state of that call's "Already signed in"
answer, so a later challenge to a call starts a new sign-in. An isError result without a sign-in URL is neither a
challenge nor a success and changes no state.

Co-authored-by: Isaac <no-reply@databricks.com>
…ed in

The proxy no longer deletes a credential. When the service challenges again after an "Already signed in" answer, the
next authenticate answers with an error that names the logout command, and clears the state, so the authenticate after
that runs the sign-in as usual. The restart wait, its notice and the restart tests are removed.

Co-authored-by: Isaac <no-reply@databricks.com>
…thenticate tool

A paginated list after a challenged tools/call gets the authenticate tool on its first page only. A list that already has a
tool named authenticate keeps it alone, and calls to that tool go to the service.

Co-authored-by: Isaac <no-reply@databricks.com>
…cancel

The browser command of serve gets no stdin, since serve's stdin is the protocol stream, and runs with a context that the
authenticate request's cancellation does not reach, so a cancelled call does not kill the browser. Open and
OpenOutputToStderr keep their behavior.

Co-authored-by: Isaac <no-reply@databricks.com>
…tdin

Co-authored-by: Isaac <no-reply@databricks.com>
mcp.SignIn opens the page only when Open is set, as it already does for OnPoll, so serve no longer passes a no-op opener.
The testserver's sign-in answer is a plain function again, the proxy's list uses the tools key constant, and the bridge's
loop keeps its variable name. The sign-in test that sets no progress callback now sets one.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
@simonfaltum
simonfaltum force-pushed the aitools-mcp/08-challenge branch from 1abf7d0 to 6b41591 Compare November 13, 2026 15:05

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants