For protecting the AI you build
MoorAI runs in front of the AI apps, agents, RAG pipelines and APIs you ship: inside your Agent SDK process, as a localhost sidecar any framework can call, between your agent and its model API, in front of remote MCP servers, and on the connections the workload opens. Each component runs next to the workload it protects, in your environment, so prompts, code, files and secrets are not sent anywhere to be checked. All of them are in the MoorAI agent, which is open source (MIT).
Rolling out coding agents to a team? For developers using AI coding agents →
One engine, at each place the AI acts
Scroll sideways →
| Where the AI acts | MoorAI component | What it checks |
|---|---|---|
| A service built on the Claude Agent SDK | @moorai/agent-sdk | Every tool call, inside your service’s own process |
| Any agent framework, before a tool runs | moorai-serve sidecar | The tool call your code asks about, on 127.0.0.1:8790 |
| The model API | moorai-model-proxy | What the agent sends and the tool calls in the model’s response, on 127.0.0.1:8791 |
| Remote MCP servers | moorai-mcp-gateway | Every MCP message, including each tool call and each streamed event |
| Every other connection | moorai-egress-proxy | The connections routed through it, against your declared egress rules |
| A kernel sandbox | Generated network policy | The same egress rules, as the sandbox’s network policy |
| A retrieval index | Scan before embed | Each chunk, before it is embedded |
They combine. A service can call the sidecar for its own tools, reach its MCP servers through the gateway, its model through the model proxy, and everything else through the egress proxy.
What each one does
Agent SDK: in process
For a service built on the Claude Agent SDK, @moorai/agent-sdk returns the hooks record for query(). PreToolUse returns the decision and reason MoorAI’s hook would give, in your code path, so the model doesn’t decide whether it is consulted. Prompts and tool results are scanned and reported by default; prompts: "enforce" blocks a denied prompt, and toolResults: "advise" tells the model a flagged result is untrusted data.
import { moorAIHooks } from "@moorai/agent-sdk";
query({ prompt, options: { hooks: moorAIHooks({ serviceId: "invoice-agent" }) } })
The moorai-serve sidecar: any framework
A localhost HTTP decision API for agent loops that are not Claude Code or the Agent SDK, such as the OpenAI Agents SDK, LangGraph, CrewAI or your own loop. Before your code runs a tool, it sends the call to POST /v1/tool-call and does what the answer says; POST /v1/scan scans text and POST /v1/index-scan judges chunks about to be embedded. Verdicts never contain the submitted text. It binds loopback only unless --allow-remote is given with a token, so run it in the agent’s pod or compose network: the agent reaches it on 127.0.0.1 and no port is opened. A stdlib-only Python client with LangGraph and CrewAI examples is in the repository.
The model proxy: tool calls checked before the framework runs them
The agent points its model SDK’s base URL at moorai-model-proxy (/anthropic for the Messages API, /openai for Chat Completions). It scans what the agent sends, including tool results fed back, where indirect injection shows up, and decides every tool call in the model’s response before the agent framework receives it, with the decision the sidecar would make, whether or not your code asks the sidecar.
- Report-only by default. Requests and responses pass through byte-identical, and findings are reported after the response is delivered.
- Enforce mode refuses a denied request, content it can’t fully scan, and a response with a denied tool call, in the provider’s own error shape. As an opt-in (
--denied-tool-call replace), it delivers that turn with its tool calls withheld and a refusal in their place, so the agent gets a complete turn and nothing from it runs. - Local models. A local OpenAI-compatible server (Ollama, LM Studio, llama.cpp server or vLLM) is a route to its loopback
/v1endpoint, read by the same parser. - The skip alert. Run beside
moorai-serve, it reports a tool call it forwarded that the framework never checked with the sidecar.
The MCP gateway: in front of remote MCP servers
moorai-mcp-gateway is an HTTP MCP guard: the client connects to the gateway on loopback, and the gateway connects to the remote server. Each message is validated in stages (JSON, JSON-RPC, structure, method, protocol version, schema). JSON with a repeated key is refused, so the value MoorAI checks is the value the server runs. Every event in a streamed response is scanned, a response over 4 MiB is refused, declared workload profiles are enforced, and calls are counted per tool. A tool call that policy denies is refused at the gateway and doesn’t reach the server. The gateway has run in front of a real remote MCP server with 62 tools, and Claude Code, cursor-agent, the MCP TypeScript SDK and MCP Inspector each completed the handshake and listed tools through it, against a test server. More in MCP security.
The egress proxy: declared egress rules on real connections
A check on command text can’t see a script or a dependency that opens its own socket. moorai-egress-proxy is a forward proxy that applies your declared egressRules and egressDefault to the connections the workload actually opens. For plain HTTP it judges host, port, method and path. For HTTPS it judges host and port only, because it doesn’t decrypt TLS. It resolves a name once and connects to exactly the address it checked, and private, loopback and cloud metadata addresses need a rule that names them exactly. Its rules come from the verified console policy or the root-owned machine-wide config, never from a flag, the environment or a repository file, and its alerts never carry a path, query, header or body.
Sandbox network policy from the same rules
The same egress rules become the network part of a kernel sandbox’s policy, for Windows MXC, macOS Seatbelt and open-source agent sandboxes, so one rule set drives both layers. A sandbox sees connections, not calls, so a rule the target can’t express (a hostname for MXC, any host but localhost for Seatbelt) is reported, never silently widened, and stays with MoorAI’s own check.
Placeholder credentials
Opt-in, in the model proxy and the MCP gateway. The agent holds a placeholder instead of the API key or MCP token. The proxy or gateway, running as another user or in its own container, swaps the real secret in only on the route the placeholder is bound to, refuses any other use of the placeholder, and masks a verbatim echo of the secret in responses.
Scan before embed, for RAG
A poisoned document in a retrieval index is read back into a later user’s context with no tool call and nobody typing it. MoorAI scans each chunk before it is embedded, for untrusted directives, text addressed to the AI, hidden instructions and tool poisoning, in three places: scanBeforeEmbed and guardEmbed in @moorai/agent-sdk for an app that runs its own ingestion, POST /v1/index-scan on the sidecar for any framework, and vector-store write tools (Chroma, Qdrant, Pinecone, Weaviate, mem0, Milvus and others) seen by the MCP proxy and gateway. It reports by default; policy.indexScanAction: "block" drops or refuses a chunk that carries an instruction.
const addDocs = guardEmbed((docs) => vectorStore.addDocuments(docs), { source: "kb/handbook" });
await addDocs(docs); // under "block", denied documents never reach addDocuments
Out of the agent’s reach, and the only way out
A guardrail the agent can edit isn’t a guardrail. The container image ghcr.io/gitayg/moorai-server holds moorai-serve, moorai-mcp-gateway, moorai-model-proxy and moorai-egress-proxy, for amd64 and arm64, and runs as a non-root user. Run them in their own container and the agent process can’t edit their policy or files.
Routing is the other half. MoorAI’s Kubernetes and docker-compose examples make MoorAI the workload’s only way out:
- Kubernetes (
deploy/k8s/moorai-egress.yaml): the egress proxy, the model proxy and the MCP gateway run in their own pod, and the agent pod’s NetworkPolicy lets it reach only that pod and cluster DNS. They are not sidecars, because a NetworkPolicy selects pods, not containers: a policy that lets a sidecar out would let the agent out too. - docker-compose (
deploy/compose/docker-compose.yml): the agent sits on an internal network, and only the MoorAI containers have a second, outside network.
In both, the agent gets the proxy variables. A tool that ignores them (a raw socket, nc, ssh, a library that dials directly) tries to connect directly and the network drops it: it fails closed. Alerts from the sidecar, the model proxy and the MCP gateway carry the workload’s pod, namespace and node, set through the Kubernetes downward API, so a SIEM can join MoorAI verdicts with host and container sensor events.
Content stays where the AI runs
Prompts, code, files and secrets are checked where the workload runs. The console receives content-free signals about what fired (the category, the risk level, the deciding rule and a keyed one-way hash), never the content that fired it, and alerts carry the workload identity rather than a person. Every rule is mapped to the OWASP Top 10 for Agentic Applications 2026 and to the OWASP MCP Top 10, still a beta list, with partial credit and its reason recorded. What leaves and what stays: trust.
What it doesn’t do
- The model proxy parses two APIs. Anthropic Messages and OpenAI Chat Completions, JSON and streamed. The OpenAI Responses API, Assistants and Realtime, Amazon Bedrock, Google Vertex AI and Gemini, and Ollama’s native API are forwarded unchecked, and so are tools that run at the provider. It doesn’t intercept TLS, so an SDK pinned to the provider’s HTTPS URL bypasses it. It is tested against a fake provider and a fake local server, not the providers’ own SDKs, a real provider or a real local model server, which is why report-only is the place to start.
- The egress proxy judges HTTPS by host and port. The SNI and
Hostinside a tunnel are not checked, so domain fronting through an allowed CDN host passes. DNS itself stays reachable, WebSocket upgrades over plain HTTP are not proxied, and a socket doesn’t say which program opened it, so a rule that names a binary never matches there. It has run in a compose demo and a local Kubernetes cluster, not against the real internet or under load. - The boundary is your network policy. The Kubernetes example needs a CNI that enforces NetworkPolicy; on one that ignores it, the agent can bypass the proxy.
- Generated sandbox policies are unit-tested only. None has run under a real MXC container or an open-source agent sandbox.
- Placeholder credentials don’t protect a key the agent can read itself or use on another path, and the agent can still use the placeholder through the proxy, so policy decides what goes through.
- Scan before embed covers only ingestion that calls MoorAI and the MCP vector-store write tools it recognises. An app that embeds without calling it, an in-process vector library and a store’s own bulk import are not covered, and a passage that is only factually false is not detected.
- The sidecar judges only what your code asks about, and the in-process forms leave the session-level controls (runaway loops, session risk, intent alignment, MCP reputation, masking) unevaluated. An Agent SDK service has not been watched end to end, and the Python examples have not been executed.
- The gateway has untested paths: OAuth discovery through it, a live model-driven denied call through it, and the Claude Desktop, VS Code and Cursor desktop apps. The gateway doesn’t judge egress rules.
Frameworks, local models and HTTPS.
Does MoorAI work with LangGraph, CrewAI or the OpenAI Agents SDK?
Yes, through the moorai-serve sidecar: your loop sends each tool call to POST /v1/tool-call on 127.0.0.1:8790 before running it and does what the answer says. A stdlib-only Python client with LangGraph and CrewAI examples is in the repository; those examples have not been executed. The model proxy also judges the tool calls in the model’s response, whether or not the framework asks.
Can MoorAI check tool calls from a local model served by Ollama or vLLM?
Yes, through the model proxy. A local OpenAI-compatible server (Ollama, LM Studio, llama.cpp server or vLLM) is a route to its loopback /v1 endpoint, and every tool call in the model’s response is decided before the framework receives it. It is tested against a fake local server only, and Ollama’s native API is not parsed.
Does the MoorAI egress proxy decrypt HTTPS?
No. For HTTPS it sees a CONNECT tunnel and judges host and port only, so method and path rules apply to plain HTTP. The SNI and Host inside the tunnel are not checked, so domain fronting through an allowed CDN host passes.
Guard the AI you ship,
where it runs.
Open source (MIT). Every component runs in your environment.