Skip to main content
Glama
Home Assistant MCP Server

Home Assistant MCP Server

Official

Set Integration

ha_set_integration
Destructive

Manage Home Assistant integration config entries: enable or disable, add via step-by-step config flows, update options, reconfigure connections, set log levels, and send integration commands.

Instructions

Manage an integration (config entry): enable/disable, add, update options, reconfigure, or send its own commands.

Modes (pick one):

  • Enable/disable: entry_id + enabled.

  • Add integration: domain (+ menu_choice, + config) — runs the domain's config flow one step per call, as the HA UI does.

  • Update options: entry_id (+ menu_choice, + config) — the entry's options flow (what the "Configure" button does in the HA UI), one step per call.

  • Reconfigure: entry_id + reconfigure=True — connection settings such as host, port, credentials, one step per call. Each call except an abort first checks the entry's identity; the first call without config returns the flow's first step.

  • Log level: log_level + domain (or entry_id) — sets how much the integration logs; read it back with ha_get_integration's log_level.

  • Integration command: entry_id + ws_command (+ ws_data) — what the integration's panel sends (KNX project, entities, telegrams). Home Assistant marks none of these commands as read-only, so a read such as knx/get_knx_project goes through this tool too.

Flows, one step per call: a call answers the step the flow is at and returns Home Assistant's next answer. A form comes back with its fields (type, default, current value, Home Assistant's help text) and flow_id; a menu with menu_options. Answer with flow_id and config (a form) or menu_choice (a menu); flow_id alone returns the step again, and flow_id with abort=True ends the flow. Without flow_id a new flow starts: its first form gets config, and without config the call returns that first step. A form Home Assistant rejects is an error with errors and error_text (Core's text), and the flow stays open to correct it. A flow stays open until Home Assistant finishes it or you abort it; while a setup flow is open, Home Assistant refuses another one for the same device as already_in_progress.

WHEN NOT TO USE:

  • Helpers (template, group, utility_meter, ...): use ha_config_set_helper. otp is the user's to set up in the HA UI: its secret is a credential enrolled in an authenticator app; do not create it.

  • Config subentries: use ha_config_set_helper(helper_type='config_subentry'); subentry_type here describes their form; a menu_choice that finishes the flow saves the subentry.

  • Removing an entry: use ha_remove_helpers_integrations.

  • ha-mcp's own ha_mcp_tools entries are refused: manage them in the HA UI, or (server settings, developer mode on) ha_dev_manage_settings / ha_dev_manage_server.

Use ha_get_integration() to find entry IDs, and ha_get_integration(entry_id=..., include_schema=True) to inspect the options fields. Its supports_reconfigure field tells you whether an entry qualifies for reconfigure=True, and ha_get_integration(entry_id=..., include_ws_commands=True) lists the commands ws_command accepts with their schemas.

Caveats: adding an integration runs its config flow exactly as the HA UI would (may pair devices, scan the network, create entities). Flows requiring a browser step (OAuth) are refused at that step and aborted. A progress step (Home Assistant working in the background) is waited out for up to 120 s; after that the error carries flow_id to ask again. When Home Assistant does not answer a submit or a progress poll (or, on reconfigure, answers with any 5xx), the error carries status applied_but_unverified: the entry may exist or the change may be saved, so check it before retrying. An aborted flow returns Core's own text next to the key (reason_text). Reconfigure edits the settings a live integration connects with: a wrong host or credential takes it offline, and there is no automatic rollback. A finished reconfigure waits for the reload Home Assistant runs and reports the entry's state as entry_state: status applied_and_verified once it is loaded, applied_but_unverified otherwise.

EXAMPLES:

  • Add: ha_set_integration(domain="local_calendar", config={"calendar_name": "Chores"})

  • Add in two steps: ha_set_integration(domain="knx", config={"connection_type": "routing"}) returns the routing form and flow_id, then ha_set_integration(domain="knx", flow_id="...", config={})

  • Answer a menu: ha_set_integration(domain="...", flow_id="...", menu_choice="")

  • See the setup form first: ha_set_integration(domain="workday")

  • Update options: ha_set_integration(entry_id="abc123", config={"scan_interval": 30})

  • Reconfigure: ha_set_integration(entry_id="abc123", reconfigure=True), then ha_set_integration(entry_id="abc123", reconfigure=True, flow_id="...", config={"host": "10.0.0.5"})

  • Cancel a flow: ha_set_integration(domain="knx", flow_id="...", abort=True)

  • Debug logging: ha_set_integration(domain="zha", log_level="DEBUG"), then log_level="DEFAULT" to stop

  • KNX project: ha_set_integration(entry_id="abc123", ws_command="knx/get_knx_project")

If a Claude Desktop user gets a 4-minute timeout with no result on this call, read the target back before repeating it, and tell them to wait a few seconds before clicking the manual-approve button, as clicking it too quickly is known to cause hangs in Claude Desktop. This workaround is specific to Claude Desktop.

integration config entry enable disable add options reconfigure log level debug logging

Tool call template

This tool declares no required parameters. Add optional parameters from the input schema as needed.

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "arguments": {},
    "name": "ha_set_integration"
  }
}

Input Schema

NameRequiredDescriptionDefault
abortNoWith flow_id: end the open flow without saving. A flow Home Assistant no longer has, or one ha-mcp did not open (or opened before it restarted), is an error.
configNoThe values for the form the flow is at: one form per call. Without flow_id it answers the new flow's first form (after menu_choice, when the flow opens with a menu). A key the form does not declare is refused before anything is sent. On an existing entry (options, reconfigure) a field you omit keeps its current value, and a field set to null is cleared where the integration's schema allows that field to be empty.
domainNoIntegration domain to add (e.g. 'workday', 'local_calendar').
enabledNoTrue to enable, False to disable the entry. Mutually exclusive with 'domain' and 'config'.
flow_idNoContinue an open flow: the flow_id a previous call returned, with the same domain or entry_id (and reconfigure=True for a reconfigure flow), until ha-mcp restarts. With config or menu_choice it answers the step; alone it returns the step again.
ws_dataNoThe ws_command's arguments, without 'type' and 'id'. Home Assistant checks them against the command's registered schema and returns its error when they do not match.
entry_idNoConfig entry ID of an existing integration.
log_levelNoSet the integration's log level, like the integration page's Enable/Disable debug logging: pass the integration with 'domain' (no config flow runs) or 'entry_id', and no other mode's parameters (log_persistence may accompany it). Lasts through the next Home Assistant restart unless log_persistence says otherwise. DEFAULT clears the override: the integration then logs at the inherited default level, and a level set for it in configuration.yaml returns only after a restart.
ws_commandNoWith entry_id: one of the integration's own '<domain>/' WebSocket commands to send, as its panel in the HA UI does (e.g. 'knx/get_knx_project'). Its result is returned under result. Streaming commands are refused.
menu_choiceNoThe option to pick at a menu step: with flow_id the menu the flow is at, without it the menu a new flow opens with. Refused when the step is not a menu or does not offer the option.
reconfigureNoUse the existing config entry's official reconfigure flow (its connection settings) instead of its options flow. Every call except an abort checks the entry's identity first. Without flow_id and config this opens the flow and returns its first step; nothing is submitted. Continue with the flow_id this entry's call returned: Home Assistant does not say which entry a reconfigure flow edits.
subentry_idNoWith entry_id and subentry_type: the existing subentry whose reconfigure form to describe.
expected_macNoRequires reconfigure=True. MAC or IEEE the entry's device must still report.
subentry_typeNoWith entry_id: the integration-defined subentry type whose form to describe as subentry_schema; the flow is aborted afterwards, unless a menu_choice finished it (flow_type create_entry: saved).
log_persistenceNoOnly with log_level: how long Home Assistant keeps the level. 'once' (default) through the next restart, 'permanent' after every restart until changed, 'none' until the next restart. Home Assistant validates the value.
expected_device_idNoRequires reconfigure=True. Device registry ID the entry must still own.
expected_unique_idNoRequires reconfigure=True, AND the ha_mcp_tools custom component: Home Assistant does not expose a config entry's unique_id over its API. Without the component this is rejected — anchor on expected_device_id, expected_mac or expected_entity_ids instead.
expected_entity_idsNoRequires reconfigure=True. Exact entity IDs that must remain associated with the entry.

Output Schema

NameRequiredDescriptionDefault

No arguments

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed13 schema fields changedv9.1.1
    • addedInput schema / properties / abort
      Added value: +{
      +  "default": false,
      +  "description": "With flow_id: end the open flow without saving. A flow Home Assistant no longer has, or one ha-mcp did not open (or opened before it restarted), is an error.",
      +  "type": "boolean"
      +}
    • changedInput schema / properties / config / description
      Previous value: -"Flow form data. Updating an existing entry — options or reconfigure — is a patch: a field you omit keeps its current value, and a field set to null is cleared where the integration's schema allows that field to be empty. Multi-step flows consume keys per step. A field two steps declare gets your one value both times; pass step_values={'<step_id>': {'<field>': <value>}} to give a step its own value, or to leave the field out of that step; a LIST of those objects supplies one per encounter when the flow presents a step more than once. Menu steps take 'next_step_id' — a string, or a list of successive selections for flows that present more than one menu (e.g. a menu revisited after each branch, ending in a finish option). The step's data_schema is returned on validation errors so field names can be corrected."New value: +"The values for the form the flow is at: one form per call. Without flow_id it answers the new flow's first form (after menu_choice, when the flow opens with a menu). A key the form does not declare is refused before anything is sent. On an existing entry (options, reconfigure) a field you omit keeps its current value, and a field set to null is cleared where the integration's schema allows that field to be empty."
    • removedInput schema / properties / confirm_token
      Removed value: -{
      -  "anyOf": [
      -    {
      -      "type": "string"
      -    },
      -    {
      -      "type": "null"
      -    }
      -  ],
      -  "default": null,
      -  "description": "Requires reconfigure=True. Any token still matching the entry's current state and the same requested config is accepted, so a token stays valid while nothing moves."
      -}
    • changedInput schema / properties / expected_device_id / description
      Previous value: -"Requires reconfigure=True. Device registry ID the entry must still own, before and after the change."New value: +"Requires reconfigure=True. Device registry ID the entry must still own."
    • addedInput schema / properties / flow_id
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "Continue an open flow: the flow_id a previous call returned, with the same domain or entry_id (and reconfigure=True for a reconfigure flow), until ha-mcp restarts. With config or menu_choice it answers the step; alone it returns the step again."
      +}
    • changedInput schema / properties / log_level / description
      Previous value: -"Set the integration's log level, like the integration page's Enable/Disable debug logging: pass the integration with 'domain' (no config flow runs) or 'entry_id', and nothing else. Lasts through the next Home Assistant restart. DEFAULT clears the override: the integration then logs at the inherited default level, and a level set for it in configuration.yaml returns only after a restart."New value: +"Set the integration's log level, like the integration page's Enable/Disable debug logging: pass the integration with 'domain' (no config flow runs) or 'entry_id', and no other mode's parameters (log_persistence may accompany it). Lasts through the next Home Assistant restart unless log_persistence says otherwise. DEFAULT clears the override: the integration then logs at the inherited default level, and a level set for it in configuration.yaml returns only after a restart."
    • addedInput schema / properties / log_persistence
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "Only with log_level: how long Home Assistant keeps the level. 'once' (default) through the next restart, 'permanent' after every restart until changed, 'none' until the next restart. Home Assistant validates the value."
      +}
    • addedInput schema / properties / menu_choice
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "The option to pick at a menu step: with flow_id the menu the flow is at, without it the menu a new flow opens with. Refused when the step is not a menu or does not offer the option."
      +}
    • changedInput schema / properties / reconfigure / description
      Previous value: -"Use the existing config entry's official reconfigure flow (its connection settings) instead of its options flow. Without confirm_token this is a read-only preflight that returns one."New value: +"Use the existing config entry's official reconfigure flow (its connection settings) instead of its options flow. Every call except an abort checks the entry's identity first. Without flow_id and config this opens the flow and returns its first step; nothing is submitted. Continue with the flow_id this entry's call returned: Home Assistant does not say which entry a reconfigure flow edits."
    • addedInput schema / properties / subentry_id
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "With entry_id and subentry_type: the existing subentry whose reconfigure form to describe."
      +}
    • addedInput schema / properties / subentry_type
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "With entry_id: the integration-defined subentry type whose form to describe as subentry_schema; the flow is aborted afterwards, unless a menu_choice finished it (flow_type create_entry: saved)."
      +}
    • addedInput schema / properties / ws_command
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "With entry_id: one of the integration's own '<domain>/' WebSocket commands to send, as its panel in the HA UI does (e.g. 'knx/get_knx_project'). Its result is returned under result. Streaming commands are refused."
      +}
    • addedInput schema / properties / ws_data
      Added value: +{
      +  "anyOf": [
      +    {
      +      "additionalProperties": true,
      +      "type": "object"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "The ws_command's arguments, without 'type' and 'id'. Home Assistant checks them against the command's registered schema and returns its error when they do not match."
      +}
  2. Changed6 schema fields changedv8.6.0
    • changedInput schema / properties / config / description
      Previous value: -"Flow form data. With 'domain': input for the new integration's config flow. With 'entry_id' alone: input for the entry's options flow (updates its options). Updating an existing entry — options or reconfigure — is a patch: a field you omit keeps its current value, and a field set to null is cleared where the integration's schema allows that field to be empty. Multi-step flows consume keys per step. A field two steps declare gets your one value both times; pass step_values={'<step_id>': {'<field>': <value>}} to give a step its own value, or to leave the field out of that step; a LIST of those objects supplies one per encounter when the flow presents a step more than once. Menu steps take 'next_step_id' — a string, or a list of successive selections for flows that present more than one menu (e.g. a menu revisited after each branch, ending in a finish option). The step's data_schema is returned on validation errors so field names can be corrected."New value: +"Flow form data. Updating an existing entry — options or reconfigure — is a patch: a field you omit keeps its current value, and a field set to null is cleared where the integration's schema allows that field to be empty. Multi-step flows consume keys per step. A field two steps declare gets your one value both times; pass step_values={'<step_id>': {'<field>': <value>}} to give a step its own value, or to leave the field out of that step; a LIST of those objects supplies one per encounter when the flow presents a step more than once. Menu steps take 'next_step_id' — a string, or a list of successive selections for flows that present more than one menu (e.g. a menu revisited after each branch, ending in a finish option). The step's data_schema is returned on validation errors so field names can be corrected."
    • changedInput schema / properties / confirm_token / description
      Previous value: -"Requires reconfigure=True. A token from a reconfigure preflight; applies the change. Any token still matching the entry's current state and the same requested config is accepted, so a token stays valid while nothing moves."New value: +"Requires reconfigure=True. Any token still matching the entry's current state and the same requested config is accepted, so a token stays valid while nothing moves."
    • changedInput schema / properties / domain / description
      Previous value: -"Integration domain to add (e.g. 'workday', 'local_calendar') — starts and drives that domain's config flow. Pass the flow's form fields in 'config'."New value: +"Integration domain to add (e.g. 'workday', 'local_calendar')."
    • changedInput schema / properties / enabled / description
      Previous value: -"True to enable, False to disable the entry. Requires entry_id; mutually exclusive with 'domain' and 'config'."New value: +"True to enable, False to disable the entry. Mutually exclusive with 'domain' and 'config'."
    • changedInput schema / properties / entry_id / description
      Previous value: -"Config entry ID of an existing integration (enable/disable and options-update modes). Omit when adding via 'domain'."New value: +"Config entry ID of an existing integration."
    • addedInput schema / properties / log_level
      Added value: +{
      +  "anyOf": [
      +    {
      +      "enum": [
      +        "DEBUG",
      +        "INFO",
      +        "WARNING",
      +        "ERROR",
      +        "CRITICAL",
      +        "DEFAULT"
      +      ],
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "Set the integration's log level, like the integration page's Enable/Disable debug logging: pass the integration with 'domain' (no config flow runs) or 'entry_id', and nothing else. Lasts through the next Home Assistant restart. DEFAULT clears the override: the integration then logs at the inherited default level, and a level set for it in configuration.yaml returns only after a restart."
      +}
  3. Changed1 schema field changedv8.4.1
    • changedInput schema / properties / config / description
      Previous value: -"Flow form data. With 'domain': input for the new integration's config flow. With 'entry_id' alone: input for the entry's options flow (updates its options). Multi-step flows consume keys per step; menu steps take 'next_step_id' — a string, or a list of successive selections for flows that present more than one menu (e.g. a menu revisited after each branch, ending in a finish option). The step's data_schema is returned on validation errors so field names can be corrected."New value: +"Flow form data. With 'domain': input for the new integration's config flow. With 'entry_id' alone: input for the entry's options flow (updates its options). Updating an existing entry — options or reconfigure — is a patch: a field you omit keeps its current value, and a field set to null is cleared where the integration's schema allows that field to be empty. Multi-step flows consume keys per step. A field two steps declare gets your one value both times; pass step_values={'<step_id>': {'<field>': <value>}} to give a step its own value, or to leave the field out of that step; a LIST of those objects supplies one per encounter when the flow presents a step more than once. Menu steps take 'next_step_id' — a string, or a list of successive selections for flows that present more than one menu (e.g. a menu revisited after each branch, ending in a finish option). The step's data_schema is returned on validation errors so field names can be corrected."
  4. Changed6 schema fields changedv8.3.0
    • addedInput schema / properties / confirm_token
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "Requires reconfigure=True. A token from a reconfigure preflight; applies the change. Any token still matching the entry's current state and the same requested config is accepted, so a token stays valid while nothing moves."
      +}
    • addedInput schema / properties / expected_device_id
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "Requires reconfigure=True. Device registry ID the entry must still own, before and after the change."
      +}
    • addedInput schema / properties / expected_entity_ids
      Added value: +{
      +  "anyOf": [
      +    {
      +      "items": {
      +        "type": "string"
      +      },
      +      "type": "array"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "Requires reconfigure=True. Exact entity IDs that must remain associated with the entry."
      +}
    • addedInput schema / properties / expected_mac
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "Requires reconfigure=True. MAC or IEEE the entry's device must still report."
      +}
    • addedInput schema / properties / expected_unique_id
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "Requires reconfigure=True, AND the ha_mcp_tools custom component: Home Assistant does not expose a config entry's unique_id over its API. Without the component this is rejected — anchor on expected_device_id, expected_mac or expected_entity_ids instead."
      +}
    • addedInput schema / properties / reconfigure
      Added value: +{
      +  "default": false,
      +  "description": "Use the existing config entry's official reconfigure flow (its connection settings) instead of its options flow. Without confirm_token this is a read-only preflight that returns one.",
      +  "type": "boolean"
      +}
  5. Changed1 schema field changedv8.2.0
    • changedInput schema / properties / config / description
      Previous value: -"Flow form data. With 'domain': input for the new integration's config flow. With 'entry_id' alone: input for the entry's options flow (updates its options). Multi-step flows consume keys per step; menu steps take 'next_step_id'. The step's data_schema is returned on validation errors so field names can be corrected."New value: +"Flow form data. With 'domain': input for the new integration's config flow. With 'entry_id' alone: input for the entry's options flow (updates its options). Multi-step flows consume keys per step; menu steps take 'next_step_id' — a string, or a list of successive selections for flows that present more than one menu (e.g. a menu revisited after each branch, ending in a finish option). The step's data_schema is returned on validation errors so field names can be corrected."
  6. First observedv7.14.2

TDQS

A4.8/5.0
Behavior5/5

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

Annotations only declare destructiveHint=true and readOnlyHint=false; the description adds far more: OAuth steps are refused and aborted, progress steps wait up to 120s, ambiguous responses return status applied_but_unverified, reconfigure has no automatic rollback and can take a live integration offline, and a finished reconfigure reports entry_state. It also documents the one-step-per-call flow lifecycle and the already_in_progress refusal, none of which the annotations convey.

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?

Structure is strong: purpose first, then modes, exclusions, flow semantics, caveats, and worked examples, all clearly delimited. It is nonetheless very long for a tool whose 18 parameters are already 100% documented, and the trailing Claude Desktop timeout workaround is tangential padding rather than tool semantics.

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?

For a highly complex, multi-mode, stateful flow-driving mutation tool, the description covers everything an agent needs: mode selection, flow continuation/abort, error shapes (errors/error_text, reason_text), verification states, and prerequisites for discovery. Return values are covered by the output schema and correctly not duplicated.

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

Parameters4/5

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

With 100% schema coverage the baseline is 3, but the description earns above that by mapping parameter combinations to modes (entry_id+enabled vs domain+config vs entry_id+reconfigure=True vs log_level+domain) and showing the ordering of flow_id across calls, which the schema only states piecewise. Some of this overlaps with schema descriptions (mutual exclusivity, 'Requires reconfigure=True'), so it is not fully additive.

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 opening line states a specific verb+resource ('Manage an integration (config entry)') and immediately enumerates the concrete operations: enable/disable, add, update options, reconfigure, send commands. It explicitly names the sibling tools it is not (ha_config_set_helper, ha_remove_helpers_integrations, ha_get_integration), so an agent can distinguish it without opening any schema.

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?

There is a dedicated 'WHEN NOT TO USE' block routing helpers, otp, config subentries, entry removal, and ha_mcp_tools entries to their correct tools, plus a per-mode parameter recipe ('Modes (pick one)') that tells the agent exactly which arguments select which behavior. It also points to ha_get_integration for discovery and include_schema/include_ws_commands for inspection.

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