Search Environments
search_environmentsFind environments by UUID or filter with keyword, project, and pagination. Returns environments with inline credentials (passwords never included). Auto-resolves project from git origin if not specified.
Instructions
Search or look up environments, with credentials expanded inline per environment.
Two modes:
uuid mode: {"uuid": ""} → single env with full detail + its credentials. NotFound if the uuid doesn't exist.
filter mode: omit uuid, optionally {"q": "", "projectUuid", "page", "pageSize"} → paginated envs, each with its credentials.
Project resolution: if projectUuid is omitted, the current git repo's origin is auto-resolved to a DebuggAI project. Returns {error:"NoProjectResolved", environments:[]} if neither is available.
Credentials are returned inline per env as {uuid, label, username, role}. Password is NEVER returned — the handler defensively strips it regardless of what the service layer provides.
Response: {project, filter, pageInfo, environments[]} — each environment includes a credentials[] array.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| uuid | No | Environment UUID. Returns single env with credentials inline. Mutually exclusive with projectUuid/q filter params. | |
| projectUuid | No | Override the auto-detected project. Used in filter mode. | |
| q | No | Free-text search over environment name. Mutually exclusive with uuid. | |
| page | No | Page number (1-indexed). | |
| pageSize | No | Page size (1..200). Default 20. |
Implementation Reference
- Main handler function that resolves projectUuid (via git detection or explicit), then operates in uuid mode (single env lookup with credentials) or filter mode (paginated search with inline credentials). Never returns passwords. Handles NotFound and NoProjectResolved errors.
/** * search_environments handler (bead 5kw) * * Absorbs list_environments + get_environment + all credential search. * Each environment in the response has its credentials expanded inline. * * Modes: * uuid mode: {uuid, projectUuid?} → {filter:{uuid}, project, pageInfo:{totalCount:1,...}, * environments:[{...env, credentials:[...]}]} * filter mode: {projectUuid?, q?, page?, pageSize?} → paginated list, creds inline per env * * Invariants: * - NEVER returns a password field anywhere in the response (defensive strip at handler edge) * - Git-fallback for projectUuid: detectRepoName() → findProjectByRepoName(); NoProjectResolved if both fail * - NotFound on unknown uuid returns isError:true */ import { SearchEnvironmentsInput, ToolContext, ToolResponse, } from '../types/index.js'; import { Logger } from '../utils/logger.js'; import { handleExternalServiceError } from '../utils/errors.js'; import { DebuggAIServerClient } from '../services/index.js'; import { config } from '../config/index.js'; import { detectRepoName } from '../utils/gitContext.js'; import { toPaginationParams, makePageInfo } from '../utils/pagination.js'; const logger = new Logger({ module: 'searchEnvironmentsHandler' }); type SafeCredential = { uuid: string; label: string; username: string; role: string | null; environmentUuid?: string }; function stripPassword(cred: any): SafeCredential { // Defensive: take only known-safe keys. Never spread the source. return { uuid: cred.uuid, label: cred.label, username: cred.username, role: cred.role ?? null, ...(cred.environmentUuid ? { environmentUuid: cred.environmentUuid } : {}), }; } function notFound(uuid: string): ToolResponse { return { content: [{ type: 'text', text: JSON.stringify({ error: 'NotFound', message: `Environment ${uuid} not found.`, uuid }, null, 2), }], isError: true, }; } function noProjectResolved(pagination: { page: number; pageSize: number }, reason: string): ToolResponse { return { content: [{ type: 'text', text: JSON.stringify({ error: 'NoProjectResolved', message: reason, pageInfo: makePageInfo(pagination.page, pagination.pageSize, 0, null), environments: [], }, null, 2), }], }; } export async function searchEnvironmentsHandler( input: SearchEnvironmentsInput, _context: ToolContext, ): Promise<ToolResponse> { const start = Date.now(); const pagination = toPaginationParams({ page: input.page, pageSize: input.pageSize }); logger.toolStart('search_environments', { ...input, ...pagination }); try { const client = new DebuggAIServerClient(config.api.key); await client.init(); // ── Resolve projectUuid ── // Bead gb4n: when projectUuid is provided directly (caller skips git // auto-resolution), `name` and `repoName` are unknown. OMIT those fields // rather than emitting nulls — null fields surprised callers and // muddied the contract. If a caller needs them, they fetch via // search_projects. let projectUuid = input.projectUuid; let project: { uuid: string; name?: string; repoName?: string } | null = null; if (!projectUuid) { const repoName = detectRepoName(); if (!repoName) { return noProjectResolved(pagination, 'No git repo detected and no projectUuid provided. Pass projectUuid (get via search_projects) or invoke from a directory with a git origin.'); } const resolved = await client.findProjectByRepoName(repoName); if (!resolved) { return noProjectResolved(pagination, `No DebuggAI project found for repo "${repoName}". Pass projectUuid explicitly.`); } projectUuid = resolved.uuid; project = { uuid: resolved.uuid }; if (resolved.name) project.name = resolved.name; const rn = resolved.repo?.name ?? repoName; if (rn) project.repoName = rn; } else { project = { uuid: projectUuid }; } // ── uuid mode ── if (input.uuid) { try { const env = await client.getEnvironment(projectUuid, input.uuid); const creds = await client.listCredentialsForEnvironment(projectUuid, input.uuid).catch(() => []); const payload = { project, filter: { uuid: input.uuid }, pageInfo: { page: 1, pageSize: 1, totalCount: 1, totalPages: 1, hasMore: false }, environments: [{ ...env, credentials: creds.map(stripPassword) }], }; logger.toolComplete('search_environments', Date.now() - start); return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] }; } catch (err: any) { if (err?.statusCode === 404 || err?.response?.status === 404) return notFound(input.uuid); throw err; } } // ── Filter mode ── const { pageInfo, environments } = await client.listEnvironmentsPaginated(projectUuid, pagination, input.q); // Expand creds per env (sequential — bounded by page size, typically ≤20) const withCreds = []; for (const env of environments) { const creds = await client.listCredentialsForEnvironment(projectUuid, env.uuid).catch(() => []); withCreds.push({ ...env, credentials: creds.map(stripPassword) }); } const payload = { project, filter: { q: input.q ?? null }, pageInfo, environments: withCreds, }; logger.toolComplete('search_environments', Date.now() - start); return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] }; } catch (error) { logger.toolError('search_environments', error as Error, Date.now() - start); throw handleExternalServiceError(error, 'DebuggAI', 'search_environments'); } } - types/index.ts:64-74 (schema)Zod schema and type for the tool input. Enforces uuid is optional, projectUuid optional, q optional, page/pageSize optional. Uses .strict() to reject unknown properties. Refine ensures uuid and q are mutually exclusive.
export const SearchEnvironmentsInputSchema = z.object({ uuid: z.string().uuid().optional(), projectUuid: z.string().uuid().optional(), q: z.string().min(1).optional(), page: z.number().int().min(1).optional(), pageSize: z.number().int().min(1).optional(), }).strict().refine( (v) => !(v.uuid && v.q !== undefined), { message: 'Cannot combine uuid with q (they are mutually exclusive — uuid mode returns one env; q filters a list).' }, ); export type SearchEnvironmentsInput = z.infer<typeof SearchEnvironmentsInputSchema>; - tools/searchEnvironments.ts:17-44 (registration)Tool definition and registration. buildSearchEnvironmentsTool creates the MCP Tool schema (name, description, inputSchema). buildValidatedSearchEnvironmentsTool wraps it with Zod validation schema and the handler function.
export function buildSearchEnvironmentsTool(): Tool { return { name: 'search_environments', title: 'Search Environments', description: DESCRIPTION, inputSchema: { type: 'object', properties: { uuid: { type: 'string', description: 'Environment UUID. Returns single env with credentials inline. Mutually exclusive with projectUuid/q filter params.' }, projectUuid: { type: 'string', description: 'Override the auto-detected project. Used in filter mode.' }, q: { type: 'string', description: 'Free-text search over environment name. Mutually exclusive with uuid.' }, page: { type: 'number', description: 'Page number (1-indexed).' }, pageSize: { type: 'number', description: 'Page size (1..200). Default 20.' }, }, additionalProperties: false, }, }; } export function buildValidatedSearchEnvironmentsTool(): ValidatedTool { const tool = buildSearchEnvironmentsTool(); return { ...tool, inputSchema: SearchEnvironmentsInputSchema, handler: searchEnvironmentsHandler }; } - tools/index.ts:24-59 (registration)Registration of search_environments in the central tool registry via initTools(). Both the unvalidated Tool and validated ValidatedTool are built and stored, and the tool is added to the toolRegistry map.
export function initTools(ctx: ProjectContext | null): void { const tools: Tool[] = [ buildTestPageChangesTool(ctx), buildTriggerCrawlTool(ctx), buildProbePageTool(), buildSearchProjectsTool(), buildSearchEnvironmentsTool(), buildCreateEnvironmentTool(), buildUpdateEnvironmentTool(), buildDeleteEnvironmentTool(), buildUpdateProjectTool(), buildDeleteProjectTool(), buildSearchExecutionsTool(), buildCreateProjectTool(), ]; const validated: ValidatedTool[] = [ buildValidatedTestPageChangesTool(ctx), buildValidatedTriggerCrawlTool(ctx), buildValidatedProbePageTool(), buildValidatedSearchProjectsTool(), buildValidatedSearchEnvironmentsTool(), buildValidatedCreateEnvironmentTool(), buildValidatedUpdateEnvironmentTool(), buildValidatedDeleteEnvironmentTool(), buildValidatedUpdateProjectTool(), buildValidatedDeleteProjectTool(), buildValidatedSearchExecutionsTool(), buildValidatedCreateProjectTool(), ]; _tools = tools; _validatedTools = validated; toolRegistry.clear(); for (const v of validated) toolRegistry.set(v.name, v); } - Helper function that strips credentials to only safe fields (uuid, label, username, role, environmentUuid). Never returns password.
function stripPassword(cred: any): SafeCredential { // Defensive: take only known-safe keys. Never spread the source. return { uuid: cred.uuid, label: cred.label, username: cred.username, role: cred.role ?? null, ...(cred.environmentUuid ? { environmentUuid: cred.environmentUuid } : {}), }; }