Skip to content

Agents over MCP, phase 1: agent keys, /mcp and read tools - #5

Merged
jcombs-pointblue merged 4 commits into
mainfrom
feature/mcp-phase1
Oct 31, 2026
Merged

jcombs-pointblue merged 4 commits into
mainfrom
feature/mcp-phase1

Conversation

@jcombs-pointblue

@jcombs-pointblue jcombs-pointblue commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Phase 1 of docs/MCP.md: agent keys, the /mcp endpoint and the read tools.

Stacked on #4: this branch contains #4's commit (the intruder-lock check, OidcLogin.locked, which agent keys use on every call). Merge #4 first; this diff then shrinks to its own changes.

What's in it

  • Agent keys (AgentKeys, Agents): made under your name > Agent keys, with the password typed again (direct sign-in) or a sign-in within 10 minutes (single sign-on).
    • Format and storage: dc_ + 32 random bytes; only the SHA-256 is kept, in agents/keys.json, outside the configuration and outside backups.
    • Roles: viewer always; editor and operator only if the person has them; never admin; no task presets.
    • Lifetimes: 30 days by default, at most the directory's longest life (≤ 90), and 14 days for keys made with single sign-on. A key unused for 14 days stops working.
    • Admins can list and revoke everyone's keys.
  • Acting as the person: the service account with proxied authorization, and the engine opened acting as them, exactly as single sign-on does.
    • On every call the console reads the account as the service account and refuses it if it is locked, disabled or expired. eDirectory accepts the control for a locked account, so this check is the console's own.
    • Direct sign-in grants are re-read in that same read, so a removed role takes effect at the next call. The note had said cached for 5 minutes; this is stricter, and the note is updated.
  • /mcp (Mcp): JSON-RPC 2.0, Streamable HTTP, stateless.
    • 22 read tools. Each one is an in-process call to the existing /api/v2 handler with the key's session (Mcp.Local), so roles, scopes and eDirectory rights stay the API's.
    • A key works only at /mcp.
  • Per-directory settings (Settings > Directory > Agent keys): on or off (off by default), the longest life, and approveChanges, required by default (Jerry, 2026-10-30). Nothing waits yet, because phase 1 has no change tools. The design note's decision 4 is updated to match.
  • Audit:
    • events agent-key.create, agent-key.revoke, and agent-key.use (refusals, at most once an hour per key and reason);
    • an agent field on the actor (label and id) for entries made with a key;
    • every new AuditLog.Actor(...) built from a session now goes through Server.who(s).
  • Proxied sessions: Principal.proxied() replaces the "oidc".equals(via) checks for "reaches other servers without signing in again".

Tests

  • AgentKeysTest covers:
    • storage keeps only the hash;
    • expiry and the idle limit;
    • the role limits;
    • single sign-on grants refreshed at the next sign-in.
  • AgentsTest (fake eDirectory, fake engine, fake identity provider) covers:
    • keys off until an admin turns them on;
    • a key made and used over /mcp: whoami, read_object by DN and by GUID, search_objects, driver_set, with the proxied control checked;
    • refusals: no key, a bad key, GET, a key on the API directly, a locked account, a lost role, a disabled account, keys turned off and on again, a revoked key;
    • role limits and break-glass accounts;
    • single sign-on: at most 14 days;
    • keys are not in backups.
  • Full suite: 162 server tests and 53 UI tests pass; npm run build passes.
  • Checked in the browser on the DemoServer: turned keys on as admin, made a key as pat (only the roles pat has were offered), saw the key dialog, then the list and revoke. /mcp was called with a demo key: initialize, tools/list, whoami, search_objects and list_driver_sets.

Live on idm254 (eDirectory 9.3.3, IDM 4.10.2), with LiveAgentsCheck: all 29 steps passed. A scratch user (no password) signed in with single sign-on and made a key (15 days refused, 14 is the most).

  • Reads as the user: whoami said via agent. read_object worked by DN under the control, and search_objects, account_status, group_memberships and object_associations all answered.
  • Identity Manager: idm_status answered with the engine acting as the user. driver_set was refused with insufficient-rights: the user has no rights there, which is correct.
  • Refusals: the key was refused while the account was locked, worked again once unlocked, and was refused once the account was disabled and after an admin revoked it. All of it was audited, and the key appears in none of the console's files.
  • Cleanup: the scratch container was deleted.

🤖 Generated with Claude Code

jcombs-pointblue and others added 4 commits October 30, 2026 13:19
eDirectory refuses the proxied-authorization control for disabled and
expired accounts, but accepts it for an account locked by intruder
detection: on the lab (9.3.3) the locked account's own bind returned 53
while a proxied read as it succeeded. So a locked person could sign in
through single sign-on and act in the console.

OidcLogin's search now reads lockedByIntruder and loginIntruderResetTime
and refuses with the new code "locked". A lock whose reset time has
passed counts as over: eDirectory leaves the attribute TRUE after the
reset until the next login (checked on the lab with a real lockout).
A single sign-on session reaching another server (Server.on) checks the
same on that server before acting as the person there. The service
account check covers reading the two attributes, and the live check
LivePhase3Check covers a locked and a lapsed account.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent keys (docs/MCP.md): a person signed in directly or with single
sign-on makes a key under their name > Agent keys (password typed again,
or a sign-in within 10 minutes). dc_ + 32 random bytes, SHA-256 kept in
agents/keys.json on the data volume, outside the versioned configuration
and outside backups. Viewer always, editor and operator only if the person
has them, never admin, no task presets. 30 days by default, at most the
directory's longest life (90) and 14 days with single sign-on; 14 days
unused stops a key. Admins list and revoke everyone's.

A key's calls run as the person the way single sign-on does: the service
account's connection with the proxied-authorization control, and the
engine opened acting as them. On every call the console reads the
person's account as the service account and refuses a locked (intruder
detection: eDirectory accepts the control for it), disabled or expired
one; direct sign-in grants come from that read, single sign-on grants
from the last sign-in.

/mcp: JSON-RPC 2.0, Streamable HTTP, stateless. 22 read tools, each an
in-process call to the existing API with the key's session (Mcp.Local),
so roles, scopes and rights stay the API's. A key works only there.

Per directory (Settings > Directory > Agent keys): on/off (off by
default), longest life, and approveChanges, required by default, for the
change tools of phase 2. Audit: agent-key.create/revoke/use, and the
actor's agent (label and id) on entries made with a key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A test user made for the run (no password) signs in through a fake
provider, makes a key, and its agent calls /mcp: whoami, reads under the
control, the object inspector, Identity Manager under the user's rights;
then a locked, an unlocked and a disabled account, and a revoked key. The
container is deleted at the end. Passed on idm254 (eDirectory 9.3.3, IDM
4.10.2), 2026-10-30.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jcombs-pointblue
jcombs-pointblue merged commit 2d0fa54 into main Oct 31, 2026
1 check passed
@jcombs-pointblue
jcombs-pointblue deleted the feature/mcp-phase1 branch October 31, 2026 00:32
@jcombs-pointblue
jcombs-pointblue restored the feature/mcp-phase1 branch October 31, 2026 00:34
@jcombs-pointblue
jcombs-pointblue deleted the feature/mcp-phase1 branch October 31, 2026 00:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant