https://github.com/radoslaw-sz/guardio
The most flexible control plane for AI Agent systems
https://github.com/radoslaw-sz/guardio
ai ai-agent ai-agents control-plane framework guard mcp security
Last synced: 2 months ago
JSON representation
The most flexible control plane for AI Agent systems
- Host: GitHub
- URL: https://github.com/radoslaw-sz/guardio
- Owner: radoslaw-sz
- License: apache-2.0
- Created: 2026-02-05T09:01:12.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-03-19T08:34:17.000Z (3 months ago)
- Last Synced: 2026-03-20T01:50:23.997Z (3 months ago)
- Topics: ai, ai-agent, ai-agents, control-plane, framework, guard, mcp, security
- Language: TypeScript
- Homepage:
- Size: 1.1 MB
- Stars: 6
- Watchers: 0
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
Guardio
Guardio is a **control plane** that sits between your **AI Agent system** and the external world. It catches and evaluates messages flowing to and from **MCP tools** and other APIs before they reach the real servers. You can enforce policies (allow, block, sanitize), require approval, simulate MCP responses and observe activity—all through a **plugin system**.
## Supported connections
| Connection type | Status | Notes |
| --------------------------- | ---------- | ----------------------------------------------------- |
| **HTTP server** | Supported | Guardio runs as an HTTP server; clients connect here. |
| **MCP tool (upstream)** | Supported | Proxying to MCP servers over HTTP/SSE. |
| **stdio** | On the way | Client transport. |
| **Other APIs / transports** | On the way | Extensible for more protocols. |
Today you run one **Guardio instance** that fronts **all** your external MCP tools and APIs (one proxy, many upstreams).
---
## Quick start
### Create a Guardio project
Scaffold a new project with config and optional plugins:
```bash
npx create-guardio
```
You will be prompted for:
- **Guardio directory** – e.g. `guardio-project` (default)
- **Guardio HTTP port** – e.g. `3939`
- **Storage and events** – optional; needed for dashboard and policy state. Choose SQLite (in-memory by default, or file `guardio.sqlite`) or PostgreSQL.
- **Example custom policy plugin?** – optional; scaffolds `plugins/example`
- **Install dashboard?** – optional; adds `@guardiojs/dashboard` and a `dashboard` run script
The scaffold creates **empty `servers`** by default. A commented example in `guardio.config.ts` shows how to add an MCP server (e.g. `{ name: "nuvei-docs", type: "url", url: "https://mcp.nuvei.com/sse" }`). All built-in policy plugins (**deny-tool-access**, **deny-regex-parameter**) are included by default.
Then:
```bash
cd
npm install # or: pnpm install, yarn, bun install, etc.
npm run guardio
```
Point your AI Agent or MCP client at `http://127.0.0.1:`. If you installed the dashboard, run `pnpm run dashboard` (or `npm run dashboard`) and point it at the same Guardio base URL.
### Run Guardio with Docker
A minimal Docker image is provided for the core Guardio HTTP server (package `@guardiojs/guardio`). Build it from the `packages/guardio` directory:
```bash
cd packages/guardio
docker build -t guardio .
```
Run the container, mounting your `guardio.config.*` into the container and mapping the HTTP port (defaults to `3939` unless overridden in config or via env):
```bash
docker run --rm \
-p 3939:3939 \
-v "$(pwd)/guardio.config.ts:/config/guardio.config.ts:ro" \
guardio \
--config /config/guardio.config.ts
```
The container:
- **Exposes** port `3939` by default (override with `GUARDIO_HTTP_PORT` / `GUARDIO_HTTP_HOST`).
- **Starts** the Guardio CLI via `node bin/guardio.mjs` (you can pass any CLI args after the image name).
---
## Concepts
### AI Agent connection
AI Agents (MCP clients) connect to **Guardio's HTTP server**, not directly to the upstream MCP servers. Guardio is the single entry point.
- **SSE (stream)** – Connect to `http://:/{serverName}/sse` for the MCP SSE stream. Use the **server name** from your config (e.g. `nuvei-docs` → `/nuvei-docs/sse`).
- **Optional `x-agent-name`** – Send this header on the SSE connection to give the agent a human-readable name. If omitted, Guardio generates one. The connection is assigned an agent id used for policy scoping.
- **POST messages** – Send JSON-RPC to `http://:/{serverName}/messages`. You can send **`x-agent-id`** (the id for the SSE connection) so policies can be applied per agent.
So: one Guardio URL base, multiple paths like `/{mcp-tool}/sse` and `/{mcp-tool}/messages` for each configured upstream.
### MCP tool connection
In your config you define a **`servers`** array. Each entry has a **`name`** (unique, used in the URL path) and an **`url`** (the upstream MCP server's HTTP/SSE base URL). Guardio proxies:
- **GET /{name}/sse** – to the upstream SSE endpoint (and manages the stream).
- **POST /{name}/messages** – to the upstream after running policies (or returns a blocked result without forwarding).
So each "MCP tool" or upstream is one entry in `servers`; a single Guardio instance serves all of them.
### Plugins
Plugins extend Guardio's behavior. Types:
| Type | Role |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| **Policy** | Evaluate `tools/call` requests: allow, block, or modify arguments. Optional; no policies means all calls pass through. |
| **Storage** | Persist state (e.g. policy assignments, agent list). Used by built-in policy config and dashboard. |
| **EventSink** | Receive events for each processed request (e.g. ALLOWED/BLOCKED, tool name, policy). |
| **EventSinkStore** | Store and query events; used by the dashboard for activity views. |
**Built-in plugins:**
- **Policy:** `deny-tool-access`, `deny-regex-parameter` (both are added by default when you scaffold with `create-guardio`)
- **Storage / EventSink / EventSinkStore:** `sqlite` or `postgres`
- **sqlite:** `config: { inMemory: true }` (default) or `config: { database: "guardio.sqlite" }` for a file
- **postgres:** `config: { connectionString: "postgresql://user:pass@host:5432/dbname" }` or discrete `host`, `port`, `user`, `password`, `database`, `ssl`
You register plugins in **`guardio.config.ts`** in the **`plugins`** array. Policy config for built-ins is typically managed at runtime (e.g. via the dashboard), not in the config file.
### Create a custom plugin
Use a **path-based** plugin: in config add an entry with **`path`** pointing to a directory that contains **`index.js`** or **`index.mjs`** (build from `index.ts` if needed).
Export a **`PolicyPluginDefinition`** with a factory function. This enables full dashboard integration: runtime configuration, multiple instances, config validation, and custom form widgets.
```ts
// plugins/my-policy/index.ts
import { z } from "zod";
import type {
PolicyPluginDefinition,
PolicyPluginInterface,
PolicyRequestContext,
PolicyResult,
PolicyPluginContext,
} from "@guardiojs/guardio";
// Config schema for dashboard validation
const configSchema = z.object({
maxLength: z.number().min(1).describe("Maximum argument length"),
blockedPatterns: z.array(z.string()).optional(),
});
type Config = z.infer;
class MyPolicyPlugin implements PolicyPluginInterface {
readonly name = "my-policy";
constructor(
private config: Config,
private context?: PolicyPluginContext,
) {}
async evaluate(ctx: PolicyRequestContext): Promise {
// Access config: this.config.maxLength
// Access plugin storage: this.context?.pluginRepository
return { verdict: "allow" };
}
}
const definition: PolicyPluginDefinition = {
name: "my-policy",
factory: (config, context) => new MyPolicyPlugin(config as Config, context),
configSchema,
uiSchema: {
maxLength: { "ui:widget": "updown" },
},
};
export default definition;
```
In config:
```ts
{ type: "policy", name: "my-policy", path: "./plugins/my-policy" }
```
Custom plugins work exactly like built-in plugins:
- Create multiple instances with different configs via the dashboard
- Configs are stored in the database and validated against your schema
- Plugins receive a `PolicyPluginContext` with a scoped `PluginRepository` for persisting plugin-specific state
If you chose "Add example custom policy plugin" when running `npx create-guardio`, see the generated `plugins/example` folder.
---
## Processing
### Evaluating policy
When a **`tools/call`** request hits Guardio (POST `/{serverName}/messages`):
1. Guardio resolves which **policy plugins** apply (from storage, optionally scoped by agent and tool).
2. Each policy's **`evaluate`** is run with the tool name and arguments.
3. If any policy returns **block**, the call is **not** forwarded. Guardio responds with a **success** JSON-RPC result that includes a human-readable message and **`_guardio`** metadata (so agent frameworks don't treat it as a fatal error).
4. If all policies **allow**, the request (with any **modified arguments**) is forwarded to the upstream MCP server and the response is proxied back.
Non–`tools/call` messages are forwarded without policy evaluation.
### Emitting events
If **EventSink** plugins are configured, Guardio emits a **GuardioEvent** for each processed `tools/call` (both allowed and blocked). The event includes:
- **decision** – `ALLOWED` or `BLOCKED`
- **tool name**, **request id**, **agent id** (if present)
- For blocks: **policy name**, **code**, **reason**
EventSinkStore (e.g. sqlite or postgres) persists these for the dashboard and for your own auditing.
---
## Simulation Mode (Testing)
Guardio supports **Simulation Mode**, which returns **mocked tool responses** instead of calling the real upstream MCP server. This is useful for testing and demos because **policies still run first**, but the upstream call is skipped.
### How Simulation Mode is applied
- **Global simulation**: when enabled, **all** `tools/call` requests are simulated (regardless of per-tool settings).
- **Per-tool simulation**: when global simulation is off, you can mark specific tools as simulated **per MCP server + tool name**.
- **Per-request header**: `X-Guardio-Mode: simulation` can simulate a single request when global simulation is off.
### Dashboard controls
In the dashboard sidebar, open **Testing → Simulation** to:
- Toggle **Global simulation** on/off
- Configure **Per-tool simulation** per MCP server + tool
These settings are stored in the database (runtime settings) and can be changed **without restarting** Guardio.
### API endpoints
- **GET** `/api/testing/simulation` – returns current simulation settings
- **PUT** `/api/testing/simulation` – updates simulation settings
Example payload:
```json
{
"globalSimulated": false,
"tools": [
{ "serverName": "docs", "toolName": "search", "simulated": true }
]
}
```
### Events
When Simulation Mode is used for a `tools/call`, Guardio records simulation details in the event so the dashboard activity feed can render it (e.g. `simulation.enabled` and `simulation.source`).
---
## Configuration and running
- **Config file:** `guardio.config.ts` (or pass `--config `). It must export a **`GuardioConfig`** with **`servers`** (array of `{ name, type: "url", url }`; can be empty) and **`plugins`**. The scaffold adds a commented example server in the config so you can uncomment and edit to add MCP upstreams.
- **Client (HTTP server):** Optional **`client`** with **`port`** (default `3939`) and **`host`** (default `127.0.0.1`). Override with **`GUARDIO_HTTP_PORT`** and **`GUARDIO_HTTP_HOST`**.
- **Debug:** `GUARDIO_DEBUG=1` to log request/response flow.
Blocked tool calls return a **success** result with **`result.isError: true`** and **`result._guardio`** (version, requestId, timestamp, policyId, action). See the repo for the full response shape.
---
## Dashboard
The **dashboard** is a Next.js web UI for Guardio. It lets you view activity (allowed/blocked tool calls), manage policies, and inspect agents and topology. You can add it when scaffolding with `npx create-guardio` (choose "Install dashboard?").
---
## License
Apache-2.0