Skip to content

feat: add agnoctl, the unified Agno CLI (connect, tokens, create, infra)#8743

Closed
ashpreetbedi wants to merge 5 commits into
feat/service-accountsfrom
feat/agno-cli
Closed

feat: add agnoctl, the unified Agno CLI (connect, tokens, create, infra)#8743
ashpreetbedi wants to merge 5 commits into
feat/service-accountsfrom
feat/agno-cli

Conversation

@ashpreetbedi

@ashpreetbedi ashpreetbedi commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Summary

One command from a running AgentOS to connected coding agents:

uvx agno connect

This PR adds agnoctl — a new distribution at libs/agno_cli (module agno_cli, command agno) — plus the two server-side changes it needs. It is stacked on feat/service-accounts (the tokens it mints are the agno_pat_* service accounts from that branch) and is destined for feat/v2.7.

Design doc: specs/agno/features/agno-cli/ (private specs repo).

The CLI (libs/agno_cli, PyPI name agnoctl)

  • agno connect — discovers the AgentOS (structured /info fields, probe fallback for older servers; tries :7777 then :8000), mints one service account per client (claude-code, codex, cursor; --name for one shared account), writes each client's MCP config (Claude Code via claude mcp add with file fallback; Codex via section-scoped ~/.codex/config.toml edit; Cursor via mcp.json merge), then reads the config back and verifies the entry the client will actually use with a real MCP initialize + tools/list handshake. Idempotent: working entries are skipped, broken ones rotated (--rotate / --skip-existing for non-interactive control). Tokens are never printed or logged.
  • agno tokens create/list/revoke — thin wrappers over POST/GET/DELETE /service-accounts. create prints the plaintext exactly once.
  • agno create NAME [-t agentos-docker|agentos-aws|agentos-railway] [-u URL] — template scaffolding (shallow clone, history stripped, example secrets copied), ported from ag infra create. The ~/.config/ag/config.json registry is deliberately dropped; commands are cwd-based.
  • agno up/down/restart — the docker compose path of the legacy ag infra CLI (same file detection and command lines), promoted to root verbs, with --file, --volumes, --dry-run, --json. The legacy per-resource Docker/AWS engine is deliberately not ported (its production path is under redesign); it can return via the agno_cli.plugins entry-point group, which lets any installed distribution mount subcommands.
  • agno status — discovered OS, auth mode, MCP URL, per-client connection state.

CLI contract (built to be driven by coding agents as well as humans): --json on every command, deterministic exit codes (0 ok / 1 failure / 2 usage / 3 partial), no prompts in --json mode, truthful outcomes (nothing is reported connected that did not verify).

Dependencies: httpx, rich, typer (+ tomli on Python < 3.11) — no dependency on the agno framework, so the package stays uvx-fast. uvx agno connect (via the core agno package) becomes literal with a follow-up two-line change in libs/agno/pyproject.toml once agnoctl is published; until then: uvx agnoctl connect.

Server-side changes (libs/agno)

  • GET /info discovery fields: mcp: {enabled, path} and auth_mode: "none" | "security_key" | "jwt" (reflecting effective enforcement precedence), so tools can discover capabilities without probing or parsing 401 messages. /info remains unauthenticated.
  • /mcp auth in non-JWT modes: the MCP sub-app previously performed no bearer validation when JWT authorization was off (the OS_SECURITY_KEY check is a router dependency, which does not apply to mounted sub-apps). A dedicated middleware now accepts the security key (constant-time compare) or a valid service-account token on /mcp, mirroring REST semantics; open dev instances stay open; JWT mode is unchanged.

Type of change

  • New feature
  • Bug fix (the /mcp unauthenticated-in-security-key-mode gap)

Checklist

  • Code complies with style guidelines
  • Ran format/validation scripts (./scripts/format.sh and ./scripts/validate.sh)
  • Self-review completed
  • Documentation updated (comments, docstrings, libs/agno_cli/README.md)
  • Examples and guides: cookbook example to follow with the v2.7 docs pass
  • Tested in clean environment
  • Tests added/updated (if applicable)

Duplicate and AI-Generated PR Check

  • I have searched existing open pull requests and confirmed that no other PR already addresses this issue
  • If a similar PR exists, I have explained below why this PR is a better approach
  • Check if this PR was entirely AI-generated (by Copilot, Claude Code, Cursor, etc.)

Additional Notes

Verification

  • 78 CLI unit tests against an in-memory fake AgentOS (httpx.MockTransport) and tmp config dirs; 1430 libs/agno unit tests pass; 26 service-accounts integration tests pass (including new end-to-end MCP-over-service-account cases: mint via REST, call /mcp, revoke, rejected).
  • Live golden path against a real AgentOS (FastMCP, SQLite, security-key mode): agno connect --json → 3 accounts minted, 3 configs written, all 8 tools of the MCP surface-v2 contract verified per client; re-run reports already-connected with zero new accounts; agno tokens revoke claude-code → the revoked token is rejected (401) on the next MCP call while other clients keep working. agno create verified against the real agentos-docker-template (which already ships AGENTS.md/CLAUDE.md), and agno up --dry-run resolves its compose.yaml.
  • The CLI code was reviewed by a 29-agent adversarial pass (4 dimensions, every finding independently verified); all 23 confirmed findings are fixed with regression tests — including a TOML rewrite that could drop [[array-of-tables]] sections, corrupt-config clobbering, inverted Claude Code scope precedence, and per-client error isolation for the --json contract.

Known/pre-existing

  • validate.sh shows 6 mypy errors in unrelated libs/agno tools files (httpx type-stub issues: jina, zoom, desi_vocal, clickup, calcom, azure_openai); these reproduce identically on the base branch.

Release sequencing (to avoid a window with two CLIs)

  1. Publish agnoctl to PyPI.
  2. Same-day agno-infra release drops its ag/agno console scripts (its used surface — create + compose up/down — is fully covered here).
  3. Core agno release adds agnoctl as a dependency plus [project.scripts] agno = "agno_cli.main:app", making uvx agno connect live (both mechanisms validated against current uv).

🤖 Generated with Claude Code

@ashpreetbedi ashpreetbedi requested a review from a team as a code owner July 4, 2026 11:24
ashpreetbedi and others added 5 commits July 4, 2026 13:27
… modes

Router dependencies never run for mounted sub-apps, so the MCP app served
at /mcp was completely unauthenticated in security-key mode, and service
account tokens were never checked there either. Add a dedicated middleware
to the MCP sub-app when JWT mode is not active, mirroring the REST rules
from agno.os.auth.get_authentication_dependency: accept the OS_SECURITY_KEY
(constant-time compare) or a valid agno_pat service account token (with
principal and scopes attached to request.state for attribution), stay open
when auth is fully disabled, and map verifier statuses to the same 401/429/503
responses as REST. JWT mode is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extends the unauthenticated /info endpoint with:
- mcp: {enabled, path} reporting MCP server availability (path /mcp when enabled)
- auth_mode: the effectively enforced authentication mode (none / security_key / jwt)

auth_mode mirrors the precedence in get_authentication_dependency: JWT
(authorization=True or JWT env config) takes precedence over the OS
security key. This lets external tools (agno connect CLI) discover the
OS configuration without probing or 401-detail sniffing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New distribution libs/agno_cli (PyPI: agnoctl, module: agno_cli, command: agno).
Talks plain HTTP to a running AgentOS; never imports the agno framework.

- agno connect: discover the AgentOS (structured /info fields with probe
  fallback), mint one service account per coding agent, write MCP configs for
  Claude Code (claude mcp add, .mcp.json fallback), Codex (config.toml
  section-scoped edit), and Cursor (mcp.json merge), then verify each
  connection with a real MCP initialize + tools/list handshake. Idempotent:
  working entries are skipped, broken ones rotated; --rotate/--skip-existing
  for non-interactive control.
- agno tokens create/list/revoke: thin wrappers over /service-accounts.
- agno status: discovered OS, auth mode, MCP URL, per-client config state.
- Agent-operator contract: --json on every command, deterministic exit codes
  (0 ok, 1 failure, 2 partial), no prompts in JSON mode, tokens never logged.
- Plugin seam: agno_cli.plugins entry-point group mounts external Typer apps.
- 54 tests against an in-memory fake AgentOS (httpx.MockTransport) and tmp
  client config dirs; ruff and mypy clean; wired into scripts/format.sh and
  scripts/validate.sh.

Design doc: specs/agno/features/agno-cli/ (uvx agno connect lands via a
follow-up two-line change in libs/agno once agnoctl is on PyPI).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…al review

One CLI: port the used surface of the legacy ag infra CLI so agno-infra can
drop its console scripts without a capability gap.

- agno create NAME [-t agentos-docker|agentos-aws|agentos-railway] [-u URL]:
  shallow-clone the template, strip git history, copy example secrets. No
  registry file: commands operate on the working directory.
- agno infra up/down/restart: the docker compose path (same detection names
  and command lines as the legacy CLI, plus --file/--volumes/--dry-run/--json).
  The legacy per-resource Docker/AWS engine is deliberately not ported; it
  plugs in later via the agno_cli.plugins entry-point group.

Review fixes (23 findings from a 29-agent adversarially verified review):
- Codex TOML rewrite no longer swallows [[array-of-tables]] sections or
  trailing comments; refuses to modify unparseable configs.
- JSON config writers refuse to clobber corrupt files and malformed
  mcpServers shapes; chmod 600 whenever a token is written.
- Claude Code: read follows real scope precedence (local > project > user);
  user-scope file fallback targets ~/.claude.json instead of a VCS-shared
  .mcp.json; subprocess calls get timeouts and no inherited stdin.
- connect: per-client isolation catches all exceptions (keeps the --json
  single-document contract); --skip-existing never touches entries pointing
  at a different AgentOS; post-write read-back verifies the entry the client
  will actually use (catches shadowing and dropped headers); --privileged
  passthrough; partial success exits 3 (2 stays click's usage-error code).
- Context-correct 409 hints; hardened response parsing; verify_mcp honors
  its never-raises contract.

78 tests; ruff and mypy clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The compose lifecycle is the hero path, and the compose file runs the whole
project (AgentOS included), so the infra noun under-described it. Root verbs
match the two-tier dev story (agno dev for in-process reload later, agno up
for container parity) and keep the quickstart to four commands:
agno create my-os && cd my-os && agno up && agno connect.

The infra command group is removed; a future infra 2.0 plugin can mount
agno deploy (or reclaim an infra group) via the agno_cli.plugins seam.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ashpreetbedi

Copy link
Copy Markdown
Contributor Author

Consolidated into #8747 (feat/v2.7) together with #8742. The branch was merged there, then the module/dir was renamed agno_cli → agnoctl (one name across PyPI/import/traceback), the uvx-agno-connect core shim landed, and the combined diff went through a full adversarial review. See #8747.

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.

1 participant