Skip to content

radoslaw-sz/guardio

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Guardio

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.

High view

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:

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:

cd <guardio-directory>
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:<port>. 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:

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):

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://<host>:<port>/{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://<host>:<port>/{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.

// 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<typeof configSchema>;

class MyPolicyPlugin implements PolicyPluginInterface {
  readonly name = "my-policy";
  constructor(
    private config: Config,
    private context?: PolicyPluginContext,
  ) {}

  async evaluate(ctx: PolicyRequestContext): Promise<PolicyResult> {
    // 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:

{ 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:

  • decisionALLOWED 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:

{
  "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 <path>). 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

High view

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

About

The most flexible control plane for AI Agent systems

Topics

Resources

License

Stars

6 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors