Skip to main content
Glama

Create network VPN peer

create_network_vpn_peer
Idempotent

Add a WireGuard device or site-to-site VPN peer to a VPSnet gateway using a one-time public reveal key, with scoped IPv4 grants or network-wide access for approved tunnels.

Instructions

Add a WireGuard device, or with kind=site_to_site a connected site, using an externally generated one-time PUBLIC reveal key. For individual access pass 0-16 owned grant-candidate IPv4 addresses: split-tunnel devices require at least one; full-tunnel devices may use an empty list; sites always require at least one. A device may instead use grant_scope=network with grants=[] for all eligible current and future servers of its network; obtain approval for that broader access. Devices must omit protocol, site and site_down_alert. Sites require site.remote_prefixes and may enable site_down_alert. Site protocol defaults to wireguard (endpoint/keepalive and optional router_model); ipsec_ikev2 uses remote_address, vendor and optional mutually exclusive pqc/legacy_compat instead. Before IPsec, read get_network_appliance_capabilities for offered vendors and get_network_vpn_gateway for capabilities.site_ipsec and ipsec_profiles; the gateway must advertise availability. Personal-device IPsec is unsupported. Keep the matching private key entirely outside MCP. The gateway generates the tunnel secret; wait for config_available before consuming the sealed configuration. For IPsec router instructions use get_network_vpn_peer_recipe; never send a raw PSK to MCP. Requires explicit networking:read for reads or networking:manage for changes; full/read defaults do not grant networking. The backend enforces ownership, feature availability and service 2FA locks. Read current capabilities and owned resource state first; obtain approval for the exact change. An accepted operation is pending, not proof of connectivity. Poll the returned operation/resource on its in_progress flag, waiting next_check_seconds between reads: while in_progress is true its reason_code is what is awaited (reason_kind waiting, e.g. a queued or pending reason, or a control plane that has not answered yet), never an error; only in_progress false with reason_kind failure is a failure. Preserve reason_code. On transport uncertainty inspect state and reuse only the original key and unchanged request; never automatically retry.

Input Schema

NameRequiredDescriptionDefault
kindNo
nameYes
siteNo
grantsYes
protocolNo
gateway_idYesExact lowercase UUID returned by an owned networking resource
grant_scopeNoservers (default) chooses individual grants; network grants a device every eligible current and future server of this network, subject to network security policy. Network scope requires empty grants and is unavailable for sites.
idempotencyKeyYesIdempotency-Key for this exact change; preserve it and the unchanged body on uncertain recovery
site_down_alertNo
reveal_public_keyYesCustomer-generated one-time X25519 PUBLIC key only. Keep its private key outside MCP; needed to decrypt the sealed configuration.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed7 schema fields changedv2.3.20
    • addedInput schema / properties / grant_scope
      Added value: +{
      +  "description": "servers (default) chooses individual grants; network grants a device every eligible current and future server of this network, subject to network security policy. Network scope requires empty grants and is unavailable for sites.",
      +  "enum": [
      +    "servers",
      +    "network"
      +  ],
      +  "type": "string"
      +}
    • addedInput schema / properties / protocol
      Added value: +{
      +  "enum": [
      +    "wireguard",
      +    "ipsec_ikev2"
      +  ],
      +  "type": "string"
      +}
    • removedInput schema / properties / site / additionalProperties
      Removed value: -false
    • addedInput schema / properties / site / anyOf
      Added value: +[
      +  {
      +    "additionalProperties": false,
      +    "properties": {
      +      "endpoint": {
      +        "anyOf": [
      +          {
      +            "pattern": "^\\d{1,3}(?:\\.\\d{1,3}){3}:\\d{1,5}$",
      +            "type": "string"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "The office router's fixed public IPv4:port; omit or null to learn a roaming endpoint"
      +      },
      +      "persistent_keepalive_seconds": {
      +        "maximum": 600,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "remote_prefixes": {
      +        "description": "The office's PRIVATE IPv4 networks (10/8, 172.16/12, 192.168/16 or 100.64/10), canonical and disjoint",
      +        "items": {
      +          "pattern": "^\\d{1,3}(?:\\.\\d{1,3}){3}\\/(?:[0-9]|[12][0-9]|3[0-2])$",
      +          "type": "string"
      +        },
      +        "maxItems": 16,
      +        "minItems": 1,
      +        "type": "array"
      +      },
      +      "router_model": {
      +        "enum": [
      +          "mikrotik",
      +          "pfsense",
      +          "opnsense",
      +          "unifi",
      +          "linux",
      +          "other"
      +        ],
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "remote_prefixes"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "properties": {
      +      "legacy_compat": {
      +        "description": "Request only when the gateway advertises legacy_compat in ipsec_profiles; cannot be true together with pqc",
      +        "type": "boolean"
      +      },
      +      "pqc": {
      +        "description": "Request only when the gateway advertises pqc in ipsec_profiles; cannot be true together with legacy_compat. Requested PQC is not proof of negotiated post_quantum.",
      +        "type": "boolean"
      +      },
      +      "remote_address": {
      +        "anyOf": [
      +          {
      +            "format": "ipv4",
      +            "type": "string"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "The site's canonical public IPv4 address; omit or null for a dynamic address. No port. The backend validates address and prefix eligibility."
      +      },
      +      "remote_prefixes": {
      +        "description": "The office's PRIVATE IPv4 networks (10/8, 172.16/12, 192.168/16 or 100.64/10), canonical and disjoint",
      +        "items": {
      +          "pattern": "^\\d{1,3}(?:\\.\\d{1,3}){3}\\/(?:[0-9]|[12][0-9]|3[0-2])$",
      +          "type": "string"
      +        },
      +        "maxItems": 16,
      +        "minItems": 1,
      +        "type": "array"
      +      },
      +      "vendor": {
      +        "description": "Advertised router recipe vendor; omitted uses generic_strongswan",
      +        "enum": [
      +          "fortigate",
      +          "mikrotik",
      +          "pfsense",
      +          "opnsense",
      +          "cisco_ios_xe",
      +          "cisco_asa",
      +          "unifi",
      +          "junos_srx",
      +          "panos",
      +          "generic_strongswan",
      +          "generic_libreswan"
      +        ],
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "remote_prefixes"
      +    ],
      +    "type": "object"
      +  }
      +]
    • removedInput schema / properties / site / properties
      Removed value: -{
      -  "endpoint": {
      -    "anyOf": [
      -      {
      -        "pattern": "^\\d{1,3}(?:\\.\\d{1,3}){3}:\\d{1,5}$",
      -        "type": "string"
      -      },
      -      {
      -        "type": "null"
      -      }
      -    ],
      -    "description": "The office router's fixed public IPv4:port; omit or null to learn a roaming endpoint"
      -  },
      -  "persistent_keepalive_seconds": {
      -    "maximum": 600,
      -    "minimum": 0,
      -    "type": "integer"
      -  },
      -  "remote_prefixes": {
      -    "description": "The office's PRIVATE IPv4 networks (10/8, 172.16/12, 192.168/16 or 100.64/10), canonical and disjoint",
      -    "items": {
      -      "pattern": "^\\d{1,3}(?:\\.\\d{1,3}){3}\\/(?:[0-9]|[12][0-9]|3[0-2])$",
      -      "type": "string"
      -    },
      -    "maxItems": 16,
      -    "minItems": 1,
      -    "type": "array"
      -  }
      -}
    • removedInput schema / properties / site / required
      Removed value: -[
      -  "remote_prefixes"
      -]
    • removedInput schema / properties / site / type
      Removed value: -"object"
  2. Changed3 schema fields changedv2.3.17
    • addedInput schema / properties / kind
      Added value: +{
      +  "enum": [
      +    "road_warrior",
      +    "site_to_site"
      +  ],
      +  "type": "string"
      +}
    • addedInput schema / properties / site
      Added value: +{
      +  "additionalProperties": false,
      +  "properties": {
      +    "endpoint": {
      +      "anyOf": [
      +        {
      +          "pattern": "^\\d{1,3}(?:\\.\\d{1,3}){3}:\\d{1,5}$",
      +          "type": "string"
      +        },
      +        {
      +          "type": "null"
      +        }
      +      ],
      +      "description": "The office router's fixed public IPv4:port; omit or null to learn a roaming endpoint"
      +    },
      +    "persistent_keepalive_seconds": {
      +      "maximum": 600,
      +      "minimum": 0,
      +      "type": "integer"
      +    },
      +    "remote_prefixes": {
      +      "description": "The office's PRIVATE IPv4 networks (10/8, 172.16/12, 192.168/16 or 100.64/10), canonical and disjoint",
      +      "items": {
      +        "pattern": "^\\d{1,3}(?:\\.\\d{1,3}){3}\\/(?:[0-9]|[12][0-9]|3[0-2])$",
      +        "type": "string"
      +      },
      +      "maxItems": 16,
      +      "minItems": 1,
      +      "type": "array"
      +    }
      +  },
      +  "required": [
      +    "remote_prefixes"
      +  ],
      +  "type": "object"
      +}
    • addedInput schema / properties / site_down_alert
      Added value: +{
      +  "type": "boolean"
      +}
  3. First observedv2.3.16

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations already declaring non-read-only, idempotent, open-world, and non-destructive behavior, the description discloses approval requirements, networking:read/networking:manage permissions, 2FA locks, gateway-generated tunnel secrets, sealed configuration handling, and pending-operation polling semantics. It also explains how to interpret in_progress vs. failure reason codes and warns against automatic retries.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core action and kind distinction, and nearly every sentence carries operational value. It is still very dense and long, with several clauses packed into run-on sentences, so it is not maximally concise despite being appropriately detailed for a complex mutation.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the mutation complexity, 10 parameters, 40% schema coverage, no output schema, and rich annotation profile, the description is complete enough to call the tool correctly. It covers permissions, approval, secret handling, kind-specific requirements, protocol selection, and post-call operation polling.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With only 40% schema description coverage across 10 parameters, the description compensates heavily by explaining grants counts and split/full-tunnel rules, grant_scope=network semantics, device-vs-site required and omitted fields, protocol defaults, and IPsec-specific fields. It adds substantial meaning beyond the raw schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: adding a WireGuard device or, with kind=site_to_site, a connected site. It clearly distinguishes the road-warrior/device path from the site path and is immediately distinguishable from sibling tools such as revoke_network_vpn_peer or take_network_vpn_peer_config.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit when-to-use guidance, including kind-specific requirements, grant-scope alternatives, protocol defaults, and prerequisites such as reading get_network_appliance_capabilities and get_network_vpn_gateway before IPsec. It also states exclusions and constraints, such as personal-device IPsec being unsupported and network grant scope being unavailable for sites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Deploy Server

Other Tools