snulbug 0.2.0__tar.gz → 0.2.2__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.
- {snulbug-0.2.0 → snulbug-0.2.2}/CHANGELOG.md +29 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/PKG-INFO +4 -2
- {snulbug-0.2.0 → snulbug-0.2.2}/README.md +2 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/SECURITY.md +10 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/devcontainers.md +2 -2
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/mcp-proxy.md +66 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/mcp-share.md +94 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/ngrok-end-to-end.md +26 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/release.md +5 -4
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/codespace_local_gateway/.devcontainer/devcontainer.json +1 -1
- {snulbug-0.2.0 → snulbug-0.2.2}/features/snulbug/README.md +1 -1
- {snulbug-0.2.0 → snulbug-0.2.2}/features/snulbug/devcontainer-feature.json +1 -1
- {snulbug-0.2.0 → snulbug-0.2.2}/pyproject.toml +2 -2
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/__init__.py +3 -1
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/cli/share.py +38 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/config.py +5 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/gateway_templates.py +3 -0
- snulbug-0.2.2/snulbug/http_routes.py +239 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/leases.py +227 -6
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_auth.py +8 -1
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/proxy.py +142 -5
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/quickstart.py +6 -1
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/redaction.py +3 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/share.py +197 -6
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/share_console.py +67 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/tunnel.py +43 -5
- snulbug-0.2.2/tests/test_automation_invites.py +538 -0
- snulbug-0.2.2/tests/test_http_routes.py +462 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/.gitignore +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/CODE_OF_CONDUCT.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/CONTRIBUTING.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/LICENSE +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/actions.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/auth-providers.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/aws-mcp.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/bundles.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/ci-policy-gates.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/comparison.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/credential-brokers.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/discovery-providers.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/event-sink-providers.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/evidence-exporters.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/getting-started.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/lua-policy-dsl.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/lua-request-api.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/mcp-auth-recipes.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/mcp-client-recipes.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/mcp-evidence.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/mcp-fabric.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/mcp-gateway.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/mcp-guide.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/mcp-impact.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/mcp-learn.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/mcp-policy.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/mcp-presets.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/mcp-protocol.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/mcp-recorder.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/mcp-schemas.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/policy-deny-backoff.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/quickstart.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/roadmap.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/security-model.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/share-doctor-checks.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/state.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/tool-catalog-projection.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/tool-risk-analyzers.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/tunnel-providers.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/docs/upstream-transports.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/auth_claim_patterns/.gitignore +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/auth_claim_patterns/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/auth_claim_patterns/ci-workload-identity.toml +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/auth_claim_patterns/group-gated-tools.toml +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/auth_claim_patterns/policy.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/auth_claim_patterns/tenant-isolation.toml +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/basic/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/basic/app.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/bundles/idempotency.snulbug/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/bundles/idempotency.snulbug/fixtures/duplicate-delivery.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/bundles/idempotency.snulbug/fixtures/first-delivery.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/bundles/idempotency.snulbug/manifest.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/bundles/idempotency.snulbug/policy.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/bundles/idempotency.snulbug/snapshots/duplicate.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/bundles/idempotency.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/bundles/mcp-gateway.snulbug/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/bundles/mcp-gateway.snulbug/fixtures/allowed-tool.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/bundles/mcp-gateway.snulbug/fixtures/blocked-tool.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/bundles/mcp-gateway.snulbug/fixtures/unauthenticated.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/bundles/mcp-gateway.snulbug/manifest.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/bundles/mcp-gateway.snulbug/policy.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/bundles/mcp-gateway.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/codespace_local_gateway/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/codespace_local_gateway/mcp-client.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/codespace_local_gateway/mock_mcp_server.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/codespace_local_gateway/policy.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/codespace_local_gateway/snulbug.env-gateway.toml +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/codespace_local_gateway/snulbug.local-gateway.toml +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/customer_policy/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/customer_policy/app.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/customer_policy/policies/acme.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/customer_policy/policies/default.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/customer_policy/policies/globex.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/customer_policy/requests/acme-invalid.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/customer_policy/requests/acme-valid.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/customer_policy/requests/globex-sandbox.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/customer_policy/requests/unknown-tenant.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/github-actions/snulbug-policy-gates.yml +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/.gitignore +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/Dockerfile.gateway +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/auth/keycloak/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/auth/keycloak/auth-init.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/auth/keycloak/client-token-request.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/auth/keycloak/commands.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/auth/keycloak/snulbug.auth.toml +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/docker-compose.yml +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/get-token.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/keycloak/snulbug-demo-realm.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/mock_mcp_server.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/policy.snulbug/policy.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/requests/blocked-write-file.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/requests/read-file.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/requests/tools-list.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/keycloak_oauth_demo/snulbug.toml +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_container_facade/Dockerfile.gateway +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_container_facade/Dockerfile.remote-peer +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_container_facade/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_container_facade/docker-compose.yml +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_container_facade/hypertele-client.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_container_facade/hypertele-server.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_container_facade/mcp-client.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_container_facade/mock_mcp_server.js +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_container_facade/mock_mcp_server.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_container_facade/policy.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_container_facade/snulbug.facade.toml +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_container_facade/snulbug.local.toml +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_gateway/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_gateway/app.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_proxy_demo/.gitignore +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_proxy_demo/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_proxy_demo/requests/blocked-tool.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_proxy_demo/requests/safe-tool.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_proxy_demo/requests/tools-list.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_proxy_demo/run_demo.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/mcp_proxy_demo/upstream.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/policy_promotion/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/policy_promotion/active.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/policy_promotion/draft.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/policy_promotion/fixtures/acme-invalid.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/policy_promotion/fixtures/acme-valid.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/provider_policy_templates/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/provider_policy_templates/cloudflare-access-group-gate.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/provider_policy_templates/entra-app-role-gate.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/provider_policy_templates/github-actions-workload-gate.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/provider_policy_templates/keycloak-role-gate.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/state_replay/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/state_replay/duplicate-state.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/state_replay/idempotency.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/state_replay/request.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/webhook_normalization/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/webhook_normalization/app.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/webhook_normalization/policies/github.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/webhook_normalization/policies/stripe.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/webhook_normalization/policies/unknown.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/webhook_normalization/requests/github-push.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/webhook_normalization/requests/stripe-invoice.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/webhook_normalization/requests/stripe-malformed.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/examples/webhook_normalization/requests/unknown-vendor.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/features/snulbug/install.sh +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/__main__.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/assets/prism.css +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/assets/prism.js +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/auth_providers.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/auth_recipes.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/auth-required.snulbug/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/auth-required.snulbug/fixtures/authenticated.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/auth-required.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/auth-required.snulbug/manifest.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/auth-required.snulbug/policy.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/contexts/inventory-lease.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/contexts/sandbox-change-lease.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/data-read.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/iam-delete-user.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/inventory-read.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/knowledge-search.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/sandbox-mutation.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/manifest.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/policy.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/fixtures/safe-tool.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/fixtures/unsafe-tool.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/manifest.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/policy.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/fixtures/safe-tool.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/fixtures/shell-tool.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/manifest.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/policy.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/fixtures/absolute-path.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/fixtures/allowed-path.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/fixtures/traversal-path.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/fixtures/unsafe-tool.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/manifest.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/policy.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/fixtures/safe-tool.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/fixtures/tools-list.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/fixtures/write-method.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/manifest.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/policy.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/fixtures/safe-tool.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/fixtures/tools-list.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/fixtures/unsafe-tool.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/manifest.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/policy.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/fixtures/batch-request.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/fixtures/safe-tool.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/fixtures/unsafe-tool.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/manifest.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/policy.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/allowed-read.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/secret-path.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/traversal-path.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/unsafe-tool.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/manifest.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/policy.lua +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/bundle.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/cli/__init__.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/cli/common.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/cli/evidence.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/cli/fabric.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/cli/policy.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/cli/rich_reports.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/cli/rich_share.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/cli/schemas.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/cli_helpers.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/cloudflare_access.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/codespaces.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/confirm.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/control_events.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/controller.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/credentials.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/discovery.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/events.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/evidence_exporters.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/fabric.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/fabric_control.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/fabric_members.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/fabric_runtime.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/guide.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/impact.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/inspection.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/json_schema.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/lab.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/learn.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/manifests.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_cache.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_capabilities.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_client_requests.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_completion.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_mrtr.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_parameter_headers.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_policy_bundles.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_progress.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_protocol.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_resources.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_schema_policy.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_schemas.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_spec_conformance.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_stdio.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_stream.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_subscriptions.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_tasks.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/mcp_tools.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/middleware.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/policy_activation.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/policy_backoff.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/policy_review.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/presets.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/promotion.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/py.typed +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/recorder.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/release_qa.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/response_policy.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/runtime.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/sarif.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/scaffolds.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/schema_policy.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/share_doctor.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/share_session.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/simulator.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/state.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/tool_catalog.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/tool_risk.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/snulbug/upstream_transports.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/fixtures/json_schema_2020_12/LICENSE +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/fixtures/json_schema_2020_12/README.md +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/fixtures/json_schema_2020_12/dependentSchemas.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/fixtures/json_schema_2020_12/dynamicRef.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/fixtures/json_schema_2020_12/if-then-else.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/fixtures/json_schema_2020_12/prefixItems.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/fixtures/json_schema_2020_12/unevaluatedProperties.json +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/fixtures/stdio_subscriptions.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_auth_claim_patterns.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_auth_recipes.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_cli_helpers.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_cloudflare_access.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_codespaces.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_config.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_credentials.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_customer_policy_demo.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_devcontainer_feature.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_discovery.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_events.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_evidence_exporters.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_fabric.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_fabric_controller.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_json_schema.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_keycloak_oauth_demo.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_leases.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_manifests.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_cache.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_gateway_demo.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_guide.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_helpers.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_impact.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_inspection.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_lab.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_learn.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_mrtr.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_parameter_headers.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_policy_bundles.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_presets.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_progress.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_protocol.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_proxy_demo.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_quickstart.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_recorder.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_resources.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_schema_policy.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_schemas.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_share.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_spec_conformance.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_stdio.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_stream.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_subscriptions.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_mcp_tools.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_middleware.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_policy_bundle.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_policy_promotion.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_policy_state.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_provider_policy_templates.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_proxy.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_redaction_audit.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_release_qa.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_response_policy.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_sandbox_abuse.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_scaffolds.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_share_console.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_state_snapshots.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_tool_catalog.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_tool_risk.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_tunnel_audit.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_tunnel_doctor.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_tunnel_init.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_upstream_transports.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_webhook_normalization_demo.py +0 -0
- {snulbug-0.2.0 → snulbug-0.2.2}/tests/test_webhooks.py +0 -0
|
@@ -6,6 +6,35 @@ The format is based on Keep a Changelog, and this project follows semantic versi
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.2] - 2026-10-16
|
|
10
|
+
|
|
11
|
+
Support always-on MCP bots with renewable automation invites, without introducing
|
|
12
|
+
a separate permission or approval system.
|
|
13
|
+
|
|
14
|
+
- Added renewable automation invites using the existing backing lease, with bounded consumer renewal/restart recovery, rotating access tokens, identity bindings, persistent usage budgets, and console/CLI setup snippets.
|
|
15
|
+
- Serialized lease mutations across local workers so renewal, revocation, cleanup, and concurrent call consumption cannot overwrite each other's state.
|
|
16
|
+
- Added renewal visibility and refresh-stable invite forms in the web console, secret-safe renewal audit events, and share closeout that revokes invites as well as the session lease.
|
|
17
|
+
|
|
18
|
+
Automation renewal is opt-in. Access leases remain short-lived; the owner's
|
|
19
|
+
renewal authorization has a separate hard expiry. Clients must adopt refreshed
|
|
20
|
+
lease headers, and revocation or exhausted budgets prevent recovery. No change
|
|
21
|
+
to the default MCP protocol version or the experimental profile's limitations.
|
|
22
|
+
|
|
23
|
+
## [0.2.1] - 2026-10-15
|
|
24
|
+
|
|
25
|
+
Expose web applications and APIs alongside MCP through one gateway and ngrok tunnel.
|
|
26
|
+
|
|
27
|
+
- Added `[[mcp.proxy.http_routes]]` mounts with longest-prefix routing, segment-boundary matching, configurable prefix stripping, and reserved MCP/gateway paths.
|
|
28
|
+
- Added repeatable `--http-route PREFIX=URL` flags to share creation and quickstart, with matching ngrok Traffic Policy rules that apply MCP edge checks only to the MCP endpoint.
|
|
29
|
+
- Added streaming HTTP uploads, responses, and SSE with disconnect cleanup, ordinary HTTP headers/cookies/content encodings, and plain HTTP upstream errors; included local proxy/facade regression coverage and setup documentation.
|
|
30
|
+
- Raised the PyJWT minimum to 2.15 and updated the lockfile to exclude the versions flagged by the current JWT security advisories.
|
|
31
|
+
|
|
32
|
+
HTTP mounts bypass MCP authentication, leases, Lua, redaction, and MCP evidence
|
|
33
|
+
logging; each backend owns its authentication. Applications must configure their
|
|
34
|
+
public base path. WebSockets and rewriting HTML, redirects, or cookie paths are
|
|
35
|
+
outside this slice. See [shared HTTP routes](docs/mcp-proxy.md#shared-http-routes)
|
|
36
|
+
and the [ngrok walkthrough](docs/ngrok-end-to-end.md).
|
|
37
|
+
|
|
9
38
|
## [0.2.0] - 2026-09-06
|
|
10
39
|
|
|
11
40
|
This release expands MCP protocol support while keeping **2025-11-25 as the default**.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: snulbug
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A local-dev MCP policy proxy with Lua policies, replay logs, and learned least-privilege bundles
|
|
5
5
|
Project-URL: Homepage, https://github.com/nahuaque/snulbug
|
|
6
6
|
Project-URL: Repository, https://github.com/nahuaque/snulbug
|
|
@@ -26,7 +26,7 @@ Requires-Python: >=3.10
|
|
|
26
26
|
Requires-Dist: httpx<1,>=0.28
|
|
27
27
|
Requires-Dist: jsonschema<5,>=4.23
|
|
28
28
|
Requires-Dist: lupa>=2.5
|
|
29
|
-
Requires-Dist: pyjwt[crypto]>=2.
|
|
29
|
+
Requires-Dist: pyjwt[crypto]>=2.15
|
|
30
30
|
Requires-Dist: referencing<1,>=0.35
|
|
31
31
|
Requires-Dist: rich>=15.0.0
|
|
32
32
|
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
@@ -63,6 +63,8 @@ It gives you a tight loop for agent-tool safety:
|
|
|
63
63
|
- review MCP-native capability requests and approve them into normal task leases
|
|
64
64
|
- use task-scoped leases for temporary tool/path grants, optionally bound to
|
|
65
65
|
OAuth subject, tenant, client, group, issuer, or auth profile
|
|
66
|
+
- give always-on bots [renewable automation invites](docs/mcp-share.md#renewable-automation-invites)
|
|
67
|
+
with short-lived leases, consumer renewal, and restart recovery
|
|
66
68
|
- turn OAuth identity into MCP-specific tool permissions
|
|
67
69
|
- pin facade upstream identity with signed manifests
|
|
68
70
|
|
|
@@ -20,6 +20,8 @@ It gives you a tight loop for agent-tool safety:
|
|
|
20
20
|
- review MCP-native capability requests and approve them into normal task leases
|
|
21
21
|
- use task-scoped leases for temporary tool/path grants, optionally bound to
|
|
22
22
|
OAuth subject, tenant, client, group, issuer, or auth profile
|
|
23
|
+
- give always-on bots [renewable automation invites](docs/mcp-share.md#renewable-automation-invites)
|
|
24
|
+
with short-lived leases, consumer renewal, and restart recovery
|
|
23
25
|
- turn OAuth identity into MCP-specific tool permissions
|
|
24
26
|
- pin facade upstream identity with signed manifests
|
|
25
27
|
|
|
@@ -59,6 +59,16 @@ Limits:
|
|
|
59
59
|
schemas pass through unless your Lua policy blocks them separately.
|
|
60
60
|
- Leases are bearer-style capabilities. Store `leases.json` locally, rotate or
|
|
61
61
|
revoke leases after sharing, and prefer short TTLs for public tunnel use.
|
|
62
|
+
- Renewable automation invites add a separate, reusable renewal credential with
|
|
63
|
+
an owner-set hard deadline. Treat it as a long-lived bot secret: theft together
|
|
64
|
+
with the required gateway authentication permits recovery within the approved
|
|
65
|
+
capability and call-budget bounds until revocation or expiry. It does not
|
|
66
|
+
grant console access. Renewal retains the existing lease's usage count and
|
|
67
|
+
identity bindings; revocation disables recovery. Access-token rotation has a
|
|
68
|
+
bounded overlap of at most 30 seconds. Lease operations are serialized on the
|
|
69
|
+
local lease file; independent gateway hosts must not treat copied JSON stores
|
|
70
|
+
as a shared revocation authority. The owner console's invite secret store
|
|
71
|
+
contains plaintext setup credentials and must remain private.
|
|
62
72
|
|
|
63
73
|
### Malicious or compromised MCP server
|
|
64
74
|
|
|
@@ -10,8 +10,8 @@ servers with a shared control plane.
|
|
|
10
10
|
```json
|
|
11
11
|
{
|
|
12
12
|
"features": {
|
|
13
|
-
"ghcr.io/nahuaque/snulbug/features/snulbug:0.2.
|
|
14
|
-
"version": "0.2.
|
|
13
|
+
"ghcr.io/nahuaque/snulbug/features/snulbug:0.2.2": {
|
|
14
|
+
"version": "0.2.2",
|
|
15
15
|
"mode": "member-agent",
|
|
16
16
|
"policy_profile": "tunnel-safe",
|
|
17
17
|
"registry": "redis://redis:6379/0",
|
|
@@ -109,6 +109,68 @@ curl -sS "${NGROK_URL}/mcp" \
|
|
|
109
109
|
-d '{"jsonrpc":"2.0","id":"tools-list","method":"tools/list","params":{}}'
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
+
## Shared HTTP Routes
|
|
113
|
+
|
|
114
|
+
One gateway listener can expose MCP at `/mcp` and ordinary HTTP backends at
|
|
115
|
+
other prefixes. Add mounts to the share's `snulbug.toml`:
|
|
116
|
+
|
|
117
|
+
```toml
|
|
118
|
+
[[mcp.proxy.http_routes]]
|
|
119
|
+
prefix = "/app"
|
|
120
|
+
upstream = "http://127.0.0.1:3000"
|
|
121
|
+
strip_prefix = true
|
|
122
|
+
|
|
123
|
+
[[mcp.proxy.http_routes]]
|
|
124
|
+
prefix = "/api"
|
|
125
|
+
upstream = "http://127.0.0.1:4000"
|
|
126
|
+
strip_prefix = false
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
With prefix stripping (the default), `/app/notes?q=1` forwards to
|
|
130
|
+
`http://127.0.0.1:3000/notes?q=1`. With `strip_prefix = false`, `/api/items`
|
|
131
|
+
forwards to `http://127.0.0.1:4000/api/items`. A path in the upstream URL is
|
|
132
|
+
prepended to the forwarded path. Prefix matches use segment boundaries:
|
|
133
|
+
`/app` matches `/app` and `/app/...`, but not `/application`. The longest
|
|
134
|
+
matching prefix wins. The MCP prefix, `/.well-known`, and `/snulbug` are reserved.
|
|
135
|
+
|
|
136
|
+
These requests bypass the MCP middleware, including its authentication, task
|
|
137
|
+
leases, Lua policies, body/response limits, redaction, and replay/audit logs.
|
|
138
|
+
Each HTTP backend owns authentication and request validation. Application
|
|
139
|
+
`Authorization` and cookies pass through; snulbug lease and internal headers
|
|
140
|
+
are removed. Responses preserve status, content encoding, duplicate cookies,
|
|
141
|
+
and end-to-end headers. Uploads and responses stream with backpressure and
|
|
142
|
+
disconnect cleanup; the proxy timeout applies to upstream inactivity.
|
|
143
|
+
|
|
144
|
+
Configure web applications to generate assets, redirects, and cookies for their
|
|
145
|
+
public prefix (for example `/app/`), using their base URL/root-path setting or
|
|
146
|
+
the forwarded host, protocol, and prefix headers. Snulbug does not rewrite HTML,
|
|
147
|
+
redirect locations, or cookie paths. Use `strip_prefix = false` for an app that
|
|
148
|
+
already serves its prefix. WebSocket upgrades are not supported by these HTTP mounts.
|
|
149
|
+
|
|
150
|
+
Restart `share run` after editing mounts. The router works with proxy and facade
|
|
151
|
+
MCP modes and any tunnel that forwards the gateway origin. For generated ngrok
|
|
152
|
+
setup, register routes at share creation so the Cloud Endpoint policy permits
|
|
153
|
+
them:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
snulbug mcp share create \
|
|
157
|
+
--directory .snulbug/shares/shared-demo \
|
|
158
|
+
--provider ngrok \
|
|
159
|
+
--url https://YOUR-NGROK-CLOUD-ENDPOINT/mcp \
|
|
160
|
+
--upstream http://127.0.0.1:9000 \
|
|
161
|
+
--http-route /app=http://127.0.0.1:3000 \
|
|
162
|
+
--http-route /api=http://127.0.0.1:4000
|
|
163
|
+
snulbug mcp share run .snulbug/shares/shared-demo
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`--http-route` is repeatable and also available on `share quickstart`. CLI-created
|
|
167
|
+
routes strip their prefix; change that field in TOML when needed. The generated
|
|
168
|
+
ngrok Traffic Policy permits only the MCP endpoint and registered HTTP prefixes,
|
|
169
|
+
and applies its bearer/method/JSON guards only to MCP. Attach the policy to the
|
|
170
|
+
public Cloud Endpoint as described in the [ngrok walkthrough](ngrok-end-to-end.md).
|
|
171
|
+
If prefixes change later, update and reattach the Cloud Endpoint policy as well
|
|
172
|
+
as restarting the gateway; editing TOML alone does not update ngrok's cloud policy.
|
|
173
|
+
|
|
112
174
|
## Streamable HTTP edge hardening
|
|
113
175
|
|
|
114
176
|
`snulbug` enforces MCP Streamable HTTP safety checks at the gateway boundary
|
|
@@ -858,6 +920,10 @@ snulbug mcp evidence impact traces/session.jsonl --lease leases.json --report-ou
|
|
|
858
920
|
In facade mode, leases use the client-facing tool name, such as
|
|
859
921
|
`files.read_file` or `git.status`.
|
|
860
922
|
|
|
923
|
+
For unattended consumers, [renewable automation invites](mcp-share.md#renewable-automation-invites)
|
|
924
|
+
extend the same task lease with bounded consumer renewal and restart recovery.
|
|
925
|
+
They retain the existing Lua, OAuth identity, revocation, and call-budget checks.
|
|
926
|
+
|
|
861
927
|
## Tool Schema Firewall
|
|
862
928
|
|
|
863
929
|
When `schema_validation = true`, snulbug learns each MCP tool's `inputSchema`
|
|
@@ -123,6 +123,94 @@ traffic is observed. The table records evidence source, confidence, risk level,
|
|
|
123
123
|
categories, and signals such as command-capable arguments, network/path
|
|
124
124
|
arguments, destructive annotations, and open argument schemas.
|
|
125
125
|
|
|
126
|
+
## Renewable Automation Invites
|
|
127
|
+
|
|
128
|
+
For an always-on bot, enable **Renewable automation invite** in the console's
|
|
129
|
+
Invites tab, or extend the normal invite command:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
snulbug mcp share invite create .snulbug/shares/my-share \
|
|
133
|
+
--recipient nightly-bot --task "Inspect project" \
|
|
134
|
+
--capability project_readonly \
|
|
135
|
+
--renewable --ttl 30m --renewal-ttl 7d
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`--ttl` controls the short-lived access lease. `--renewal-ttl` is the owner's
|
|
139
|
+
hard authorization deadline, measured from invite creation, not each renewal.
|
|
140
|
+
The backing lease keeps its ID, capabilities, identity bindings, usage count,
|
|
141
|
+
and total `--max-calls` budget. Renewal does not approve policy changes or
|
|
142
|
+
replenish an exhausted budget. Use the existing Lua rate limits for recurring
|
|
143
|
+
request limits; this is not a new daily quota system.
|
|
144
|
+
|
|
145
|
+
The setup packet includes a renewal credential, a curl command, and a Python
|
|
146
|
+
integration example. Store the renewal credential as a bot secret, separately
|
|
147
|
+
from the short-lived `x-snulbug-lease` token. It is reusable until its deadline
|
|
148
|
+
or revocation, is not an OAuth refresh token, and cannot administer the console
|
|
149
|
+
or create other invites. It is shown only through the existing owner CLI or
|
|
150
|
+
secret-gated console. Protect the share directory and its `invite-secrets.json`.
|
|
151
|
+
|
|
152
|
+
Consumers use ordinary HTTPS against the **same MCP URL**, so ngrok and other
|
|
153
|
+
generated tunnel routes need no additional public endpoint:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
export SNULBUG_MCP_URL="https://your-tunnel.example/mcp"
|
|
157
|
+
export SNULBUG_INVITE_ID="invite_..."
|
|
158
|
+
export SNULBUG_RENEWAL_TOKEN="sbr_..."
|
|
159
|
+
# Set SNULBUG_BEARER_TOKEN to the bearer token from the invite packet.
|
|
160
|
+
curl --fail-with-body -sS "$SNULBUG_MCP_URL" \
|
|
161
|
+
-H "Authorization: Bearer ${SNULBUG_BEARER_TOKEN}" \
|
|
162
|
+
-H "x-snulbug-renewal: ${SNULBUG_RENEWAL_TOKEN}" \
|
|
163
|
+
-H 'Content-Type: application/json' \
|
|
164
|
+
-H 'Accept: application/json, text/event-stream' \
|
|
165
|
+
-d "{\"jsonrpc\":\"2.0\",\"id\":\"renew\",\"method\":\"snulbug/lease/renew\",\"params\":{\"invite_id\":\"${SNULBUG_INVITE_ID}\"}}"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`snulbug/lease/renew` is a snulbug JSON-RPC extension, not a standard MCP method
|
|
169
|
+
or an upstream tool. Its only parameter is `invite_id`; the consumer cannot
|
|
170
|
+
choose a longer TTL, larger budget, or different capabilities. The gateway
|
|
171
|
+
terminates the request without forwarding the renewal credential upstream.
|
|
172
|
+
Errors use `error.data.reason_code`, even when the HTTP status is 200.
|
|
173
|
+
|
|
174
|
+
On success, `result.headers` contains the current lease header;
|
|
175
|
+
`result.lease.expires_at` and `result.lease.renewal.renew_after` tell the bot
|
|
176
|
+
when to refresh its MCP client's headers. Call again at `renew_after`, with
|
|
177
|
+
jitter and backoff for network failures. Retries inside the same lease window
|
|
178
|
+
return the same access token. A renewal rotates it with at most 30 seconds of
|
|
179
|
+
overlap for in-flight requests, bounded by the old token's original expiry.
|
|
180
|
+
After a process restart or an expired access lease, the **same operation**
|
|
181
|
+
recovers access while the owner authorization remains valid. No old lease token
|
|
182
|
+
or installed snulbug client is required. Fixed-header clients need a refresh
|
|
183
|
+
integration; a static client configuration does not renew itself.
|
|
184
|
+
|
|
185
|
+
OAuth shares still require a valid access token obtained from the configured
|
|
186
|
+
identity provider. Bind automation invites using `--allow-issuer` plus
|
|
187
|
+
`--allow-subject` or `--allow-client-id`; optionally add `--allow-tenant`.
|
|
188
|
+
Use that access token as the renewal request's `Authorization` header and the
|
|
189
|
+
separate invite renewal credential as `x-snulbug-renewal`. Required OAuth scopes,
|
|
190
|
+
DPoP requirements, and identity bindings still apply. Tool scope maps, claim
|
|
191
|
+
policies, and Lua continue to govern every ordinary MCP tool call.
|
|
192
|
+
For DPoP-required shares, the consumer must also sign and attach a fresh proof
|
|
193
|
+
for each renewal request using its existing OAuth/DPoP client integration; the
|
|
194
|
+
plain curl/Python examples do not mint proofs or OAuth access tokens.
|
|
195
|
+
|
|
196
|
+
Revoking the invite or backing lease disables renewal and restart recovery.
|
|
197
|
+
Closing the share with its default revocation option revokes its invites too.
|
|
198
|
+
Even `invite revoke --keep-lease` revokes renewal authority; it leaves only the
|
|
199
|
+
already-issued access lease usable until expiry. Removed capability labels,
|
|
200
|
+
exhausted budgets, and expired renewal authorizations fail closed. Create a new
|
|
201
|
+
invite for a new approval; operator reactivation cannot reset a renewable lease.
|
|
202
|
+
Cleanup retains expired access leases while their renewal authorization is
|
|
203
|
+
active. The console shows authorization expiry, access expiry, next renewal,
|
|
204
|
+
and last renewal/count, and keeps setup snippets synchronized with token rotation.
|
|
205
|
+
Renewal decisions go to the existing audit/event sinks, not the Lua replay log:
|
|
206
|
+
renewing a credential is a control operation, not a simulated policy decision.
|
|
207
|
+
|
|
208
|
+
Renewal and lease consumption use the existing JSON lease store with file locks
|
|
209
|
+
and atomic replacement, including across local worker processes. Persist that
|
|
210
|
+
file and the share directory across gateway restarts. This does not introduce
|
|
211
|
+
a distributed lease database; separate hosts need a shared authority rather
|
|
212
|
+
than independent copies of `leases.json`.
|
|
213
|
+
|
|
126
214
|
## Generated files
|
|
127
215
|
|
|
128
216
|
By default, the command writes under `.snulbug/shares/share-*` and creates:
|
|
@@ -563,6 +651,12 @@ Point clients at `mcp-client.facade.json` for this facade recipe.
|
|
|
563
651
|
|
|
564
652
|
## Public tunnel providers
|
|
565
653
|
|
|
654
|
+
Shares can also mount HTTP applications on the same tunnel. Add repeatable
|
|
655
|
+
`--http-route /app=http://127.0.0.1:3000` flags to `share create`; MCP stays at
|
|
656
|
+
`/mcp`, while the app route bypasses MCP auth/policy and uses the backend's own
|
|
657
|
+
authentication. Generated ngrok policies permit these prefixes and scope MCP
|
|
658
|
+
checks to `/mcp`. See [shared HTTP routes](mcp-proxy.md#shared-http-routes).
|
|
659
|
+
|
|
566
660
|
The share command also works with existing tunnel providers:
|
|
567
661
|
|
|
568
662
|
```bash
|
|
@@ -72,6 +72,32 @@ The share writes the important files here:
|
|
|
72
72
|
`mcp-client.json` contains the bearer token and lease token for the generated
|
|
73
73
|
session. Treat it as secret-bearing material.
|
|
74
74
|
|
|
75
|
+
### Optional: Share a Webapp on the Same Tunnel
|
|
76
|
+
|
|
77
|
+
To expose an app on port 3000, add this flag to the `share create` command in
|
|
78
|
+
step 2 before creating the share:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
--http-route /app=http://127.0.0.1:3000
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The generated gateway config and ngrok Traffic Policy now allow `/app` and
|
|
85
|
+
`/app/...` alongside `/mcp`. After starting the app, gateway, and ngrok and
|
|
86
|
+
attaching the generated policy, test both hops without MCP credentials:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
curl -i http://127.0.0.1:8080/app/
|
|
90
|
+
curl -i "${NGROK_PUBLIC_ORIGIN}/app/"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The route strips `/app` before forwarding, so the app receives `/`. Configure
|
|
94
|
+
its public base path as `/app/` for asset links and redirects. App traffic
|
|
95
|
+
bypasses all MCP authentication/policy/recording; the app owns its authentication.
|
|
96
|
+
See [shared HTTP routes](mcp-proxy.md#shared-http-routes) for multiple backends
|
|
97
|
+
and preserving prefixes. Existing shares need both a TOML route and an updated
|
|
98
|
+
Cloud Endpoint Traffic Policy; changing TOML alone leaves ngrok's old deny rule
|
|
99
|
+
in effect.
|
|
100
|
+
|
|
75
101
|
## 3. Export the generated client headers
|
|
76
102
|
|
|
77
103
|
The generated share requires both bearer auth and an `x-snulbug-lease` header
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
1. Finalize `CHANGELOG.md`, with a dated release section and upgrade notes. Keep
|
|
4
4
|
protocol preview limitations distinct from package release readiness.
|
|
5
5
|
2. Bump `version` in `pyproject.toml` and `snulbug/__init__.py`, update the
|
|
6
|
-
devcontainer feature manifest and its docs/example references
|
|
6
|
+
devcontainer feature manifest and its docs/example references (including
|
|
7
|
+
`examples/codespace_local_gateway/.devcontainer/devcontainer.json`), and run `uv lock`.
|
|
7
8
|
3. Run verification:
|
|
8
9
|
|
|
9
10
|
```bash
|
|
@@ -24,7 +25,7 @@ The release QA suite runs:
|
|
|
24
25
|
- isolated built-wheel CLI/module smoke tests outside the source checkout
|
|
25
26
|
- installed-wheel protocol discovery and offline JSON Schema 2020-12 checks
|
|
26
27
|
|
|
27
|
-
These checks do not certify client/provider E2E interoperability. For 0.2.
|
|
28
|
+
These checks do not certify client/provider E2E interoperability. For 0.2.x,
|
|
28
29
|
2025-11-25 remains the default and 2026-07-28 remains an opt-in preview.
|
|
29
30
|
|
|
30
31
|
Before publishing, include all new modules, docs, tests, and fixtures in the
|
|
@@ -48,7 +49,7 @@ python -m zipfile -l dist/snulbug-*.whl
|
|
|
48
49
|
8. Install from TestPyPI in a clean environment and smoke test:
|
|
49
50
|
|
|
50
51
|
```bash
|
|
51
|
-
uvx --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ snulbug==0.2.
|
|
52
|
+
uvx --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ snulbug==0.2.2 --help
|
|
52
53
|
```
|
|
53
54
|
|
|
54
55
|
9. Run the manual `Publish` workflow with `repository = pypi` when the TestPyPI
|
|
@@ -56,4 +57,4 @@ uvx --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org
|
|
|
56
57
|
10. Create the GitHub release and tag after the PyPI package is available.
|
|
57
58
|
|
|
58
59
|
The `Publish` workflow uses `workflow_dispatch`, not a tag trigger. Creating or
|
|
59
|
-
pushing `v0.2.
|
|
60
|
+
pushing `v0.2.2` does not publish a package or automatically create a GitHub release.
|
{snulbug-0.2.0 → snulbug-0.2.2}/examples/codespace_local_gateway/.devcontainer/devcontainer.json
RENAMED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "snulbug Codespace MCP member",
|
|
3
3
|
"features": {
|
|
4
4
|
"ghcr.io/devcontainers/features/python:1": {},
|
|
5
|
-
"ghcr.io/nahuaque/snulbug/features/snulbug:0.2.
|
|
5
|
+
"ghcr.io/nahuaque/snulbug/features/snulbug:0.2.2": {
|
|
6
6
|
"install_source": "github",
|
|
7
7
|
"github_ref": "main",
|
|
8
8
|
"mode": "member-agent",
|
|
@@ -6,7 +6,7 @@ workspace helpers for Codespaces/devcontainers.
|
|
|
6
6
|
```json
|
|
7
7
|
{
|
|
8
8
|
"features": {
|
|
9
|
-
"ghcr.io/nahuaque/snulbug/features/snulbug:0.2.
|
|
9
|
+
"ghcr.io/nahuaque/snulbug/features/snulbug:0.2.2": {
|
|
10
10
|
"install_source": "github",
|
|
11
11
|
"github_ref": "main",
|
|
12
12
|
"mode": "member-agent",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "snulbug",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"name": "snulbug",
|
|
5
5
|
"description": "Install snulbug and optional helpers for MCP policy gateways and remote fabric members in devcontainers.",
|
|
6
6
|
"documentationURL": "https://github.com/nahuaque/snulbug/blob/main/docs/devcontainers.md",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "snulbug"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "A local-dev MCP policy proxy with Lua policies, replay logs, and learned least-privilege bundles"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -38,7 +38,7 @@ dependencies = [
|
|
|
38
38
|
"httpx>=0.28,<1",
|
|
39
39
|
"jsonschema>=4.23,<5",
|
|
40
40
|
"lupa>=2.5",
|
|
41
|
-
"pyjwt[crypto]>=2.
|
|
41
|
+
"pyjwt[crypto]>=2.15",
|
|
42
42
|
"referencing>=0.35,<1",
|
|
43
43
|
"rich>=15.0.0",
|
|
44
44
|
"tomli>=2.0; python_version < '3.11'",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Local-dev MCP policy proxy and programmable Lua ASGI policy layer."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.2.
|
|
3
|
+
__version__ = "0.2.2"
|
|
4
4
|
|
|
5
5
|
from .auth_providers import (
|
|
6
6
|
AuthProvider,
|
|
@@ -206,6 +206,7 @@ from .leases import (
|
|
|
206
206
|
create_lease,
|
|
207
207
|
list_leases,
|
|
208
208
|
reactivate_lease,
|
|
209
|
+
renew_lease,
|
|
209
210
|
revoke_lease,
|
|
210
211
|
)
|
|
211
212
|
from .manifests import load_manifest, manifest_digest, sign_upstream_manifest, verify_upstream_manifest, write_manifest
|
|
@@ -746,6 +747,7 @@ __all__ = [
|
|
|
746
747
|
"record_audit_event",
|
|
747
748
|
"record_policy_request",
|
|
748
749
|
"reactivate_lease",
|
|
750
|
+
"renew_lease",
|
|
749
751
|
"reconcile_fabric_controller",
|
|
750
752
|
"reconcile_policy_activation",
|
|
751
753
|
"register_fabric_member",
|
|
@@ -20,6 +20,7 @@ from ..cli_helpers import (
|
|
|
20
20
|
write_result_output,
|
|
21
21
|
write_sarif_output,
|
|
22
22
|
)
|
|
23
|
+
from ..http_routes import parse_http_route
|
|
23
24
|
from .common import read_required_env
|
|
24
25
|
|
|
25
26
|
PROVIDERS = ("generic", "ngrok", "cloudflare", "tailscale", "pinggy", "ssh", "holepunch")
|
|
@@ -445,6 +446,7 @@ def handle_mcp_share_command(args: argparse.Namespace, parser: argparse.Argument
|
|
|
445
446
|
config_output=args.config_output,
|
|
446
447
|
traces_dir=args.traces_dir,
|
|
447
448
|
upstream=args.upstream,
|
|
449
|
+
http_routes=args.http_route,
|
|
448
450
|
token=args.token,
|
|
449
451
|
token_env=args.token_env,
|
|
450
452
|
allowed_tools=args.allow_tool or None,
|
|
@@ -506,6 +508,7 @@ def handle_mcp_share_command(args: argparse.Namespace, parser: argparse.Argument
|
|
|
506
508
|
provider=args.provider,
|
|
507
509
|
preset=args.preset,
|
|
508
510
|
upstream=args.upstream,
|
|
511
|
+
http_routes=args.http_route,
|
|
509
512
|
hostname=args.hostname,
|
|
510
513
|
public_url=args.url,
|
|
511
514
|
ngrok_internal_url=args.ngrok_internal_url,
|
|
@@ -624,6 +627,12 @@ def handle_mcp_share_command(args: argparse.Namespace, parser: argparse.Argument
|
|
|
624
627
|
ttl=args.ttl,
|
|
625
628
|
max_calls=args.max_calls,
|
|
626
629
|
client_name=args.client_name,
|
|
630
|
+
renewable=args.renewable,
|
|
631
|
+
renewal_ttl=args.renewal_ttl,
|
|
632
|
+
allow_subjects=args.allow_subject,
|
|
633
|
+
allow_issuers=args.allow_issuer,
|
|
634
|
+
allow_tenants=args.allow_tenant,
|
|
635
|
+
allow_client_ids=args.allow_client_id,
|
|
627
636
|
)
|
|
628
637
|
elif args.share_invite_command == "list":
|
|
629
638
|
result = list_mcp_share_invites(args.directory, include_revoked=not args.active_only)
|
|
@@ -940,6 +949,7 @@ def _add_quickstart_args(parser: argparse.ArgumentParser) -> None:
|
|
|
940
949
|
parser.add_argument("--config-output", type=Path, default=Path("snulbug.toml"), help="config file path")
|
|
941
950
|
parser.add_argument("--traces-dir", type=Path, default=Path("traces"), help="trace directory path")
|
|
942
951
|
parser.add_argument("--upstream", default="http://127.0.0.1:9000", help="upstream MCP HTTP server URL")
|
|
952
|
+
_add_http_route_arg(parser)
|
|
943
953
|
add_token_arg(parser, help="bearer token to render into generated policy")
|
|
944
954
|
add_token_env_arg(parser, help="context key used by generated policy for env-derived token lookup")
|
|
945
955
|
parser.add_argument("--allow-tool", action="append", default=[], help="allowed MCP tool name")
|
|
@@ -1169,6 +1179,17 @@ def _add_cloudflare_access_setup_args(parser: argparse.ArgumentParser) -> None:
|
|
|
1169
1179
|
)
|
|
1170
1180
|
|
|
1171
1181
|
|
|
1182
|
+
def _add_http_route_arg(parser: argparse.ArgumentParser) -> None:
|
|
1183
|
+
parser.add_argument(
|
|
1184
|
+
"--http-route",
|
|
1185
|
+
action="append",
|
|
1186
|
+
type=parse_http_route,
|
|
1187
|
+
default=[],
|
|
1188
|
+
metavar="PREFIX=URL",
|
|
1189
|
+
help="HTTP backend outside MCP auth/policy, e.g. /app=http://127.0.0.1:3000; strips prefix; repeatable",
|
|
1190
|
+
)
|
|
1191
|
+
|
|
1192
|
+
|
|
1172
1193
|
def _add_share_create_args(parser: argparse.ArgumentParser) -> None:
|
|
1173
1194
|
parser.add_argument("--directory", type=Path, help="share session directory")
|
|
1174
1195
|
parser.add_argument(
|
|
@@ -1179,6 +1200,7 @@ def _add_share_create_args(parser: argparse.ArgumentParser) -> None:
|
|
|
1179
1200
|
)
|
|
1180
1201
|
parser.add_argument("--preset", default="tunnel-safe", help="MCP policy preset")
|
|
1181
1202
|
parser.add_argument("--upstream", default="http://127.0.0.1:9000", help="upstream MCP HTTP server")
|
|
1203
|
+
_add_http_route_arg(parser)
|
|
1182
1204
|
parser.add_argument(
|
|
1183
1205
|
"--hostname",
|
|
1184
1206
|
help="provider hostname to use when --url is omitted; for --provider ssh this is the SSH target user@host",
|
|
@@ -1472,6 +1494,22 @@ def _add_share_invite_args(parser: argparse.ArgumentParser) -> None:
|
|
|
1472
1494
|
invite_create.add_argument("--ttl", default="30m", help="invite lease TTL, such as 30m, 2h, or 1d")
|
|
1473
1495
|
invite_create.add_argument("--max-calls", type=int, help="maximum number of allowed tools/call uses")
|
|
1474
1496
|
invite_create.add_argument("--client-name", help="MCP client config server name")
|
|
1497
|
+
invite_create.add_argument(
|
|
1498
|
+
"--renewable", action="store_true", help="allow this automation to renew/recover its short-lived lease"
|
|
1499
|
+
)
|
|
1500
|
+
invite_create.add_argument(
|
|
1501
|
+
"--renewal-ttl", default="7d", help="hard renewal authorization lifetime; defaults to 7d"
|
|
1502
|
+
)
|
|
1503
|
+
invite_create.add_argument(
|
|
1504
|
+
"--allow-subject", action="append", default=[], help="bind the invite to this authenticated subject"
|
|
1505
|
+
)
|
|
1506
|
+
invite_create.add_argument(
|
|
1507
|
+
"--allow-issuer", action="append", default=[], help="bind the invite to this OAuth issuer"
|
|
1508
|
+
)
|
|
1509
|
+
invite_create.add_argument("--allow-tenant", action="append", default=[], help="bind the invite to this tenant")
|
|
1510
|
+
invite_create.add_argument(
|
|
1511
|
+
"--allow-client-id", action="append", default=[], help="bind the invite to this OAuth workload client"
|
|
1512
|
+
)
|
|
1475
1513
|
add_compact_arg(invite_create)
|
|
1476
1514
|
|
|
1477
1515
|
invite_list = invite_subparsers.add_parser("list", help="list MCP share invites without revealing tokens")
|
|
@@ -8,6 +8,7 @@ from .credentials import attach_upstream_credentials, normalize_fabric_credentia
|
|
|
8
8
|
from .discovery import apply_fabric_discovery
|
|
9
9
|
from .events import normalize_event_sink_configs
|
|
10
10
|
from .gateway_templates import render_toml_array_table
|
|
11
|
+
from .http_routes import normalize_http_routes
|
|
11
12
|
from .mcp_protocol import DEFAULT_MCP_PROTOCOL_VERSION
|
|
12
13
|
from .policy_backoff import (
|
|
13
14
|
DEFAULT_POLICY_BACKOFF_EXCLUDE_REASON_CODES,
|
|
@@ -81,6 +82,7 @@ DEFAULT_MCP_PROXY_CONFIG = {
|
|
|
81
82
|
"upstream": "http://127.0.0.1:9000",
|
|
82
83
|
"upstream_credential": None,
|
|
83
84
|
"upstreams": [],
|
|
85
|
+
"http_routes": [],
|
|
84
86
|
"policy": "policy.snulbug/policy.lua",
|
|
85
87
|
"host": "127.0.0.1",
|
|
86
88
|
"port": 8080,
|
|
@@ -671,6 +673,9 @@ def normalize_mcp_proxy_config(config: Mapping[str, Any], *, base_dir: str | Pat
|
|
|
671
673
|
raise ValueError("mcp.proxy.resource_subscription_policy_action must be 'allow', 'warn', or 'block'")
|
|
672
674
|
if not normalized["streamable_http_endpoint_path"].startswith("/"):
|
|
673
675
|
normalized["streamable_http_endpoint_path"] = f"/{normalized['streamable_http_endpoint_path']}"
|
|
676
|
+
normalized["http_routes"] = normalize_http_routes(
|
|
677
|
+
normalized.get("http_routes", []), mcp_path=normalized["streamable_http_endpoint_path"]
|
|
678
|
+
)
|
|
674
679
|
normalized["streamable_http_allowed_origins"] = _normalize_string_list(
|
|
675
680
|
normalized.get("streamable_http_allowed_origins", []),
|
|
676
681
|
field="streamable_http_allowed_origins",
|
|
@@ -13,6 +13,7 @@ class GatewayTemplate:
|
|
|
13
13
|
|
|
14
14
|
proxy: Mapping[str, Any] = field(default_factory=dict)
|
|
15
15
|
upstreams: Sequence[Mapping[str, Any]] = ()
|
|
16
|
+
http_routes: Sequence[Mapping[str, Any]] = ()
|
|
16
17
|
auth: Mapping[str, Any] | None = None
|
|
17
18
|
event_sinks: Sequence[Mapping[str, Any]] = ()
|
|
18
19
|
fabric: Mapping[str, Any] | None = None
|
|
@@ -39,6 +40,8 @@ def render_gateway_toml(template: GatewayTemplate, *, header: str | None = None)
|
|
|
39
40
|
_append_block(lines, render_toml_table("mcp.proxy", template.proxy))
|
|
40
41
|
for upstream in template.upstreams:
|
|
41
42
|
_append_block(lines, render_toml_array_table("mcp.proxy.upstreams", upstream))
|
|
43
|
+
for route in template.http_routes:
|
|
44
|
+
_append_block(lines, render_toml_array_table("mcp.proxy.http_routes", route))
|
|
42
45
|
if template.auth is not None:
|
|
43
46
|
_append_block(lines, render_toml_table("mcp.auth", template.auth))
|
|
44
47
|
for sink in template.event_sinks:
|