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 | 17x 17x 17x 17x 17x 20x 20x 1x 19x 4x 4x 4x 4x 10x 10x 10x 10x 5x 5x 5x 5x 17x 1x 17x 4x 17x 3x 3x | import * as z from "zod";
import { BrowseSearchSchema } from "./schema-readonly";
import { ToolRegistry, EnhancedToolDefinition } from "../../types";
import { isActionDenied } from "../../config";
import { gitlab, paths, toQuery } from "../../utils/gitlab-api";
/**
* Search tools registry - 1 read-only CQRS tool
*
* browse_search (Query): global, project, group
*
* Search is read-only by design - no manage_search tool needed.
*/
export const searchToolRegistry: ToolRegistry = new Map<string, EnhancedToolDefinition>([
// ============================================================================
// browse_search - CQRS Query Tool (discriminated union schema)
// TypeScript automatically narrows types in each switch case
// ============================================================================
[
"browse_search",
{
name: "browse_search",
description:
"Search across GitLab resources globally or within a scope. Actions: global (entire instance), project (within specific project), group (within specific group). Searchable: projects, issues, merge_requests, milestones, users, blobs (code), commits, wiki_blobs, notes.",
inputSchema: z.toJSONSchema(BrowseSearchSchema),
gate: { envVar: "USE_SEARCH", defaultValue: true },
handler: async (args: unknown): Promise<unknown> => {
const input = BrowseSearchSchema.parse(args);
// Runtime validation: reject denied actions even if they bypass schema filtering
if (isActionDenied("browse_search", input.action)) {
throw new Error(`Action '${input.action}' is not allowed for browse_search tool`);
}
switch (input.action) {
case "global": {
// TypeScript knows: input has scope, search, and optional filters
const { scope, ...params } = input;
// Build query params excluding action (not an API parameter)
const query = toQuery(params, ["action"]);
// Global search endpoint
const results = await gitlab.get<unknown[]>("search", {
query: { ...query, scope },
});
return {
scope,
count: results.length,
results,
};
}
case "project": {
// TypeScript knows: input has project_id, scope, search, and optional filters
const { project_id, scope, ref, ...params } = input;
// Build query params excluding action (project_id, scope, ref are already destructured)
const query = toQuery(params, ["action"]);
// Project-scoped search endpoint
const results = await gitlab.get<unknown[]>(`${paths.project(project_id)}/search`, {
query: { ...query, scope, ...(ref && { ref }) },
});
return {
project_id,
scope,
count: results.length,
results,
};
}
case "group": {
// TypeScript knows: input has group_id, scope, search, and optional filters
const { group_id, scope, ...params } = input;
// Build query params excluding action (group_id, scope are already destructured)
const query = toQuery(params, ["action"]);
// Group-scoped search endpoint
const results = await gitlab.get<unknown[]>(`${paths.group(group_id)}/search`, {
query: { ...query, scope },
});
return {
group_id,
scope,
count: results.length,
results,
};
}
/* 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
* Search is entirely read-only, so all tools are read-only
*/
export function getSearchReadOnlyToolNames(): string[] {
return ["browse_search"];
}
/**
* Get all tool definitions from the registry
*/
export function getSearchToolDefinitions(): EnhancedToolDefinition[] {
return Array.from(searchToolRegistry.values());
}
/**
* Get filtered tools based on read-only mode
* Since search is read-only, this always returns all tools
*/
export function getFilteredSearchTools(readOnlyMode: boolean = false): EnhancedToolDefinition[] {
// Search is always read-only, so readOnlyMode doesn't affect filtering
void readOnlyMode;
return getSearchToolDefinitions();
}
|