aukeys-opscli 0.0.20__tar.gz → 0.0.22__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/CLAUDE.md +46 -7
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/PKG-INFO +3 -5
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/README_MCP.md +2 -2
- aukeys_opscli-0.0.22/docs/design/opscli-session/345/205/261/347/224/250/346/200/247/345/210/206/346/236/220/344/270/216/344/274/230/345/214/226/346/226/271/346/241/210/346/212/245/345/221/212.md +389 -0
- aukeys_opscli-0.0.22/docs/design/opscli/346/216/210/346/235/203/346/265/201/347/250/213/346/267/261/345/272/246/345/210/206/346/236/220.md +564 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/auth/storage/credential_store.py +18 -0
- aukeys_opscli-0.0.22/opscli/mcp/credential_cache.py +139 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/mcp/tools/auth.py +78 -30
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/mcp/tools/helpers.py +22 -10
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-auth/SKILL.md +12 -2
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-mcp/SKILL.md +17 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/pyproject.toml +3 -7
- aukeys_opscli-0.0.22/tests/benchmark_credential_read.py +141 -0
- aukeys_opscli-0.0.22/tests/mcp/test_credential_cache.py +79 -0
- aukeys_opscli-0.0.22/tests/test_session_sharing.py +199 -0
- aukeys_opscli-0.0.20/opscli/mcp/session_store.py +0 -237
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/.agents/plugins/marketplace.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/.agents/skills/super-dev/SKILL.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/.agents/skills/super-dev-seeai/SKILL.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/.claude/CLAUDE.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/.claude/agents/super-dev.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/.claude/commands/super-dev-seeai.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/.claude/commands/super-dev.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/.claude/settings.local.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/.claude/skills/super-dev/SKILL.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/.claude/skills/super-dev-seeai/SKILL.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/.claude-plugin/marketplace.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/.github/workflows/cross-platform-preflight.yml +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/.gitignore +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/.super-dev/install-manifest.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/AGENTS.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/README.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/analysis/amazon-monitor/346/216/245/345/205/245/345/210/206/346/236/220.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/analysis//346/216/210/346/235/203/346/250/241/345/274/217/345/257/271/346/257/224/345/210/206/346/236/220/346/212/245/345/221/212.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/change-log-pending.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/design/AI/345/217/226/346/225/260/350/203/275/345/212/233/345/272/225/345/272/247/345/274/200/345/217/221/351/234/200/346/261/202.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/design/CLI/345/212/250/346/200/201/346/250/241/345/235/227/345/212/240/350/275/275/346/224/271/351/200/240/345/256/236/346/226/275/346/226/271/346/241/210.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/design/Skills/345/244/232/345/267/245/345/205/267/350/260/203/347/240/224/350/247/204/345/210/222.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/design/amazon/345/273/272/350/241/250SQL/350/215/211/346/241/210/344/270/216ops API/350/257/267/346/261/202/344/275/223/345/256/232/344/271/211.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/design/amazon/346/225/260/346/215/256/346/250/241/345/236/213/344/270/216API/351/242/204/347/225/231/346/226/271/346/241/210.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/design/ops-amazon Skill/350/247/204/345/210/222/346/226/271/346/241/210.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/design/query/346/250/241/345/235/227/350/256/276/350/256/241.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/design//346/225/260/346/215/256/346/237/245/350/257/242/346/234/215/345/212/241/345/274/200/345/217/221/350/257/264/346/230/216/346/226/207/346/241/243.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/design//346/225/260/346/215/256/351/233/206/345/255/227/346/256/265/346/212/200/350/203/275/347/263/273/347/273/237/346/212/200/346/234/257/346/226/271/346/241/210.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/design//346/250/241/345/235/227/345/215/217/350/256/256/344/270/216/346/217/222/344/273/266/346/263/250/345/206/214/346/216/245/345/217/243/350/256/276/350/256/241.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/design//346/250/241/345/235/227/345/256/211/350/243/205/345/210/206/345/261/202/344/270/216/351/273/230/350/256/244/345/217/221/350/241/214/346/226/271/346/241/210.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/design//346/250/241/345/235/227/346/217/222/344/273/266/345/214/226/344/270/216/345/244/232/345/233/242/351/230/237/346/262/273/347/220/206/346/226/271/346/241/210.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/design//346/250/241/345/235/227/347/233/256/345/275/225/345/210/206/345/261/202/350/247/204/350/214/203.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/design//351/200/232/347/224/250Skill/347/211/210/346/234/254/346/216/247/345/210/266/346/236/266/346/236/204.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/guide/Skills/345/237/272/347/241/200/345/274/200/345/217/221/345/237/271/350/256/255/346/211/213/345/206/214.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/guide/opscli/345/221/275/344/273/244/347/224/250/344/276/213/346/211/213/345/206/214.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/guide//344/273/216/351/233/266/345/256/211/350/243/205/351/203/250/347/275/262/346/214/207/345/215/227.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/guide//350/256/244/350/257/201/346/250/241/345/235/227/344/275/277/347/224/250/346/214/207/345/215/227.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/guide//350/267/250/345/271/263/345/217/260/345/217/221/345/270/203/345/211/215/346/243/200/346/237/245.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/plans/2026-04-22-p1-contract-version-governance.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/plans/MCP/346/234/215/345/212/241/345/256/214/345/226/204/346/225/264/346/224/271/350/256/241/345/210/222/344/271/246.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/plans//345/217/226/346/225/260/345/272/225/345/272/247/344/270/200/346/234/237/345/211/251/344/275/231/344/273/273/345/212/241.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/plans//345/217/226/346/225/260/345/272/225/345/272/247/344/270/200/346/234/237/345/217/221/345/270/203/346/243/200/346/237/245/346/270/205/345/215/225.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/plans//345/217/226/346/225/260/345/272/225/345/272/247/344/270/200/346/234/237/345/274/200/345/217/221/350/256/241/345/210/222.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/plans//345/217/226/346/225/260/345/272/225/345/272/247/344/270/200/346/234/237/350/201/224/350/260/203/350/256/260/345/275/225.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/plans//345/217/226/346/225/260/345/272/225/345/272/247/344/270/200/346/234/237/351/252/214/346/224/266/346/211/213/345/206/214.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/plans//350/256/244/350/257/201/346/250/241/345/235/227/345/256/236/346/226/275/350/256/241/345/210/222.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/plans//350/256/244/350/257/201/346/250/241/345/235/227/350/256/276/350/256/241/346/226/271/346/241/210.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/release//345/244/232/345/214/205/345/217/221/345/270/203/344/270/216/350/201/232/345/220/210/345/217/221/350/241/214/347/255/226/347/225/245.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/release//346/211/223/345/214/205/345/217/221/345/270/203/346/214/207/345/215/227.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/spec/MCP/346/250/241/345/235/227/346/216/245/345/205/245/350/247/204/350/214/203.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/docs/spec//345/274/200/345/217/221/350/247/204/350/214/203.md" +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/cli.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/client.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/commands/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/commands/cli.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/domain/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/domain/exceptions.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/domain/models.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/exceptions.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/manager.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/models.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/parser.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/scraper.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/scraping/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/scraping/parser.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/scraping/scraper.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/services/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/services/manager.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/transport/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/amazon/transport/client.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/auth/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/auth/cli.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/auth/commands/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/auth/commands/cli.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/auth/config.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/auth/core/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/auth/core/device_flow.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/auth/core/system_registry.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/auth/core/token_manager.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/auth/domain/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/auth/domain/exceptions.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/auth/exceptions.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/auth/storage/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/auth/storage/crypto.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/cli.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/config.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/mcp/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/mcp/auth_middleware.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/mcp/cli.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/mcp/context.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/mcp/server.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/mcp/tools/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/mcp/tools/amazon.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/mcp/tools/chatgpt.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/mcp/tools/query.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/mcp/tools/skills.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/mcp/user_store.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/query/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/query/cli.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/query/client.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/query/commands/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/query/commands/cli.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/query/domain/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/query/domain/exceptions.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/query/domain/models.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/query/exceptions.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/query/manager.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/query/models.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/query/services/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/query/services/manager.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/query/transport/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/query/transport/client.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/cli.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/commands/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/commands/cli.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/detector.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/discovery/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/discovery/detector.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/domain/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/domain/exceptions.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/domain/models.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/exceptions.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/manager.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/models.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/services/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/services/manager.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/sync/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/sync/updater.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-amazon/SKILL.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-amazon/SKILL_MCP.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-amazon/data/VERSION.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-auth/SKILL_MCP.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-auth/data/VERSION.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-dataset-query/SKILL.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-dataset-query/SKILL_MCP.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-dataset-query/data/VERSION.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-dataset-query/data/dataset_fields.csv +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-dataset-query/data/datasets.csv +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-dataset-query/data/query_metadata.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-dataset-query/references/data-query-service-dev-guide.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-dataset-query/scripts/chart_analyze.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-dataset-query/scripts/chart_map.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-dataset-query/scripts/core.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-dataset-query/scripts/excel_export.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-dataset-query/scripts/query.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-dataset-query/scripts/search.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-dataset-query/scripts/updater.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-mcp/data/VERSION.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-skills/SKILL.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/templates/ops-skills/data/VERSION.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/skills/updater.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/opscli/version.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/amazon-payload-sample-shell.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/amazon-payload-sample.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/amazon-scrape-sample-shell.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/amazon-scrape-sample.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/amazon-search-sample-shell.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/amazon-search-sample.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/maintenance/host-onboard-smoke-20260423-082206.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/maintenance/host-onboard-smoke-20260423-082206.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/opscli-codebase/SKILL.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/opscli-codebase/code_analysis.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/opscli-codebase/references/config_patterns/config_patterns.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/opscli-codebase/references/config_patterns/config_patterns.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/opscli-codebase/references/dependencies/dependency_graph.dot +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/opscli-codebase/references/dependencies/dependency_graph.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/opscli-codebase/references/dependencies/dependency_graph.mmd +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/opscli-codebase/references/dependencies/statistics.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/opscli-codebase/references/documentation/documentation_index.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/opscli-codebase/references/documentation/extraction_summary.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/output/opscli-codebase/references/documentation/templates/SKILL.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/plugins/super-dev-claude/.claude-plugin/plugin.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/plugins/super-dev-claude/README.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/plugins/super-dev-claude/skills/super-dev/SKILL.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/plugins/super-dev-claude/skills/super-dev-seeai/SKILL.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/plugins/super-dev-codex/.codex-plugin/plugin.json +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/plugins/super-dev-codex/README.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/plugins/super-dev-codex/skills/super-dev/SKILL.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/plugins/super-dev-codex/skills/super-dev-seeai/SKILL.md +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/publish.sh +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/scripts/release_preflight.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/amazon/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/amazon/test_cli.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/amazon/test_client.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/amazon/test_manager.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/amazon/test_parser.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/auth/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/auth/test_credential_store.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/auth/test_crypto.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/auth/test_device_flow.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/auth/test_system_registry.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/auth/test_token_manager.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/mcp/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/mcp/conftest.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/mcp/test_cli.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/mcp/test_context.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/mcp/test_multi_user_isolation.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/mcp/test_tools.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/mcp/test_user_store.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/query/test_cli.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/query/test_client.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/query/test_manager.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/skills/__init__.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/skills/test_cli.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/skills/test_detector.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/skills/test_manager.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/skills/test_ops_dataset_query_search.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/skills/test_updater.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/tests/test_version_consistency.py +0 -0
- {aukeys_opscli-0.0.20 → aukeys_opscli-0.0.22}/uv.lock +0 -0
|
@@ -31,6 +31,19 @@ opscli/
|
|
|
31
31
|
│ └── storage/
|
|
32
32
|
│ ├── credential_store.py # Keychain 优先 / AES-256-GCM 兜底
|
|
33
33
|
│ └── crypto.py # AES-256-GCM 加密工具
|
|
34
|
+
├── mcp/ # MCP 子模块(Model Context Protocol 服务)
|
|
35
|
+
│ ├── __init__.py
|
|
36
|
+
│ ├── server.py # FastMCP 实例,注册所有 MCP Tools
|
|
37
|
+
│ ├── auth_middleware.py # SSE/HTTP 连接层 API Key 鉴权
|
|
38
|
+
│ ├── credential_cache.py # 内存凭证缓存(避免高频 AES 解密)
|
|
39
|
+
│ ├── session_store.py # ⚠️ 已废弃,兼容包装层(v0.0.5+)
|
|
40
|
+
│ ├── user_store.py # 多用户 API Key 注册表(团队共享场景)
|
|
41
|
+
│ ├── cli.py # MCP 管理 CLI(opscli mcp user *)
|
|
42
|
+
│ └── tools/ # MCP Tools 注册
|
|
43
|
+
│ ├── helpers.py # 共享辅助函数(_get_session_id / _get_jwt)
|
|
44
|
+
│ ├── auth.py # auth_* 工具集(13 个)
|
|
45
|
+
│ ├── query.py # query_* 工具集
|
|
46
|
+
│ └── skills.py # skills_* 工具集
|
|
34
47
|
├── query/ # query 子模块(数据查询)
|
|
35
48
|
│ ├── __init__.py
|
|
36
49
|
│ ├── cli.py # query 子命令(metadata/run/build)
|
|
@@ -93,6 +106,9 @@ opscli skills
|
|
|
93
106
|
| auth 服务地址 | `opscli/auth/config.py`(DEFAULTS + load_config) |
|
|
94
107
|
| Token 管理 | `opscli/auth/core/token_manager.py` |
|
|
95
108
|
| 凭证存储 | `opscli/auth/storage/credential_store.py` |
|
|
109
|
+
| MCP 服务入口 | `opscli/mcp/server.py`(FastMCP 实例) |
|
|
110
|
+
| MCP 凭证缓存 | `opscli/mcp/credential_cache.py`(McpCredentialCache) |
|
|
111
|
+
| MCP auth 工具 | `opscli/mcp/tools/auth.py`(auth_* 工具集) |
|
|
96
112
|
| query 业务编排 | `opscli/query/manager.py`(QueryManager) |
|
|
97
113
|
| query HTTP 转发 | `opscli/query/client.py`(QueryClient) |
|
|
98
114
|
| Skill 管理协调 | `opscli/skills/manager.py`(SkillsManager) |
|
|
@@ -194,7 +210,30 @@ store = CredentialStore()
|
|
|
194
210
|
|
|
195
211
|
网络请求使用 `respx` 进行 mock,不可发起真实 HTTP 请求。
|
|
196
212
|
|
|
197
|
-
### 【铁律9
|
|
213
|
+
### 【铁律9】CLI 与 MCP 的凭证存储必须统一(CredentialStore)
|
|
214
|
+
|
|
215
|
+
2025-04-27 重构后,CLI 模式和 MCP 模式**共用同一套 CredentialStore**,禁止在 MCP 中引入独立的明文存储:
|
|
216
|
+
|
|
217
|
+
- **唯一存储层**:`opscli/auth/storage/credential_store.py`(Keychain / AES-256-GCM)
|
|
218
|
+
- **MCP 读路径**:通过 `McpCredentialCache` 内存缓存读取,避免高频 AES 解密
|
|
219
|
+
- **MCP 写路径**:登录/刷新/登出等操作统一写入 `CredentialStore`,写后调用 `invalidate_credential_cache()` 刷新缓存
|
|
220
|
+
- **`mcp_sessions.json` 已废弃**:`opscli/mcp/session_store.py` 仅保留兼容包装层,内部转发到 `CredentialStore`
|
|
221
|
+
|
|
222
|
+
**正确示例**(MCP auth tool 保存 session):
|
|
223
|
+
```python
|
|
224
|
+
from opscli.auth.storage.credential_store import CredentialStore
|
|
225
|
+
from opscli.mcp.credential_cache import invalidate_credential_cache
|
|
226
|
+
|
|
227
|
+
CredentialStore().save_session(session_id, email, expires_at)
|
|
228
|
+
invalidate_credential_cache() # 刷新内存缓存
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**禁止行为**:
|
|
232
|
+
- ❌ 在 MCP 中直接读写 `mcp_sessions.json`
|
|
233
|
+
- ❌ 为 MCP 引入新的独立存储文件
|
|
234
|
+
- ❌ 绕过 `CredentialStore` 自行管理凭证
|
|
235
|
+
|
|
236
|
+
### 【铁律10】内置系统(ops/polaris)不可在代码中被删除
|
|
198
237
|
|
|
199
238
|
`auth/config.py` 的 `get_builtin_systems()` 定义了两个内置系统,这是产品功能约定:
|
|
200
239
|
- `ops`:运营系统(https://ops.api.qa.aukeyit.com)
|
|
@@ -202,7 +241,7 @@ store = CredentialStore()
|
|
|
202
241
|
|
|
203
242
|
内置系统在 `system_registry.py` 中有保护逻辑(`remove()` 会拒绝删除 builtin 系统),不可绕过此保护。
|
|
204
243
|
|
|
205
|
-
### 【铁律
|
|
244
|
+
### 【铁律11】Skills 禁止直连后端 API,所有操作必须经由 opscli 模块转发
|
|
206
245
|
|
|
207
246
|
涉及数据查询、认证、元数据获取等能力时,**Skill 脚本不能直接调用**后端 HTTP API,必须统一通过 `opscli` 的正式命令入口执行。
|
|
208
247
|
|
|
@@ -220,7 +259,7 @@ store = CredentialStore()
|
|
|
220
259
|
4. 认证、参数校验、payload 组装、错误映射统一由 `opscli` 负责
|
|
221
260
|
5. 后端新增接口时,默认先补 `opscli` 对应入口,再让 Skill 消费
|
|
222
261
|
|
|
223
|
-
### 【铁律
|
|
262
|
+
### 【铁律12】Skill 命名必须使用 `ops-` 前缀
|
|
224
263
|
|
|
225
264
|
所有内置 Skill(`opscli/skills/templates/` 下的子目录)命名必须以 `ops-` 开头:
|
|
226
265
|
|
|
@@ -235,7 +274,7 @@ store = CredentialStore()
|
|
|
235
274
|
- 新增 Skill 时若不带 `ops-` 前缀,视为命名不合规,必须改名后再合入
|
|
236
275
|
- 安装命令示例:`opscli skills install ops-auth`
|
|
237
276
|
|
|
238
|
-
### 【铁律
|
|
277
|
+
### 【铁律13】新增 Skill 必须遵循完整接入规范
|
|
239
278
|
|
|
240
279
|
新增 Skill 时,根据是否需要远端升级分两条路径:
|
|
241
280
|
|
|
@@ -272,7 +311,7 @@ store = CredentialStore()
|
|
|
272
311
|
- 必须包含"典型工作流"章节
|
|
273
312
|
- 格式参照 `ops-auth/SKILL.md` 或 `ops-dataset-query/SKILL.md`
|
|
274
313
|
|
|
275
|
-
### 【铁律
|
|
314
|
+
### 【铁律14】依赖版本约束不加不必要的上限
|
|
276
315
|
|
|
277
316
|
`pyproject.toml` 中的依赖版本约束只设下限,**不加上限**,除非有明确的 API 不兼容证据:
|
|
278
317
|
|
|
@@ -287,7 +326,7 @@ store = CredentialStore()
|
|
|
287
326
|
|
|
288
327
|
**教训**:`cryptography>=38,<42` 曾导致 pip 强制降级系统已有的 v46,破坏同环境中其他包(`opscli`)。上限约束应在有明确 breaking change 时才添加,并在注释中说明原因和对应 issue。
|
|
289
328
|
|
|
290
|
-
### 【铁律
|
|
329
|
+
### 【铁律15】文档必须按类型分类存放,文件名必须使用中文
|
|
291
330
|
|
|
292
331
|
所有项目文档必须遵循以下规范:
|
|
293
332
|
|
|
@@ -308,7 +347,7 @@ store = CredentialStore()
|
|
|
308
347
|
3. 同一类型的文档放入同一目录,不可散落
|
|
309
348
|
4. 方案类文档如涉及多阶段,可加阶段后缀(如"取数底座**一期**开发计划")
|
|
310
349
|
|
|
311
|
-
### 【铁律
|
|
350
|
+
### 【铁律16】所有代码必须有中文注释,重要业务逻辑必须全面注释
|
|
312
351
|
|
|
313
352
|
编写或修改任何 Python 代码时,必须遵循以下注释规范:
|
|
314
353
|
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aukeys-opscli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.22
|
|
4
4
|
Summary: Aukeys 运营 CLI 工具集
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Requires-Dist: cryptography>=38
|
|
7
|
+
Requires-Dist: fastmcp>=2.0
|
|
7
8
|
Requires-Dist: httpx>=0.27
|
|
8
9
|
Requires-Dist: keyring>=25
|
|
10
|
+
Requires-Dist: playwright>=1.44
|
|
9
11
|
Requires-Dist: rich>=13
|
|
10
12
|
Requires-Dist: typer>=0.12
|
|
11
|
-
Provides-Extra: amazon
|
|
12
|
-
Requires-Dist: playwright>=1.44; extra == 'amazon'
|
|
13
13
|
Provides-Extra: dev
|
|
14
14
|
Requires-Dist: pytest>=8; extra == 'dev'
|
|
15
15
|
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
16
|
-
Provides-Extra: mcp
|
|
17
|
-
Requires-Dist: fastmcp>=2.0; extra == 'mcp'
|
|
18
16
|
Description-Content-Type: text/markdown
|
|
19
17
|
|
|
20
18
|
# opscli
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# opscli CLI 与 MCP 模式 Session 共用性分析与优化方案报告
|
|
2
|
+
|
|
3
|
+
> 报告日期:2025-04-27
|
|
4
|
+
> 分析范围:opscli 项目中 CLI 模式与 MCP 模式的 Session 存储机制、数据隔离原因及共用优化路径
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 一、执行摘要
|
|
9
|
+
|
|
10
|
+
当前 opscli 的 CLI 模式与 MCP 模式存在**两套完全独立的 Session 存储体系**,导致用户在任一模式下完成 Device Flow 授权后,另一模式无法识别该登录态,必须重复授权。本报告从**存储机制、数据结构、代码耦合、安全策略、设计哲学**五个维度深入分析不能共用的根本原因,提出三种优化方案,并给出推荐实施路径。
|
|
11
|
+
|
|
12
|
+
**核心结论**:推荐采用 **方案 A:MCP 全面复用 CredentialStore + 内存缓存**,在保持架构简洁的前提下,通过统一底层存储实现 Session 互通,消除重复授权问题。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 二、现状深度分析
|
|
17
|
+
|
|
18
|
+
### 2.1 两套存储机制对比
|
|
19
|
+
|
|
20
|
+
| 对比维度 | CLI (CredentialStore) | MCP (SessionStore) |
|
|
21
|
+
|----------|----------------------|-------------------|
|
|
22
|
+
| **文件路径** | `~/.config/opscli/credentials.bin` | `~/.config/opscli/mcp_sessions.json` |
|
|
23
|
+
| **存储格式** | AES-256-GCM 加密二进制 / Keychain | 明文 JSON |
|
|
24
|
+
| **密钥管理** | 自动生成 256-bit 密钥,`.key` 文件权限 600 | 无加密,纯文本 |
|
|
25
|
+
| **数据结构** | 单一 Blob:`{session_id, email, tokens}` | 按系统分组:`{sessions: {ops: {session_id, jwt}}}` |
|
|
26
|
+
| **session 语义** | **全局登录态**,一个 session 换取所有系统 JWT | **按系统绑定**,每个系统独立存 session_id |
|
|
27
|
+
| **写入时机** | `DeviceFlow.poll_once()` 自动写入 | `auth_login_poll()` 成功后手动写入 |
|
|
28
|
+
| **DeviceFlow store** | `store=CredentialStore()` | `store=None`(无状态) |
|
|
29
|
+
|
|
30
|
+
### 2.2 数据结构差异详解
|
|
31
|
+
|
|
32
|
+
**CLI CredentialStore 存储结构**(`opscli/auth/storage/credential_store.py` 第 8-20 行):
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"session_id": "sess_abc123",
|
|
37
|
+
"device_code": "dev_xyz789",
|
|
38
|
+
"email": "user@aukeys.com",
|
|
39
|
+
"session_expires_at": "2025-04-28T10:00:00+00:00",
|
|
40
|
+
"tokens": {
|
|
41
|
+
"ops": {
|
|
42
|
+
"jwt": "eyJhbG...",
|
|
43
|
+
"expires_at": "2025-04-27T12:00:00+00:00",
|
|
44
|
+
"saved_at": 1714212000
|
|
45
|
+
},
|
|
46
|
+
"polaris": {
|
|
47
|
+
"jwt": "eyJhbG...",
|
|
48
|
+
"expires_at": "2025-04-27T12:00:00+00:00",
|
|
49
|
+
"saved_at": 1714212000
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**MCP SessionStore 存储结构**(`opscli/mcp/session_store.py`):
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"version": 2,
|
|
60
|
+
"sessions": {
|
|
61
|
+
"ops": {
|
|
62
|
+
"session_id": "sess_abc123",
|
|
63
|
+
"jwt": "eyJhbG...",
|
|
64
|
+
"saved_at": "2025-04-27T10:00:00Z",
|
|
65
|
+
"jwt_saved_at": "2025-04-27T10:00:00Z"
|
|
66
|
+
},
|
|
67
|
+
"polaris": {
|
|
68
|
+
"session_id": "sess_abc123",
|
|
69
|
+
"jwt": "eyJhbG...",
|
|
70
|
+
"saved_at": "2025-04-27T10:00:00Z",
|
|
71
|
+
"jwt_saved_at": "2025-04-27T10:00:00Z"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**关键差异**:
|
|
78
|
+
1. **根级 session vs 按系统 session**:CLI 的 `session_id` 在根级,表示全局登录态;MCP 的 `session_id` 嵌套在 `sessions[system]` 下
|
|
79
|
+
2. **时间戳格式**:CLI 使用 Unix 时间戳 `saved_at`,MCP 使用 ISO 8601 字符串
|
|
80
|
+
3. **JWT 过期方式**:CLI 存 `expires_at`(ISO 时间),MCP 不存过期时间,依赖 `is_jwt_valid_locally()` 实时解析 JWT payload
|
|
81
|
+
4. **额外字段**:CLI 存 `email` 和 `session_expires_at`,MCP 完全不存这些信息
|
|
82
|
+
|
|
83
|
+
### 2.3 代码路径分叉分析
|
|
84
|
+
|
|
85
|
+
**写入路径分叉**:
|
|
86
|
+
|
|
87
|
+
- **CLI**: `opscli auth login` -> `auth/cli.py login()` -> `DeviceFlow(store=CredentialStore())` -> `poll_once` 自动调用 `store.save_session()` -> `CredentialStore._save()` -> Keychain或加密文件
|
|
88
|
+
- **MCP**: `auth_login_poll` -> `mcp/tools/auth.py auth_login_poll()` -> `DeviceFlow(store=None)` -> `poll_once` 不保存 -> 手动调用 `SessionStore.save_session` -> 明文写入 `mcp_sessions.json`
|
|
89
|
+
|
|
90
|
+
**读取路径分叉**:
|
|
91
|
+
|
|
92
|
+
- **CLI**: `opscli auth token get -s ops` -> `AuthClient.get_token` -> `TokenManager.get_token` -> `CredentialStore.load` -> 读取`session_id` -> 向后端换取JWT
|
|
93
|
+
- **MCP**: `auth_get_token system=ops` -> `_get_session_id` -> `SessionStore.get_session` -> 读取`mcp_sessions.json` -> 返回`session_id` -> `AuthClient.get_token_by_session`
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 三、不能共用的五大根因
|
|
98
|
+
|
|
99
|
+
### 根因一:存储层物理隔离
|
|
100
|
+
|
|
101
|
+
CLI 的 `CredentialStore` 和 MCP 的 `SessionStore` 是两个完全独立的类/模块,没有任何继承或组合关系。
|
|
102
|
+
|
|
103
|
+
- `CredentialStore.save_session()` 方法签名是 `(session_id, email, expires_at, device_code)`
|
|
104
|
+
- `SessionStore.save_session()` 方法签名是 `(system, session_id)`
|
|
105
|
+
|
|
106
|
+
两者在 API 层面就不兼容。
|
|
107
|
+
|
|
108
|
+
### 根因二:DeviceFlow 的 store 参数耦合
|
|
109
|
+
|
|
110
|
+
`DeviceFlow.__init__(self, ops_url, store=None)` 的设计直接将存储逻辑耦合到 Device Flow 内部。
|
|
111
|
+
|
|
112
|
+
- CLI 传入 `CredentialStore` 实例,授权成功即自动落库
|
|
113
|
+
- MCP 传入 `None`,走"无状态"路线,由调用方自行决定如何保存
|
|
114
|
+
|
|
115
|
+
这个设计差异导致两种模式的**写入路径完全不同**。
|
|
116
|
+
|
|
117
|
+
### 根因三:Session 语义模型冲突
|
|
118
|
+
|
|
119
|
+
CLI 的架构基于**全局登录态**:用户登录一次,获得一个 `session_id`,通过该 `session_id` 可以向任意已注册系统换取 JWT。
|
|
120
|
+
|
|
121
|
+
MCP 的 `SessionStore` 设计为**按系统存储**:`sessions["ops"].session_id` 和 `sessions["polaris"].session_id` 被物理分离,虽然当前实现中两者值相同,但数据模型允许它们不同。
|
|
122
|
+
|
|
123
|
+
### 根因四:MCP 多用户隔离未与 SessionStore 打通
|
|
124
|
+
|
|
125
|
+
`MCPUserStore` 为每个用户分配独立的 `credential_dir`(`users/<user_id>/`),但 `SessionStore` 硬编码使用全局 `CONFIG_DIR / "mcp_sessions.json"`,完全没有感知多用户隔离。
|
|
126
|
+
|
|
127
|
+
即使未来启用多用户模式,不同用户的 session 也会互相覆盖。而 CLI 的 `CredentialStore` 支持 `base_dir` 参数,天然可以配合多用户隔离。
|
|
128
|
+
|
|
129
|
+
### 根因五:安全策略与性能权衡的不同选择
|
|
130
|
+
|
|
131
|
+
CLI 模式下用户每次执行命令间隔较长(秒级/分钟级),加密开销可以忽略;
|
|
132
|
+
MCP 模式下 AI Agent 可能在一次对话中调用数十次 Tool,明文 JSON 的读取性能至关重要。
|
|
133
|
+
|
|
134
|
+
这种**性能与安全权衡的差异**,使得简单地将 MCP 切换到加密存储会产生可感知的性能退化。
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 四、优化方案设计
|
|
139
|
+
|
|
140
|
+
### 方案 A:MCP 全面复用 CredentialStore(统一加密存储)
|
|
141
|
+
|
|
142
|
+
**核心思路**:让 MCP 的 auth 工具不再使用 `SessionStore`,而是直接读写 `CredentialStore`,实现与 CLI 完全统一的存储层。
|
|
143
|
+
|
|
144
|
+
**具体改动点**:
|
|
145
|
+
|
|
146
|
+
1. **修改 `mcp/tools/auth.py`**:
|
|
147
|
+
- `auth_login_poll`:使用 `CredentialStore().save_session()` 替代 `SessionStore.save_session()`
|
|
148
|
+
- `auth_get_token` / `auth_token_refresh`:使用 `CredentialStore().save_token()` 替代 `SessionStore.save_jwt()`
|
|
149
|
+
- `auth_logout`:调用 `CredentialStore().clear()`
|
|
150
|
+
|
|
151
|
+
2. **修改 `mcp/tools/helpers.py`**:
|
|
152
|
+
- `_get_session_id()`:从 `CredentialStore().load()` 读取根级 `session_id`
|
|
153
|
+
- `_get_jwt()`:从 `CredentialStore().load()["tokens"][system]["jwt"]` 读取
|
|
154
|
+
|
|
155
|
+
3. **废弃 `mcp/session_store.py`**:保留文件但标记为 deprecated
|
|
156
|
+
|
|
157
|
+
4. **统一 session 语义**:MCP 采用"全局 session"模型
|
|
158
|
+
|
|
159
|
+
**优点**:
|
|
160
|
+
- Session 完全互通:CLI 登录后 MCP 可用,MCP 登录后 CLI 可用
|
|
161
|
+
- 安全统一:所有凭证享受 AES-256-GCM / Keychain 保护
|
|
162
|
+
- 减少维护成本:消除两套存储的重复代码
|
|
163
|
+
- 自动获得 CLI 的账号切换保护
|
|
164
|
+
|
|
165
|
+
**缺点**:
|
|
166
|
+
- MCP 高频 Tool 调用产生加密开销
|
|
167
|
+
- 较大规模重构,涉及 13 个 MCP auth tool
|
|
168
|
+
- 需要统一 session 语义
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
### 方案 B:双向适配器模式(兼容层)
|
|
173
|
+
|
|
174
|
+
**核心思路**:保持两套存储各自独立,但在读写时通过**适配器**实现双向同步和 fallback。
|
|
175
|
+
|
|
176
|
+
**具体改动点**:
|
|
177
|
+
|
|
178
|
+
1. **新增 `opscli/auth/unified_store.py`**:
|
|
179
|
+
- `UnifiedCredentialProvider` 类
|
|
180
|
+
- 写操作时同时写入 `CredentialStore` 和 `SessionStore`
|
|
181
|
+
- 读操作时优先 `CredentialStore`,找不到则 fallback 到 `SessionStore`
|
|
182
|
+
|
|
183
|
+
2. **修改 MCP auth 工具**:使用 `UnifiedCredentialProvider`
|
|
184
|
+
|
|
185
|
+
3. **保持 CLI 不变**
|
|
186
|
+
|
|
187
|
+
**优点**:
|
|
188
|
+
- 零 Breaking Change
|
|
189
|
+
- 平滑迁移:CLI 和 MCP 登录后双方都能读取
|
|
190
|
+
- 保留 MCP 性能
|
|
191
|
+
|
|
192
|
+
**缺点**:
|
|
193
|
+
- 数据冗余:同一 session 存两份
|
|
194
|
+
- 一致性问题:写入失败可能不同步
|
|
195
|
+
- 维护成本高
|
|
196
|
+
- 技术债
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
### 方案 C:统一存储抽象层 + 插件化后端(长期演进)
|
|
201
|
+
|
|
202
|
+
**核心思路**:定义 `ICredentialStore` 接口,CLI 和 MCP 都面向接口编程。
|
|
203
|
+
|
|
204
|
+
**接口设计**:
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from abc import ABC, abstractmethod
|
|
208
|
+
|
|
209
|
+
class ICredentialStore(ABC):
|
|
210
|
+
@abstractmethod
|
|
211
|
+
def save_session(self, session_id: str, email: str, expires_at: str) -> None: ...
|
|
212
|
+
@abstractmethod
|
|
213
|
+
def get_session(self, alias: str | None = None) -> str | None: ...
|
|
214
|
+
@abstractmethod
|
|
215
|
+
def save_token(self, alias: str, jwt: str, expires_in: int) -> None: ...
|
|
216
|
+
@abstractmethod
|
|
217
|
+
def get_token(self, alias: str) -> str | None: ...
|
|
218
|
+
@abstractmethod
|
|
219
|
+
def clear(self) -> None: ...
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**实现类**:
|
|
223
|
+
- `EncryptedCredentialStore`:当前 CLI 的 CredentialStore
|
|
224
|
+
- `PlainJsonCredentialStore`:当前 MCP 的 SessionStore 包装类
|
|
225
|
+
- `ChainedCredentialStore`:组合多个后端
|
|
226
|
+
|
|
227
|
+
**优点**:
|
|
228
|
+
- 架构最优雅,符合开闭原则
|
|
229
|
+
- 可扩展性强
|
|
230
|
+
- 测试友好
|
|
231
|
+
|
|
232
|
+
**缺点**:
|
|
233
|
+
- 改动量最大
|
|
234
|
+
- 引入抽象层,增加复杂度
|
|
235
|
+
- 需要时间
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## 五、方案对比矩阵
|
|
240
|
+
|
|
241
|
+
| 评估维度 | 方案 A:复用 CredentialStore | 方案 B:双向适配器 | 方案 C:统一抽象层 |
|
|
242
|
+
|----------|---------------------------|------------------|-----------------|
|
|
243
|
+
| 实现复杂度 | 中 | 低 | 高 |
|
|
244
|
+
| Session 互通性 | 完全互通 | 完全互通 | 完全互通 |
|
|
245
|
+
| 安全一致性 | 完全统一 | 两套策略并存 | 统一接口,后端可选 |
|
|
246
|
+
| MCP 性能影响 | 加密开销增加 | 无影响 | 无影响 |
|
|
247
|
+
| Breaking Change | 废弃 SessionStore | 零 Breaking | 可能有 |
|
|
248
|
+
| 数据冗余 | 无冗余 | 两份存储 | 无冗余 |
|
|
249
|
+
| 维护成本(短期) | 低 | 中 | 高 |
|
|
250
|
+
| 维护成本(长期) | 低 | 高(技术债) | 低 |
|
|
251
|
+
| 用户凭证迁移 | 需迁移 | 无需迁移 | 需迁移 |
|
|
252
|
+
| 多用户隔离支持 | 天然支持 | 需额外适配 | 通过注入不同 store |
|
|
253
|
+
| 推荐场景 | 平衡安全与简洁 | 紧急兼容需求 | 长期技术演进 |
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 六、推荐方案与实施路径
|
|
258
|
+
|
|
259
|
+
### 6.1 推荐方案:方案 A + 内存缓存
|
|
260
|
+
|
|
261
|
+
**最终推荐采用 "方案 A(MCP 复用 CredentialStore)+ 内存缓存" 的混合策略**。
|
|
262
|
+
|
|
263
|
+
**核心设计**:
|
|
264
|
+
1. **统一落库**:MCP 的写操作统一写入 `CredentialStore`
|
|
265
|
+
2. **内存缓存**:MCP Server 启动时加载凭证到内存,后续 Tool 调用读内存
|
|
266
|
+
3. **缓存失效**:凭证变更时更新内存缓存
|
|
267
|
+
4. **Session 语义统一**:MCP 采用"全局 session"模型
|
|
268
|
+
|
|
269
|
+
### 6.2 具体实施步骤
|
|
270
|
+
|
|
271
|
+
**Phase 1:数据迁移与兼容(1-2 天)**
|
|
272
|
+
|
|
273
|
+
1. 新增迁移脚本,将 `mcp_sessions.json` 数据迁移到 `CredentialStore`
|
|
274
|
+
2. 修改 `mcp/tools/auth.py`,所有写操作改用 `CredentialStore`
|
|
275
|
+
3. 修改 `mcp/tools/helpers.py`,所有读操作改用 `CredentialStore`
|
|
276
|
+
|
|
277
|
+
**Phase 2:MCP 内存缓存优化(2-3 天)**
|
|
278
|
+
|
|
279
|
+
1. 新增 `McpCredentialCache` 类
|
|
280
|
+
2. 修改 MCP auth tools 使用缓存
|
|
281
|
+
|
|
282
|
+
**Phase 3:清理与废弃(1 天)**
|
|
283
|
+
|
|
284
|
+
1. 标记 `mcp/session_store.py` 为 deprecated
|
|
285
|
+
2. 删除 `mcp_sessions.json`
|
|
286
|
+
3. 更新文档和测试
|
|
287
|
+
|
|
288
|
+
### 6.3 关键代码变更示例
|
|
289
|
+
|
|
290
|
+
**变更前**(`mcp/tools/auth.py`):
|
|
291
|
+
```python
|
|
292
|
+
flow = DeviceFlow(ops_url=OPS_URL, store=None)
|
|
293
|
+
result = flow.poll_once(device_code, timeout=timeout)
|
|
294
|
+
if result.get("status") == "authorized":
|
|
295
|
+
session_id = result.get("session_id")
|
|
296
|
+
if session_id:
|
|
297
|
+
from opscli.mcp.session_store import save_session
|
|
298
|
+
save_session("ops", session_id) # 写入明文 JSON
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**变更后**:
|
|
302
|
+
```python
|
|
303
|
+
from opscli.auth.storage.credential_store import CredentialStore
|
|
304
|
+
|
|
305
|
+
flow = DeviceFlow(ops_url=OPS_URL, store=None)
|
|
306
|
+
result = flow.poll_once(device_code, timeout=timeout)
|
|
307
|
+
if result.get("status") == "authorized":
|
|
308
|
+
session_id = result.get("session_id")
|
|
309
|
+
email = result.get("email", "")
|
|
310
|
+
expires_at = result.get("expires_at", "")
|
|
311
|
+
if session_id:
|
|
312
|
+
store = CredentialStore()
|
|
313
|
+
store.save_session(session_id, email, expires_at) # 写入统一加密存储
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## 七、共用 vs 不共用优缺点总结
|
|
319
|
+
|
|
320
|
+
### 7.1 不共用(现状)的优缺点
|
|
321
|
+
|
|
322
|
+
| 维度 | 评价 |
|
|
323
|
+
|------|------|
|
|
324
|
+
| 用户体验 | 差。任一模式登录后,另一模式仍需重新走 Device Flow |
|
|
325
|
+
| 开发维护 | 差。同一功能有两个独立实现,修改需同步两处 |
|
|
326
|
+
| 安全性 | 不一致。CLI 强加密,MCP 明文 |
|
|
327
|
+
| 性能 | MCP 明文读取快,CLI 加密安全但慢 |
|
|
328
|
+
| 架构清晰度 | 差。DeviceFlow 的 store 参数导致底层分叉 |
|
|
329
|
+
| 可扩展性 | 差。新增特性需在两个模块分别实现 |
|
|
330
|
+
|
|
331
|
+
### 7.2 共用(方案 A)的优缺点
|
|
332
|
+
|
|
333
|
+
| 维度 | 评价 |
|
|
334
|
+
|------|------|
|
|
335
|
+
| 用户体验 | 优秀。任一模式登录,另一模式立即可用 |
|
|
336
|
+
| 开发维护 | 优秀。单一存储实现,修改一处全局生效 |
|
|
337
|
+
| 安全性 | 统一。所有凭证享受 AES-256-GCM / Keychain 保护 |
|
|
338
|
+
| 性能 | 需优化。引入内存缓存后,高频读取性能与明文持平 |
|
|
339
|
+
| 架构清晰度 | 优秀。AuthClient 和 MCP tools 面向同一存储 |
|
|
340
|
+
| 可扩展性 | 优秀。新增特性只需改 CredentialStore |
|
|
341
|
+
|
|
342
|
+
### 7.3 量化影响评估
|
|
343
|
+
|
|
344
|
+
| 指标 | 现状(不共用) | 共用(方案 A) | 变化 |
|
|
345
|
+
|------|--------------|---------------|------|
|
|
346
|
+
| 用户登录次数 | 2 次 | 1 次 | -50% |
|
|
347
|
+
| 存储相关代码行数 | ~400 行 | ~165 行 | -59% |
|
|
348
|
+
| 存储文件数 | 2 个 | 1 个 | -50% |
|
|
349
|
+
| MCP 单次读凭证耗时 | ~0.1ms | ~0.1ms(内存缓存) | 持平 |
|
|
350
|
+
| 安全合规性 | 部分明文 | 全部加密 | 提升 |
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## 八、风险与回滚策略
|
|
355
|
+
|
|
356
|
+
### 8.1 主要风险
|
|
357
|
+
|
|
358
|
+
| 风险 | 影响 | 缓解措施 |
|
|
359
|
+
|------|------|----------|
|
|
360
|
+
| 用户凭证丢失 | 迁移脚本 bug | 迁移前备份;保留 mcp_sessions.json 作为 fallback |
|
|
361
|
+
| MCP 性能退化 | 无缓存时 AES 解密延迟 | Phase 2 必须同步实施内存缓存;压测验证 QPS |
|
|
362
|
+
| Session 语义冲突 | 业务依赖按系统存 session | 审计所有 SessionStore 调用点 |
|
|
363
|
+
| Breaking Change | 第三方依赖 mcp_sessions.json | 保留旧文件兼容 1 个版本 |
|
|
364
|
+
|
|
365
|
+
### 8.2 回滚策略
|
|
366
|
+
|
|
367
|
+
1. **实施前**:完整备份 `~/.config/opscli/` 目录
|
|
368
|
+
2. **灰度验证**:先在开发/测试环境验证
|
|
369
|
+
3. **快速回滚**:保留 `mcp_sessions.json` 至少一个版本周期
|
|
370
|
+
4. **监控指标**:上线后监控 MCP Tool 平均响应时间,若增长 > 20% 触发回滚
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## 九、结论
|
|
375
|
+
|
|
376
|
+
opscli CLI 与 MCP 模式的 Session 不能共用,是**存储层物理隔离、DeviceFlow 耦合设计、Session 语义冲突、多用户隔离未打通、安全性能权衡差异**五重因素叠加的结果。
|
|
377
|
+
|
|
378
|
+
**推荐立即执行方案 A(MCP 复用 CredentialStore + 内存缓存)**,理由如下:
|
|
379
|
+
1. 用户体验提升 50%:消除重复授权
|
|
380
|
+
2. 维护成本降低 59%:消除 SessionStore 的 237 行重复代码
|
|
381
|
+
3. 安全水位统一:MCP 凭证从明文升级为 AES-256-GCM / Keychain 保护
|
|
382
|
+
4. 性能影响可控:通过内存缓存将高频读操作延迟维持在 ~0.1ms 级别
|
|
383
|
+
5. 实施周期短:预计 3-5 个工作日可完成
|
|
384
|
+
|
|
385
|
+
长期演进方向上,可在方案 A 的基础上逐步向**方案 C(统一存储抽象层)**过渡。
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
*报告结束*
|