On this page
Prerequisites
- Node.js ≥ 18 and npm ≥ 9
- An MCP host: Claude Desktop, Cursor, Windsurf, or any agent that speaks MCP
- An upstream MCP server you want to govern (e.g.
@modelcontextprotocol/server-filesystem)
1. Install
You can run @euno/mcp directly with npx — no global install required:
npx -y @euno/mcp --version
Or install once for repeated use:
npm install -g @euno/mcp
2. Author a policy
A policy is a single YAML file describing which tools are allowed and the conditions on each call. Save the following as euno.policy.yaml:
agentId: filesystem-agent
name: Filesystem Agent
version: 0.1.0
requiredCapabilities:
- resource: read_file
actions: [call]
conditions:
- type: allowedExtensions
extensions: [".csv", ".json", ".txt", ".md"]
- type: maxCalls
count: 50
windowSeconds: 60
- resource: list_directory
actions: [call]
The schema is a literal subset of AgentCapabilityManifest from @euno/common-core — there is exactly one shape, no drift between proxy and the rest of the system.
3. Validate the policy
npx -y @euno/mcp validate ./euno.policy.yaml
Validation runs without contacting any server and rejects unknown condition types and malformed YAML up front.
4. Run the proxy
Wrap any MCP server with the proxy. Everything after -- is the upstream command:
npx -y @euno/mcp proxy \
--policy ./euno.policy.yaml \
-- npx -y @modelcontextprotocol/server-filesystem /data
The proxy speaks MCP on stdio in both directions and is transparent to clients.
5. Try it from a host
Point your MCP host (or any client) at the proxy command above. From Claude Desktop, attempt to read /data/keys.pem — the proxy denies it before the upstream is contacted, and the agent receives a structured CapabilityDenied error.
6. Inspect the audit log
Every decision — allow or deny — is recorded as a signed OCSF API Activity event:
tail -F ~/.euno/audit.jsonl | jq
For aggregated denial counts, run:
npx -y @euno/mcp stats
This prints an ASCII histogram grouped by condition type and denial code, including rotated log files.
Connect a client
How you point a client at the proxy depends on whether it launches the server itself (stdio) or connects to a running process over HTTP. The full guide covering Claude Desktop, Cursor, Windsurf, VS Code, JetBrains, web clients, CLI tools, and LangChain is on the client setup page. A quick reference for the most common stdio clients is below.
Claude Desktop / Cursor / Windsurf
Add the proxy as an mcpServers entry in the client's config file (e.g. claude_desktop_config.json for Claude Desktop, ~/.cursor/mcp.json for Cursor, ~/.codeium/windsurf/mcp_config.json for Windsurf):
{
"mcpServers": {
"filesystem-governed": {
"command": "npx",
"args": [
"-y", "@euno/mcp", "proxy",
"--policy", "/absolute/path/to/euno.policy.yaml",
"--",
"npx", "-y", "@modelcontextprotocol/server-filesystem", "/data"
]
}
}
}
Use an absolute path for --policy — these clients set the working directory to a system path. Restart or reload the client after saving.
VS Code (GitHub Copilot agent mode)
VS Code uses a different schema. Create .vscode/mcp.json in your project:
{
"servers": {
"filesystem-governed": {
"type": "stdio",
"command": "npx",
"args": [
"-y", "@euno/mcp", "proxy",
"--policy", "${workspaceFolder}/euno.policy.yaml",
"--",
"npx", "-y", "@modelcontextprotocol/server-filesystem", "/data"
]
}
}
}
Note "servers" (not "mcpServers") and the required "type": "stdio" field. VS Code expands ${workspaceFolder} at runtime.
For JetBrains, web clients, CLI tools, and programmatic SDKs see the full client setup guide →
Without --policy the proxy is transparent — handy for capturing an audit trail before you write any rules.
HTTP transport · LangChain.js
For agents that connect over HTTP rather than stdio, run the proxy as an HTTP server:
npx -y @euno/mcp proxy \
--transport http --port 7391 \
--policy ./euno.policy.yaml \
-- node ./my-mcp-server.js
Connect your client to http://127.0.0.1:7391/mcp. The same policy file works on both transports — and ipRange conditions become enforceable here.
In-process LangChain.js
If your agent runs LangChain tools in-process and you don't want a separate proxy process, install @euno/langchain and wrap your tool:
import { createLocalRuntime, wrapAsLangChainTool } from '@euno/langchain';
const runtime = await createLocalRuntime({ policyFile: './euno.policy.yaml' });
const queryTool = wrapAsLangChainTool(runtime, {
name: 'query_db',
description: 'Run a read-only SQL query',
schema: { type: 'object', required: ['sql'],
properties: { sql: { type: 'string' } } },
handler: async ({ sql }) => db.query(String(sql)),
});
Same YAML, same conditions, same audit format — no transport hop.
Next steps
- Drop in a ready-made policy from the reference policies page.
- Browse the full condition matrix with worked demos for each.
- Read how the proxy enforces policy end-to-end.
- See the full client setup guide for JetBrains, web clients, CLI tools, and more.
- Explore the source on GitHub — issues and PRs welcome.