Documentation
Everything you need to connect a backend, expose it as governed natural-language tools, and put your AI agents in front of it — safely.
Core concepts
Five ideas run through the whole product:
- System — a connected backend (ServiceNow, Workday, SAP, a Dataverse table, or anything that speaks MCP). One image fronts many systems; you turn them on and off. In MCP terms, a remote server is reached over HTTP (how agents reach EAG, and how EAG federates MCP backends); a local server is a process spoken to over stdio (EAG supports it for desktop development).
- Tool — a canonical, plain-language capability an agent calls by intent (e.g.
case.create,hr.timeoff.balance.get). The catalog is stable; the backend behind a tool can change without the agent noticing. - Scope — a permission string (e.g.
hr.read,case.write) a tool requires. A caller only sees tools their scopes grant. - Mapping — how a tool actually runs on a live backend: which table answers it, whose rows, and how columns become fields. Mock systems serve samples with no mapping; a live system needs one.
- Tier & gating — every tool is read, write, or privileged. Policy gates decide confirm-before-write and block/approve for privileged actions.
The console
Everything below is done from the admin console (served at /admin), no YAML editing required. Eight tabs:
- Systems — the fleet: toggle systems on/off, expand a row for tools, gating, config, and Connect.
- Tool catalog — the canonical contract; add custom tools here.
- Mappings — bind a tool to a live table.
- Identity probe — "what would this user see?" before they ask.
- Live audit — every call, streaming, with identity + outcome.
- Users — console roles and tool-scope grants.
- Onboard a system — generate a new system's manifest.
- Licensing — install a key, see seats and the expiry countdown.
Use the ☀/◐ button in the header to switch light/dark.
Add a system (connect a backend)
Connecting a backend is config, not code — the whole flow lives in the console. It has two halves: generate the manifest (what the system is), then connect & enable it (make it live).
1 · Generate the manifest
Open the Onboard a system tab and fill the form:
- Backend id — a short, stable, lowercase id, e.g.
acme-crm. This is how the system is referenced everywhere (audit, mappings, URLs). - Adapter — the driver that speaks this vendor's protocol. Pick from the list:
servicenow,salesforce,workday,successfactors,ukg,bamboohr,zendesk,sap,snowflake,sql,dynamics365,dataverse,graph(Microsoft 365),azuredevops,entra-identity,mcp-proxy(federate a backend that already speaks MCP — no driver code), ormock(canned sample data, no backend). If your backend has no adapter yet, one has to be built first — see the Adapter Development Guide. - Display name — the human label shown on the system card, e.g. "Acme CRM".
- Base URL — optional; the backend's root URL if the adapter needs one.
- Start in mock mode — leave checked to serve sample data immediately (great for wiring up agents before credentials exist); uncheck when you're ready to go live.
Click Generate YAML to preview the manifest, then Save to manifests/. It always saves disabled and references secrets by env-var name only — nothing sensitive is written. The new system now appears on the Systems tab, off, under its business-domain group.
2 · Connect credentials
On the Systems tab, find the system and click Connect…. The credential dialog is tailored to the adapter and lists its auth methods, safest first (e.g. an integration/service user, OAuth client-credentials, or per-user OAuth On-Behalf-Of). Then:
- Choose the auth method from the dropdown.
- Fill the connection parameters it shows (host, username/password, client id/secret, token URL — whatever that method needs).
- Click Test connection — the gateway spins up a throwaway instance of the adapter and runs a real
health()against the backend. Fix anything that fails here before going live. - Click Save & connect. Secret values go to a gitignored runtime store on the writable data volume (mode 0600); the manifest keeps only the secret names. Secrets never enter git, the image, or the browser.
3 · Verify & enable
- Expand the system's row. Under Secrets (env vars) confirm each referenced secret shows a green ✓ (present), not ✗ (missing).
- Click Run conformance to execute the adapter's test kit against the system (identity isolation, error mapping, health) and read pass/fail inline.
- Flip the toggle switch on. Its tools appear to authorized agents on the next
tools/list. (Reversible — toggling off shows a toast with Undo.)
mock off and add a mapping (next sections) when credentials land. And if the backend has no API yet, model it in Dataverse, serve it live through the same catalog, and repoint to the real backend later — all agent traffic switches in seconds .Create a tool & its scope
The base catalog ships a stable set of canonical tools (case.*, hr.*, crm.*, erp.*, data.*). When you need a verb the base doesn't cover — an engagement-specific capability — add a custom tool. It runs through the same policy, gating, and audit pipeline, and can never override a base id.
1 · Add the tool
In Tool catalog, open Add a custom tool and set each field:
- id — dotted and namespaced by domain, e.g.
crm.lead.get. Stable forever once used; agents call it by name. (On the wire, dots become underscores:crm_lead_get.) - tier —
read,write, orprivileged. This drives gating automatically: write tools require a policy/confirm; privileged tools ship blocked until a security sign-off. Choose the least privilege that fits. - classification —
internal,public, orpii. Marks results for audit and redaction; usepiifor anything personal. - scopes — the permission string(s) a caller must carry to see and call the tool, comma-separated, e.g.
crm.read. This is where a scope is defined — you invent the scope name here, then grant it to users (step 3). A tool and its scope must exist before it can be mapped to a system. - description — write it for the model. The agent chooses tools by matching the user's words against this text, so state plainly what it does, what it returns, and any limits. This is the single highest-leverage field for good routing.
- input schema — optional JSON Schema for the tool's arguments, e.g.
The gateway validates every call's args against this and shows it to the agent.{ "type": "object", "properties": { "leadId": { "type": "string" } }, "required": ["leadId"] }
Click Add tool. It appears in the catalog immediately (a custom badge distinguishes it) and is written to a git-trackable file — no code change.
2 · Grant the scope
A tool with a required scope is hidden from anyone who lacks it. On the Users tab, add the scope (e.g. crm.read) to the users or roles who should have it. Use Identity probe to confirm the right people now see the tool and others don't.
3 · Make it do something
A new tool has no backend behind it yet. A mock system will serve it sample data right away; a live system needs a mapping — that's the next section.
Map a tool to live data
A tool's catalog entry says what it is; the mapping says what it does on a real backend — which table answers it, whose rows it returns, and how columns become the tool's output fields. Mock systems serve samples with no mapping; a live system needs one per tool.
1 · Pick the target
In Mappings, choose the system (the live backend) and the tool you're binding. The form below configures how that one tool runs on that one system.
2 · Which table answers it?
Pick the binding mode, then the entity set (the table/collection, e.g. crm_cases):
- one — return a single record (a get-by-id).
- list — return several records (a search/list).
- create — write a new record.
- aggregate — group + sum (advanced; configured as JSON).
- handoff — don't call the backend; send the user to a self-service URL instead.
3 · Whose rows?
This is the identity boundary — it decides whether a caller can see only their own data or more:
- Only the caller's — scoped by column — rows are filtered by a lookup column that holds the owner, e.g.
_crm_owner_value. The most common, safest choice. - The caller's own identity row (keyed) — the caller is the record (e.g. their own HR profile).
- Org-wide (explicitly unscoped) — every row, no per-user filter. An opt-in you should choose deliberately (reference data, service-account reads).
4 · Draw the lines (columns → fields)
Map each output field ← table column. The column boxes are dropdowns fed by the system's live table metadata, so you pick real columns, not guesses. Add a row per field. Then, as needed:
- Filter by tool input — an OData-style template using the tool's arguments, e.g.
sn_number eq '{caseId}'. Substitutions are escaped for you. - Result key / order by / max rows — for list mode, name the array, sort it (e.g.
createdon desc), and cap it. - Decode picklists — a choices map turns numeric codes into labels, e.g.
{ "crm_state": { "0": "Active", "1": "Resolved" } }.
5 · Mode extras
- create — set attribute new rows to the caller (a lookup property + its entity set) so records are owned by the signed-in user, plus any defaults JSON (e.g.
{ "crm_state": 0 }). - handoff — set the message shown to the user and a link label (e.g. "Open CRM").
- Advanced (JSON) — drop to raw binding JSON for anything the form doesn't cover (aggregate is configured here).
6 · Save
Click Save mapping. It's validated server-side (missing columns, bad templates are caught), written to the manifest with a preserved-comment edit, and applied on the very next call — no restart. Test it with Identity probe and watch it run in Live audit.
Govern by tier
Open a system's row and set its gating: write = require-policy / require-approval / off; privileged = block / require-approval / off. Loosening gating prompts for an audited reason — security sign-off is an event, not a checkbox.
Before you hand tools to agents, use Identity probe: type any scope set (or pick a persona) and see the exact tools that identity would be served — allowed, gated, or hidden — with the reason. Governance you can demonstrate, not assert. Everything runs through Live audit: user, tool, system, outcome, latency.
Users & roles
Your identity provider (Entra) proves who a caller is; the Users tab decides what they may do: a console role (admin / read-only) and granted tool scopes. Add a user by email or Entra object id, pick a role, list their scopes, and save. Unlisted users fall back to the configured default (usually: nothing until granted).
Licensing
Every install starts with a 30-day trial — all features unlocked. A signed key then sets your tier, verified offline (air-gap friendly, no phone-home). In the Licensing tab (or click the plan badge in the header): paste the key, Verify & install. It hot-applies and persists.
The header badge and Licensing tab show a days-remaining countdown (amber at 14 days, red at 5). Renew and install the new key before it lapses, or the gateway locks and tool calls refuse until a valid key is installed. Mock and demo systems never count against live-system caps or the adapter allowlist. (A locked gateway — trial over, no key — refuses all tool calls, mock included, until a key is installed.)
Deploy
EAG ships as one container image, config per customer. Run it on Docker, Azure Container Apps, App Service, on-prem, or fully air-gapped. Point it at your Entra tenant, mount a writable data volume, and connect your systems from the console. Your systems never leave your cloud.
docker run -d -p 3000:3000 --env-file .env \
-v eag-data:/app/data your-registry/eag-gateway:latest
Console at /admin, MCP endpoint at /mcp. Register that one endpoint in Copilot Studio, Azure AI Foundry, Claude, or any MCP client, and it fronts your whole governed fleet.
Get help
Questions, setup help, or a license request — email [email protected]. We read every message.