How it works

A deterministic gate in front of your connectors.

Assistants speak MCP to Aggrete instead of to your systems, which are reachable only through it, so there is no path around the policy. And because assistants reach it by speaking standard MCP, there is no endpoint agent, browser extension or client change on anyone's device; control lives on the data path, not the laptop. Every call is evaluated against the policy and the caller's recent history. There is no model in the enforcement path, so a decision is reproducible and auditable.

1 · Identify
auth.py
The caller is derived from the OAuth token (JWT via JWKS/PEM) over HTTP. On stdio, identity is advisory.
2 · Accumulate
accumulator.py · entities.py
Person IDs are extracted from each result and tallied per user, per domain, with a TTL. Email is the canonical key across connectors.
3 · Decide
policy.py · pre_call / post_call
The call is matched to a policy domain and checked. A denied pre-call request never reaches the upstream; post-call denials redact the result.
4 · Audit
audit.jsonl
One JSON object per decision, to stderr and a file. The refusal carries the clause and a remediation path, not an opaque error.
Architecture

Where the pieces sit.

Connectors are network-fenced so only the proxy can reach them; the proxy holds the credentials, never the user. An optional read-only console reads the two files the proxy writes.

Assistants
Claude, Copilot, ChatGPT, Cursor
Connect over MCP and sign in once. Every tool call on a user's behalf goes to the proxy, never to a system directly.
Aggrete proxy
mcp.yourco.com
Knows who is asking, what they have already seen today, and the policy. Refuses before fetching. One audit line per decision.
Your systems
HR, finance, Drive, Slack, CRM
Reachable only from the proxy host. Bring connectors as stdio commands or HTTP upstreams; a service-account Drive connector ships in the box.
Policy & state
coc.yaml · Redis
Policy is one versioned YAML file. Per-user state is in-memory for one node, or Redis across replicas via the Helm chart.

Full diagram, the Google Drive connector, and the OAuth sign-in flow are documented in the README.

Policy

Rule types.

New rule types are added in Engine.pre_call / Engine.post_call and dispatched by the type: key. Prefer pre-call decidability where possible. Every rule takes optional scoping: allowed_users, blocked_users, since, until.

typephasewhat it decides
domain_joinpreRefuses the call that would complete a forbidden set of domains for one person (e.g. personnel + budget + rotation), with entity overlap required by default.
domain_blockpreA domain that assistants may never reach (legal hold, privileged material).
wallpreWho may reach a domain, and until when: embargoes, investigation walls, privilege, restricted health/absence data.
min_grouppostAggregate-only answers: a result naming fewer than k people is treated as one person's data.
self_comparisonpostThe requester's own record placed next to colleagues' in the same domain, the precondition for benchmarking teammates.
entity_budgetpostCaps the number of distinct people one user may accumulate in a domain over the window.
flowprePrompt-injection shield: once a session has read untrusted content, it may not reach an egress domain. Any write counts as egress.
arg_matchpreDecides a call from its arguments, not just its kind: allow an export scoped to your own team, refuse the same export scoped to the whole company. Operators: equals, in, regex, gt, lt, exists, missing.

Writes (create, update, upload, post, send) are governed as egress; a rule can target writes only with applies: write. Rules group into protection packs you toggle on and off. Actions start at alert and move to deny after tuning; the test harness requires an allow and a deny test per rule.

Beyond the rule types.

The rules decide what a request may assemble. These run around every call, each deterministic and opt-in from the config, and each writes to the same audit line.

controlwhat it does
checkAsk whether a sequence of calls would be allowed before running any of them: the decision, the rule, the clause and the fix, with nothing fetched. A built-in aggrete__check tool.
tool_integrityFingerprint every upstream tool on first sight; flag a later change to its description or schema (a rug pull), and scan descriptions for hidden instructions (poisoning). Alert or block.
rate_limitA per-user ceiling on calls per window, shared across replicas via Redis. A denial-of-wallet and abuse control.
scan_inboundScan tool arguments for credential-shaped strings and block or mask them before they reach an upstream.
audit_forwardStream each audit row to Splunk, Elastic, Datadog or syslog as it is written, off the hot path. The hash-chained local log stays the system of record.
aggrete-lintStatic checks on the policy for fail-open and dead rules: a critical rule that only alerts, an expired embargo, an unreachable domain. Exits non-zero for CI.