Skip to main content
Glama
OctopusDeploy

Octopus Deploy MCP Server

Official

Find interruptions

find_interruptions
Read-only

List interruptions such as manual interventions or deployment approvals in Octopus Deploy. Filter by assignment, entity, or fetch a specific interruption by ID.

Instructions

Find interruptions (manual interventions, guided failures, deployment approvals) in an Octopus Deploy space.

Interruptions are the Octopus surface equivalent to pending approvals: a deployment or runbook run pauses and waits for a human to take action. Use this tool to enumerate them or to look up a single one.

Modes (picked by which arguments you supply):

  • interruptionId → fetch the slim summary for that interruption.

  • assignedToMe → list interruptions the authenticated user can act on; resolves /users/me (cached per session).

  • regarding → list interruptions related to a specific entity (ServerTasks-…, Deployments-…). Native server-side filter.

  • (none) → list all interruptions, optionally filtered by pendingOnly (default: true) and skip/take.

Each summary includes:

  • resourceUri → octopus://spaces/{spaceName}/interruptions/{id} for the FULL body (form definition with Markdown instructions, button options, control types, and any already-submitted values). Dereference this when the user asks for details about a specific interruption.

  • taskResourceUri → octopus://spaces/{spaceName}/tasks/{taskId} for the surrounding deployment/runbook task.

  • publicUrl → Octopus portal deep link to take action.

  • formElementNames → just the field names (e.g. Instructions, Notes, Result). Field values are NOT in the slim summary; fetch resourceUri for those.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • Main handler function for the find_interruptions tool. Supports single-ID lookup, assignedToMe post-filtering with pagination scanning, and standard list mode with server-side pagination.
    export async function findInterruptionsHandler(params: FindInterruptionsParams) {
      const {
        spaceName,
        interruptionId,
        pendingOnly = true,
        assignedToMe,
        regarding,
        skip,
        take,
      } = params;
    
      // Cheap pre-check: validate the ID format before any network round-trip,
      // so callers get a clear error without paying for the space resolve first.
      if (interruptionId) {
        validateEntityId(interruptionId, "interruption", ENTITY_PREFIXES.interruption);
      }
    
      const configuration = getClientConfigurationFromEnvironment();
      const client = await Client.create(configuration);
    
      let spaceId: string;
      try {
        spaceId = await resolveSpaceId(client, spaceName);
      } catch (error) {
        handleOctopusApiError(error, { spaceName });
      }
    
      // Single-id lookup mode.
      if (interruptionId) {
        try {
          const interruption = await client.get<InterruptionResource>(
            "~/api/{spaceId}/interruptions/{interruptionId}",
            { spaceId, interruptionId },
          );
    
          return {
            content: [
              {
                type: "text" as const,
                text: JSON.stringify(
                  interruptionSummary(interruption, spaceName, configuration.instanceURL),
                ),
              },
            ],
          };
        } catch (error) {
          handleOctopusApiError(error, {
            entityType: "interruption",
            entityId: interruptionId,
            spaceName,
            helpText:
              "Call find_interruptions without interruptionId to list valid IDs.",
          });
        }
      }
    
      // assignedToMe: scan multiple server pages and post-filter, since Octopus
      // has no native "responsibleUserId" query param. Without paging, a first
      // server page that contains zero matches would cause us to return an empty
      // result and silently miss actionable interruptions on later pages.
      if (assignedToMe) {
        const user = await getCurrentUserCached(client);
        const currentUserId = user.Id;
    
        const scan = await scanAssignedInterruptions(client, spaceId, {
          pendingOnly,
          regarding,
          currentUserId,
        });
    
        const start = skip ?? 0;
        const end =
          take !== undefined
            ? Math.min(start + take, scan.matched.length)
            : scan.matched.length;
        const sliced = scan.matched.slice(start, end);
    
        return {
          content: [
            {
              type: "text" as const,
              text: JSON.stringify({
                // Post-filter counts: the LLM's "totalResults" is the number of
                // interruptions actually assigned to the user, not the unfiltered
                // server total. The latter is surfaced under filteredAs for
                // transparency.
                totalResults: scan.matched.length,
                itemsPerPage: sliced.length,
                numberOfPages: 1,
                lastPageNumber: 0,
                filteredAs: {
                  userId: currentUserId,
                  serverTotalScanned: scan.serverScanned,
                  serverTotalAvailable: scan.serverTotal,
                  scanComplete: scan.scanComplete,
                  ...(scan.scanComplete
                    ? {}
                    : {
                        scanIncompleteHint:
                          "Hit the safety cap before exhausting the server result set. " +
                          "Narrow the query (e.g. set regarding to a specific task, or keep pendingOnly: true) " +
                          "to ensure complete results.",
                      }),
                },
                items: sliced.map((interruption) =>
                  interruptionSummary(interruption, spaceName, configuration.instanceURL),
                ),
              }),
            },
          ],
        };
      }
    
      // List mode (no per-user filter): pass server pagination through directly.
      const response = await client.get<ResourceCollection<InterruptionResource>>(
        "~/api/{spaceId}/interruptions{?skip,take,pendingOnly,regarding}",
        {
          spaceId,
          skip,
          take,
          pendingOnly,
          regarding,
        },
      );
    
      return {
        content: [
          {
            type: "text" as const,
            text: JSON.stringify({
              totalResults: response.TotalResults,
              itemsPerPage: response.ItemsPerPage,
              numberOfPages: response.NumberOfPages,
              lastPageNumber: response.LastPageNumber,
              items: (response.Items ?? []).map((interruption) =>
                interruptionSummary(interruption, spaceName, configuration.instanceURL),
              ),
            }),
          },
        ],
      };
    }
  • Zod schema (findInterruptionsSchema) with input validation and superRefine to prevent conflict between interruptionId and list filters.
    const findInterruptionsSchema = z
      .object({
        spaceName: z.string().describe("Space name."),
        interruptionId: z
          .string()
          .optional()
          .describe(
            "Fetch the slim summary for a single interruption by ID (e.g. Interruptions-1). " +
              "Mutually exclusive with regarding/assignedToMe/pendingOnly. " +
              "For the full body (form definition, instructions, button options, submitted values) dereference the returned resourceUri.",
          ),
        pendingOnly: z
          .boolean()
          .optional()
          .default(true)
          .describe("Return only unprocessed (pending) interruptions. Defaults to true. Ignored when interruptionId is set."),
        assignedToMe: z
          .boolean()
          .optional()
          .describe(
            "Limit to interruptions the authenticated user can act on (CanTakeResponsibility or HasResponsibility, " +
              "or where the user is the explicit ResponsibleUserId). When true, /users/me is resolved (cached per session). " +
              "Octopus has no responsibleUserId query parameter, so the tool pages through the server result set and " +
              "post-filters; pages are scanned up to a safety cap (filteredAs.scanComplete signals whether the entire " +
              "result set was inspected). totalResults reflects the post-filter count; the unfiltered server total is " +
              "exposed under filteredAs. Ignored when interruptionId is set.",
          ),
        regarding: z
          .string()
          .optional()
          .describe(
            "Native server-side filter to interruptions related to a specific entity ID " +
              "(e.g. ServerTasks-1234, Deployments-5678). Ignored when interruptionId is set.",
          ),
        skip: z.number().optional().describe("Pagination offset. Ignored when interruptionId is set."),
        take: z.number().optional().describe("Pagination page size. Ignored when interruptionId is set."),
      })
      .superRefine((args, ctx) => {
        if (!args.interruptionId) return;
        const conflicting: Array<keyof typeof args> = [
          "regarding",
          "assignedToMe",
          "skip",
          "take",
        ];
        for (const key of conflicting) {
          if (args[key] !== undefined) {
            ctx.addIssue({
              code: z.ZodIssueCode.custom,
              message:
                "Provide either interruptionId (to fetch a single interruption) or list filters " +
                `(${conflicting.join(", ")}), not both.`,
              path: [key],
            });
          }
        }
      });
  • Registration function (registerFindInterruptionsTool) that registers the tool on the MCP server with title, description, inputSchema, and handler.
    export function registerFindInterruptionsTool(server: McpServer) {
      server.registerTool(
        "find_interruptions",
        {
          title: "Find interruptions",
          description: `Find interruptions (manual interventions, guided failures, deployment approvals) in an Octopus Deploy space.
    
    Interruptions are the Octopus surface equivalent to pending approvals: a deployment or runbook run pauses and waits for a human to take action. Use this tool to enumerate them or to look up a single one.
    
    Modes (picked by which arguments you supply):
    - interruptionId  → fetch the slim summary for that interruption.
    - assignedToMe    → list interruptions the authenticated user can act on; resolves /users/me (cached per session).
    - regarding       → list interruptions related to a specific entity (ServerTasks-…, Deployments-…). Native server-side filter.
    - (none)          → list all interruptions, optionally filtered by pendingOnly (default: true) and skip/take.
    
    Each summary includes:
    - resourceUri      → octopus://spaces/{spaceName}/interruptions/{id} for the FULL body (form definition with Markdown instructions, button options, control types, and any already-submitted values). Dereference this when the user asks for details about a specific interruption.
    - taskResourceUri  → octopus://spaces/{spaceName}/tasks/{taskId} for the surrounding deployment/runbook task.
    - publicUrl        → Octopus portal deep link to take action.
    - formElementNames → just the field names (e.g. Instructions, Notes, Result). Field values are NOT in the slim summary; fetch resourceUri for those.`,
          inputSchema: findInterruptionsSchema,
          annotations: { readOnlyHint: true },
        },
        findInterruptionsHandler,
      );
    }
  • Tool definition registration via registerToolDefinition, linking the tool name to config (toolset: 'interruptions', readOnly: true) and the registerFn.
    registerToolDefinition({
      toolName: "find_interruptions",
      config: { toolset: "interruptions", readOnly: true },
      registerFn: registerFindInterruptionsTool,
    });
  • Helper function scanAssignedInterruptions that pages through the server result set and post-filters interruptions the current user can act on (safety capped at 500 records).
    async function scanAssignedInterruptions(
      client: Client,
      spaceId: string,
      params: { pendingOnly: boolean; regarding?: string; currentUserId: string },
    ): Promise<AssignedScanResult> {
      const matched: InterruptionResource[] = [];
      let serverScanned = 0;
      let serverTotal = 0;
      let scanComplete = false;
    
      while (serverScanned < ASSIGNED_SCAN_MAX) {
        const remaining = ASSIGNED_SCAN_MAX - serverScanned;
        const pageTake = Math.min(ASSIGNED_SCAN_PAGE_SIZE, remaining);
    
        const page = await client.get<ResourceCollection<InterruptionResource>>(
          "~/api/{spaceId}/interruptions{?skip,take,pendingOnly,regarding}",
          {
            spaceId,
            skip: serverScanned,
            take: pageTake,
            pendingOnly: params.pendingOnly,
            regarding: params.regarding,
          },
        );
    
        serverTotal = page.TotalResults;
        const items = page.Items ?? [];
    
        for (const interruption of items) {
          if (
            interruption.CanTakeResponsibility ||
            interruption.HasResponsibility ||
            interruption.ResponsibleUserId === params.currentUserId
          ) {
            matched.push(interruption);
          }
        }
    
        serverScanned += items.length;
    
        // Empty page (or short page) means the server has no more.
        if (items.length === 0 || items.length < pageTake || serverScanned >= serverTotal) {
          scanComplete = true;
          break;
        }
      }
    
      return { matched, serverTotal, serverScanned, scanComplete };
    }
Behavior5/5

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

The description adds significant behavioral context beyond the readOnlyHint annotation: it explains the different query modes, caching behavior for assignedToMe, native server-side filtering for regarding, and the structure of slim vs. full summaries. There is no contradiction with annotations.

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

Conciseness5/5

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

The description is well-structured: a one-line purpose, followed by detailed bullet points for each mode and return value. It front-loads the core function and uses clear formatting. Every sentence adds value without redundancy.

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?

No output schema is provided, but the description fully documents what each summary includes (resourceUri, taskResourceUri, publicUrl, formElementNames) and when to fetch the full body. For a tool with no output schema, this is complete and actionable.

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

Parameters3/5

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

The input schema has no properties, but the description describes arguments like interruptionId, assignedToMe, regarding, pendingOnly, skip/take. This adds meaning beyond the schema, but the mismatch between schema and described parameters is confusing. With schema coverage 100% (empty schema), baseline is 3, and the description provides useful but inconsistent info.

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 finds interruptions (manual interventions, guided failures, deployment approvals) in an Octopus Deploy space. The verb 'find' and the specific resource 'interruptions' make purpose unambiguous, and it is distinct from sibling tools like find_accounts or find_releases.

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

Usage Guidelines4/5

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

The description provides explicit modes (interruptionId, assignedToMe, regarding, none) and explains when to use each based on supplied arguments. It advises to dereference resourceUri for full details. However, it does not explicitly state when not to use this tool or compare it to alternatives like find_accounts, though the context makes this clear.

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