# Security policies and management API

## Effective access

Access is bounded by the real issuer's current permissions, original tenant/module/read-write grant, organization policy, connection policy, record authorization, underlying business rules and usage limits. Narrowing policy cannot enlarge the original grant. Enforcement is checked again at execution, including approved writes.

In Settings → MCP → Security, use Organization policy for tenant-wide controls and Connection permissions for narrower grants. Monitoring exposes accountable users, last activity, expiry, alerts, affected-record references and quota usage.

## Policy replacement semantics

Policy PUT endpoints **replace** writable policy fields: they are not PATCH. Missing writable properties reset to validator defaults. Unknown properties and invalid tool/rule names are rejected. Always GET the latest policy, merge your intended changes locally and PUT the full writable result. An incomplete `{enabled:false}` also resets other writable policy fields to defaults.

**Exception: platform-managed usage ceilings are preserved.** Omit `organizationLimits`, `userLimits` and `connectionLimits` from tenant PUT requests. Older clients may echo unchanged values, but attempting to change these fields (including sending stale ceilings) returns 403. This prevents an ordinary security edit from changing or resetting platform allocations.

## Platform-managed usage budgets

### Adaptive server protection

Platform Admin → MCP Administration → Server protection manages a separate
platform-wide admission policy through GET/PUT `/api/platform/mcp/protection`.
Tenant admins cannot read or change this configuration. A shared PostgreSQL token
bucket and expiring, heartbeated request leases cap admission across server
instances; local rate, IP and concurrency guards run before body parsing and
authentication. MCP transport, OAuth and MCP file paths receive this protection.
Management controls remain available when admission is paused.

Every two seconds, each instance samples event-loop delay/utilization, memory
pressure and database queuing. Busy instances reduce their own throughput,
burst and concurrency capacity by 30% per busy sample, with a 10% minimum.
Healthy samples recover five percentage points at a time. Severe pressure rejects
new admission entirely. Live telemetry is explicitly for the responding instance;
shared admission counts and burst tokens are global. These guards do not replace
the organization's, user's or connection's persistent usage budgets.

Rate/concurrency refusals return 429 with `Retry-After`; platform pause or an
unavailable shared admission store returns 503. Body reads have an absolute
15-second deadline and requests have a configurable HTTP deadline. An HTTP
timeout does not prove an already-running business write was cancelled:
check activity/approval status before retrying a mutation. Transport work retains
its capacity until its handler settles, including after client disconnect.

Configuration is persisted and audited. The new table is created only through
normal schema migrations/Publish, never deployment-time or request-time DDL.

Platform Admin → MCP Administration → Usage budgets lets platform administrators
select an organization and manage its organization, per-user and per-connection
ceilings for requests/minute, concurrent operations, changes/hour and download
bytes/hour. Existing allocations are retained; this ownership change does not
reset them. Individual connection policies can impose tighter limits, and
runtime enforcement always applies the platform ceilings.

GET `/api/platform/tenants/{tenantId}/mcp-usage-limits` returns `tenantId`,
`tenantName`, `organizationLimits`, `userLimits` and `connectionLimits`.
PUT at the same endpoint accepts the three limit groups only, validates them,
preserves the organization's other security rules, and records before/after
values and the actor in MCP activity. Authentication and platform authorization
are required; normal tenant admins cannot use either endpoint. Existing platform
API-key scope restrictions also apply.

The exact machine-readable validators and defaults are available at `/api/mcp-public/security-schema` and `/api/mcp-public/reference`. Use these rather than copying a stale schema from this guide.

## Connection policy fields

| Fields | Meaning / default |
| --- | --- |
| `modules` | Further narrowing; empty means retain the original module grant |
| `actions` | read/create/update/delete; all four default to eligible, bounded by original read/guarded-write access |
| `allowedTools`, `deniedTools` | Exact tool names; empty allowedTools means no extra allowlist; denial wins |
| `siteIds`, `projectIds`, `recordIds` | UUID allowlists; recordIds maps module names to record UUIDs; empty lists impose no extra narrowing |
| `approvalRules` | Exact tool, action or sensitive/ordinary family → required or not_required |
| `allowDocuments`, `allowExports` | Both default false on a connection; organization must also permit sharing |
| `maskedFields` | Up to 50 case-insensitive JSON keys/dotted paths returned as `[masked]` |
| `maxExportRows` | 500 by default; maximum 10,000 |
| `maxResponseBytes` | 2,097,152 by default and maximum |
| `maxBatchChanges` | 25 by default; maximum 100 |
| `limits` | Persistent request/concurrency/change/download budgets |

UUID scope lists allow at most 200 entries each. Unsupported scopes fail closed: a child/document/report operation that cannot safely relate its records to the allowlist is denied, not broadened. Exact existing-record scopes do not authorize creating arbitrary new records. Scoped lists omit an unfiltered global total. Use both scope-filtered pagination and warnings accurately.

Field masks recursively match object keys or dotted paths; array positions are not part of those paths. Organization and connection masks are combined. Masks do not redact attachment contents or guarantee that a separately named aggregate cannot reveal a derived fact.

Array results exceeding 100 entries require both export grants. Row/response caps still apply when exports are enabled. Oversized datasets are rejected rather than silently truncated by the security layer. Outcome reports may independently disclose bounded/incomplete scans.

## Organization policy

Organization fields include `enabled`, `restrictClients`, `approvedClientIds`, `approvalRules`, `approvalExpiryMinutes`, `allowDocuments`, `allowExports`, `maskedFields`, row/response/batch caps, `organizationLimits`, `userLimits` and `connectionLimits`.

Defaults: enabled true, client restriction false, approved clients empty, approval expiry 1,440 minutes (maximum 24 hours), organization document/export eligibility true. This eligibility does not opt connections into sharing.

When enabled, client restriction permits only approved actual OAuth registration IDs. Names and redirect labels are self-declared, not brand verification. Manual bearer connections are blocked under that restriction.

## Approval precedence

For a mutation the first matching rule wins:

1. Connection exact tool.
2. Connection action.
3. Connection sensitive/ordinary family.
4. Organization exact tool.
5. Organization action.
6. Organization sensitive/ordinary family.
7. Implemented tool's default sensitive classification.

Reads do not require MCP human approval. GET is classified read; DELETE and delete_/remove_ names are delete; PATCH and replace_/update_/assign_/set_/reorder_/send_ names are update; other mutations are create.

Users may explicitly select `not_required` for chosen changes. This only disables the extra MCP review—not existing business approvals, locks or validation. A required approval binds immutable arguments, expires at the configured interval and is single-use. Organization or connection policy updates reject pending/approved requests so they require fresh review.

## Persistent quota defaults

| Scope | Requests/minute | Concurrent client RPCs | Changes/hour | Download bytes/hour |
| --- | --- | --- | --- | --- |
| Organization | 600 | 20 | 500 | 209,715,200 |
| Issuing user | 240 | 8 | 200 | 104,857,600 |
| Connection | 120 | 4 | 100 | 52,428,800 |

All relevant dimensions must have capacity. Connection limits cannot exceed organization connection ceilings. Requests use fixed one-minute buckets; changes and bytes use fixed one-hour buckets, not rolling windows. Concurrency uses durable expiring leases with renewal. Global app/IP rate limits also remain in force.

Change volume is charged before executing a mutation, so failed attempts can consume budget. Batch volume uses the largest submitted top-level array, not a recursive count of all affected database rows. JSON output accounting conservatively doubles serialized bytes for text/structured representations; managed file downloads are charged separately. These are enforcement budgets, not provider token or billing counts.

## Management endpoints

All endpoints below require a signed-in owner/admin browser session in the selected tenant. Obtain/send CSRF according to the app's existing session flow. Do not use an MCP bearer token, invent a tenant ID, or embed a privileged admin session in an AI widget.

| Method and path | Payload / result |
| --- | --- |
| GET `/api/mcp-admin/overview` | Connections, open/recent approvals, activity, tool definitions, provider readiness |
| POST `/api/mcp-admin/connections` | name, access (`read`/`guarded_write`), modules, expiresInDays (1–90), optional connection policy; returns `{connection,token}` once |
| POST `/api/mcp-admin/connections/:id/revoke` | No replacement credential; returns revoked status |
| POST `/api/mcp-admin/approvals/:id/review` | `{decision:"approve"}` or `{decision:"reject",reason:"..."}`; reviews without executing |
| GET `/api/mcp-admin/security` | `{policy,connections,alerts,usage,clients,activity}` |
| PUT `/api/mcp-admin/security` | Full writable organization policy; platform usage ceilings preserved; returns saved policy |
| PUT `/api/mcp-admin/connections/:id/policy` | Full connection policy; returns saved policy |
| POST `/api/mcp-admin/security/alerts/:id/acknowledge` | Returns acknowledged status; tenant-bound |

Security connections/activity use database-style snake_case fields, whereas the older overview uses ORM-style camelCase fields. Alerts/activity are bounded to the latest 200 entries each; usage to 500 recent buckets; registered-client metadata to 500 entries. No credentials or client secrets are returned. Client registrations are server-wide public registrations, not records proving a client's organization.

Example organization update, after fetching the latest policy:

```json
{
  "approvalRules": {"delete":"required","update_task":"not_required"},
  "approvalExpiryMinutes": 60
}
```

**Merge these fields into the fetched full policy before PUT.** Likewise merge `{"actions":["read"],"allowedTools":["list_sites","get_site"],"maskedFields":["address"]}` into the fetched connection policy for a narrowly readable connection.

## Shutdown and alerts

Setting `enabled:false` permanently revokes all existing connections and rejects pending/approved approvals. Re-enabling does not restore tokens; create/link new connections. Revocation does not recall data already shared with a provider or undo an already committed write.

In-app alerts cover repeated denied actions, suspicious expired/revoked/client access, quota exceedance, large downloads and repeated deletion/replace activity. Thresholds are heuristic signals, not a complete intrusion detection guarantee. Alerts can be acknowledged; acknowledgement does not change permissions.

No email, SMS, Slack or external alert delivery is currently provided by this MCP dashboard.
