This guide shows how to use the FortiManager Code Mode MCP Server with AI agents like VS Code Copilot, Claude Desktop, or any MCP-compatible client.
Before using this server, you must generate the API spec files from Fortinet's FortiManager JSON API Reference documentation. The spec files are not included in this repository.
- Download the HTML docs from the Fortinet Developer Network (FNDN) — requires a Fortinet account
- Extract the HTML files into
docs/api-reference/(see README.md for exact folder structure) - Generate the spec:
npm run generate:spec - Build:
npm run build
The server will not start without the spec files. If you see an "API SPEC NOT FOUND" error, you haven't completed this step.
Create .vscode/mcp.json in your workspace (assumes you built the server locally):
{
"servers": {
"fortimanager": {
"type": "stdio",
"command": "node",
"args": ["/path/to/fortimanager-code-mode-mcp/dist/index.js"],
"env": {
"FMG_HOST": "https://your-fmg.example.com",
"FMG_API_TOKEN": "your-api-token",
"FMG_VERIFY_SSL": "false",
"FMG_API_VERSION": "7.6"
}
}
}
}Note: You must generate the spec files first —
npm run generate:spec— before building the Docker image.
# docker-compose.yml
services:
fmg-mcp:
build:
context: .
dockerfile: Dockerfile
ports:
- "8000:8000"
environment:
FMG_HOST: https://your-fmg.example.com
FMG_API_TOKEN: your-api-token
FMG_VERIFY_SSL: "false"
FMG_API_VERSION: "7.6"
MCP_TRANSPORT: http
MCP_HTTP_PORT: "8000"The server exposes exactly two tools:
Use this tool to discover API endpoints, look up object attributes, find methods, and understand the FortiManager API structure — all without making live API calls.
Use this tool to interact with FortiManager: read configuration, create objects, modify settings, run diagnostic commands, and more.
// List all modules with their object counts
moduleList.map(function(m) {
return m.name + ": " + m.objectCount + " objects";
})// Search for firewall-related objects
specIndex.filter(function(o) {
return o.name.indexOf("firewall") !== -1;
}).map(function(o) {
return { name: o.name, module: o.module, methods: o.methods };
})// Get all attributes for a specific object
var obj = getObject("firewall policy");
obj ? obj.attributes.map(function(a) {
return { name: a.name, type: a.type, description: a.description };
}) : "Not found"// Look up an object by its API URL
var obj = getObject("/pm/config/adom/{adom}/pkg/{pkg}/firewall/policy");
obj ? { name: obj.name, methods: obj.methods, attrCount: obj.attributes.length } : "Not found"// Find specific error codes
errorCodes.filter(function(e) {
return e.message.toLowerCase().indexOf("permission") !== -1;
})// List all objects in the dvmdb module
specIndex.filter(function(o) {
return o.module === "dvmdb";
}).map(function(o) {
return { name: o.name, methods: o.methods };
})// Which objects support the 'exec' method?
specIndex.filter(function(o) {
return o.methods.indexOf("exec") !== -1;
}).slice(0, 20).map(function(o) {
return { name: o.name, url: o.urls[0] };
})var resp = fortimanager.request("get", [{
url: "/sys/status"
}]);
respvar resp = fortimanager.request("get", [{
url: "/dvmdb/adom",
option: ["no scope member"]
}]);
resp.result[0].data.map(function(a) {
return { name: a.name, os_ver: a.os_ver, mr: a.mr };
})var resp = fortimanager.request("get", [{
url: "/dvmdb/device",
option: ["no scope member"]
}]);
resp.result[0].data.map(function(d) {
return { name: d.name, ip: d.ip, platform_str: d.platform_str, conn_status: d.conn_status };
})var resp = fortimanager.request("get", [{
url: "/pm/config/adom/root/pkg/default/firewall/policy"
}]);
resp.result[0].datavar resp = fortimanager.request("add", [{
url: "/pm/config/adom/root/obj/firewall/address",
data: {
name: "test-server-01",
type: 0,
subnet: ["10.0.1.100", "255.255.255.255"],
comment: "Created via MCP"
}
}]);
resp.result[0].statusvar resp = fortimanager.request("get", [{
url: "/pm/config/adom/root/obj/firewall/address/test-server-01"
}]);
resp.result[0].datavar resp = fortimanager.request("update", [{
url: "/pm/config/adom/root/obj/firewall/address/test-server-01",
data: {
comment: "Updated via MCP"
}
}]);
resp.result[0].statusvar resp = fortimanager.request("delete", [{
url: "/pm/config/adom/root/obj/firewall/address/test-server-01"
}]);
resp.result[0].status// Get devices and ADOMs in a single call
var resp = fortimanager.request("get", [
{ url: "/dvmdb/device", option: ["no scope member"] },
{ url: "/dvmdb/adom", option: ["no scope member"] }
]);
({
devices: resp.result[0].data.length,
adoms: resp.result[1].data.length
})// Get only connected devices
var resp = fortimanager.request("get", [{
url: "/dvmdb/device",
filter: [["conn_status", "==", 1]],
option: ["no scope member"]
}]);
resp.result[0].data.map(function(d) {
return d.name + " (" + d.ip + ")";
})// Get first 5 firewall addresses
var resp = fortimanager.request("get", [{
url: "/pm/config/adom/root/obj/firewall/address",
range: [0, 5]
}]);
resp.result[0].data.map(function(a) { return a.name; })var resp = fortimanager.request("get", [{
url: "/pm/config/adom/root/obj/firewall/address"
}]);
var status = resp.result[0].status;
if (status.code !== 0) {
"Error: " + status.message + " (code " + status.code + ")";
} else {
"Got " + resp.result[0].data.length + " addresses";
}| Code | Meaning |
|---|---|
| 0 | OK / Success |
| -2 | Object already exists |
| -3 | Object not found |
| -6 | Invalid URL |
| -10 | Object dependency prevents action |
| -11 | No permission |
| -13 | Session expired |
The typical agent workflow is:
- Search to find the right API endpoint and understand its attributes
- Execute to perform the actual operation
Example conversation:
User: "List all FortiGate devices and show their firmware versions"
Agent (search call):
specIndex.filter(function(o) { return o.name === "device" && o.module === "dvmdb"; }).map(function(o) { return { urls: o.urls, attributeNames: o.attributeNames }; })Agent (execute call):
var resp = fortimanager.request("get", [{ url: "/dvmdb/device", fields: ["name", "ip", "os_ver", "mr", "patch", "platform_str"], option: ["no scope member"] }]); resp.result[0].data
For complex tasks, chain multiple execute calls:
// Step 1: Create address
var r1 = fortimanager.request("add", [{
url: "/pm/config/adom/root/obj/firewall/address",
data: { name: "web-server", type: 0, subnet: ["10.0.1.10", "255.255.255.255"] }
}]);
// Step 2: Create address group referencing the address
var r2 = fortimanager.request("add", [{
url: "/pm/config/adom/root/obj/firewall/addrgrp",
data: { name: "web-servers", member: ["web-server"] }
}]);
({ address: r1.result[0].status, group: r2.result[0].status })- Use
varinstead ofconst/let: QuickJS runs in global mode;const/letat top-level can cause issues with result capture. - No
await:fortimanager.request()is synchronous in the sandbox. Do NOT useasync/await. - Use
function()syntax: Arrow functions work butfunction()is more reliable across QuickJS versions. - Return the result as the last expression: The tool returns the value of the last expression in your code.
- Use
fieldsparameter to request only the attributes you need - Use
filterto reduce result sets server-side - Use
rangefor pagination on large collections - Batch related requests into a single
fortimanager.request()call - Use
option: ["no scope member"]to skip scope member resolution (faster)
- Never hardcode credentials in tool calls — they're configured via environment variables
- Use read-only operations (
get) for discovery before modifying anything - Test changes in a non-production ADOM first
- The sandbox limits you to 50 API calls per execution to prevent runaway operations