diff --git a/src/api/apiClient.ts b/src/api/apiClient.ts index 8e4afd1fc..a77369f21 100644 --- a/src/api/apiClient.ts +++ b/src/api/apiClient.ts @@ -109,10 +109,18 @@ export const errorMap = (issue: ZodIssueOptionalMessage, ctx: ErrorMapCtx) => { } } -export const apiClient = createApiClient(BASE_URL, { +// TLDR: the inferred TS schema was too big, so this is a workaround to fix it. +// Create intermediate type alias to break complex type inference +const _createApiClient = createApiClient +type ApiClientType = ReturnType + +// Create the actual instance with explicit type annotation +const apiClient: ApiClientType = _createApiClient(BASE_URL, { axiosInstance: axiosClient, validate: 'request', -}) +}) as ApiClientType + +export { apiClient } export default apiClient export const v2ApiClient = createV2ApiClient(BASE_URL) diff --git a/src/api/zodClient.ts b/src/api/zodClient.ts index d814b843d..4078fef54 100644 --- a/src/api/zodClient.ts +++ b/src/api/zodClient.ts @@ -202,29 +202,59 @@ const UpdateEnvironmentDto = z const GenerateSdkTokensDto = z .object({ client: z.boolean(), server: z.boolean(), mobile: z.boolean() }) .partial() -const AllFilter = z.object({ type: z.literal('all').default('all') }) -const OptInFilter = z.object({ type: z.literal('optIn').default('optIn') }) +const AllFilter = z.object({ + type: z.literal('all').default('all'), + _audiences: z.array(z.string()).optional(), + values: z.array(z.string()).optional(), +}) +const OptInFilter = z.object({ + type: z.literal('optIn').default('optIn'), + _audiences: z.array(z.string()).optional(), + values: z.array(z.string()).optional(), +}) const UserFilter = z.object({ subType: z.enum(['user_id', 'email', 'platform', 'deviceModel']), - comparator: z.enum(['=', '!=', 'exist', '!exist', 'contain', '!contain']), + comparator: z.enum([ + '=', + '!=', + 'exist', + '!exist', + 'contain', + '!contain', + 'endWith', + 'startWith', + ]), values: z.array(z.string()).optional(), + _audiences: z.array(z.string()).optional(), type: z.literal('user').default('user'), }) const UserCountryFilter = z.object({ subType: z.literal('country').default('country'), - comparator: z.enum(['=', '!=', 'exist', '!exist', 'contain', '!contain']), + comparator: z.enum([ + '=', + '!=', + 'exist', + '!exist', + 'contain', + '!contain', + 'endWith', + 'startWith', + ]), values: z.array(z.string()), + _audiences: z.array(z.string()).optional(), type: z.literal('user').default('user'), }) const UserAppVersionFilter = z.object({ comparator: z.enum(['=', '!=', '>', '>=', '<', '<=', 'exist', '!exist']), values: z.array(z.string()).optional(), + _audiences: z.array(z.string()).optional(), type: z.literal('user').default('user'), subType: z.literal('appVersion').default('appVersion'), }) const UserPlatformVersionFilter = z.object({ comparator: z.enum(['=', '!=', '>', '>=', '<', '<=', 'exist', '!exist']), values: z.array(z.string()).optional(), + _audiences: z.array(z.string()).optional(), type: z.literal('user').default('user'), subType: z.literal('platformVersion').default('platformVersion'), }) @@ -244,6 +274,7 @@ const UserCustomFilter = z.object({ dataKey: z.string().min(1), dataKeyType: z.enum(['String', 'Boolean', 'Number']), values: z.array(z.union([z.boolean(), z.string(), z.number()])).optional(), + _audiences: z.array(z.string()).optional(), type: z.literal('user').default('user'), subType: z.literal('customData').default('customData'), }) @@ -564,8 +595,10 @@ const Target = z.object({ _id: z.string(), name: z.string().optional(), audience: TargetAudience, + filters: z.array(z.any()).optional(), rollout: Rollout.nullable().optional(), distribution: z.array(TargetDistribution), + bucketingKey: z.string().optional(), }) const FeatureConfig = z.object({ _feature: z.string(), @@ -576,6 +609,7 @@ const FeatureConfig = z.object({ updatedAt: z.string().datetime(), targets: z.array(Target), readonly: z.boolean(), + hasStaticConfig: z.boolean().optional(), }) const UpdateTargetDto = z.object({ _id: z.string().optional(), diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 9f39a81ad..d18062f77 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -28,20 +28,41 @@ import { selfTargetingToolHandlers, } from './tools/selfTargetingTools' +// Environment variable to control output schema inclusion +const ENABLE_OUTPUT_SCHEMAS = process.env.ENABLE_OUTPUT_SCHEMAS === 'true' +if (ENABLE_OUTPUT_SCHEMAS) { + console.error('DevCycle MCP Server - Output Schemas: ENABLED') +} + +const ENABLE_DVC_MCP_DEBUG = process.env.ENABLE_DVC_MCP_DEBUG === 'true' + // Tool handler function type export type ToolHandler = ( args: unknown, apiClient: DevCycleApiClient, ) => Promise +// Function to conditionally remove outputSchema from tool definitions +const processToolDefinitions = (tools: Tool[]): Tool[] => { + if (ENABLE_OUTPUT_SCHEMAS) { + return tools + } + + // Remove outputSchema from all tools when disabled + return tools.map((tool) => { + const { outputSchema, ...toolWithoutSchema } = tool + return toolWithoutSchema + }) +} + // Combine all tool definitions -const allToolDefinitions: Tool[] = [ +const allToolDefinitions: Tool[] = processToolDefinitions([ ...featureToolDefinitions, ...environmentToolDefinitions, ...projectToolDefinitions, ...variableToolDefinitions, ...selfTargetingToolDefinitions, -] +]) // Combine all tool handlers const allToolHandlers: Record = { @@ -241,7 +262,36 @@ export class DevCycleMCPServer { } const result = await handler(args, this.apiClient) - return { + + // Return structured content only if output schemas are enabled + if (ENABLE_OUTPUT_SCHEMAS) { + // Check if tool has output schema + const toolDef = allToolDefinitions.find( + (tool) => tool.name === name, + ) + + if (toolDef?.outputSchema) { + // For tools with output schemas, return structured JSON content + const mcpResult = { + content: [ + { + type: 'json', + json: result, + }, + ], + } + if (ENABLE_DVC_MCP_DEBUG) { + console.error( + `MCP ${name} structured JSON result:`, + JSON.stringify(mcpResult, null, 2), + ) + } + return mcpResult + } + } + + // Default: return as text content (for disabled schemas or tools without schemas) + const mcpResult = { content: [ { type: 'text', @@ -249,6 +299,13 @@ export class DevCycleMCPServer { }, ], } + if (ENABLE_DVC_MCP_DEBUG) { + console.error( + `MCP ${name} text result:`, + JSON.stringify(mcpResult, null, 2), + ) + } + return mcpResult } catch (error) { return this.handleToolError(error, name) } diff --git a/src/mcp/tools/environmentTools.ts b/src/mcp/tools/environmentTools.ts index 2f1e53d10..d588af994 100644 --- a/src/mcp/tools/environmentTools.ts +++ b/src/mcp/tools/environmentTools.ts @@ -1,5 +1,5 @@ import { Tool } from '@modelcontextprotocol/sdk/types.js' -import { DevCycleApiClient } from '../utils/api' +import { DevCycleApiClient, handleZodiosValidationErrors } from '../utils/api' import { fetchEnvironments, fetchEnvironmentByKey, @@ -264,7 +264,10 @@ export const environmentToolHandlers: Record = { 'listEnvironments', validatedArgs, async (authToken, projectKey) => { - return await fetchEnvironments(authToken, projectKey) + return await handleZodiosValidationErrors( + () => fetchEnvironments(authToken, projectKey), + 'listEnvironments', + ) }, generateEnvironmentDashboardLink, ) @@ -276,10 +279,14 @@ export const environmentToolHandlers: Record = { 'getSdkKeys', validatedArgs, async (authToken, projectKey) => { - const environment = await fetchEnvironmentByKey( - authToken, - projectKey, - validatedArgs.environmentKey, + const environment = await handleZodiosValidationErrors( + () => + fetchEnvironmentByKey( + authToken, + projectKey, + validatedArgs.environmentKey, + ), + 'fetchEnvironmentByKey', ) const sdkKeys = environment.sdkKeys @@ -306,10 +313,10 @@ export const environmentToolHandlers: Record = { 'createEnvironment', validatedArgs, async (authToken, projectKey) => { - return await createEnvironment( - authToken, - projectKey, - validatedArgs, + return await handleZodiosValidationErrors( + () => + createEnvironment(authToken, projectKey, validatedArgs), + 'createEnvironment', ) }, generateEnvironmentDashboardLink, @@ -323,11 +330,15 @@ export const environmentToolHandlers: Record = { validatedArgs, async (authToken, projectKey) => { const { key, ...updateParams } = validatedArgs - return await updateEnvironment( - authToken, - projectKey, - key, - updateParams, + return await handleZodiosValidationErrors( + () => + updateEnvironment( + authToken, + projectKey, + key, + updateParams, + ), + 'updateEnvironment', ) }, generateEnvironmentDashboardLink, diff --git a/src/mcp/tools/featureTools.ts b/src/mcp/tools/featureTools.ts index 717ec0f25..1ca469dbc 100644 --- a/src/mcp/tools/featureTools.ts +++ b/src/mcp/tools/featureTools.ts @@ -44,6 +44,7 @@ import { VARIATION_KEY_PROPERTY, TARGET_AUDIENCE_PROPERTY, } from './commonSchemas' +import { handleZodiosValidationErrors } from '../utils/api' // Helper function to generate feature dashboard links const generateFeaturesDashboardLink = ( @@ -778,7 +779,10 @@ export const featureToolHandlers: Record = { 'listFeatures', validatedArgs, async (authToken, projectKey) => { - return await fetchFeatures(authToken, projectKey, validatedArgs) + return await handleZodiosValidationErrors( + () => fetchFeatures(authToken, projectKey, validatedArgs), + 'listFeatures', + ) }, generateFeaturesDashboardLink, ) @@ -806,7 +810,10 @@ export const featureToolHandlers: Record = { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { interactive, ...featureData } = validatedArgs - return await createFeature(authToken, projectKey, featureData) + return await handleZodiosValidationErrors( + () => createFeature(authToken, projectKey, featureData), + 'createFeature', + ) }, (orgId, projectKey, result) => generateFeatureDashboardLink( @@ -826,11 +833,9 @@ export const featureToolHandlers: Record = { async (authToken, projectKey) => { const { key, ...updateData } = validatedArgs - return await updateFeature( - authToken, - projectKey, - key, - updateData, + return await handleZodiosValidationErrors( + () => updateFeature(authToken, projectKey, key, updateData), + 'updateFeature', ) }, (orgId, projectKey, result) => @@ -854,11 +859,15 @@ export const featureToolHandlers: Record = { async (authToken, projectKey) => { const { key, ...statusData } = validatedArgs - return await updateFeatureStatus( - authToken, - projectKey, - key, - statusData, + return await handleZodiosValidationErrors( + () => + updateFeatureStatus( + authToken, + projectKey, + key, + statusData, + ), + 'updateFeatureStatus', ) }, (orgId, projectKey, result) => @@ -877,7 +886,11 @@ export const featureToolHandlers: Record = { 'deleteFeature', validatedArgs, async (authToken, projectKey) => { - await deleteFeature(authToken, projectKey, validatedArgs.key) + await handleZodiosValidationErrors( + () => + deleteFeature(authToken, projectKey, validatedArgs.key), + 'deleteFeature', + ) return { message: `Feature '${validatedArgs.key}' deleted successfully`, } @@ -895,10 +908,14 @@ export const featureToolHandlers: Record = { 'fetchFeatureVariations', validatedArgs, async (authToken, projectKey) => { - return await fetchVariations( - authToken, - projectKey, - validatedArgs.feature_key, + return await handleZodiosValidationErrors( + () => + fetchVariations( + authToken, + projectKey, + validatedArgs.feature_key, + ), + 'fetchVariations', ) }, (orgId, projectKey) => @@ -922,11 +939,15 @@ export const featureToolHandlers: Record = { async (authToken, projectKey) => { const { feature_key, ...variationData } = validatedArgs - return await createVariation( - authToken, - projectKey, - feature_key, - variationData, + return await handleZodiosValidationErrors( + () => + createVariation( + authToken, + projectKey, + feature_key, + variationData, + ), + 'createVariation', ) }, (orgId, projectKey, result) => @@ -951,12 +972,16 @@ export const featureToolHandlers: Record = { const { feature_key, variation_key, ...variationData } = validatedArgs - return await updateVariation( - authToken, - projectKey, - feature_key, - variation_key, - variationData, + return await handleZodiosValidationErrors( + () => + updateVariation( + authToken, + projectKey, + feature_key, + variation_key, + variationData, + ), + 'updateVariation', ) }, (orgId, projectKey, result) => @@ -978,11 +1003,15 @@ export const featureToolHandlers: Record = { 'enableTargeting', validatedArgs, async (authToken, projectKey) => { - await enableTargeting( - authToken, - projectKey, - validatedArgs.feature_key, - validatedArgs.environment_key, + await handleZodiosValidationErrors( + () => + enableTargeting( + authToken, + projectKey, + validatedArgs.feature_key, + validatedArgs.environment_key, + ), + 'enableTargeting', ) return { message: `Targeting enabled for feature '${validatedArgs.feature_key}' in environment '${validatedArgs.environment_key}'`, @@ -1007,11 +1036,15 @@ export const featureToolHandlers: Record = { 'disableTargeting', validatedArgs, async (authToken, projectKey) => { - await disableTargeting( - authToken, - projectKey, - validatedArgs.feature_key, - validatedArgs.environment_key, + await handleZodiosValidationErrors( + () => + disableTargeting( + authToken, + projectKey, + validatedArgs.feature_key, + validatedArgs.environment_key, + ), + 'disableTargeting', ) return { message: `Targeting disabled for feature '${validatedArgs.feature_key}' in environment '${validatedArgs.environment_key}'`, @@ -1036,11 +1069,15 @@ export const featureToolHandlers: Record = { 'listFeatureTargeting', validatedArgs, async (authToken, projectKey) => { - return await fetchTargetingForFeature( - authToken, - projectKey, - validatedArgs.feature_key, - validatedArgs.environment_key, + return await handleZodiosValidationErrors( + () => + fetchTargetingForFeature( + authToken, + projectKey, + validatedArgs.feature_key, + validatedArgs.environment_key, + ), + 'fetchTargetingForFeature', ) }, (orgId, projectKey) => @@ -1065,12 +1102,16 @@ export const featureToolHandlers: Record = { const { feature_key, environment_key, ...configData } = validatedArgs - return await updateFeatureConfigForEnvironment( - authToken, - projectKey, - feature_key, - environment_key, - configData, + return await handleZodiosValidationErrors( + () => + updateFeatureConfigForEnvironment( + authToken, + projectKey, + feature_key, + environment_key, + configData, + ), + 'updateFeatureConfigForEnvironment', ) }, (orgId, projectKey) => @@ -1092,11 +1133,15 @@ export const featureToolHandlers: Record = { 'getFeatureAuditLogHistory', validatedArgs, async (authToken, projectKey) => { - return await getFeatureAuditLogHistory( - authToken, - projectKey, - validatedArgs.feature_key, - validatedArgs.days_back || 30, + return await handleZodiosValidationErrors( + () => + getFeatureAuditLogHistory( + authToken, + projectKey, + validatedArgs.feature_key, + validatedArgs.days_back || 30, + ), + 'getFeatureAuditLogHistory', ) }, (orgId, projectKey) => diff --git a/src/mcp/tools/projectTools.ts b/src/mcp/tools/projectTools.ts index da89d1632..40fb1e178 100644 --- a/src/mcp/tools/projectTools.ts +++ b/src/mcp/tools/projectTools.ts @@ -1,5 +1,5 @@ import { Tool } from '@modelcontextprotocol/sdk/types.js' -import { DevCycleApiClient } from '../utils/api' +import { DevCycleApiClient, handleZodiosValidationErrors } from '../utils/api' import { fetchProjects, fetchProject, @@ -209,7 +209,10 @@ export const projectToolHandlers: Record = { validatedArgs, async (authToken) => { // projectKey not used for listing all projects - return await fetchProjects(authToken, validatedArgs) + return await handleZodiosValidationErrors( + () => fetchProjects(authToken, validatedArgs), + 'fetchProjects', + ) }, generateOrganizationSettingsLink, ) @@ -222,7 +225,10 @@ export const projectToolHandlers: Record = { 'getCurrentProject', null, async (authToken, projectKey) => { - return await fetchProject(authToken, projectKey) + return await handleZodiosValidationErrors( + () => fetchProject(authToken, projectKey), + 'fetchProject', + ) }, generateProjectDashboardLink, ) @@ -235,7 +241,10 @@ export const projectToolHandlers: Record = { validatedArgs, async (authToken) => { // projectKey not used for creating projects - return await createProject(authToken, validatedArgs) + return await handleZodiosValidationErrors( + () => createProject(authToken, validatedArgs), + 'createProject', + ) }, generateProjectDashboardLink, ) @@ -249,7 +258,10 @@ export const projectToolHandlers: Record = { validatedArgs, async (authToken) => { // projectKey not used - we use the key from validated args - return await updateProject(authToken, key, updateParams) + return await handleZodiosValidationErrors( + () => updateProject(authToken, key, updateParams), + 'updateProject', + ) }, generateEditProjectLink, ) diff --git a/src/mcp/tools/selfTargetingTools.ts b/src/mcp/tools/selfTargetingTools.ts index fc6d4432e..e67056d74 100644 --- a/src/mcp/tools/selfTargetingTools.ts +++ b/src/mcp/tools/selfTargetingTools.ts @@ -1,5 +1,5 @@ import { Tool } from '@modelcontextprotocol/sdk/types.js' -import { DevCycleApiClient } from '../utils/api' +import { DevCycleApiClient, handleZodiosValidationErrors } from '../utils/api' import { fetchUserProfile, updateUserProfile } from '../../api/userProfile' import { fetchProjectOverridesForUser, @@ -203,7 +203,10 @@ export const selfTargetingToolHandlers: Record = { 'getSelfTargetingIdentity', null, async (authToken, projectKey) => { - return await fetchUserProfile(authToken, projectKey) + return await handleZodiosValidationErrors( + () => fetchUserProfile(authToken, projectKey), + 'fetchUserProfile', + ) }, generateSelfTargetingDashboardLink, ) @@ -218,9 +221,13 @@ export const selfTargetingToolHandlers: Record = { 'updateSelfTargetingIdentity', validatedArgs, async (authToken, projectKey) => { - return await updateUserProfile(authToken, projectKey, { - dvcUserId: validatedArgs.dvc_user_id, - }) + return await handleZodiosValidationErrors( + () => + updateUserProfile(authToken, projectKey, { + dvcUserId: validatedArgs.dvc_user_id, + }), + 'updateUserProfile', + ) }, generateSelfTargetingDashboardLink, ) @@ -233,7 +240,10 @@ export const selfTargetingToolHandlers: Record = { 'listSelfTargetingOverrides', null, async (authToken, projectKey) => { - return await fetchProjectOverridesForUser(authToken, projectKey) + return await handleZodiosValidationErrors( + () => fetchProjectOverridesForUser(authToken, projectKey), + 'fetchProjectOverridesForUser', + ) }, generateSelfTargetingDashboardLink, ) @@ -248,14 +258,18 @@ export const selfTargetingToolHandlers: Record = { 'setSelfTargetingOverride', validatedArgs, async (authToken, projectKey) => { - return await updateOverride( - authToken, - projectKey, - validatedArgs.feature_key, - { - environment: validatedArgs.environment_key, - variation: validatedArgs.variation_key, - }, + return await handleZodiosValidationErrors( + () => + updateOverride( + authToken, + projectKey, + validatedArgs.feature_key, + { + environment: validatedArgs.environment_key, + variation: validatedArgs.variation_key, + }, + ), + 'updateOverride', ) }, generateSelfTargetingDashboardLink, @@ -271,11 +285,15 @@ export const selfTargetingToolHandlers: Record = { 'clearFeatureSelfTargetingOverrides', validatedArgs, async (authToken, projectKey) => { - await deleteFeatureOverrides( - authToken, - projectKey, - validatedArgs.feature_key, - validatedArgs.environment_key, + await handleZodiosValidationErrors( + () => + deleteFeatureOverrides( + authToken, + projectKey, + validatedArgs.feature_key, + validatedArgs.environment_key, + ), + 'deleteFeatureOverrides', ) return { @@ -293,7 +311,10 @@ export const selfTargetingToolHandlers: Record = { 'clearAllSelfTargetingOverrides', null, async (authToken, projectKey) => { - await deleteAllProjectOverrides(authToken, projectKey) + await handleZodiosValidationErrors( + () => deleteAllProjectOverrides(authToken, projectKey), + 'deleteAllProjectOverrides', + ) return { message: 'Cleared all overrides for the project' } }, generateSelfTargetingDashboardLink, diff --git a/src/mcp/tools/variableTools.ts b/src/mcp/tools/variableTools.ts index f9b231632..97cdaad58 100644 --- a/src/mcp/tools/variableTools.ts +++ b/src/mcp/tools/variableTools.ts @@ -1,5 +1,5 @@ import { Tool } from '@modelcontextprotocol/sdk/types.js' -import { DevCycleApiClient } from '../utils/api' +import { DevCycleApiClient, handleZodiosValidationErrors } from '../utils/api' import { fetchVariables, createVariable, @@ -296,10 +296,9 @@ export const variableToolHandlers: Record = { 'listVariables', validatedArgs, async (authToken, projectKey) => { - return await fetchVariables( - authToken, - projectKey, - validatedArgs, + return await handleZodiosValidationErrors( + () => fetchVariables(authToken, projectKey, validatedArgs), + 'fetchVariables', ) }, generateVariablesDashboardLink, @@ -312,10 +311,9 @@ export const variableToolHandlers: Record = { 'createVariable', validatedArgs, async (authToken, projectKey) => { - return await createVariable( - authToken, - projectKey, - validatedArgs, + return await handleZodiosValidationErrors( + () => createVariable(authToken, projectKey, validatedArgs), + 'createVariable', ) }, generateVariablesDashboardLink, @@ -330,11 +328,10 @@ export const variableToolHandlers: Record = { async (authToken, projectKey) => { const { key, ...updateData } = validatedArgs - return await updateVariable( - authToken, - projectKey, - key, - updateData, + return await handleZodiosValidationErrors( + () => + updateVariable(authToken, projectKey, key, updateData), + 'updateVariable', ) }, generateVariablesDashboardLink, @@ -347,7 +344,15 @@ export const variableToolHandlers: Record = { 'deleteVariable', validatedArgs, async (authToken, projectKey) => { - await deleteVariable(authToken, projectKey, validatedArgs.key) + await handleZodiosValidationErrors( + () => + deleteVariable( + authToken, + projectKey, + validatedArgs.key, + ), + 'deleteVariable', + ) return { message: `Variable '${validatedArgs.key}' deleted successfully`, } diff --git a/src/mcp/utils/api.ts b/src/mcp/utils/api.ts index 6cf03b42e..3a7745965 100644 --- a/src/mcp/utils/api.ts +++ b/src/mcp/utils/api.ts @@ -1,5 +1,57 @@ import { DevCycleAuth } from './auth' +/** + * Utility function to handle Zodios validation errors by extracting response data + * when HTTP call succeeds (200 OK) but schema validation fails + */ +export async function handleZodiosValidationErrors( + apiCall: () => Promise, + operationName?: string, +): Promise { + try { + return await apiCall() + } catch (error) { + // Check if this is a Zodios validation error with successful HTTP response + if ( + error instanceof Error && + error.message.includes('Zodios: Invalid response') && + error.message.includes('status: 200 OK') + ) { + if (operationName) { + console.error( + `MCP ${operationName}: Zodios validation failed but HTTP 200 OK - extracting response data`, + ) + } + + // Extract response data from error object using common patterns + const errorAny = error as any + const responseData = + errorAny.data || // Zodios primary location + errorAny.response?.data || // Axios standard location + errorAny.cause?.data || // Alternative nested location + null + + if (responseData) { + if (operationName) { + console.error( + `Successfully extracted response data for ${operationName}`, + ) + } + return responseData + } + + if (operationName) { + console.error( + `Could not extract response data from Zodios error for ${operationName}`, + ) + } + } + + // Re-throw the original error if we can't extract data + throw error + } +} + function getErrorMessage(error: unknown): string { if (error instanceof Error && error.message) { return error.message @@ -51,13 +103,7 @@ export class DevCycleApiClient { const authToken = this.auth.getAuthToken() const projectKey = requiresProject ? this.auth.getProjectKey() : '' - - const result = await operation(authToken, projectKey) - console.error( - `MCP ${operationName} result:`, - JSON.stringify(result, null, 2), - ) - return result + return await operation(authToken, projectKey) } catch (error) { console.error( `MCP ${operationName} error:`, diff --git a/src/ui/targetingTree.ts b/src/ui/targetingTree.ts index b5cf3b510..c24071ba0 100644 --- a/src/ui/targetingTree.ts +++ b/src/ui/targetingTree.ts @@ -43,6 +43,10 @@ const comparatorMap = { '>=': 'is greater than or equal to', '<': 'is less than', '<=': 'is less than or equal to', + endWith: 'ends with', + '!endWith': 'does not end with', + startWith: 'starts with', + '!startWith': 'does not start with', } export const renderTargetingTree = (