loading…
Search for a command to run...
loading…
Enables AI agents to interact with Fortinet FortiManager through a minimal MCP interface using sandboxed JavaScript for querying the API spec and executing JSON
Enables AI agents to interact with Fortinet FortiManager through a minimal MCP interface using sandboxed JavaScript for querying the API spec and executing JSON-RPC calls.
Status: Stable (v1.0.0) — validated against live FortiManager v7.6.6 with 152 tests.
An MCP (Model Context Protocol) server for Fortinet FortiManager that uses the Code Mode pattern — just 2 tools instead of 590+.
Instead of wrapping each API endpoint as a separate tool (which consumes ~118K tokens of context), this server exposes only search and execute. The AI agent writes JavaScript code that runs inside a secure QuickJS WASM sandbox to query the API spec or execute live FortiManager JSON-RPC calls.
| Approach | Tools | Context Tokens | API Coverage |
|---|---|---|---|
| Traditional MCP (1 tool per endpoint) | 590+ | ~118,000 | Full |
| Code Mode (this project) | 2 | ~1,000 | Full |
~99% reduction in context tokens while maintaining full API coverage.
The Code Mode pattern was pioneered by Cloudflare's MCP server and adapted here for the FortiManager JSON-RPC API.
search — Query the FortiManager API spec (URLs, objects, attributes, methods, error codes) via sandboxed JavaScriptexecute — Run live FortiManager JSON-RPC API calls via sandboxed JavaScript with fortimanager.request() proxyThis server will NOT work without generating the API spec files first.
The API spec files are derived from Fortinet's FortiManager JSON API Reference documentation, which is proprietary and cannot be redistributed. You must download the HTML docs yourself and generate the spec locally.
Extract the downloaded archive and place the HTML files in the docs/api-reference/ directory:
docs/api-reference/
├── FortiManager-7.4.9-JSON-API-Reference/
│ └── html/
│ ├── adomobj-errors.htm
│ ├── adomobj-methods.htm
│ └── ... (all .htm files)
└── FortiManager-7.6.5-JSON-API-Reference/
└── html/
├── adomobj-errors.htm
├── adomobj-methods.htm
└── ... (all .htm files)
You only need the version(s) you plan to use. The folder names must match the pattern above.
npm run generate:spec
This parses the HTML docs and produces:
src/spec/fmg-api-spec-7.4.json (~99 MB)src/spec/fmg-api-spec-7.6.json (~127 MB)npm run build
The build step copies the generated spec files to dist/spec/. The server is now ready to use.
# Clone the repository
git clone https://github.com/jmpijll/fortimanager-code-mode-mcp.git
cd fortimanager-code-mode-mcp
# Install dependencies
npm install
# Generate API spec (requires HTML docs in docs/api-reference/ — see above)
npm run generate:spec
# Build
npm run build
# Configure environment
cp .env.example .env
# Edit .env with your FortiManager details
# Start (stdio mode)
FMG_HOST=https://fmg.example.com FMG_API_TOKEN=your-token npm start
# Or development mode with hot reload
FMG_HOST=https://fmg.example.com FMG_API_TOKEN=your-token npm run dev
Note: You must generate the spec files before building the Docker image. The Dockerfile copies them from
src/spec/at build time.
# Clone and install
git clone https://github.com/jmpijll/fortimanager-code-mode-mcp.git
cd fortimanager-code-mode-mcp
npm install
# Generate API spec (requires HTML docs — see above)
npm run generate:spec
# Configure environment
cp .env.example .env
# Edit .env with your FortiManager details
# Run with Docker Compose
docker compose up -d --build
# Verify
curl http://localhost:8000/health
# → {"status":"ok","version":"1.0.0"}
Prerequisite: You must have built the server from source with spec files generated first. Use
"command": "node"with the path to your local build.
Create .vscode/mcp.json in your workspace:
{
"servers": {
"fortimanager": {
"type": "stdio",
"command": "node",
"args": ["/path/to/fortimanager-code-mode-mcp/dist/index.js"],
"env": {
"FMG_HOST": "https://fortimanager.example.com",
"FMG_PORT": "443",
"FMG_API_TOKEN": "your-api-token-here",
"FMG_VERIFY_SSL": "true",
"FMG_API_VERSION": "7.6",
"MCP_TRANSPORT": "stdio"
}
}
}
}
Prerequisite: You must have built the server from source with spec files generated first.
Add to your Claude Desktop configuration (claude_desktop_config.json):
{
"mcpServers": {
"fortimanager": {
"command": "node",
"args": ["/path/to/fortimanager-code-mode-mcp/dist/index.js"],
"env": {
"FMG_HOST": "https://fortimanager.example.com",
"FMG_API_TOKEN": "your-api-token",
"FMG_API_VERSION": "7.6",
"MCP_TRANSPORT": "stdio"
}
}
}
}
┌──────────────────────────────────────────────────────────────┐
│ AI Agent / LLM │
│ │
│ "Find all firewall address objects and list their URLs" │
└────────────────────────┬─────────────────────────────────────┘
│ MCP Protocol (stdio or HTTP)
▼
┌──────────────────────────────────────────────────────────────┐
│ MCP Server (Node.js) │
│ │
│ ┌─────────────────────┐ ┌────────────────────────────────┐ │
│ │ search tool │ │ execute tool │ │
│ │ │ │ │ │
│ │ JS code → QuickJS │ │ JS code → QuickJS (async) │ │
│ │ sandbox │ │ sandbox │ │
│ │ │ │ │ │
│ │ Globals: │ │ Globals: │ │
│ │ • specIndex │ │ • fortimanager.request() │ │
│ │ • getObject() │ │ • console.log() │ │
│ │ • moduleList │ │ │ │
│ │ • errorCodes │ │ Proxies to ──┐ │ │
│ │ • specVersion │ │ │ │ │
│ └─────────────────────┘ └───────────────┼───────────────┘ │
│ │ │
│ ┌─────────────▼──────────────┐ │
│ │ FortiManager JSON-RPC │ │
│ │ Client (fetch + auth) │ │
│ └─────────────┬──────────────┘ │
└────────────────────────────────────────────┼─────────────────┘
│ HTTPS JSON-RPC
▼
┌────────────────────┐
│ FortiManager │
│ (7.4.x / 7.6.x) │
└────────────────────┘
search — Query the API Spec// Find all firewall-related objects
specIndex.filter(function(o) {
return o.name.includes('firewall');
}).map(function(o) {
return { name: o.name, urls: o.urls, type: o.type };
})
// Get full details of a specific object (all attributes, URLs, methods)
getObject('firewall/address')
// Search by attribute name
specIndex.filter(function(o) { return o.attributeNames.includes('srcaddr'); }).map(function(o) { return o.name; })
// Find objects by URL pattern
specIndex.filter(function(o) {
return o.urls.some(function(u) { return u.includes('/dvmdb/'); });
}).map(function(o) { return { name: o.name, urls: o.urls }; })
execute — Call the FortiManager API// List all ADOMs
var resp = fortimanager.request('get', [{ url: '/dvmdb/adom' }]);
resp.result[0].data;
// Get system status
var resp = fortimanager.request('get', [{ url: '/sys/status' }]);
resp.result[0].data;
// Create a firewall address object
var resp = fortimanager.request('add', [
{
url: '/pm/config/adom/root/obj/firewall/address',
data: {
name: 'web-server',
subnet: ['10.0.1.100', '255.255.255.255'],
},
},
]);
resp.result[0].status;
// Device proxy — get interfaces from a managed FortiGate
var resp = fortimanager.request('exec', [
{
url: '/sys/proxy/json',
data: {
target: ['/adom/root/device/my-fortigate'],
action: 'get',
resource: '/api/v2/monitor/system/interface',
},
},
]);
resp.result[0].data;
| Variable | Required | Default | Description |
|---|---|---|---|
FMG_HOST |
Yes | — | FortiManager URL (e.g., https://fmg.example.com) |
FMG_PORT |
No | 443 |
HTTPS port |
FMG_API_TOKEN |
Yes | — | API token for authentication (how to create) |
FMG_VERIFY_SSL |
No | true |
Verify TLS certificates (false for self-signed certs) |
FMG_API_VERSION |
No | 7.6 |
API spec version (7.4 or 7.6) |
MCP_TRANSPORT |
No | stdio |
Transport mode (http or stdio) |
MCP_HTTP_PORT |
No | 8000 |
HTTP server port (only used with http transport) |
MCP_API_KEY |
No | — | If set, /mcp requires Authorization: Bearer <key>. HTTP transport only. |
MCP_TOKEN_PASSTHROUGH |
No | false |
If true, read X-FMG-Token per request and forward to FortiManager. Missing header → fall back to FMG_API_TOKEN. HTTP only. |
The HTTP transport supports two independent auth dimensions, both off by default. The stdio transport is single-process and unaffected.
Gate access to the MCP server itself. Set MCP_API_KEY=<long-random-string> and every /mcp request must include Authorization: Bearer <MCP_API_KEY>. Missing or wrong tokens get 401. /health stays open for health-checkers.
Per-client FortiManager identity. Set MCP_TOKEN_PASSTHROUGH=true and each MCP client can supply its own FortiManager admin token via the X-FMG-Token header. That token is used (instead of FMG_API_TOKEN) for live API calls made by the execute tool for that request only, so FortiManager's own admin-profile RBAC enforces per-user permissions. Requests without X-FMG-Token fall back to FMG_API_TOKEN, so existing single-tenant deployments keep working.
Both can be combined: Authorization gates the server, X-FMG-Token carries the per-client FMG token.
{
"mcpServers": {
"fortimanager": {
"command": "npx",
"args": [
"mcp-remote",
"https://fmg-mcp.example.com/mcp",
"--header", "Authorization:Bearer ${MCP_API_KEY}",
"--header", "X-FMG-Token:${MY_FMG_TOKEN}"
]
}
}
}
# Install dependencies
npm install
# Run unit tests (66 tests across 5 suites)
npm test
# Run integration tests against a live FortiManager (requires .env)
npx tsx scripts/live-test.ts
# Lint
npm run lint
# Type check
npm run typecheck
# Format code
npm run format
# Build
npm run build
# Re-generate API specs from HTML docs
npm run generate:spec
src/
├── client/ # FortiManager JSON-RPC client
│ ├── types.ts # Request/response types, error codes
│ ├── auth.ts # Token & session auth providers
│ └── fmg-client.ts # HTTP client (get/set/add/update/delete/exec/clone/move)
├── executor/ # QuickJS WASM sandbox executors
│ ├── types.ts # ExecuteResult, LogEntry, ExecutorOptions
│ ├── executor.ts # Base executor (lifecycle, console capture, limits)
│ ├── search-executor.ts # Spec index + getObject() injection
│ └── code-executor.ts # fortimanager.request() proxy (async)
├── server/ # MCP server and transport
│ ├── server.ts # McpServer with search + execute tools
│ └── transport.ts # Stdio + Streamable HTTP transports
├── spec/ # Generated API spec JSON files (git-ignored, generated locally)
│ ├── fmg-api-spec-7.4.json # 72 modules, 17,426 objects, 38,586 URLs
│ └── fmg-api-spec-7.6.json # 82 modules, 22,060 objects, 49,285 URLs
├── types/ # Shared type definitions
├── config.ts # Zod-validated environment config
├── __tests__/ # Unit tests (66 tests across 5 suites)
│ └── fixtures/ # Sample spec, response builders
└── index.ts # Entry point
scripts/
├── generate-spec.ts # HTML docs → JSON spec generator
├── e2e-test.ts # End-to-end scenario tests (live FMG)
├── live-test.ts # Integration test suite (86 tests against live FMG)
└── spec-coverage.ts # API spec coverage report & live URL validation
process, require, fs, or any Node.js APIs.eval() or new Function(). Only the WASM sandbox executes untrusted code.get, set, add, update, delete, exec, clone, move, replace) are forwarded from sandbox code.url fields before forwarding.FMG_VERIFY_SSL=true). Disable only for development with self-signed certificates.Authorization: Bearer header. No passwords stored.See SECURITY.md for vulnerability reporting.
This project was co-developed by Jamie van der Pijll and GitHub Copilot (Claude).
Inspired by:
MIT © 2026 Jamie van der Pijll
Выполни в терминале:
claude mcp add fortimanager-code-mode-mcp -- npx Безопасность
Низкий рискАвтоматическая эвристика по публичным данным — не гарантия безопасности.