Skip to main content
Glama
OctopusDeploy

Octopus Deploy MCP Server

Official

Find runbooks in a project

find_runbooks
Read-onlyIdempotent

Retrieve runbooks for a project, either by ID for a specific summary or list all (filtered by name if desired). Each summary includes snapshot, multi-tenancy mode, and environment scope for validating targets before executing.

Instructions

Find runbooks in an Octopus Deploy project.

Two modes, picked by which arguments are supplied:

  • runbookId → fetch the summary for that runbook.

  • neither → list all runbooks in the project (optionally filtered by partialName).

Each summary includes the publishedRunbookSnapshotId (which run_runbook uses by default), the multiTenancyMode, and the environmentScope so callers can determine which environments and tenants are valid targets before invoking run_runbook.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • The main handler function 'registerFindRunbooksTool' which registers the 'find_runbooks' tool with the MCP server. Contains the core logic: validates the project exists, then either fetches a single runbook by ID or lists runbooks (optionally filtered by partialName). Includes error handling via handleOctopusApiError.
    export function registerFindRunbooksTool(server: McpServer) {
      server.registerTool(
        "find_runbooks",
        {
          title: "Find runbooks in a project",
          description: `Find runbooks in an Octopus Deploy project.
    
    Two modes, picked by which arguments are supplied:
    - runbookId  → fetch the summary for that runbook.
    - neither    → list all runbooks in the project (optionally filtered by partialName).
    
    Each summary includes the publishedRunbookSnapshotId (which run_runbook uses by default), the multiTenancyMode, and the environmentScope so callers can determine which environments and tenants are valid targets before invoking run_runbook.`,
          inputSchema: findRunbooksSchema,
          annotations: READ_ONLY_TOOL_ANNOTATIONS,
        },
        async ({ spaceName, projectName, runbookId, partialName, skip, take }) => {
          try {
            const client = await Client.create(
              getClientConfigurationFromEnvironment(),
            );
            const projectRepository = new ProjectRepository(client, spaceName);
    
            const projectMatches = await projectRepository.list({
              partialName: projectName,
              take: 100,
            });
            const project = projectMatches.Items.find(
              (p) => p.Name === projectName,
            );
            if (!project) {
              // Returned directly (not thrown) so the error surfaces to the LLM
              // with a project-specific message. handleOctopusApiError below would
              // otherwise rewrite a thrown "Project not found" into a misleading
              // "Space not found" because it requires entityId for the entity
              // branch and falls through to the space branch when entityId is
              // undefined (we haven't reached the runbook lookup yet).
              return {
                content: [
                  {
                    type: "text",
                    text: JSON.stringify(
                      {
                        success: false,
                        error:
                          `Project '${projectName}' not found in space '${spaceName}'. ` +
                          `Use list_projects to find valid project names. Project names are case-sensitive.`,
                      },
                      null,
                      2,
                    ),
                  },
                ],
                isError: true,
              };
            }
    
            const runbookRepository = new RunbookRepository(
              client,
              spaceName,
              project,
            );
    
            if (runbookId) {
              const runbook = await runbookRepository.get(runbookId);
              return {
                content: [
                  {
                    type: "text",
                    text: JSON.stringify(runbookSummary(runbook, spaceName)),
                  },
                ],
              };
            }
    
            const runbooksResponse = await runbookRepository.list({
              partialName,
              skip,
              take,
            });
    
            return {
              content: [
                {
                  type: "text",
                  text: JSON.stringify({
                    totalResults: runbooksResponse.TotalResults,
                    itemsPerPage: runbooksResponse.ItemsPerPage,
                    numberOfPages: runbooksResponse.NumberOfPages,
                    lastPageNumber: runbooksResponse.LastPageNumber,
                    items: runbooksResponse.Items.map((runbook) =>
                      runbookSummary(runbook, spaceName),
                    ),
                  }),
                },
              ],
            };
          } catch (error) {
            handleOctopusApiError(error, {
              entityType: "runbook",
              entityId: runbookId,
              spaceName,
              helpText:
                "Use list_projects to find valid project names. Call find_runbooks without runbookId to list all runbooks for a project.",
            });
          }
        },
      );
    }
  • Input schema (Zod) for the find_runbooks tool. Defines required fields 'spaceName' and 'projectName', and optional fields 'runbookId', 'partialName', 'skip', 'take'. Uses superRefine validation to reject combining runbookId with partialName.
    const findRunbooksSchema = z
      .object({
        spaceName: z.string().describe("Space name."),
        projectName: z
          .string()
          .describe(
            "Project name. Runbooks are scoped to a project, so this is required for both single fetch and listing.",
          ),
        runbookId: z
          .string()
          .optional()
          .describe(
            "Fetch a single runbook by ID. Mutually exclusive with partialName/skip/take.",
          ),
        partialName: z
          .string()
          .optional()
          .describe("Filter listing by partial runbook name (case-insensitive)."),
        skip: z.number().optional().describe("Pagination offset."),
        take: z.number().optional().describe("Pagination page size."),
      })
      .superRefine((args, ctx) => {
        if (args.runbookId && args.partialName) {
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            message:
              "partialName cannot be combined with runbookId. Use runbookId to fetch a single runbook; use partialName to filter the listing.",
            path: ["partialName"],
          });
        }
      });
  • Self-registration via registerToolDefinition(). Registers 'find_runbooks' with toolset 'runbooks', readOnly: true, and points to registerFindRunbooksTool function.
    registerToolDefinition({
      toolName: "find_runbooks",
      config: { toolset: "runbooks", readOnly: true },
      registerFn: registerFindRunbooksTool,
    });
  • Helper function 'runbookSummary' that extracts and returns a summary of a runbook (id, name, description, projectId, runbookProcessId, publishedRunbookSnapshotId, multiTenancyMode, environmentScope, environments, and a resource URI).
    function runbookSummary(runbook: Runbook, spaceName: string) {
      const encodedSpace = encodeURIComponent(spaceName);
      const encodedId = encodeURIComponent(runbook.Id);
    
      return {
        id: runbook.Id,
        name: runbook.Name,
        description: runbook.Description,
        projectId: runbook.ProjectId,
        runbookProcessId: runbook.RunbookProcessId,
        publishedRunbookSnapshotId: runbook.PublishedRunbookSnapshotId,
        multiTenancyMode: runbook.MultiTenancyMode,
        environmentScope: runbook.EnvironmentScope,
        environments: runbook.Environments,
        resourceUri: `octopus://spaces/${encodedSpace}/runbooks/${encodedId}`,
      };
    }
  • Import that triggers self-registration of the find_runbooks tool when tools/index.ts is loaded.
    import "./findRunbooks.js";
    import "./findTenants.js";
    import "./findDeploymentTargets.js";
    import "./findCertificates.js";
Behavior3/5

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

Annotations already declare readOnlyHint=true, destructiveHint=false, idempotentHint=true. The description adds that the tool returns summaries with specific fields and implies it is safe to call. However, the inconsistency between the described parameters and the empty input schema reduces transparency and may confuse the agent.

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 three sentences, front-loading the main purpose. It is reasonably concise and avoids extraneous details. Minor improvement could combine the first two sentences.

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

Completeness3/5

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

For a read-only query tool with no output schema, the description provides reasonable context about output fields and their relevance to `run_runbook`. However, it lacks details on error handling, pagination, or the exact structure of the output, and the parameter mismatch leaves gaps.

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

Parameters2/5

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

The input schema has no properties (100% coverage but empty), yet the description describes two implied parameters (runbookId and partialName) with distinct behaviors. This mismatch undermines the semantic value the description tries to add; the agent cannot reconcile the described parameters with the schema.

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

Purpose4/5

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

The description clearly states the tool finds runbooks in an Octopus Deploy project and distinguishes two modes (by runbookId or listing all). However, the input schema has no properties, contradicting the described parameters, which slightly undermines clarity.

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 guidance on when to use each mode based on supplied arguments. It also notes that the returned summary fields (e.g., publishedRunbookSnapshotId, environmentScope) are useful before calling `run_runbook`, indicating a clear workflow. However, it does not directly contrast with sibling tools like `find_releases`.

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