Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions src/funcs/codeReferencesGetStatistics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { LaunchDarklyCore } from "../core.js";
import { encodeFormQuery } from "../lib/encodings.js";
import * as M from "../lib/matchers.js";
import { compactMap } from "../lib/primitives.js";
import { safeParse } from "../lib/schemas.js";
import { RequestOptions } from "../lib/sdks.js";
import { extractSecurity, resolveGlobalSecurity } from "../lib/security.js";
import { pathToFunc } from "../lib/url.js";
import * as components from "../models/components/index.js";
import {
ConnectionError,
InvalidRequestError,
RequestAbortedError,
RequestTimeoutError,
UnexpectedClientError,
} from "../models/errors/httpclienterrors.js";
import * as errors from "../models/errors/index.js";
import { LaunchDarklyError } from "../models/errors/launchdarklyerror.js";
import { ResponseValidationError } from "../models/errors/responsevalidationerror.js";
import { SDKValidationError } from "../models/errors/sdkvalidationerror.js";
import * as operations from "../models/operations/index.js";
import { APICall, APIPromise } from "../types/async.js";
import { Result } from "../types/fp.js";

/**
* Get code reference statistics for a project
*
* @remarks
* Get statistics about code references for feature flags in a given project.
* Returns repo count, reference count, and per-repository breakdown for each flag.
* Optionally filter by a specific flag key.
*/
export function codeReferencesGetStatistics(
client: LaunchDarklyCore,
request: operations.GetCodeRefStatisticsRequest,
options?: RequestOptions,
): APIPromise<
Result<
components.StatisticCollectionRep,
| errors.UnauthorizedErrorRep
| errors.ForbiddenErrorRep
| errors.RateLimitedErrorRep
| LaunchDarklyError
| ResponseValidationError
| ConnectionError
| RequestAbortedError
| RequestTimeoutError
| InvalidRequestError
| UnexpectedClientError
| SDKValidationError
>
> {
return new APIPromise($do(client, request, options));
}

async function $do(
client: LaunchDarklyCore,
request: operations.GetCodeRefStatisticsRequest,
options?: RequestOptions,
): Promise<
[
Result<
components.StatisticCollectionRep,
| errors.UnauthorizedErrorRep
| errors.ForbiddenErrorRep
| errors.RateLimitedErrorRep
| LaunchDarklyError
| ResponseValidationError
| ConnectionError
| RequestAbortedError
| RequestTimeoutError
| InvalidRequestError
| UnexpectedClientError
| SDKValidationError
>,
APICall,
]
> {
const parsed = safeParse(
request,
(value) =>
operations.GetCodeRefStatisticsRequest$outboundSchema.parse(value),
"Input validation failed",
);
if (!parsed.ok) {
return [parsed, { status: "invalid" }];
}
const payload = parsed.value;
const body = null;

const pathParams = { projectKey: payload.projectKey };
const path = pathToFunc("/api/v2/code-refs/statistics/{projectKey}")(
pathParams,
);

const query = encodeFormQuery({
"flagKey": payload.flagKey,
});

const headers = new Headers(compactMap({
Accept: "application/json",
}));

const secConfig = await extractSecurity(client._options.apiKey);
const securityInput = secConfig == null ? {} : { apiKey: secConfig };
const requestSecurity = resolveGlobalSecurity(securityInput);

const context = {
options: client._options,
baseURL: options?.serverURL ?? client._baseURL ?? "",
operationID: "getCodeRefStatistics",
oAuth2Scopes: null,
resolvedSecurity: requestSecurity,
securitySource: client._options.apiKey,
retryConfig: options?.retries
|| client._options.retryConfig
|| { strategy: "none" },
retryCodes: options?.retryCodes || ["429", "500", "502", "503", "504"],
};

const requestRes = client._createRequest(context, {
security: requestSecurity,
method: "GET",
baseURL: options?.serverURL,
path: path,
headers: headers,
query: query,
body: body,
userAgent: client._options.userAgent,
timeoutMs: options?.timeoutMs || client._options.timeoutMs || -1,
}, options);
if (!requestRes.ok) {
return [requestRes, { status: "invalid" }];
}
const req = requestRes.value;

const doResult = await client._do(req, {
context,
errorCodes: ["401", "403", "404", "429", "4XX", "5XX"],
retryConfig: context.retryConfig,
retryCodes: context.retryCodes,
});
if (!doResult.ok) {
return [doResult, { status: "request-error", request: req }];
}
const response = doResult.value;

const responseFields = {
HttpMeta: { Response: response, Request: req },
};

const [result] = await M.match<
components.StatisticCollectionRep,
| errors.UnauthorizedErrorRep
| errors.ForbiddenErrorRep
| errors.RateLimitedErrorRep
| LaunchDarklyError
| ResponseValidationError
| ConnectionError
| RequestAbortedError
| RequestTimeoutError
| InvalidRequestError
| UnexpectedClientError
| SDKValidationError
>(
M.json(200, components.StatisticCollectionRep$inboundSchema),
M.jsonErr(401, errors.UnauthorizedErrorRep$inboundSchema),
M.jsonErr(403, errors.ForbiddenErrorRep$inboundSchema),
M.jsonErr(429, errors.RateLimitedErrorRep$inboundSchema),
M.fail("4XX"),
M.fail("5XX"),
)(response, req, { extraFields: responseFields });
if (!result.ok) {
return [result, { status: "complete", request: req, response }];
}

return [result, { status: "complete", request: req, response }];
}
2 changes: 2 additions & 0 deletions src/mcp-server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { tool$aiConfigsUpdate } from "./tools/aiConfigsUpdate.js";
import { tool$aiConfigsUpdateTargeting } from "./tools/aiConfigsUpdateTargeting.js";
import { tool$aiConfigsUpdateVariation } from "./tools/aiConfigsUpdateVariation.js";
import { tool$auditLogListEntries } from "./tools/auditLogListEntries.js";
import { tool$codeReferencesGetStatistics } from "./tools/codeReferencesGetStatistics.js";
import { tool$codeReferencesListRepositories } from "./tools/codeReferencesListRepositories.js";
import { tool$environmentsListByProject } from "./tools/environmentsListByProject.js";
import { tool$featureFlagsCreate } from "./tools/featureFlagsCreate.js";
Expand Down Expand Up @@ -76,6 +77,7 @@ export function createMCPServer(deps: {

tool(tool$auditLogListEntries);
tool(tool$codeReferencesListRepositories);
tool(tool$codeReferencesGetStatistics);
tool(tool$featureFlagsGetStatus);
tool(tool$featureFlagsList);
tool(tool$featureFlagsCreate);
Expand Down
36 changes: 36 additions & 0 deletions src/mcp-server/tools/codeReferencesGetStatistics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { codeReferencesGetStatistics } from "../../funcs/codeReferencesGetStatistics.js";
import * as operations from "../../models/operations/index.js";
import { formatResult, ToolDefinition } from "../tools.js";

const args = {
request: operations.GetCodeRefStatisticsRequest$inboundSchema,
};

export const tool$codeReferencesGetStatistics: ToolDefinition<typeof args> = {
name: "get-code-ref-statistics",
description:
`Returns the number of repositories and code references for each feature flag in a project. ` +
`Use this tool before archiving or removing a flag to verify it is no longer referenced in source code. ` +
`Optionally filter to a specific flag with the flagKey parameter. ` +
`Response includes repoCount, refCount, and a per-repository breakdown with branch and file paths.`,
scopes: ["read"],
args,
tool: async (client, args, ctx) => {
const [result, apiCall] = await codeReferencesGetStatistics(
client,
args.request,
{ fetchOptions: { signal: ctx.signal } },
).$inspect();

if (!result.ok) {
return {
content: [{ type: "text", text: result.error.message }],
isError: true,
};
}

const value = result.value;

return formatResult(value, apiCall);
},
};
52 changes: 52 additions & 0 deletions src/models/components/coderefstatisticsrep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as z from "zod/v3";
import { remap as remap$ } from "../../lib/primitives.js";
import {
Link,
Link$inboundSchema,
} from "./link.js";

export type StatisticRep = {
name: string;
type: "bitbucket" | "custom" | "github" | "gitlab";
sourceLink: string;
defaultBranch: string;
enabled: boolean;
version: number;
hunkCount: number;
fileCount: number;
links: { [k: string]: Link };
latestCommitTime?: number | undefined;
};

export type StatisticCollectionRep = {
flags: { [flagKey: string]: Array<StatisticRep> };
links: { [k: string]: Link };
};

/** @internal */
const StatisticRep$inboundSchema: z.ZodType<
StatisticRep,
z.ZodTypeDef,
unknown
> = z.object({
name: z.string(),
type: z.enum(["bitbucket", "custom", "github", "gitlab"]),
sourceLink: z.string(),
defaultBranch: z.string(),
enabled: z.boolean(),
version: z.number().int(),
hunkCount: z.number().int(),
fileCount: z.number().int(),
_links: z.record(Link$inboundSchema),
latestCommitTime: z.number().optional(),
}).transform((v) => remap$(v, { "_links": "links" }));

/** @internal */
export const StatisticCollectionRep$inboundSchema: z.ZodType<
StatisticCollectionRep,
z.ZodTypeDef,
unknown
> = z.object({
flags: z.record(z.array(StatisticRep$inboundSchema)),
_links: z.record(Link$inboundSchema),
}).transform((v) => remap$(v, { "_links": "links" }));
1 change: 1 addition & 0 deletions src/models/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export * from "./auditlogentrylistingrep.js";
export * from "./auditlogentrylistingrepcollection.js";
export * from "./authorizedappdatarep.js";
export * from "./branchrep.js";
export * from "./coderefstatisticsrep.js";
export * from "./clause.js";
export * from "./clientsideavailability.js";
export * from "./clientsideavailabilitypost.js";
Expand Down
59 changes: 59 additions & 0 deletions src/models/operations/getcoderefstatistics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as z from "zod/v3";
import { safeParse } from "../../lib/schemas.js";
import { Result as SafeParseResult } from "../../types/fp.js";
import { SDKValidationError } from "../errors/sdkvalidationerror.js";

export type GetCodeRefStatisticsRequest = {
/**
* The LaunchDarkly project key
*/
projectKey: string;
/**
* Filter results to a specific flag key
*/
flagKey?: string | undefined;
};

/** @internal */
export const GetCodeRefStatisticsRequest$inboundSchema: z.ZodType<
GetCodeRefStatisticsRequest,
z.ZodTypeDef,
unknown
> = z.object({
projectKey: z.string(),
flagKey: z.string().optional(),
});

/** @internal */
export type GetCodeRefStatisticsRequest$Outbound = {
projectKey: string;
flagKey?: string | undefined;
};

/** @internal */
export const GetCodeRefStatisticsRequest$outboundSchema: z.ZodType<
GetCodeRefStatisticsRequest$Outbound,
z.ZodTypeDef,
GetCodeRefStatisticsRequest
> = z.object({
projectKey: z.string(),
flagKey: z.string().optional(),
});

export function getCodeRefStatisticsRequestToJSON(
req: GetCodeRefStatisticsRequest,
): string {
return JSON.stringify(
GetCodeRefStatisticsRequest$outboundSchema.parse(req),
);
}

export function getCodeRefStatisticsRequestFromJSON(
jsonString: string,
): SafeParseResult<GetCodeRefStatisticsRequest, SDKValidationError> {
return safeParse(
jsonString,
(x) => GetCodeRefStatisticsRequest$inboundSchema.parse(JSON.parse(x)),
`Failed to parse 'GetCodeRefStatisticsRequest' from JSON`,
);
}
1 change: 1 addition & 0 deletions src/models/operations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export * from "./getenvironmentsbyproject.js";
export * from "./getfeatureflag.js";
export * from "./getfeatureflags.js";
export * from "./getfeatureflagstatusacrossenvironments.js";
export * from "./getcoderefstatistics.js";
export * from "./getrepositories.js";
export * from "./patchaiconfig.js";
export * from "./patchaiconfigtargeting.js";
Expand Down