Skip to main content
Glama
OctopusDeploy

Octopus Deploy MCP Server

Official

Update a feature toggle

update_feature_toggle
Destructive

Adjust an existing feature toggle: flip an environment on/off, change rollout percentages, or update the description and default state.

Instructions

Adjust an existing customer feature toggle in an Octopus Deploy project.

Narrow surface — flip an environment on/off, change rollout percentages, or update the toggle-level description / default state. Internally fetches the current toggle, applies your patches in memory, and PUTs the merged body, so unmentioned environments and unmentioned fields are preserved.

Deliberately not exposed: name/slug rename, tag changes, rollout group attach/detach, tenant targeting, segments, minimum version, adding or removing environment configurations entirely. For those, use the Octopus UI.

Patches that reference an environment not already configured on the toggle are rejected with reason: environment_not_configured. The tool does not add new environment configurations.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • The main handler function that executes the update_feature_toggle logic: fetches current toggle, applies patches, confirms with user, and PUTs the merged body back via the Octopus API.
    export async function updateFeatureToggleHandler(
      server: McpServer,
      params: UpdateFeatureToggleParams,
    ) {
      const { spaceName, projectId, slug, environments, confirm } = params;
    
      const noPatches =
        params.defaultIsEnabled === undefined &&
        params.description === undefined &&
        (!environments || environments.length === 0);
      if (noPatches) {
        return {
          content: [
            {
              type: "text" as const,
              text: JSON.stringify(
                {
                  success: false,
                  reason: "no_patches",
                  message:
                    "No fields supplied to update. Pass at least one of defaultIsEnabled, description, or environments[].",
                },
                null,
                2,
              ),
            },
          ],
          isError: true,
        };
      }
    
      const client = await Client.create(getClientConfigurationFromEnvironment());
    
      let spaceId: string;
      try {
        spaceId = await resolveSpaceId(client, spaceName);
      } catch (error) {
        handleOctopusApiError(error, { spaceName });
      }
    
      let current: FeatureToggleResource;
      try {
        current = await client.get<FeatureToggleResource>(
          "~/api/{spaceId}/projects/{projectId}/featuretoggles/{slug}",
          { spaceId, projectId, slug },
        );
      } catch (error) {
        handleOctopusApiError(error, {
          entityType: "feature toggle",
          entityId: slug,
          spaceName,
          helpText:
            "Use find_feature_toggles to list valid slugs. If 404 persists across all toggles in the project, the customer feature toggles capability may be disabled on the Octopus instance.",
        });
      }
    
      // Match patch entries to existing environment configurations. Unknown
      // environments are rejected — the contract is "slight adjustment", not
      // "configure new environments".
      const envPatches: PatchedEnvironment[] = [];
      if (environments) {
        for (const patch of environments) {
          const existing = current.Environments.find(
            (e) => e.DeploymentEnvironmentId === patch.deploymentEnvironmentId,
          );
          if (!existing) {
            const configured = current.Environments.map(
              (e) => e.DeploymentEnvironmentId,
            );
            return {
              content: [
                {
                  type: "text" as const,
                  text: JSON.stringify(
                    {
                      success: false,
                      reason: "environment_not_configured",
                      message:
                        `Environment '${patch.deploymentEnvironmentId}' is not configured on toggle '${current.Slug}'. ` +
                        "This tool only adjusts existing environment configurations; it does not add new ones. " +
                        "Use the Octopus UI to add an environment to a toggle.",
                      configuredEnvironmentIds: configured,
                    },
                    null,
                    2,
                  ),
                },
              ],
              isError: true,
            };
          }
          envPatches.push(applyEnvironmentPatch(existing, patch));
        }
      }
    
      // Detect a no-op call (everything provided already matches current state)
      // before involving the user with a confirmation dialog.
      const toggleLevelChanges =
        (params.defaultIsEnabled !== undefined &&
          params.defaultIsEnabled !== current.DefaultIsEnabled) ||
        (params.description !== undefined &&
          params.description !== (current.Description ?? undefined));
      const envLevelChanges = envPatches.some((p) => p.changedFields.length > 0);
      if (!toggleLevelChanges && !envLevelChanges) {
        return {
          content: [
            {
              type: "text" as const,
              text: JSON.stringify(
                {
                  success: true,
                  noOp: true,
                  message:
                    "All supplied fields already match the current toggle state — nothing to update.",
                  slug: current.Slug,
                },
                null,
                2,
              ),
            },
          ],
        };
      }
    
      const merged: FeatureToggleResource = {
        ...current,
        DefaultIsEnabled:
          params.defaultIsEnabled !== undefined
            ? params.defaultIsEnabled
            : current.DefaultIsEnabled,
        Description:
          params.description !== undefined ? params.description : current.Description,
        Environments: current.Environments.map((env) => {
          const patch = envPatches.find(
            (p) => p.current.DeploymentEnvironmentId === env.DeploymentEnvironmentId,
          );
          return patch ? patch.merged : env;
        }),
      };
    
      const confirmation = await requireConfirmation(server, {
        message: `Update feature toggle "${current.Name}" (${current.Slug}) in space ${spaceName}?`,
        fallbackConfirm: confirm,
        change: {
          source: buildDiffSource(current, params, envPatches),
          target: buildDiffTarget(params, envPatches),
        },
      });
      if (!confirmation.confirmed) {
        return unconfirmedResponse(confirmation, {
          action: "feature toggle update",
        });
      }
    
      try {
        await client.doUpdate<FeatureToggleResource>(
          "~/api/{spaceId}/projects/{projectId}/featuretoggles",
          merged,
          { spaceId, projectId },
        );
      } catch (error) {
        handleOctopusApiError(error, {
          entityType: "feature toggle",
          entityId: current.Id,
          spaceName,
          helpText:
            "Verify you have FeatureToggleEdit on the project and every environment referenced. Server-side validation rules (max segments, ephemeral environment clamping, etc.) are documented in the Octopus feature toggles API reference.",
        });
      }
    
      const encodedSpace = encodeURIComponent(spaceName);
      const encodedProjectId = encodeURIComponent(projectId);
      const encodedSlug = encodeURIComponent(current.Slug);
      const resourceUri = `octopus://spaces/${encodedSpace}/projects/${encodedProjectId}/featuretoggles/${encodedSlug}`;
    
      return {
        content: [
          {
            type: "text" as const,
            text: JSON.stringify(
              {
                success: true,
                id: current.Id,
                slug: current.Slug,
                name: current.Name,
                resourceUri,
                message: `Feature toggle '${current.Slug}' updated successfully.`,
                helpText:
                  "Dereference resourceUri to read the updated toggle body.",
              },
              null,
              2,
            ),
          },
        ],
      };
    }
  • Zod schema (updateFeatureToggleSchema) defining input validation for the tool: spaceName, projectId, slug, defaultIsEnabled, description, environments array (each with deploymentEnvironmentId, isEnabled, rolloutPercentage, clientRolloutPercentage), and confirm flag. Includes a superRefine that rejects duplicate environment entries.
    export const updateFeatureToggleSchema = z
      .object({
        spaceName: z.string().describe("Space name."),
        projectId: z
          .string()
          .describe("Project ID (e.g. Projects-123). Feature toggles are scoped per project."),
        slug: z
          .string()
          .describe("The toggle's Slug (not its Id). Find it via find_feature_toggles."),
        defaultIsEnabled: z
          .boolean()
          .optional()
          .describe(
            "Toggle-level default. The value returned for environments that have no explicit per-environment configuration.",
          ),
        description: z
          .string()
          .optional()
          .describe("Toggle-level description (max 1000 chars). Markdown supported in the UI."),
        environments: z
          .array(environmentPatchSchema)
          .optional()
          .describe(
            "Per-environment patches. Environments not listed here are preserved as-is. Each entry must reference a deploymentEnvironmentId that already exists on the toggle; unknown environments are rejected rather than silently added. Each environment may appear at most once in this array — duplicates are rejected.",
          ),
        confirm: z
          .boolean()
          .optional()
          .describe(
            "Required only when the MCP client does not support elicitation. Set to true to confirm the update; otherwise the tool aborts.",
          ),
      })
      .superRefine((args, ctx) => {
        // Reject duplicate deploymentEnvironmentIds up front. Without this,
        // the merge picks the FIRST patch for the env while the confirmation
        // diff overwrites earlier entries with later ones using the same key —
        // the user could see one change in the prompt and a different one
        // actually go through.
        if (!args.environments) return;
        const seen = new Map<string, number>();
        for (let i = 0; i < args.environments.length; i++) {
          const id = args.environments[i].deploymentEnvironmentId;
          const prev = seen.get(id);
          if (prev !== undefined) {
            ctx.addIssue({
              code: z.ZodIssueCode.custom,
              message:
                `Duplicate environment entry for '${id}' at index ${i} (also at index ${prev}). ` +
                "Combine the patches into a single entry per environment.",
              path: ["environments", i, "deploymentEnvironmentId"],
            });
          }
          seen.set(id, i);
        }
      });
  • Registration function registerUpdateFeatureToggleTool that registers the tool with the MCP server under the name 'update_feature_toggle', with title, description, input schema, destructive write annotations, and the handler. Also includes the registerToolDefinition call that adds it to the global TOOL_REGISTRY under the 'featureToggles' toolset.
    export function registerUpdateFeatureToggleTool(server: McpServer) {
      server.registerTool(
        "update_feature_toggle",
        {
          title: "Update a feature toggle",
          description: `Adjust an existing customer feature toggle in an Octopus Deploy project.
    
    Narrow surface — flip an environment on/off, change rollout percentages, or update the toggle-level description / default state. Internally fetches the current toggle, applies your patches in memory, and PUTs the merged body, so unmentioned environments and unmentioned fields are preserved.
    
    Deliberately not exposed: name/slug rename, tag changes, rollout group attach/detach, tenant targeting, segments, minimum version, adding or removing environment configurations entirely. For those, use the Octopus UI.
    
    Patches that reference an environment not already configured on the toggle are rejected with reason: environment_not_configured. The tool does not add new environment configurations.`,
          inputSchema: updateFeatureToggleSchema,
          annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
        },
        (args) => updateFeatureToggleHandler(server, args),
      );
    }
    
    registerToolDefinition({
      toolName: "update_feature_toggle",
      config: { toolset: "featureToggles", readOnly: false },
      registerFn: registerUpdateFeatureToggleTool,
      minimumOctopusVersion: "2026.1.10655",
    });
  • Side-effect import that triggers self-registration of the updateFeatureToggle tool when the tools module is loaded.
    import "./updateFeatureToggle.js";
  • Type definitions for FeatureToggleResource and FeatureToggleEnvironmentResource used by the handler to parse the API response and construct the merged toggle body.
    /**
     * Wire-format types for the customer-facing Feature Toggles API.
     *
     * Octopus's @octopusdeploy/api-client (3.8.0) does not expose typed Repository
     * classes or Resource interfaces for feature toggles, so we declare the minimum
     * surface we touch here. Field names and casing match the JSON response from
     * /api/{spaceId}/projects/{projectId}/featuretoggles{,/{slug}}.
     */
    
    export interface FeatureToggleEnvironmentResource {
      FeatureToggleId?: string;
      DeploymentEnvironmentId: string;
      IsEnabled: boolean;
      RolloutPercentage?: number;
      ClientRolloutPercentage?: number;
      TenantIds?: string[];
      ExcludedTenantIds?: string[];
      TenantTags?: string[];
      ExcludedTenantTags?: string[];
      Segments?: Array<{ Key: string; Value: string }>;
      MinimumVersion?: string | null;
    }
    
    export interface FeatureToggleResource {
      Id: string;
      SpaceId: string;
      ProjectId: string;
      Name: string;
      Slug: string;
      DefaultIsEnabled: boolean;
      Description?: string | null;
      Environments: FeatureToggleEnvironmentResource[];
      Tags: string[];
      RolloutGroupId?: string | null;
    }
    
    export interface RolloutGroupResource {
      Id: string;
      SpaceId: string;
      ProjectId: string;
      Name: string;
      FeatureToggleUsages: Array<{ Id: string; Name: string }>;
    }
Behavior5/5

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

The description discloses internal behavior: fetches current state, applies patches in memory, PUTs merged body, preserves unmentioned fields. It also states rejection reason for unconfigured environments, adding value beyond the destructiveHint annotation.

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 moderately concise with three paragraphs. It front-loads the main action but includes some redundancy ('Narrow surface' and 'Deliberately not exposed' could be merged). Overall, it earns its length.

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

Completeness2/5

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

With no output schema and empty input schema, the description should fully equip the agent. It explains behavior but critically omits how to specify the update (e.g., which environments or fields). This major gap hinders correct invocation.

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?

Input schema has zero parameters, baseline is 4. The description mentions adjustable aspects (environments, rollout percentages, description, default state) but does not clarify how these are specified, which is acceptable given no parameters.

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 clearly states the tool adjusts an existing feature toggle in Octopus Deploy, with specific verb ('adjust') and resource ('feature toggle'). It distinguishes from sibling tools like find_feature_toggles by implying mutation.

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?

The description explicitly enumerates operations that are not exposed (e.g., renaming, tagging) and directs the user to the Octopus UI for those, providing clear when-not-to-use guidance and alternatives.

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

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/OctopusDeploy/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server