All files / src/entities/environments registry.ts

97.95% Statements 48/49
90.9% Branches 20/22
100% Functions 3/3
100% Lines 42/42

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 15117x 17x 17x 17x   17x                       17x                           6x   4x   4x   4x   1x 1x           2x       1x 1x                                                   11x   7x   7x   7x   2x 2x 2x 2x 2x   2x             2x 2x 2x 2x 2x   2x             1x 1x 1x   1x             1x 1x 1x       1x 1x                                   17x 1x    
import * as z from 'zod';
import { BrowseEnvironmentsSchema } from './schema-readonly';
import { ManageEnvironmentSchema } from './schema';
import { gitlab, toQuery } from '../../utils/gitlab-api';
import { ToolRegistry, EnhancedToolDefinition } from '../../types';
import { assertActionAllowed } from '../utils';
 
/**
 * Environments tools registry - 2 CQRS tools
 *
 * browse_environments (Query): list, get, list_deployments
 * manage_environment (Command): create, update, stop, delete, update_deployment_status
 *
 * Deployments are folded into these tools as actions (list_deployments,
 * update_deployment_status) rather than a separate CQRS pair, to keep the MCP
 * tool count lean. Gated behind USE_ENVIRONMENTS. Free tier.
 */
export const environmentsToolRegistry: ToolRegistry = new Map<string, EnhancedToolDefinition>([
  // ============================================================================
  // browse_environments - CQRS Query Tool (discriminated union schema)
  // ============================================================================
  [
    'browse_environments',
    {
      name: 'browse_environments',
      description:
        'Inspect project environments and their deployments. Actions: list (environments filtered by state/name), get (single environment with its last deployment), list_deployments (deployment history, filterable by environment and status). Related: manage_environment to create, update, stop, or delete environments and update deployment status.',
      inputSchema: z.toJSONSchema(BrowseEnvironmentsSchema),
      requirements: { default: { tier: 'free', minVersion: '8.0' } },
      gate: { envVar: 'USE_ENVIRONMENTS', defaultValue: true },
      handler: async (args: unknown): Promise<unknown> => {
        const input = BrowseEnvironmentsSchema.parse(args);
 
        assertActionAllowed('browse_environments', input.action);
 
        const encodedProjectId = encodeURIComponent(input.project_id);
 
        switch (input.action) {
          case 'list': {
            const { action: _action, project_id: _projectId, ...queryOptions } = input;
            return gitlab.get(`projects/${encodedProjectId}/environments`, {
              query: toQuery(queryOptions, []),
            });
          }
 
          case 'get': {
            return gitlab.get(`projects/${encodedProjectId}/environments/${input.environment_id}`);
          }
 
          case 'list_deployments': {
            const { action: _action, project_id: _projectId, ...queryOptions } = input;
            return gitlab.get(`projects/${encodedProjectId}/deployments`, {
              query: toQuery(queryOptions, []),
            });
          }
 
          /* istanbul ignore next -- unreachable with Zod discriminatedUnion */
          default:
            throw new Error(`Unknown action: ${(input as { action: string }).action}`);
        }
      },
    },
  ],
 
  // ============================================================================
  // manage_environment - CQRS Command Tool (discriminated union schema)
  // ============================================================================
  [
    'manage_environment',
    {
      name: 'manage_environment',
      description:
        'Create and control project environments and deployment status. Actions: create (new environment), update (external_url/tier/description), stop (required before delete), delete (stopped environment), update_deployment_status (set a non-pipeline deployment to running/success/failed/canceled). Related: browse_environments to list and inspect.',
      inputSchema: z.toJSONSchema(ManageEnvironmentSchema),
      requirements: { default: { tier: 'free', minVersion: '8.0' } },
      gate: { envVar: 'USE_ENVIRONMENTS', defaultValue: true },
      handler: async (args: unknown): Promise<unknown> => {
        const input = ManageEnvironmentSchema.parse(args);
 
        assertActionAllowed('manage_environment', input.action);
 
        const encodedProjectId = encodeURIComponent(input.project_id);
 
        switch (input.action) {
          case 'create': {
            const { name, external_url, tier, description } = input;
            const body: Record<string, unknown> = { name };
            if (external_url !== undefined) body.external_url = external_url;
            if (tier !== undefined) body.tier = tier;
            Iif (description !== undefined) body.description = description;
 
            return gitlab.post(`projects/${encodedProjectId}/environments`, {
              body,
              contentType: 'json',
            });
          }
 
          case 'update': {
            const { environment_id, external_url, tier, description } = input;
            const body: Record<string, unknown> = {};
            if (external_url !== undefined) body.external_url = external_url;
            if (tier !== undefined) body.tier = tier;
            if (description !== undefined) body.description = description;
 
            return gitlab.put(`projects/${encodedProjectId}/environments/${environment_id}`, {
              body,
              contentType: 'json',
            });
          }
 
          case 'stop': {
            const { environment_id, force } = input;
            const body: Record<string, unknown> = {};
            Eif (force !== undefined) body.force = force;
 
            return gitlab.post(`projects/${encodedProjectId}/environments/${environment_id}/stop`, {
              body,
              contentType: 'json',
            });
          }
 
          case 'delete': {
            const { environment_id } = input;
            await gitlab.delete(`projects/${encodedProjectId}/environments/${environment_id}`);
            return { deleted: true, environment_id };
          }
 
          case 'update_deployment_status': {
            const { deployment_id, status } = input;
            return gitlab.put(`projects/${encodedProjectId}/deployments/${deployment_id}`, {
              body: { status },
              contentType: 'json',
            });
          }
 
          /* istanbul ignore next -- unreachable with Zod discriminatedUnion */
          default:
            throw new Error(`Unknown action: ${(input as { action: string }).action}`);
        }
      },
    },
  ],
]);
 
/**
 * Get read-only tool names from the registry
 */
export function getEnvironmentsReadOnlyToolNames(): string[] {
  return ['browse_environments'];
}