diff --git a/README.md b/README.md index fc014ef6..3e8879e1 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,28 @@ To reduce the chance of running in to this issue: - Try to be specific, keep your queries concise. - If a single request calls multiple tools, try to to break it into several smaller tool calls to keep the responses short. +## Tool-level policy enforcement + +These MCP servers expose tools that can create and delete D1 databases, R2 storage buckets, KV namespaces, and Hyperdrive configurations — as well as execute arbitrary commands in containers. Cloudflare OAuth controls which tools are available, but not how often or how aggressively an agent uses them once access is granted. + +You can add rate limits, daily caps, and access control by wrapping the server with [PolicyLayer Intercept](https://github.com/policylayer/intercept), an open-source MCP proxy: + +```sh +npx -y @policylayer/intercept \ + --policy policies/recommended.yaml \ + -- npx mcp-remote https://bindings.mcp.cloudflare.com/mcp +``` + +Three policy presets are included in [`/policies`](/policies): + +| Policy | Description | +|--------|-------------| +| `recommended.yaml` | Rate limits on writes, blocks destructive operations (delete databases, buckets, namespaces), reads allowed freely | +| `strict.yaml` | Default deny — only read and observability tools allowed unless explicitly opted in | +| `permissive.yaml` | Everything allowed, rate limits on destructive and resource-creation operations | + +Policies are YAML files you can customise. See the [Intercept docs](https://github.com/policylayer/intercept) for the full policy reference. + ## Paid Features Some features may require a paid Cloudflare Workers plan. Ensure your Cloudflare account has the necessary subscription level for the features you intend to use. diff --git a/policies/permissive.yaml b/policies/permissive.yaml new file mode 100644 index 00000000..4119ba57 --- /dev/null +++ b/policies/permissive.yaml @@ -0,0 +1,81 @@ +version: "1" +description: "Cloudflare MCP — permissive policy. All tools allowed. Rate limits on destructive and resource-creation operations." +default: allow + +tools: + # Destructive tools — rate limited even in permissive mode + kv_namespace_delete: + rules: + - name: "rate-limit" + rate_limit: "5/hour" + on_deny: "Max 5 KV namespace deletions per hour" + + r2_bucket_delete: + rules: + - name: "rate-limit" + rate_limit: "5/hour" + on_deny: "Max 5 R2 bucket deletions per hour" + + d1_database_delete: + rules: + - name: "rate-limit" + rate_limit: "5/hour" + on_deny: "Max 5 D1 database deletions per hour" + + hyperdrive_config_delete: + rules: + - name: "rate-limit" + rate_limit: "5/hour" + on_deny: "Max 5 Hyperdrive config deletions per hour" + + container_file_delete: + rules: + - name: "rate-limit" + rate_limit: "10/hour" + on_deny: "Max 10 container file deletions per hour" + + # Resource creation — rate limited + kv_namespace_create: + rules: + - name: "rate-limit" + rate_limit: "20/hour" + on_deny: "Max 20 KV namespace creations per hour" + + r2_bucket_create: + rules: + - name: "rate-limit" + rate_limit: "20/hour" + on_deny: "Max 20 R2 bucket creations per hour" + + d1_database_create: + rules: + - name: "rate-limit" + rate_limit: "20/hour" + on_deny: "Max 20 D1 database creations per hour" + + hyperdrive_config_create: + rules: + - name: "rate-limit" + rate_limit: "20/hour" + on_deny: "Max 20 Hyperdrive config creations per hour" + + # Container execution — rate limited + container_exec: + rules: + - name: "rate-limit" + rate_limit: "120/hour" + on_deny: "Max 120 container commands per hour" + + # GraphQL queries — rate limited + graphql_query: + rules: + - name: "rate-limit" + rate_limit: "120/hour" + on_deny: "Max 120 GraphQL queries per hour" + + # Global safety net + "*": + rules: + - name: "global-rate-limit" + rate_limit: "120/minute" + on_deny: "Global rate limit — max 120 tool calls per minute" diff --git a/policies/recommended.yaml b/policies/recommended.yaml new file mode 100644 index 00000000..5aa0f588 --- /dev/null +++ b/policies/recommended.yaml @@ -0,0 +1,130 @@ +version: "1" +description: "Cloudflare MCP — recommended policy. Rate limits on writes, blocks destructive operations, reads allowed freely." +default: allow + +tools: + # Destructive tools — blocked by default + # These permanently destroy databases, storage buckets, and KV namespaces. + # Uncomment and add rate limits if you need them. + kv_namespace_delete: + rules: + - action: deny + on_deny: "KV namespace deletion blocked by policy — remove this rule if you need to delete namespaces" + + r2_bucket_delete: + rules: + - action: deny + on_deny: "R2 bucket deletion blocked by policy — remove this rule if you need to delete buckets" + + d1_database_delete: + rules: + - action: deny + on_deny: "D1 database deletion blocked by policy — remove this rule if you need to delete databases" + + hyperdrive_config_delete: + rules: + - action: deny + on_deny: "Hyperdrive config deletion blocked by policy — remove this rule if you need to delete configs" + + container_file_delete: + rules: + - action: deny + on_deny: "Container file deletion blocked by policy" + + # Write tools — moderate rate limits + kv_namespace_create: + rules: + - name: "burst-limit" + rate_limit: "5/minute" + on_deny: "Slow down — max 5 KV namespace creations per minute" + - name: "hourly-cap" + rate_limit: "20/hour" + on_deny: "Max 20 KV namespace creations per hour" + + r2_bucket_create: + rules: + - name: "burst-limit" + rate_limit: "3/minute" + on_deny: "Slow down — max 3 R2 bucket creations per minute" + - name: "hourly-cap" + rate_limit: "10/hour" + on_deny: "Max 10 R2 bucket creations per hour" + + d1_database_create: + rules: + - name: "burst-limit" + rate_limit: "3/minute" + on_deny: "Slow down — max 3 D1 database creations per minute" + - name: "hourly-cap" + rate_limit: "10/hour" + on_deny: "Max 10 D1 database creations per hour" + + hyperdrive_config_create: + rules: + - name: "burst-limit" + rate_limit: "3/minute" + on_deny: "Slow down — max 3 Hyperdrive config creations per minute" + - name: "hourly-cap" + rate_limit: "10/hour" + on_deny: "Max 10 Hyperdrive config creations per hour" + + hyperdrive_config_edit: + rules: + - name: "rate-limit" + rate_limit: "10/hour" + on_deny: "Max 10 Hyperdrive config edits per hour" + + kv_namespace_update: + rules: + - name: "rate-limit" + rate_limit: "20/hour" + on_deny: "Max 20 KV namespace updates per hour" + + set_active_account: + rules: + - name: "rate-limit" + rate_limit: "10/hour" + on_deny: "Max 10 account switches per hour" + + # Container tools — rate limited + container_exec: + rules: + - name: "burst-limit" + rate_limit: "10/minute" + on_deny: "Slow down — max 10 container commands per minute" + - name: "hourly-cap" + rate_limit: "60/hour" + on_deny: "Max 60 container commands per hour" + + container_file_write: + rules: + - name: "rate-limit" + rate_limit: "30/hour" + on_deny: "Max 30 container file writes per hour" + + # D1 queries — rate limited to prevent runaway queries + d1_database_query: + rules: + - name: "burst-limit" + rate_limit: "10/minute" + on_deny: "Slow down — max 10 D1 queries per minute" + - name: "hourly-cap" + rate_limit: "120/hour" + on_deny: "Max 120 D1 queries per hour" + + # GraphQL queries — rate limited + graphql_query: + rules: + - name: "burst-limit" + rate_limit: "10/minute" + on_deny: "Slow down — max 10 GraphQL queries per minute" + - name: "hourly-cap" + rate_limit: "60/hour" + on_deny: "Max 60 GraphQL queries per hour" + + # Global safety net + "*": + rules: + - name: "global-rate-limit" + rate_limit: "60/minute" + on_deny: "Global rate limit — max 60 tool calls per minute across all Cloudflare tools" diff --git a/policies/strict.yaml b/policies/strict.yaml new file mode 100644 index 00000000..8a72fab8 --- /dev/null +++ b/policies/strict.yaml @@ -0,0 +1,476 @@ +version: "1" +description: "Cloudflare MCP — strict policy. Default deny. Only read and observability tools are allowed." +default: deny + +tools: + # Account — read only + accounts_list: + rules: + - action: allow + rate_limit: "10/minute" + + # Workers — read only + workers_list: + rules: + - action: allow + rate_limit: "30/minute" + + workers_get_worker: + rules: + - action: allow + rate_limit: "30/minute" + + workers_get_worker_code: + rules: + - action: allow + rate_limit: "20/minute" + + # KV — read only + kv_namespaces_list: + rules: + - action: allow + rate_limit: "30/minute" + + kv_namespace_get: + rules: + - action: allow + rate_limit: "30/minute" + + # R2 — read only + r2_buckets_list: + rules: + - action: allow + rate_limit: "30/minute" + + r2_bucket_get: + rules: + - action: allow + rate_limit: "30/minute" + + # D1 — read only (queries allowed, creation/deletion blocked) + d1_databases_list: + rules: + - action: allow + rate_limit: "30/minute" + + d1_database_get: + rules: + - action: allow + rate_limit: "30/minute" + + d1_database_query: + rules: + - action: allow + rate_limit: "10/minute" + + # Hyperdrive — read only + hyperdrive_configs_list: + rules: + - action: allow + rate_limit: "30/minute" + + hyperdrive_config_get: + rules: + - action: allow + rate_limit: "30/minute" + + # Observability — read only + query_worker_observability: + rules: + - action: allow + rate_limit: "20/minute" + + observability_keys: + rules: + - action: allow + rate_limit: "20/minute" + + observability_values: + rules: + - action: allow + rate_limit: "20/minute" + + # Builds — read only + workers_builds_list_builds: + rules: + - action: allow + rate_limit: "20/minute" + + workers_builds_get_build: + rules: + - action: allow + rate_limit: "20/minute" + + workers_builds_get_build_logs: + rules: + - action: allow + rate_limit: "20/minute" + + workers_builds_set_active_worker: + rules: + - action: allow + rate_limit: "10/minute" + + # Logpush — read only + logpush_jobs_by_account_id: + rules: + - action: allow + rate_limit: "10/minute" + + # AI Gateway — read only + list_gateways: + rules: + - action: allow + rate_limit: "20/minute" + + list_logs: + rules: + - action: allow + rate_limit: "20/minute" + + get_log_details: + rules: + - action: allow + rate_limit: "20/minute" + + get_log_request_body: + rules: + - action: allow + rate_limit: "20/minute" + + get_log_response_body: + rules: + - action: allow + rate_limit: "20/minute" + + # AutoRAG — read only + list_rags: + rules: + - action: allow + rate_limit: "20/minute" + + search: + rules: + - action: allow + rate_limit: "20/minute" + + ai_search: + rules: + - action: allow + rate_limit: "20/minute" + + # Audit Logs — read only + auditlogs_by_account_id: + rules: + - action: allow + rate_limit: "10/minute" + + # DNS Analytics — read only + zones_list: + rules: + - action: allow + rate_limit: "20/minute" + + dns_report: + rules: + - action: allow + rate_limit: "10/minute" + + show_account_dns_settings: + rules: + - action: allow + rate_limit: "10/minute" + + show_zone_dns_settings: + rules: + - action: allow + rate_limit: "10/minute" + + # Documentation — read only + search_cloudflare_documentation: + rules: + - action: allow + rate_limit: "30/minute" + + # Browser Rendering — read only + get_url_html_content: + rules: + - action: allow + rate_limit: "10/minute" + + get_url_markdown: + rules: + - action: allow + rate_limit: "10/minute" + + get_url_screenshot: + rules: + - action: allow + rate_limit: "10/minute" + + # GraphQL — schema browsing only, no query execution + graphql_schema_search: + rules: + - action: allow + rate_limit: "20/minute" + + graphql_schema_overview: + rules: + - action: allow + rate_limit: "10/minute" + + graphql_type_details: + rules: + - action: allow + rate_limit: "20/minute" + + graphql_complete_schema: + rules: + - action: allow + rate_limit: "5/minute" + + graphql_api_explorer: + rules: + - action: allow + rate_limit: "10/minute" + + # Container — read only + container_ping: + rules: + - action: allow + rate_limit: "10/minute" + + container_files_list: + rules: + - action: allow + rate_limit: "20/minute" + + container_file_read: + rules: + - action: allow + rate_limit: "20/minute" + + # DEX — read only + dex_list_tests: + rules: + - action: allow + rate_limit: "10/minute" + + dex_test_statistics: + rules: + - action: allow + rate_limit: "10/minute" + + dex_http_test_details: + rules: + - action: allow + rate_limit: "10/minute" + + dex_traceroute_test_details: + rules: + - action: allow + rate_limit: "10/minute" + + dex_traceroute_test_network_path: + rules: + - action: allow + rate_limit: "10/minute" + + dex_traceroute_test_result_network_path: + rules: + - action: allow + rate_limit: "10/minute" + + dex_list_remote_captures: + rules: + - action: allow + rate_limit: "10/minute" + + dex_list_remote_warp_diag_contents: + rules: + - action: allow + rate_limit: "10/minute" + + dex_explore_remote_warp_diag_output: + rules: + - action: allow + rate_limit: "10/minute" + + dex_fleet_status_live: + rules: + - action: allow + rate_limit: "10/minute" + + dex_fleet_status_over_time: + rules: + - action: allow + rate_limit: "10/minute" + + dex_fleet_status_logs: + rules: + - action: allow + rate_limit: "10/minute" + + dex_list_warp_change_events: + rules: + - action: allow + rate_limit: "10/minute" + + dex_list_colos: + rules: + - action: allow + rate_limit: "10/minute" + + # Radar — all tools are read-only + get_ai_data: + rules: + - action: allow + rate_limit: "20/minute" + + get_annotations: + rules: + - action: allow + rate_limit: "20/minute" + + get_outages: + rules: + - action: allow + rate_limit: "20/minute" + + get_outages_by_location: + rules: + - action: allow + rate_limit: "20/minute" + + get_traffic_anomalies: + rules: + - action: allow + rate_limit: "20/minute" + + get_traffic_anomalies_by_location: + rules: + - action: allow + rate_limit: "20/minute" + + get_as112_data: + rules: + - action: allow + rate_limit: "20/minute" + + list_autonomous_systems: + rules: + - action: allow + rate_limit: "20/minute" + + get_as_details: + rules: + - action: allow + rate_limit: "20/minute" + + get_as_set: + rules: + - action: allow + rate_limit: "20/minute" + + get_as_relationships: + rules: + - action: allow + rate_limit: "20/minute" + + get_bgp_hijacks: + rules: + - action: allow + rate_limit: "20/minute" + + get_bgp_leaks: + rules: + - action: allow + rate_limit: "20/minute" + + get_bgp_route_stats: + rules: + - action: allow + rate_limit: "20/minute" + + get_bgp_timeseries: + rules: + - action: allow + rate_limit: "20/minute" + + get_bgp_top_ases: + rules: + - action: allow + rate_limit: "20/minute" + + get_bgp_top_prefixes: + rules: + - action: allow + rate_limit: "20/minute" + + get_bgp_moas: + rules: + - action: allow + rate_limit: "20/minute" + + get_bgp_pfx2as: + rules: + - action: allow + rate_limit: "20/minute" + + get_bgp_ip_space_timeseries: + rules: + - action: allow + rate_limit: "20/minute" + + get_bgp_routes_realtime: + rules: + - action: allow + rate_limit: "20/minute" + + get_bgp_routing_table_ases: + rules: + - action: allow + rate_limit: "20/minute" + + get_bgp_top_ases_by_prefixes: + rules: + - action: allow + rate_limit: "20/minute" + + get_bots_data: + rules: + - action: allow + rate_limit: "20/minute" + + list_bots: + rules: + - action: allow + rate_limit: "20/minute" + + get_bot_details: + rules: + - action: allow + rate_limit: "20/minute" + + get_bots_crawlers_data: + rules: + - action: allow + rate_limit: "20/minute" + + get_certificate_transparency_data: + rules: + - action: allow + rate_limit: "20/minute" + + list_ct_authorities: + rules: + - action: allow + rate_limit: "20/minute" + + get_ct_authority_details: + rules: + - action: allow + rate_limit: "20/minute" + + # Everything else blocked by default: deny + # To allow writes, copy rules from recommended.yaml