agentnode-sdk 0.22.0__tar.gz → 0.23.0__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.
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/CHANGELOG.md +20 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/PKG-INFO +1 -1
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/__init__.py +1 -1
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/main.py +28 -0
- agentnode_sdk-0.23.0/agentnode_sdk/cli/mcp_ownership.py +207 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/pyproject.toml +1 -1
- agentnode_sdk-0.23.0/tests/test_mcp_ownership_cli.py +243 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/.env.example +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/.gitignore +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/README.md +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/REGISTRY_SIGNING_ACTIVATION.md +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/REGISTRY_SIGNING_SPEC.md +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/THREAT_MODEL.md +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/TRUST_STACK.md +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode.lock +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/_fileutil.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/async_client.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/capability_graph.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/capability_taxonomy.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/__init__.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/__main__.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/audit.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/auth.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/cassette_audit.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/commands.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/complements.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/init.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/mcp_commands.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/mcp_status.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/mcp_submit.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/mcp_verify.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/output.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/publish.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/record_cases.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/sandbox_commands.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/serve.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/setup_wizard.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/smart_run.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/templates.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/validate.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/verify_local.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/client.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/compatibility.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/config.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/credential_handle.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/credential_resolver.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/credential_store.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/detect.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/exceptions.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/guard.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/input_guard.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/installer.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/key_status.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/llm_providers.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/lock_integrity.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/mcp_server.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/models.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/planner.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/policy.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/references.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/registry_trust.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/resolve.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/resource_provider.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/risk_profile.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/run_log.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runner.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtime.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/__init__.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/agent_llm_broker.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/agent_llm_policy.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/agent_runner.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/agent_sandbox.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/mcp_consent.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/mcp_consent_store.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/mcp_runner.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/python_runner.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/remote_runner.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/toolpack_credentials.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/__init__.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/agent_container_wrapper.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/agent_rpc.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/agent_session.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/backend.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/container_backend.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/domain_policy.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/egress.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/egress_proxy.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/mcp_preinstall.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/policy.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/types.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/signature.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/signing_key.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/skill.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/data/compatibility/current.json +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/data/compatibility/removed_2026-07-09.txt +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/dist-0.18.0/.gitignore +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/dist-0.19.0/.gitignore +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/sandbox-image/Dockerfile +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/sandbox-image/README.md +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/scripts/analyze_scores.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/scripts/batch_verify.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/scripts/check_model_drift.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/scripts/ci_smoke_test.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/scripts/generate_compatibility_artifacts.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/scripts/merge_batches.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/scripts/verify_toolcalls.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/scripts/weekly_retest.sh +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/spikes/agent_sandbox_routing/README.md +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/spikes/agent_sandbox_routing/__init__.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/spikes/agent_sandbox_routing/container_agent_wrapper.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/spikes/agent_sandbox_routing/fake_llm.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/spikes/agent_sandbox_routing/host_driver.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/spikes/agent_sandbox_routing/trivial_agent.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/__init__.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/conftest.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/data/allowed_domains_vectors.json +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_llm_broker.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_llm_policy.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_rpc.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_runner.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_sandbox_e2e.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_sandbox_hardening.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_sandbox_routing.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_sandbox_spike.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_session_container.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_async_client.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_audit_ux.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_auth_cli_vault.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_auto_upgrade_policy.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_cli.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_cli_lock.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_cli_run_resolution.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_client.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_client_json_guard.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_client_sprint_b.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_config.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_consent_prompt.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_credential_handle.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_credential_integration.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_credential_resolver.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_credential_store.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_credential_vault.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_detect.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_detect_and_install.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_domain_policy.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_e2e_runtime.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_edge_cases.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_egress_domain_validation.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_egress_e2e.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_egress_lifecycle.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_egress_proxy_unit.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_egress_wrap.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_env_passthrough_wrap.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_check.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_config_cache.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_policy.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_preview.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_schema.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_set.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_status.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_tool_override.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_tool_override_audit.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_tool_override_cli.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_ux.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_host_trust_policy.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_host_trust_policy_routing.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_input_guard.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_input_guard_escalation.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_install_hardening.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_installer_sprint_b.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_intelligence.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_key_status.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_llm_binding.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_llm_call_runlog.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_llm_providers.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_llm_vault_binding.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_lock_integrity.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_lock_runtime.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_allowed_domains_seal.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_audit.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_cli_scaffold.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_consent.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_consent_resolver.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_consent_store.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_credentialed_launch.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_doctor.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_preinstall.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_preinstall_run.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_sandbox.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_server.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_submit.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_observability.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_planner.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_policy.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_policy_integration.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_prompt_specs.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_provider_matrix.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_publish.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_references.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_registry_trust.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_remote_hardening.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_remote_runner.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_resource_provider.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_resource_specs.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_risk_profile.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_run_log.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_runner.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_runtime.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_runtime_audit.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_sandbox_backend.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_sandbox_doctor.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_sandbox_e2e.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_sandbox_gate.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_security_hardening.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_setup_wizard.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_signature.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_signing_key.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_skill.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_smart.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_stability.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_toolpack_credentials.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_toolpack_credentials_b.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_toolpack_sandbox.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_v02.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_validate.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_validate_skill.py +0 -0
- {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/validation_lockfile.json +0 -0
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.23.0 — Prove MCP package ownership from the terminal
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `agentnode mcp ownership challenge --registry npm|pypi <package>` issues a
|
|
8
|
+
one-time challenge: the server returns a token and a keyword
|
|
9
|
+
(`agentnode-ownership-<token>`). Add the keyword to your package's metadata
|
|
10
|
+
and publish a new version — only someone with publish rights can do that. The
|
|
11
|
+
token is shown once and is stored server-side only as a hash; it is never
|
|
12
|
+
written to local config.
|
|
13
|
+
- `agentnode mcp ownership verify --registry npm|pypi <package>` checks the
|
|
14
|
+
latest published version for the keyword and, on a match, records strong
|
|
15
|
+
ownership evidence. It reports `verified` / `pending` / expired / no-challenge
|
|
16
|
+
/ package-not-found / registry-unavailable clearly, and exits non-zero until
|
|
17
|
+
ownership is verified (CI-gateable).
|
|
18
|
+
|
|
19
|
+
Verifying ownership does **not** publish anything: MCP listings still remain
|
|
20
|
+
review-gated until the sandbox-smoke gate is built. The same flow is available
|
|
21
|
+
in the browser on `/mcp/submit`.
|
|
22
|
+
|
|
3
23
|
## 0.22.0 — MCP from the terminal + refreshed compatibility
|
|
4
24
|
|
|
5
25
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentnode-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.23.0
|
|
4
4
|
Summary: Python SDK for AgentNode — the open upgrade and discovery infrastructure for AI agents.
|
|
5
5
|
Project-URL: Homepage, https://agentnode.net
|
|
6
6
|
Project-URL: Repository, https://github.com/agentnode-ai/agentnode
|
|
@@ -61,6 +61,19 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
61
61
|
mcp_revoke_p.add_argument("--key", default=None, help="Revoke only this consent_key")
|
|
62
62
|
mcp_grants_p = mcp_sub.add_parser("grants", help="List stored consent grants (metadata only)")
|
|
63
63
|
mcp_grants_p.add_argument("--json", dest="json_output", action="store_true", help="JSON output")
|
|
64
|
+
# Slice 2b-2: prove package ownership via a publish-challenge (npm/PyPI).
|
|
65
|
+
mcp_own_p = mcp_sub.add_parser("ownership", help="Prove package ownership (publish-challenge)")
|
|
66
|
+
mcp_own_sub = mcp_own_p.add_subparsers(dest="ownership_command")
|
|
67
|
+
mcp_own_ch = mcp_own_sub.add_parser("challenge", help="Issue a one-time publish-challenge token")
|
|
68
|
+
mcp_own_ch.add_argument("package", help="Package name on the registry")
|
|
69
|
+
mcp_own_ch.add_argument("--registry", required=True, choices=["npm", "pypi"], help="Package registry")
|
|
70
|
+
mcp_own_ch.add_argument("--token", default=None, help="API key (default: AGENTNODE_API_KEY)")
|
|
71
|
+
mcp_own_ch.add_argument("--json", dest="json_output", action="store_true", help="JSON output")
|
|
72
|
+
mcp_own_vf = mcp_own_sub.add_parser("verify", help="Verify a pending publish-challenge")
|
|
73
|
+
mcp_own_vf.add_argument("package", help="Package name on the registry")
|
|
74
|
+
mcp_own_vf.add_argument("--registry", required=True, choices=["npm", "pypi"], help="Package registry")
|
|
75
|
+
mcp_own_vf.add_argument("--token", default=None, help="API key (default: AGENTNODE_API_KEY)")
|
|
76
|
+
mcp_own_vf.add_argument("--json", dest="json_output", action="store_true", help="JSON output")
|
|
64
77
|
|
|
65
78
|
# reset
|
|
66
79
|
sub.add_parser("reset", help="Reset configuration")
|
|
@@ -268,6 +281,21 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
268
281
|
if args.mcp_command == "grants":
|
|
269
282
|
from agentnode_sdk.cli.mcp_commands import cmd_mcp_grants
|
|
270
283
|
return cmd_mcp_grants(json_output=args.json_output)
|
|
284
|
+
if args.mcp_command == "ownership":
|
|
285
|
+
from agentnode_sdk.cli.mcp_ownership import (
|
|
286
|
+
cmd_mcp_ownership_challenge,
|
|
287
|
+
cmd_mcp_ownership_verify,
|
|
288
|
+
)
|
|
289
|
+
if args.ownership_command == "challenge":
|
|
290
|
+
return cmd_mcp_ownership_challenge(
|
|
291
|
+
args.registry, args.package, token=args.token, json_output=args.json_output
|
|
292
|
+
)
|
|
293
|
+
if args.ownership_command == "verify":
|
|
294
|
+
return cmd_mcp_ownership_verify(
|
|
295
|
+
args.registry, args.package, token=args.token, json_output=args.json_output
|
|
296
|
+
)
|
|
297
|
+
mcp_own_p.print_help()
|
|
298
|
+
return 0
|
|
271
299
|
mcp_parser.print_help()
|
|
272
300
|
return 0
|
|
273
301
|
if args.command == "sandbox":
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Publish-challenge ownership for MCP packages (Slice 2b-2 UX).
|
|
2
|
+
|
|
3
|
+
Two verbs let a publisher prove they control an npm/PyPI package:
|
|
4
|
+
|
|
5
|
+
agentnode mcp ownership challenge --registry npm|pypi <package>
|
|
6
|
+
agentnode mcp ownership verify --registry npm|pypi <package>
|
|
7
|
+
|
|
8
|
+
`challenge` asks the backend for a one-time token + keyword; the publisher adds
|
|
9
|
+
the keyword to their package's metadata and publishes a new version. `verify`
|
|
10
|
+
then asks the backend to look at the latest published version and confirm the
|
|
11
|
+
keyword -- only someone with publish rights can put it there, so a match is
|
|
12
|
+
strong ownership evidence.
|
|
13
|
+
|
|
14
|
+
The token is never written to disk here -- it is shown once, straight from the
|
|
15
|
+
API response. Verifying ownership does NOT publish anything: MCP listings stay
|
|
16
|
+
review-gated until the sandbox-smoke gate is built.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
import httpx
|
|
25
|
+
|
|
26
|
+
_REVIEW_NOTE = (
|
|
27
|
+
" Ownership can be verified, but MCP listings still remain review-gated\n"
|
|
28
|
+
" until the sandbox-smoke gate is built."
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _resolve_api_base() -> str:
|
|
33
|
+
from agentnode_sdk.client import DEFAULT_BASE_URL
|
|
34
|
+
return os.environ.get("AGENTNODE_API_URL", DEFAULT_BASE_URL).rstrip("/")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _api_key(token: str | None) -> str:
|
|
38
|
+
return token or os.environ.get("AGENTNODE_API_KEY", "")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _no_key() -> int:
|
|
42
|
+
print(
|
|
43
|
+
" Error: No AgentNode API key configured.\n"
|
|
44
|
+
" Set AGENTNODE_API_KEY or pass --token <key>.",
|
|
45
|
+
file=sys.stderr,
|
|
46
|
+
)
|
|
47
|
+
return 1
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _handle_error(resp: httpx.Response, json_output: bool) -> int:
|
|
51
|
+
"""Print a clear error for a non-2xx response and return 1.
|
|
52
|
+
|
|
53
|
+
Backend error messages are already user-facing (expired challenge, no
|
|
54
|
+
challenge, package not found, registry unavailable), so we surface them
|
|
55
|
+
directly rather than re-inventing copy per status code.
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
err = resp.json()
|
|
59
|
+
code = err.get("code", "")
|
|
60
|
+
message = err.get("message", resp.text)
|
|
61
|
+
except Exception:
|
|
62
|
+
err = None
|
|
63
|
+
code = ""
|
|
64
|
+
message = resp.text
|
|
65
|
+
|
|
66
|
+
if json_output and err is not None:
|
|
67
|
+
print(json.dumps(err, indent=2))
|
|
68
|
+
return 1
|
|
69
|
+
|
|
70
|
+
if resp.status_code == 401:
|
|
71
|
+
print(" Error: Authentication failed. Check your API key.", file=sys.stderr)
|
|
72
|
+
elif resp.status_code == 403:
|
|
73
|
+
if "PUBLISHER" in code:
|
|
74
|
+
print(" Error: You need a publisher profile.", file=sys.stderr)
|
|
75
|
+
print(" Create one at https://agentnode.net/dashboard", file=sys.stderr)
|
|
76
|
+
else:
|
|
77
|
+
print(f" Error: {message}", file=sys.stderr)
|
|
78
|
+
else:
|
|
79
|
+
print(f" Error ({resp.status_code}): {message}", file=sys.stderr)
|
|
80
|
+
return 1
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def cmd_mcp_ownership_challenge(
|
|
84
|
+
registry: str,
|
|
85
|
+
package: str,
|
|
86
|
+
*,
|
|
87
|
+
token: str | None = None,
|
|
88
|
+
json_output: bool = False,
|
|
89
|
+
) -> int:
|
|
90
|
+
"""Issue a one-time publish-challenge for a package."""
|
|
91
|
+
api_key = _api_key(token)
|
|
92
|
+
if not api_key:
|
|
93
|
+
return _no_key()
|
|
94
|
+
|
|
95
|
+
registry = (registry or "").strip().lower()
|
|
96
|
+
url = f"{_resolve_api_base()}/v1/mcp/ownership/challenge"
|
|
97
|
+
try:
|
|
98
|
+
resp = httpx.post(
|
|
99
|
+
url,
|
|
100
|
+
json={"registry": registry, "package_name": package},
|
|
101
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
102
|
+
timeout=30,
|
|
103
|
+
)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
print(f" Error: Connection failed: {e}", file=sys.stderr)
|
|
106
|
+
return 1
|
|
107
|
+
|
|
108
|
+
if resp.status_code != 200:
|
|
109
|
+
return _handle_error(resp, json_output)
|
|
110
|
+
|
|
111
|
+
data = resp.json()
|
|
112
|
+
if json_output:
|
|
113
|
+
print(json.dumps(data, indent=2))
|
|
114
|
+
return 0
|
|
115
|
+
|
|
116
|
+
keyword = data.get("keyword", "")
|
|
117
|
+
tok = data.get("token", "")
|
|
118
|
+
expires = (data.get("expires_at") or "")[:10]
|
|
119
|
+
verify_cmd = f"agentnode mcp ownership verify --registry {registry} {package}"
|
|
120
|
+
|
|
121
|
+
print()
|
|
122
|
+
print(" AgentNode Ownership Challenge")
|
|
123
|
+
print(" " + "-" * 29)
|
|
124
|
+
print(f" Registry: {registry}")
|
|
125
|
+
print(f" Package: {package}")
|
|
126
|
+
print()
|
|
127
|
+
print(" Prove package ownership by publishing this keyword in your package")
|
|
128
|
+
print(" metadata. Only someone with publish rights can do that.")
|
|
129
|
+
print()
|
|
130
|
+
print(f" Token (shown once): {tok}")
|
|
131
|
+
print(f" Keyword: {keyword}")
|
|
132
|
+
if expires:
|
|
133
|
+
print(f" Expires: {expires}")
|
|
134
|
+
print()
|
|
135
|
+
print(" Next steps:")
|
|
136
|
+
print(f' 1. Add "{keyword}" to your package\'s keywords.')
|
|
137
|
+
print(f" 2. Publish a new version to {registry}.")
|
|
138
|
+
print(f" 3. Run: {verify_cmd}")
|
|
139
|
+
print()
|
|
140
|
+
print(" The token is shown only once and is stored only as a hash. If you")
|
|
141
|
+
print(" lose it, just issue a new challenge -- that replaces the old one.")
|
|
142
|
+
print()
|
|
143
|
+
print(_REVIEW_NOTE)
|
|
144
|
+
print()
|
|
145
|
+
return 0
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def cmd_mcp_ownership_verify(
|
|
149
|
+
registry: str,
|
|
150
|
+
package: str,
|
|
151
|
+
*,
|
|
152
|
+
token: str | None = None,
|
|
153
|
+
json_output: bool = False,
|
|
154
|
+
) -> int:
|
|
155
|
+
"""Verify a pending publish-challenge against the published package."""
|
|
156
|
+
api_key = _api_key(token)
|
|
157
|
+
if not api_key:
|
|
158
|
+
return _no_key()
|
|
159
|
+
|
|
160
|
+
registry = (registry or "").strip().lower()
|
|
161
|
+
url = f"{_resolve_api_base()}/v1/mcp/ownership/challenge/verify"
|
|
162
|
+
try:
|
|
163
|
+
resp = httpx.post(
|
|
164
|
+
url,
|
|
165
|
+
json={"registry": registry, "package_name": package},
|
|
166
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
167
|
+
timeout=30,
|
|
168
|
+
)
|
|
169
|
+
except Exception as e:
|
|
170
|
+
print(f" Error: Connection failed: {e}", file=sys.stderr)
|
|
171
|
+
return 1
|
|
172
|
+
|
|
173
|
+
if resp.status_code != 200:
|
|
174
|
+
return _handle_error(resp, json_output)
|
|
175
|
+
|
|
176
|
+
data = resp.json()
|
|
177
|
+
verified = bool(data.get("verified"))
|
|
178
|
+
if json_output:
|
|
179
|
+
print(json.dumps(data, indent=2))
|
|
180
|
+
return 0 if verified else 1
|
|
181
|
+
|
|
182
|
+
status = data.get("status", "?")
|
|
183
|
+
message = data.get("message", "")
|
|
184
|
+
version = data.get("version")
|
|
185
|
+
|
|
186
|
+
print()
|
|
187
|
+
print(" AgentNode Ownership Verify")
|
|
188
|
+
print(" " + "-" * 26)
|
|
189
|
+
print(f" Registry: {registry}")
|
|
190
|
+
print(f" Package: {package}")
|
|
191
|
+
print(f" Status: {status}")
|
|
192
|
+
if version:
|
|
193
|
+
print(f" Version: {version}")
|
|
194
|
+
print()
|
|
195
|
+
if message:
|
|
196
|
+
print(f" {message}")
|
|
197
|
+
if verified:
|
|
198
|
+
print(" Strong ownership evidence recorded for this package.")
|
|
199
|
+
else:
|
|
200
|
+
print(
|
|
201
|
+
f" Not verified yet. Publish a version carrying the challenge keyword, "
|
|
202
|
+
f"then run:\n agentnode mcp ownership verify --registry {registry} {package}"
|
|
203
|
+
)
|
|
204
|
+
print()
|
|
205
|
+
print(_REVIEW_NOTE)
|
|
206
|
+
print()
|
|
207
|
+
return 0 if verified else 1
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentnode-sdk"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.23.0"
|
|
8
8
|
description = "Python SDK for AgentNode — the open upgrade and discovery infrastructure for AI agents."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""Tests for the `agentnode mcp ownership challenge|verify` CLI (Slice 2b-2 UX).
|
|
2
|
+
|
|
3
|
+
The two verbs drive the live publish-challenge endpoints. These tests mock httpx
|
|
4
|
+
at the CLI boundary — no real network / registry call — and assert the parser
|
|
5
|
+
wiring, auth/URL handling, human output, and every error case. They also pin the
|
|
6
|
+
honesty line: the CLI never claims auto-publish.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from unittest.mock import MagicMock, patch
|
|
11
|
+
|
|
12
|
+
from agentnode_sdk.cli import mcp_ownership
|
|
13
|
+
from agentnode_sdk.cli.main import main
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _resp(status_code: int, payload: dict) -> MagicMock:
|
|
17
|
+
r = MagicMock()
|
|
18
|
+
r.status_code = status_code
|
|
19
|
+
r.json.return_value = payload
|
|
20
|
+
r.text = str(payload)
|
|
21
|
+
return r
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# --- parser wiring -----------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_parser_routes_challenge(monkeypatch):
|
|
28
|
+
monkeypatch.setenv("AGENTNODE_API_KEY", "key-1")
|
|
29
|
+
with patch(
|
|
30
|
+
"agentnode_sdk.cli.mcp_ownership.cmd_mcp_ownership_challenge", return_value=0
|
|
31
|
+
) as fn:
|
|
32
|
+
rc = main(["mcp", "ownership", "challenge", "--registry", "npm", "my-mcp"])
|
|
33
|
+
assert rc == 0
|
|
34
|
+
fn.assert_called_once()
|
|
35
|
+
args, kwargs = fn.call_args
|
|
36
|
+
assert args[0] == "npm" and args[1] == "my-mcp"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_parser_routes_verify(monkeypatch):
|
|
40
|
+
monkeypatch.setenv("AGENTNODE_API_KEY", "key-1")
|
|
41
|
+
with patch(
|
|
42
|
+
"agentnode_sdk.cli.mcp_ownership.cmd_mcp_ownership_verify", return_value=0
|
|
43
|
+
) as fn:
|
|
44
|
+
rc = main(["mcp", "ownership", "verify", "--registry", "pypi", "my-pkg"])
|
|
45
|
+
assert rc == 0
|
|
46
|
+
args, _ = fn.call_args
|
|
47
|
+
assert args[0] == "pypi" and args[1] == "my-pkg"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_invalid_registry_rejected_by_parser(monkeypatch):
|
|
51
|
+
# argparse choices=[npm,pypi] -> exits before the handler runs.
|
|
52
|
+
monkeypatch.setenv("AGENTNODE_API_KEY", "key-1")
|
|
53
|
+
try:
|
|
54
|
+
main(["mcp", "ownership", "challenge", "--registry", "cargo", "x"])
|
|
55
|
+
raise AssertionError("expected SystemExit for an invalid registry")
|
|
56
|
+
except SystemExit as e:
|
|
57
|
+
assert e.code == 2
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# --- auth / URL --------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_challenge_requires_api_key(monkeypatch, capsys):
|
|
64
|
+
monkeypatch.delenv("AGENTNODE_API_KEY", raising=False)
|
|
65
|
+
rc = mcp_ownership.cmd_mcp_ownership_challenge("npm", "x", token=None)
|
|
66
|
+
assert rc == 1
|
|
67
|
+
assert "api key" in capsys.readouterr().err.lower()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_challenge_uses_api_url_and_bearer(monkeypatch):
|
|
71
|
+
monkeypatch.setenv("AGENTNODE_API_URL", "https://api.example.test")
|
|
72
|
+
ok = _resp(
|
|
73
|
+
200,
|
|
74
|
+
{
|
|
75
|
+
"token": "tok-secret",
|
|
76
|
+
"keyword": "agentnode-ownership-tok-secret",
|
|
77
|
+
"registry": "npm",
|
|
78
|
+
"package_name": "my-mcp",
|
|
79
|
+
"expires_at": "2026-08-09T10:00:00+00:00",
|
|
80
|
+
"instructions": "...",
|
|
81
|
+
},
|
|
82
|
+
)
|
|
83
|
+
with patch("agentnode_sdk.cli.mcp_ownership.httpx.post", return_value=ok) as post:
|
|
84
|
+
rc = mcp_ownership.cmd_mcp_ownership_challenge("npm", "my-mcp", token="key-9")
|
|
85
|
+
assert rc == 0
|
|
86
|
+
url = post.call_args.args[0]
|
|
87
|
+
assert url == "https://api.example.test/v1/mcp/ownership/challenge"
|
|
88
|
+
headers = post.call_args.kwargs["headers"]
|
|
89
|
+
assert headers["Authorization"] == "Bearer key-9"
|
|
90
|
+
body = post.call_args.kwargs["json"]
|
|
91
|
+
assert body == {"registry": "npm", "package_name": "my-mcp"}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# --- challenge output --------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_challenge_success_shows_token_keyword_instructions(capsys):
|
|
98
|
+
ok = _resp(
|
|
99
|
+
200,
|
|
100
|
+
{
|
|
101
|
+
"token": "tok-secret",
|
|
102
|
+
"keyword": "agentnode-ownership-tok-secret",
|
|
103
|
+
"registry": "npm",
|
|
104
|
+
"package_name": "my-mcp",
|
|
105
|
+
"expires_at": "2026-08-09T10:00:00+00:00",
|
|
106
|
+
"instructions": "...",
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
with patch("agentnode_sdk.cli.mcp_ownership.httpx.post", return_value=ok):
|
|
110
|
+
rc = mcp_ownership.cmd_mcp_ownership_challenge("npm", "my-mcp", token="key-9")
|
|
111
|
+
assert rc == 0
|
|
112
|
+
out = capsys.readouterr().out
|
|
113
|
+
assert "tok-secret" in out # token shown
|
|
114
|
+
assert "agentnode-ownership-tok-secret" in out # keyword shown
|
|
115
|
+
assert "shown once" in out.lower()
|
|
116
|
+
assert "verify --registry npm my-mcp" in out
|
|
117
|
+
assert "2026-08-09" in out # expiry (date only)
|
|
118
|
+
# honesty line, no auto-publish claim
|
|
119
|
+
assert "review-gated" in out.lower()
|
|
120
|
+
for bad in ("auto-publish", "publishes automatically", "goes live after verify"):
|
|
121
|
+
assert bad not in out.lower()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_challenge_json_output(capsys):
|
|
125
|
+
ok = _resp(
|
|
126
|
+
200,
|
|
127
|
+
{"token": "t", "keyword": "agentnode-ownership-t", "registry": "npm",
|
|
128
|
+
"package_name": "p", "expires_at": "2026-08-09T00:00:00+00:00", "instructions": "x"},
|
|
129
|
+
)
|
|
130
|
+
with patch("agentnode_sdk.cli.mcp_ownership.httpx.post", return_value=ok):
|
|
131
|
+
rc = mcp_ownership.cmd_mcp_ownership_challenge(
|
|
132
|
+
"npm", "p", token="k", json_output=True
|
|
133
|
+
)
|
|
134
|
+
assert rc == 0
|
|
135
|
+
import json
|
|
136
|
+
|
|
137
|
+
parsed = json.loads(capsys.readouterr().out)
|
|
138
|
+
assert parsed["keyword"] == "agentnode-ownership-t"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# --- verify output -----------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_verify_success_is_strong_and_exit_zero(capsys):
|
|
145
|
+
ok = _resp(
|
|
146
|
+
200,
|
|
147
|
+
{
|
|
148
|
+
"verified": True,
|
|
149
|
+
"status": "verified",
|
|
150
|
+
"message": "Ownership verified via a published version — strong evidence.",
|
|
151
|
+
"version": "1.1.0",
|
|
152
|
+
},
|
|
153
|
+
)
|
|
154
|
+
with patch("agentnode_sdk.cli.mcp_ownership.httpx.post", return_value=ok):
|
|
155
|
+
rc = mcp_ownership.cmd_mcp_ownership_verify("npm", "my-mcp", token="key-9")
|
|
156
|
+
assert rc == 0
|
|
157
|
+
out = capsys.readouterr().out
|
|
158
|
+
assert "verified" in out.lower()
|
|
159
|
+
assert "1.1.0" in out
|
|
160
|
+
assert "strong ownership evidence" in out.lower()
|
|
161
|
+
assert "review-gated" in out.lower()
|
|
162
|
+
assert "auto-publish" not in out.lower()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_verify_pending_tells_user_to_publish_and_retry(capsys):
|
|
166
|
+
pending = _resp(
|
|
167
|
+
200,
|
|
168
|
+
{
|
|
169
|
+
"verified": False,
|
|
170
|
+
"status": "pending",
|
|
171
|
+
"message": (
|
|
172
|
+
"Token not found in the latest published version's keywords yet. "
|
|
173
|
+
"Publish a version with the keyword, then verify again."
|
|
174
|
+
),
|
|
175
|
+
},
|
|
176
|
+
)
|
|
177
|
+
with patch("agentnode_sdk.cli.mcp_ownership.httpx.post", return_value=pending):
|
|
178
|
+
rc = mcp_ownership.cmd_mcp_ownership_verify("npm", "my-mcp", token="key-9")
|
|
179
|
+
# not verified -> non-zero, so CI can gate on it
|
|
180
|
+
assert rc == 1
|
|
181
|
+
out = capsys.readouterr().out
|
|
182
|
+
assert "verify again" in out.lower()
|
|
183
|
+
assert "not verified yet" in out.lower()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# --- error cases -------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def test_verify_no_challenge_404(capsys):
|
|
190
|
+
err = _resp(
|
|
191
|
+
404, {"code": "MCP_NO_CHALLENGE", "message": "No publish-challenge to verify — issue one first."}
|
|
192
|
+
)
|
|
193
|
+
with patch("agentnode_sdk.cli.mcp_ownership.httpx.post", return_value=err):
|
|
194
|
+
rc = mcp_ownership.cmd_mcp_ownership_verify("npm", "x", token="k")
|
|
195
|
+
assert rc == 1
|
|
196
|
+
assert "issue one first" in capsys.readouterr().err.lower()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_verify_package_not_found_404(capsys):
|
|
200
|
+
err = _resp(
|
|
201
|
+
404, {"code": "MCP_PACKAGE_NOT_FOUND", "message": "Package 'x' not found on npm."}
|
|
202
|
+
)
|
|
203
|
+
with patch("agentnode_sdk.cli.mcp_ownership.httpx.post", return_value=err):
|
|
204
|
+
rc = mcp_ownership.cmd_mcp_ownership_verify("npm", "x", token="k")
|
|
205
|
+
assert rc == 1
|
|
206
|
+
assert "not found on npm" in capsys.readouterr().err.lower()
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def test_verify_expired_400(capsys):
|
|
210
|
+
err = _resp(
|
|
211
|
+
400, {"code": "MCP_CHALLENGE_EXPIRED", "message": "The challenge has expired — issue a new one."}
|
|
212
|
+
)
|
|
213
|
+
with patch("agentnode_sdk.cli.mcp_ownership.httpx.post", return_value=err):
|
|
214
|
+
rc = mcp_ownership.cmd_mcp_ownership_verify("npm", "x", token="k")
|
|
215
|
+
assert rc == 1
|
|
216
|
+
assert "expired" in capsys.readouterr().err.lower()
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_verify_registry_unavailable_503(capsys):
|
|
220
|
+
err = _resp(
|
|
221
|
+
503,
|
|
222
|
+
{"code": "MCP_REGISTRY_UNAVAILABLE", "message": "The registry is temporarily unavailable — try verify again shortly."},
|
|
223
|
+
)
|
|
224
|
+
with patch("agentnode_sdk.cli.mcp_ownership.httpx.post", return_value=err):
|
|
225
|
+
rc = mcp_ownership.cmd_mcp_ownership_verify("npm", "x", token="k")
|
|
226
|
+
assert rc == 1
|
|
227
|
+
assert "temporarily unavailable" in capsys.readouterr().err.lower()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_challenge_401_auth_failed(capsys):
|
|
231
|
+
err = _resp(401, {"code": "UNAUTHORIZED", "message": "bad token"})
|
|
232
|
+
with patch("agentnode_sdk.cli.mcp_ownership.httpx.post", return_value=err):
|
|
233
|
+
rc = mcp_ownership.cmd_mcp_ownership_challenge("npm", "x", token="k")
|
|
234
|
+
assert rc == 1
|
|
235
|
+
assert "authentication failed" in capsys.readouterr().err.lower()
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def test_challenge_403_needs_publisher(capsys):
|
|
239
|
+
err = _resp(403, {"code": "PUBLISHER_REQUIRED", "message": "publisher profile required"})
|
|
240
|
+
with patch("agentnode_sdk.cli.mcp_ownership.httpx.post", return_value=err):
|
|
241
|
+
rc = mcp_ownership.cmd_mcp_ownership_challenge("npm", "x", token="k")
|
|
242
|
+
assert rc == 1
|
|
243
|
+
assert "publisher profile" in capsys.readouterr().err.lower()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|