Bosphorus

← Knowledge base

FortiGate: each person's own API token through the vault

Let every person reach FortiGate firewalls with their own FortiOS API token and settings, set once on My variables, never typed into a chat.

ToolsMy variablesFirewallsSecurityChecked against the product on

Animated diagram: a request carrying one person's own variable through the gateway to a tool serverMehmetengineeringAyşeengineeringZeynepoperationsaimochat + agentsGateway/api/mcp/githubkeyteamgrantvarsno per-person valueread for this request onlyMCP servergithubGitHub APIsees one identityVAULTOpenBao or sealed · per person, serverno values storedAUDIT LOG
  1. Step 01

    Before: one key for everyone

    • The gateway added the single credential an administrator stored on the server to every call.
    • Ayşe, Mehmet and Zeynep all arrived at the far end as the same identity.
    • Rate limits, permissions and audit trails over there could not tell your people apart.
  2. Step 02

    An administrator declares what the server accepts

    • On the console's /mcp page, Variables tab: a name, whether it is secret, whether it is required, and how it travels.
    • Names only. An administrator never types anyone's value here.
    • A name that was not declared is never stored and never sent. The fields follow the environment-variable shape of the MCP Registry's server.json.
    On the wireGITHUB_API_KEY secret · required header Authorization: Bearer ${value}
  3. Step 03

    Ayşe asks. The gateway stops.

    • Key, team scope and her grant all pass. Then the required variable turns out to be missing.
    • No request leaves for the tool server. Nothing reaches GitHub.
    • The tool reply links to her personal page, and a client that supports MCP link prompts shows it as a prompt. The value is never asked for in chat: the MCP specification forbids collecting secrets that way.
    On the wireisError: true "This tool needs your own GITHUB_API_KEY … Set it once at /vault?server=github"
  4. Step 04

    Ayşe sets it once, on her own page

    • My variables (/vault), behind the same sign-in as the console. She sees only her own rows.
    • Where the installation runs OpenBao, it goes into her own folder, kv/users/<her email>/mcp/github, which she can open there herself. Otherwise it is stored as ciphertext bound to (person, server, name), so a row cannot be moved to another person.
    • Never shown here again. The page shows a short fingerprint instead; administrators and the model never see the value. Only Ayşe can read it back, and only in her own OpenBao folder.
    On the wirekv/users/ayse.demir@corp.example/mcp/github GITHUB_API_KEY = ••••••••
  5. Step 05

    Now the call is hers

    • The gateway reads her value for this one request only, from her OpenBao folder or by decrypting her sealed row, and renders the administrator's template into a header.
    • Never cached and never placed in the model's context.
    • The audit row records the variable's name. If the tool echoes the value, the stored copy of the response is masked.
    On the wirerequestContext.userVars = { injected: ["GITHUB_API_KEY"] }
  6. Step 06

    Everyone on their own key

    • Ayşe and Mehmet reach GitHub as themselves. Zeynep has set nothing, so she gets the link to her page instead.
    • An administrator sees how many people hold a value. Never which value.
    • Offboarding is one delete on the Variables tab. An administrator can delete a person's values but never read them.
    On the wiregithub · 2 of 3 people have set GITHUB_API_KEY
  7. Step 07

    Three ways in, one vault

    • Header template, live: for third-party servers you do not own. Nothing changes on their side.
    • Sealed bundle, live: for servers you run yourself. One encrypted header, bound to that server's name and valid for 60 seconds.
    • Environment bridge, live: for tools that only read environment variables. One child process per person, its environment filled from the vault.
  8. Step 08

    What it protects, and what it does not

    • What the vault protects today, and where the remaining trust sits.

    Never sees a value

    The chat, the model's context, the audit log, the log stream, metrics, a console administrator, another person, and any tool server the variable was not declared for.

    Could still read a value

    The platform itself: it reads values unattended, so a scheduled 03:00 agent run still works. With OpenBao it signs in as its own service account, holds no stored credential and may read only people's mcp/ folders, while OpenBao's own administrators can reach every folder. Without it, whoever holds both the vault key and the database. Backup archives carry the vault, so guard them like it.

Header delivery, sealed bundles and the environment bridge are live, and each person's values can live in their own OpenBao folder.

A firewall management tool server is exactly the kind of tool where every person should act as themselves. Each administrator’s FortiOS REST API token carries their own admin profile, their own trusted hosts and their own entry in the firewall’s log. A single shared token would erase all three. This article shows how an administrator connects a FortiGate tool server once, and how each person then brings their own token and settings through My variables, without ever typing a token into a chat.

It is written from a real FortiGate tool server we examined on 27 September 2026: 240 tools, every one of them taking the firewall address, the VDOM and the API token as tool arguments. That design is the first thing to change, and the next section explains why.

Who does what

Step Who Where
Make the tool server read the token from the request, not from tool arguments The server’s developer The tool server’s code
Register the server, declare its variables, choose who may use it A console administrator /mcp
Create a FortiOS API token and save it with their settings Each person FortiGate, then My variables (/vault)
Ask the chat about their firewalls Each person The chat workspace, the editor extension, their agents

People cannot register servers or declare variables themselves. The section Why people cannot set this up themselves explains the reasons.

Before you start: the token must not be a tool argument

Open the server’s row on /mcp and look at its tools’ parameters. If a tool takes something like api_token, password or secret as an argument, stop here.

A tool argument is written by the model. For the model to fill in api_token, the token has to be in the conversation, so the person pastes it into the chat. From there it is:

On the server we examined, a token of 16 or more characters sat in the stored arguments of 4 of its 12 recorded calls. The vault cannot help here: it fills request headers or a sealed per-person bundle, and it never writes a tool argument. If your server looks like this, rotate any token that was ever typed into a chat.

The same applies, in a milder form, to a host argument. A model that can choose the address can be talked into pointing the tool at an address nobody intended.

How a server can receive a person’s own values

The vault has three delivery paths. Which one fits depends on how the server is built.

The server Path Status
Is a remote HTTP tool server you can change, or a third-party service that reads a header Header: the value lands in a header the administrator names, on that person’s calls only Live
Is a server you run and can extend Sealed bundle: all of the person’s values in one encrypted header that only this server can open Live
Is a stdio package that reads its token from environment variables at start-up Environment bridge: one process per person, started with that person’s values Live, public HTTPS only (see below)

For a FortiGate tool server that speaks HTTP, headers are the simplest path, and the rest of this article uses them.

Part A: the administrator

A1. Have the server read the request, not the arguments

Ask the server’s developer for one change: take the FortiOS token, and optionally the target device and VDOM, from the request headers of each call, and remove those parameters from every tool’s input schema. Here is a sketch of the idea using the Python MCP SDK; adapt it to whatever framework the server uses:

from mcp.server.fastmcp import FastMCP, Context

mcp = FastMCP("fortigate")

DEVICES = {  # the firewalls this server may reach, chosen by its operator
    "hq-fw":     "https://203.0.113.10",
    "branch-fw": "https://203.0.113.20",
}

def fortios_target(ctx: Context):
    h = ctx.request_context.request.headers
    token = h.get("authorization", "").removeprefix("Bearer ").strip()
    if not token:
        raise ValueError("No FortiOS token: set FORTIGATE_API_TOKEN on My variables.")
    device = h.get("x-fortigate-device", "hq-fw")
    if device not in DEVICES:
        raise ValueError(f"Unknown device {device!r}. Known: {', '.join(DEVICES)}")
    vdom = h.get("x-fortigate-vdom") or "root"
    return DEVICES[device], token, vdom

@mcp.tool()
async def system_status(ctx: Context) -> dict:
    """FortiOS system status: firmware, hostname, serial, uptime, HA state."""
    base, token, vdom = fortios_target(ctx)
    ...  # GET {base}/api/v2/monitor/system/status?vdom={vdom}
         # with the header  Authorization: Bearer {token}

Two design choices in that sketch are deliberate:

A2. Register the server

On /mcp, Register a server:

Field Value
Name FortiGate
URL the server’s streamable HTTP endpoint, for example https://fortigate-mcp.corp.example/mcp
Auth custom header, header X-Api-Key, with the server’s own access key as Secret
Identity require user (signed assertion) ticked

Press Register & sync. Discovery uses the server’s own access key, so the tool list loads even before anyone has saved a FortiOS token.

A3. Declare the variables

On the Variables tab of /mcp, declare one variable per value a person brings. The first is the one that matters; the other two are the optional settings this design uses. The header names are illustrative: use whatever your server reads.

Variable Delivery Header Template Required Secret Format
FORTIGATE_API_TOKEN header Authorization Bearer ${value} yes yes string
FORTIGATE_DEVICE header X-FortiGate-Device ${value} no no string
FORTIGATE_VDOM header X-FortiGate-VDOM ${value} no no string

Write a description for each, because people read it on their own page. For example: “Your FortiOS REST API token (read-only is enough). Create it under System → Administrators → REST API Admin.”

What the platform enforces for you:

Other settings a FortiGate server could reasonably accept per person, all illustrative:

Variable What it would do Advice
FORTIGATE_TIMEOUT_SECONDS Allow slow operations, such as a large policy export Harmless; format number
FORTIGATE_READ_ONLY Ask the server to refuse write tools for this person Useful as a second guard; the token’s admin profile is the real one
FORTIGATE_VERIFY_TLS Turn certificate checks off Do not offer. A person must not be able to switch off the check that proves they are talking to their firewall
FORTIGATE_HOST A free address instead of a device name Avoid it, for the reasons in A1

A4. Choose who may use it

In the server’s Access column, choose granted and add the people who administer firewalls. Everyone else gets the same answer as for a server that does not exist. A server in granted mode is also kept out of the chat workspace’s shared tool list, because one shared connection cannot tell people apart. The editor extension and agents list it per person.

Part B: each person

B1. Create your own FortiOS REST API token

On the FortiGate, as an administrator with the right to create accounts:

  1. Go to System → Administrators → Create New → REST API Admin.
  2. Give it a name that says whose it is, for example api-ayse.
  3. Pick an admin profile. Read-only is enough to ask about status, sessions and policies. Grant write access only if you mean to make changes from a chat.
  4. Under Trusted Hosts, enter the address the tool server calls from. The token then works from nowhere else, even if it leaks.
  5. Press OK and copy the key. FortiOS shows it once.

B2. Save it on My variables

Sign in to the console, open My variables (/vault) and find FortiGate:

Variable What to enter
FORTIGATE_API_TOKEN the key you just copied
FORTIGATE_DEVICE the device name your administrator gave you, for example hq-fw (optional)
FORTIGATE_VDOM your VDOM, for example root (optional)

Press Save on each. The token row then shows set and a short fingerprint; the value is never shown again, not even to you. The device and VDOM are not secret, so they are shown back.

B3. Ask

In a chat with the FortiGate tools enabled, ask for example:

Each call carries your token to your device and VDOM. The firewall’s own log records api-ayse, not a shared account.

What travels where, on one call

you ──chat──▶ model ──tool call, no token──▶ gateway
gateway ──X-Api-Key: <server key>
          Authorization: Bearer <your FortiOS token>
          X-FortiGate-Device: hq-fw
          X-FortiGate-VDOM: root ──────────────▶ FortiGate tool server ──▶ your firewall
audit trail: tool, arguments, and the variable NAMES used. Never the token.

If the server only reads environment variables

Some FortiGate tool servers are command-line packages that read FORTIGATE_HOST and FORTIGATE_API_TOKEN from their environment once, at start-up. For those, the environment bridge runs one process per person, started with that person’s values. The administrator lists the server in an instance file, which is illustrative here:

{
  "slug": "fortigate",
  "name": "FortiGate",
  "command": "fortigate-mcp",
  "args": [],
  "egress": "public-https",
  "vars": [
    { "name": "FORTIGATE_API_TOKEN", "env": "FORTIGATE_API_TOKEN", "isSecret": true, "isRequired": true },
    { "name": "FORTIGATE_VDOM", "env": "FORTIGATE_VDOM", "isSecret": false }
  ]
}

Know one limit before choosing this path: today the bridge offers no network at all or public HTTPS only. A firewall whose management interface is on a private network is unreachable from it until a private-network egress option exists. For a firewall on the LAN, use the header path above.

Why people cannot set this up themselves

It is tempting to let each person register their own tool server and declare their own variables. Each of those steps is a decision about where the platform sends traffic and credentials, so they stay with administrators:

What people do control is everything that is theirs: their own token, device and VDOM, set and replaced on their own page, never visible to anyone else.

When something is not right

Situation What to do
The tools ask you for a token in the chat The server still takes the token as an argument (see Before you start). Do not paste it; tell your administrator.
This tool needs your own FORTIGATE_API_TOKEN Save your token on My variables; the message links there.
401 from the firewall The token was revoked, expired, or your address is not in its Trusted Hosts. Create a new one and Replace the value.
403 on a change Your admin profile is read-only. That is the intended guard.
Unknown device Use a device name your administrator lists. The server refuses anything else.
FortiGate is not on My variables You have not been granted it yet. Ask your administrator.
Someone leaves They, or an administrator on the Variables tab, delete their values. Also delete their REST API admin on the FortiGate.

The walkthrough at the top of this page shows the same flow with a different tool, and the GitHub article uses the header path with a third-party service.