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.
Files changed (228) hide show
  1. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/CHANGELOG.md +20 -0
  2. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/PKG-INFO +1 -1
  3. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/__init__.py +1 -1
  4. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/main.py +28 -0
  5. agentnode_sdk-0.23.0/agentnode_sdk/cli/mcp_ownership.py +207 -0
  6. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/pyproject.toml +1 -1
  7. agentnode_sdk-0.23.0/tests/test_mcp_ownership_cli.py +243 -0
  8. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/.env.example +0 -0
  9. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/.gitignore +0 -0
  10. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/README.md +0 -0
  11. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/REGISTRY_SIGNING_ACTIVATION.md +0 -0
  12. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/REGISTRY_SIGNING_SPEC.md +0 -0
  13. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/THREAT_MODEL.md +0 -0
  14. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/TRUST_STACK.md +0 -0
  15. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode.lock +0 -0
  16. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/_fileutil.py +0 -0
  17. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/async_client.py +0 -0
  18. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/capability_graph.py +0 -0
  19. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/capability_taxonomy.py +0 -0
  20. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/__init__.py +0 -0
  21. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/__main__.py +0 -0
  22. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/audit.py +0 -0
  23. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/auth.py +0 -0
  24. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/cassette_audit.py +0 -0
  25. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/commands.py +0 -0
  26. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/complements.py +0 -0
  27. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/init.py +0 -0
  28. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/mcp_commands.py +0 -0
  29. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/mcp_status.py +0 -0
  30. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/mcp_submit.py +0 -0
  31. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/mcp_verify.py +0 -0
  32. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/output.py +0 -0
  33. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/publish.py +0 -0
  34. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/record_cases.py +0 -0
  35. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/sandbox_commands.py +0 -0
  36. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/serve.py +0 -0
  37. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/setup_wizard.py +0 -0
  38. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/smart_run.py +0 -0
  39. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/templates.py +0 -0
  40. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/validate.py +0 -0
  41. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/cli/verify_local.py +0 -0
  42. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/client.py +0 -0
  43. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/compatibility.py +0 -0
  44. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/config.py +0 -0
  45. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/credential_handle.py +0 -0
  46. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/credential_resolver.py +0 -0
  47. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/credential_store.py +0 -0
  48. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/detect.py +0 -0
  49. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/exceptions.py +0 -0
  50. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/guard.py +0 -0
  51. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/input_guard.py +0 -0
  52. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/installer.py +0 -0
  53. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/key_status.py +0 -0
  54. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/llm_providers.py +0 -0
  55. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/lock_integrity.py +0 -0
  56. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/mcp_server.py +0 -0
  57. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/models.py +0 -0
  58. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/planner.py +0 -0
  59. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/policy.py +0 -0
  60. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/references.py +0 -0
  61. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/registry_trust.py +0 -0
  62. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/resolve.py +0 -0
  63. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/resource_provider.py +0 -0
  64. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/risk_profile.py +0 -0
  65. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/run_log.py +0 -0
  66. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runner.py +0 -0
  67. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtime.py +0 -0
  68. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/__init__.py +0 -0
  69. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/agent_llm_broker.py +0 -0
  70. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/agent_llm_policy.py +0 -0
  71. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/agent_runner.py +0 -0
  72. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/agent_sandbox.py +0 -0
  73. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/mcp_consent.py +0 -0
  74. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/mcp_consent_store.py +0 -0
  75. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/mcp_runner.py +0 -0
  76. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/python_runner.py +0 -0
  77. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/remote_runner.py +0 -0
  78. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/runtimes/toolpack_credentials.py +0 -0
  79. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/__init__.py +0 -0
  80. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/agent_container_wrapper.py +0 -0
  81. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/agent_rpc.py +0 -0
  82. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/agent_session.py +0 -0
  83. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/backend.py +0 -0
  84. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/container_backend.py +0 -0
  85. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/domain_policy.py +0 -0
  86. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/egress.py +0 -0
  87. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/egress_proxy.py +0 -0
  88. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/mcp_preinstall.py +0 -0
  89. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/policy.py +0 -0
  90. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/sandbox/types.py +0 -0
  91. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/signature.py +0 -0
  92. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/signing_key.py +0 -0
  93. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/agentnode_sdk/skill.py +0 -0
  94. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/data/compatibility/current.json +0 -0
  95. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/data/compatibility/removed_2026-07-09.txt +0 -0
  96. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/dist-0.18.0/.gitignore +0 -0
  97. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/dist-0.19.0/.gitignore +0 -0
  98. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/sandbox-image/Dockerfile +0 -0
  99. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/sandbox-image/README.md +0 -0
  100. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/scripts/analyze_scores.py +0 -0
  101. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/scripts/batch_verify.py +0 -0
  102. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/scripts/check_model_drift.py +0 -0
  103. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/scripts/ci_smoke_test.py +0 -0
  104. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/scripts/generate_compatibility_artifacts.py +0 -0
  105. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/scripts/merge_batches.py +0 -0
  106. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/scripts/verify_toolcalls.py +0 -0
  107. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/scripts/weekly_retest.sh +0 -0
  108. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/spikes/agent_sandbox_routing/README.md +0 -0
  109. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/spikes/agent_sandbox_routing/__init__.py +0 -0
  110. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/spikes/agent_sandbox_routing/container_agent_wrapper.py +0 -0
  111. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/spikes/agent_sandbox_routing/fake_llm.py +0 -0
  112. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/spikes/agent_sandbox_routing/host_driver.py +0 -0
  113. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/spikes/agent_sandbox_routing/trivial_agent.py +0 -0
  114. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/__init__.py +0 -0
  115. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/conftest.py +0 -0
  116. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/data/allowed_domains_vectors.json +0 -0
  117. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_llm_broker.py +0 -0
  118. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_llm_policy.py +0 -0
  119. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_rpc.py +0 -0
  120. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_runner.py +0 -0
  121. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_sandbox_e2e.py +0 -0
  122. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_sandbox_hardening.py +0 -0
  123. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_sandbox_routing.py +0 -0
  124. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_sandbox_spike.py +0 -0
  125. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_agent_session_container.py +0 -0
  126. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_async_client.py +0 -0
  127. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_audit_ux.py +0 -0
  128. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_auth_cli_vault.py +0 -0
  129. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_auto_upgrade_policy.py +0 -0
  130. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_cli.py +0 -0
  131. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_cli_lock.py +0 -0
  132. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_cli_run_resolution.py +0 -0
  133. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_client.py +0 -0
  134. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_client_json_guard.py +0 -0
  135. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_client_sprint_b.py +0 -0
  136. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_config.py +0 -0
  137. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_consent_prompt.py +0 -0
  138. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_credential_handle.py +0 -0
  139. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_credential_integration.py +0 -0
  140. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_credential_resolver.py +0 -0
  141. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_credential_store.py +0 -0
  142. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_credential_vault.py +0 -0
  143. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_detect.py +0 -0
  144. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_detect_and_install.py +0 -0
  145. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_domain_policy.py +0 -0
  146. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_e2e_runtime.py +0 -0
  147. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_edge_cases.py +0 -0
  148. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_egress_domain_validation.py +0 -0
  149. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_egress_e2e.py +0 -0
  150. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_egress_lifecycle.py +0 -0
  151. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_egress_proxy_unit.py +0 -0
  152. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_egress_wrap.py +0 -0
  153. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_env_passthrough_wrap.py +0 -0
  154. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard.py +0 -0
  155. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_check.py +0 -0
  156. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_config_cache.py +0 -0
  157. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_policy.py +0 -0
  158. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_preview.py +0 -0
  159. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_schema.py +0 -0
  160. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_set.py +0 -0
  161. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_status.py +0 -0
  162. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_tool_override.py +0 -0
  163. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_tool_override_audit.py +0 -0
  164. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_tool_override_cli.py +0 -0
  165. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_guard_ux.py +0 -0
  166. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_host_trust_policy.py +0 -0
  167. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_host_trust_policy_routing.py +0 -0
  168. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_input_guard.py +0 -0
  169. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_input_guard_escalation.py +0 -0
  170. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_install_hardening.py +0 -0
  171. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_installer_sprint_b.py +0 -0
  172. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_intelligence.py +0 -0
  173. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_key_status.py +0 -0
  174. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_llm_binding.py +0 -0
  175. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_llm_call_runlog.py +0 -0
  176. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_llm_providers.py +0 -0
  177. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_llm_vault_binding.py +0 -0
  178. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_lock_integrity.py +0 -0
  179. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_lock_runtime.py +0 -0
  180. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_allowed_domains_seal.py +0 -0
  181. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_audit.py +0 -0
  182. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_cli_scaffold.py +0 -0
  183. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_consent.py +0 -0
  184. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_consent_resolver.py +0 -0
  185. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_consent_store.py +0 -0
  186. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_credentialed_launch.py +0 -0
  187. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_doctor.py +0 -0
  188. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_preinstall.py +0 -0
  189. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_preinstall_run.py +0 -0
  190. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_sandbox.py +0 -0
  191. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_server.py +0 -0
  192. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_mcp_submit.py +0 -0
  193. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_observability.py +0 -0
  194. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_planner.py +0 -0
  195. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_policy.py +0 -0
  196. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_policy_integration.py +0 -0
  197. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_prompt_specs.py +0 -0
  198. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_provider_matrix.py +0 -0
  199. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_publish.py +0 -0
  200. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_references.py +0 -0
  201. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_registry_trust.py +0 -0
  202. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_remote_hardening.py +0 -0
  203. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_remote_runner.py +0 -0
  204. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_resource_provider.py +0 -0
  205. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_resource_specs.py +0 -0
  206. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_risk_profile.py +0 -0
  207. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_run_log.py +0 -0
  208. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_runner.py +0 -0
  209. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_runtime.py +0 -0
  210. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_runtime_audit.py +0 -0
  211. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_sandbox_backend.py +0 -0
  212. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_sandbox_doctor.py +0 -0
  213. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_sandbox_e2e.py +0 -0
  214. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_sandbox_gate.py +0 -0
  215. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_security_hardening.py +0 -0
  216. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_setup_wizard.py +0 -0
  217. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_signature.py +0 -0
  218. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_signing_key.py +0 -0
  219. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_skill.py +0 -0
  220. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_smart.py +0 -0
  221. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_stability.py +0 -0
  222. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_toolpack_credentials.py +0 -0
  223. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_toolpack_credentials_b.py +0 -0
  224. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_toolpack_sandbox.py +0 -0
  225. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_v02.py +0 -0
  226. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_validate.py +0 -0
  227. {agentnode_sdk-0.22.0 → agentnode_sdk-0.23.0}/tests/test_validate_skill.py +0 -0
  228. {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.22.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
@@ -38,7 +38,7 @@ from agentnode_sdk.runtime import AgentNodeRuntime
38
38
  Client = AgentNodeClient
39
39
  ToolError = AgentNodeToolError
40
40
 
41
- __version__ = "0.22.0"
41
+ __version__ = "0.23.0"
42
42
  __all__ = [
43
43
  "AgentNode",
44
44
  "AsyncAgentNode",
@@ -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.22.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