Files
mcp-gateway-nexus/docs/modules/14-agent-profiles.md
drjones 8d3ffef920 docs: initial architecture and design for MCP Nexus
MCP Nexus is a self-hosted control plane for MCP servers — "Kubernetes for
MCP." Agents connect to one endpoint; Nexus discovers services, installs the
right MCP servers, aggregates them behind a namespaced router, secures access
with auth/RBAC, and heals/updates them via a reconcile loop.

This first commit is design-phase only (no runnable code yet):
- README.md            project front door + module map
- docs/ARCHITECTURE.md target design: tenets, two-plane split, reconcile
                       loop, domain types, storage, security, deployment
- docs/ROADMAP.md      phased delivery (foundations -> walking skeleton ->
                       discover+install -> secure -> operate -> extend/scale)
- docs/modules/01-20   one design doc per module, all cross-linked

Backend stack decision: Go (single static binary, embedded SQLite + embedded
React dashboard). Repo initialized in /root with a whitelist .gitignore so
only project files are tracked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 04:38:27 +00:00

121 lines
9.6 KiB
Markdown

# AI Agent Profiles
> Module 14 · Plane: Data · Roadmap phase: 3
> Part of [MCP Nexus architecture](../ARCHITECTURE.md).
## Purpose
An **AgentProfile** (Architecture §7) is the agent-facing binding of *identity → roles → visible tool set*. Where [Authentication](06-authentication.md) proves *who* is connecting and [RBAC](08-rbac.md) is the general role→tool engine, an AgentProfile is the first-class object that ties a specific AI agent — Claude, Cursor, VSCode, OpenWebUI, Codex, Aider, Continue.dev — to the roles it holds and therefore the exact tools it may see and call at the [Gateway](04-gateway.md). It is what makes "one endpoint, many agents" (tenet #1) concrete: every connecting agent resolves to a profile, and the profile decides its worldview.
## Responsibilities
- Own the **AgentProfile** model: `{id, identity, allowed_roles}` (Architecture §7), plus display metadata (agent kind, description, owner) and an association to one or more credentials.
- **Identify** a connecting agent: map the credential presented at the MCP `initialize` handshake (an API key, token, or client identifier resolved by [Authentication](06-authentication.md)) to exactly one AgentProfile.
- **Resolve** the profile to a concrete visible tool set by handing its `allowed_roles` to the [RBAC](08-rbac.md) `Enforcer` — the profile does not compute tool visibility itself; it *binds* the identity to roles that RBAC expands.
- Provide the Router with the profile's resolved `AllowedSet` so `tools/list` and `tools/call` are scoped per agent (enforcement stays in RBAC/Router, per tenet #5).
- Maintain per-profile operational metadata: last-seen, connection count, and (optionally) per-profile rate-limit / quota hints for [Security](19-security.md).
- Support **provisioning**: create a profile, mint/attach its agent credential (via [Authentication](06-authentication.md)), and assign roles — the operator workflow for onboarding a new agent.
## Non-goals
- **Authenticating the credential.** Verifying the presented secret is [Authentication](06-authentication.md); the profile consumes the resolved `Principal`.
- **Computing tool visibility.** The allow/deny expansion is [RBAC](08-rbac.md); the profile only supplies the roles.
- **Being a human-user object.** Human operators are `Users` in the Identity store; profiles model *agents*. Both share the RBAC engine, but a profile is the agent binding specifically (this is how it differs from raw RBAC).
- **Per-tool parameter policy.** Fine-grained call constraints, if any, belong to [RBAC](08-rbac.md)/[Security](19-security.md).
- **Managing agent-side config.** Nexus does not push settings into Claude/Cursor; it presents an endpoint and scopes it.
## Interfaces
```go
// The agent-facing binding of identity -> roles (Architecture §7).
type AgentProfile struct {
ID string // "profile:claude-primary"
Identity string // stable agent identity key, matches Principal.ID for KindAgent
Kind string // "claude" | "cursor" | "vscode" | "openwebui" | "codex" | "aider" | "continue"
DisplayName string
AllowedRoles []string // role names expanded by RBAC
CredentialIDs []string // API keys / tokens that resolve to this profile
CreatedAt time.Time
LastSeen time.Time
}
type ProfileStore interface {
Upsert(ctx context.Context, p AgentProfile) error
Get(ctx context.Context, id string) (AgentProfile, error)
// ByCredential maps an authenticated credential -> its profile (identify step).
ByCredential(ctx context.Context, credentialID string) (AgentProfile, error)
List(ctx context.Context) ([]AgentProfile, error)
}
// Resolver ties identify -> roles -> visible tool set via RBAC.
type Resolver interface {
// Identify: authenticated Principal (an agent) -> its profile.
Identify(ctx context.Context, p Principal) (AgentProfile, error)
// VisibleTools: profile's roles expanded by the RBAC Enforcer against the live catalog.
VisibleTools(ctx context.Context, prof AgentProfile) (AllowedSet, error)
}
```
HTTP/API surface (control-plane API, behind Auth, RBAC-gated; Admin/operator):
- `GET /api/v1/agents` · `POST /api/v1/agents` — list / create profiles.
- `GET /api/v1/agents/{id}` · `PUT /api/v1/agents/{id}` · `DELETE /api/v1/agents/{id}`.
- `POST /api/v1/agents/{id}/roles` — set the profile's `allowed_roles`.
- `POST /api/v1/agents/{id}/credentials` — mint/attach an agent credential (delegates to [Authentication](06-authentication.md)).
- `GET /api/v1/agents/{id}/tools` — debug: the resolved visible tool set for this profile.
MCP data-plane integration: on `initialize`, the Router calls `Identify(Principal)` to pin the profile to the session; `tools/list`/`tools/call` are then scoped by the profile's `VisibleTools` (enforced in RBAC/Router).
Identify → resolve flow (per agent session):
```
1. Agent (e.g. Cursor) opens an MCP session and sends `initialize`
carrying its credential (API key / token on Streamable HTTP).
2. Authentication (Module 06) verifies it → Principal{Kind: agent, ID}.
3. Resolver.Identify(Principal) → ProfileStore.ByCredential → AgentProfile
- no match → fail closed (empty set) unless a guest profile is set.
4. Resolver.VisibleTools(profile): hand profile.AllowedRoles to the RBAC
Enforcer, which expands them against the live tool catalog (Module 05)
→ AllowedSet (the agent's worldview).
5. Profile + AllowedSet pinned to the MCP session.
6. tools/list → RBAC.Filter(AllowedSet); tools/call → RBAC.Can(...).
7. last_seen updated; provisioning/role changes audited (Module 19).
```
Example: profile `claude-primary` (identity `agent:claude-01`) holds roles
`[Developer, Home Automation]`; RBAC expands these to `github.*`, `postgres.*`,
`filesystem.*`, `homeassistant.*` — so this agent sees exactly those namespaces
and nothing from Networking/Finance/Admin.
## Data
- **Reads/writes** `AgentProfile` rows in the **Identity** store (Architecture §8), alongside `Users`, `Roles`, and API keys; a credential→profile index supports the identify step.
- **Reads** the [RBAC](08-rbac.md) role definitions to expand `allowed_roles`, and the [Dynamic Tool Registry](05-dynamic-tool-registry.md) (via RBAC) for the live catalog.
- **Writes** profile provisioning and role-binding changes to the **Audit** store.
- Module-local: `last_seen`/connection counters updated on the hot path (cheap, async).
## Dependencies
- Credential verification and the `Principal` come from [Authentication](06-authentication.md).
- Role expansion and enforcement come from [RBAC](08-rbac.md); profiles supply the roles.
- Visibility is scoped against the [Dynamic Tool Registry](05-dynamic-tool-registry.md).
- Enforced at the [Gateway](04-gateway.md)/Router on the hot path.
- Managed through the [Web Dashboard](12-web-dashboard.md) (agent onboarding).
- Provisioning + access audited per [Security](19-security.md).
## Failure modes & handling
| Failure | Behavior |
|---|---|
| Credential resolves to no profile | Treated as an unrecognized agent → no roles → empty visible set (**fail closed**); connection allowed only if a default/guest profile is explicitly configured. |
| One credential maps to two profiles | Rejected at provisioning; the credential→profile index is unique. Ambiguity is a configuration error, not a runtime coin-flip. |
| Profile references a deleted role | Missing role contributes nothing (no phantom access); resolution logs the dangling reference. |
| Roles changed while agent connected | RBAC cache invalidated; next `tools/list`/`tools/call` reflects the new set without dropping the session. |
| Profile deleted while agent connected | Session's pinned profile is invalidated; subsequent requests fail closed and the agent must reconnect. |
| Identity store unavailable | Fail closed — unresolved profile means no tools; surfaced via [Notifications](13-notifications.md). |
## Security notes
Honors Architecture §9 and tenets #1/#5. A profile is the point where an opaque agent connection becomes a **scoped, named identity** — everything downstream (visibility, invocation, audit) keys off it. Default is **fail closed**: an agent whose credential maps to no profile, or a profile with no roles, sees nothing. The profile never widens access on its own; it can only reference roles, and RBAC enforces them server-side, so a compromised/misconfigured agent cannot self-elevate. Every profile lifecycle event (create, role change, credential attach/detach) is audited with principal + target. Per-profile rate-limit hints feed the [Security](19-security.md) baseline so one noisy agent cannot starve others (tenet #1's "many agents" fairness).
## Open questions
- Should a profile map to exactly one credential or many (e.g. Claude Desktop + Claude Code sharing one profile vs. distinct profiles)?
- Do we ship a curated starter profile pack (Claude, Cursor, VSCode, …) with sensible default roles, or start every profile empty?
- Is there value in profile-level tool *overrides* on top of roles, or does that erode the clean identity→roles→tools chain?
- How do profiles interact with per-agent quotas/budgets (token usage) tracked in [Metrics](18-metrics.md)?
- Guest/anonymous profile: supported at all, or is every agent required to be provisioned first?
## Milestone
Delivered in **Phase 3** (Secure). Thin slice first: the `AgentProfile` model, credential→profile `Identify`, and role expansion via [RBAC](08-rbac.md) so a connecting agent resolves to a scoped visible tool set at the Router. Directly powers the Phase 3 exit proof: two agents (distinct profiles, distinct roles) connect and each sees a different tool set — the profile is what binds each agent to its slice.