Skip to content

Commit d33751e

Browse files
feat: add result endpoints to MCP tools (#469)
* feat: add result endpoints to MCP tools * fix: yarn build * fix: revert feature tools changes
1 parent d1b554d commit d33751e

5 files changed

Lines changed: 334 additions & 0 deletions

File tree

src/api/results.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import apiClient from './apiClient'
2+
import { buildHeaders } from './common'
3+
import {
4+
FeatureTotalEvaluationsQuerySchema,
5+
ProjectTotalEvaluationsQuerySchema,
6+
} from '../mcp/types'
7+
import { z } from 'zod'
8+
9+
export const fetchFeatureTotalEvaluations = async (
10+
token: string,
11+
project_id: string,
12+
feature_key: string,
13+
queries: z.infer<typeof FeatureTotalEvaluationsQuerySchema> = {},
14+
) => {
15+
return apiClient.get(
16+
'/v1/projects/:project/features/:feature/results/total-evaluations',
17+
{
18+
headers: buildHeaders(token),
19+
params: {
20+
project: project_id,
21+
feature: feature_key,
22+
},
23+
queries,
24+
},
25+
)
26+
}
27+
28+
export const fetchProjectTotalEvaluations = async (
29+
token: string,
30+
project_id: string,
31+
queries: z.infer<typeof ProjectTotalEvaluationsQuerySchema> = {},
32+
) => {
33+
return apiClient.get('/v1/projects/:project/results/total-evaluations', {
34+
headers: buildHeaders(token),
35+
params: {
36+
project: project_id,
37+
},
38+
queries,
39+
})
40+
}

src/mcp/server.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ import {
2727
selfTargetingToolDefinitions,
2828
selfTargetingToolHandlers,
2929
} from './tools/selfTargetingTools'
30+
import {
31+
resultsToolDefinitions,
32+
resultsToolHandlers,
33+
} from './tools/resultsTools'
3034

3135
// Environment variable to control output schema inclusion
3236
const ENABLE_OUTPUT_SCHEMAS = process.env.ENABLE_OUTPUT_SCHEMAS === 'true'
@@ -62,6 +66,7 @@ const allToolDefinitions: Tool[] = processToolDefinitions([
6266
...projectToolDefinitions,
6367
...variableToolDefinitions,
6468
...selfTargetingToolDefinitions,
69+
...resultsToolDefinitions,
6570
])
6671

6772
// Combine all tool handlers
@@ -71,6 +76,7 @@ const allToolHandlers: Record<string, ToolHandler> = {
7176
...projectToolHandlers,
7277
...variableToolHandlers,
7378
...selfTargetingToolHandlers,
79+
...resultsToolHandlers,
7480
}
7581

7682
export class DevCycleMCPServer {

src/mcp/tools/commonSchemas.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,3 +346,72 @@ export const TARGET_AUDIENCE_PROPERTY = {
346346
},
347347
required: ['filters'] as const,
348348
}
349+
350+
// =============================================================================
351+
// RESULTS AND ANALYTICS PROPERTIES
352+
// =============================================================================
353+
354+
export const EVALUATION_QUERY_PROPERTIES = {
355+
startDate: {
356+
type: 'number' as const,
357+
description: 'Start date as Unix timestamp (milliseconds since epoch)',
358+
},
359+
endDate: {
360+
type: 'number' as const,
361+
description: 'End date as Unix timestamp (milliseconds since epoch)',
362+
},
363+
platform: {
364+
type: 'string' as const,
365+
description: 'Platform filter for evaluation results',
366+
},
367+
variable: {
368+
type: 'string' as const,
369+
description: 'Variable key filter for evaluation results',
370+
},
371+
environment: {
372+
type: 'string' as const,
373+
description: 'Environment key to filter results',
374+
},
375+
period: {
376+
type: 'string' as const,
377+
enum: ['day', 'hour', 'month'] as const,
378+
description: 'Time aggregation period for results',
379+
},
380+
sdkType: {
381+
type: 'string' as const,
382+
enum: ['client', 'server', 'mobile', 'api'] as const,
383+
description: 'Filter by SDK type',
384+
},
385+
}
386+
387+
export const EVALUATION_DATA_POINT_SCHEMA = {
388+
type: 'object' as const,
389+
properties: {
390+
date: {
391+
type: 'string' as const,
392+
format: 'date-time' as const,
393+
description: 'ISO timestamp for this data point',
394+
},
395+
values: {
396+
type: 'object' as const,
397+
description: 'Evaluation values for this time period',
398+
},
399+
},
400+
required: ['date', 'values'] as const,
401+
}
402+
403+
export const PROJECT_DATA_POINT_SCHEMA = {
404+
type: 'object' as const,
405+
properties: {
406+
date: {
407+
type: 'string' as const,
408+
format: 'date-time' as const,
409+
description: 'ISO timestamp for this data point',
410+
},
411+
value: {
412+
type: 'number' as const,
413+
description: 'Total evaluations in this time period',
414+
},
415+
},
416+
required: ['date', 'value'] as const,
417+
}

src/mcp/tools/resultsTools.ts

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import { Tool } from '@modelcontextprotocol/sdk/types.js'
2+
import { DevCycleApiClient, handleZodiosValidationErrors } from '../utils/api'
3+
import {
4+
fetchFeatureTotalEvaluations,
5+
fetchProjectTotalEvaluations,
6+
} from '../../api/results'
7+
import {
8+
GetFeatureTotalEvaluationsArgsSchema,
9+
GetProjectTotalEvaluationsArgsSchema,
10+
FeatureTotalEvaluationsQuerySchema,
11+
ProjectTotalEvaluationsQuerySchema,
12+
} from '../types'
13+
import { ToolHandler } from '../server'
14+
import {
15+
DASHBOARD_LINK_PROPERTY,
16+
FEATURE_KEY_PROPERTY,
17+
EVALUATION_QUERY_PROPERTIES,
18+
EVALUATION_DATA_POINT_SCHEMA,
19+
PROJECT_DATA_POINT_SCHEMA,
20+
} from './commonSchemas'
21+
22+
// Helper functions to generate dashboard links
23+
const generateFeatureAnalyticsDashboardLink = (
24+
orgId: string,
25+
projectKey: string,
26+
featureKey: string,
27+
): string => {
28+
return `https://app.devcycle.com/o/${orgId}/p/${projectKey}/features/${featureKey}/analytics`
29+
}
30+
31+
const generateProjectAnalyticsDashboardLink = (
32+
orgId: string,
33+
projectKey: string,
34+
): string => {
35+
return `https://app.devcycle.com/o/${orgId}/p/${projectKey}/analytics`
36+
}
37+
38+
// =============================================================================
39+
// INPUT SCHEMAS
40+
// =============================================================================
41+
42+
const FEATURE_EVALUATION_QUERY_PROPERTIES = {
43+
featureKey: FEATURE_KEY_PROPERTY,
44+
...EVALUATION_QUERY_PROPERTIES,
45+
}
46+
47+
const PROJECT_EVALUATION_QUERY_PROPERTIES = EVALUATION_QUERY_PROPERTIES
48+
49+
// =============================================================================
50+
// OUTPUT SCHEMAS
51+
// =============================================================================
52+
53+
const FEATURE_EVALUATIONS_OUTPUT_SCHEMA = {
54+
type: 'object' as const,
55+
properties: {
56+
result: {
57+
type: 'object' as const,
58+
description: 'Feature evaluation data aggregated by time period',
59+
properties: {
60+
evaluations: {
61+
type: 'array' as const,
62+
description: 'Array of evaluation data points',
63+
items: EVALUATION_DATA_POINT_SCHEMA,
64+
},
65+
cached: {
66+
type: 'boolean' as const,
67+
description: 'Whether this result came from cache',
68+
},
69+
updatedAt: {
70+
type: 'string' as const,
71+
format: 'date-time' as const,
72+
description: 'When the data was last updated',
73+
},
74+
},
75+
required: ['evaluations', 'cached', 'updatedAt'],
76+
},
77+
dashboardLink: DASHBOARD_LINK_PROPERTY,
78+
},
79+
required: ['result', 'dashboardLink'],
80+
}
81+
82+
const PROJECT_EVALUATIONS_OUTPUT_SCHEMA = {
83+
type: 'object' as const,
84+
properties: {
85+
result: {
86+
type: 'object' as const,
87+
description: 'Project evaluation data aggregated by time period',
88+
properties: {
89+
evaluations: {
90+
type: 'array' as const,
91+
description: 'Array of evaluation data points',
92+
items: PROJECT_DATA_POINT_SCHEMA,
93+
},
94+
cached: {
95+
type: 'boolean' as const,
96+
description: 'Whether this result came from cache',
97+
},
98+
updatedAt: {
99+
type: 'string' as const,
100+
format: 'date-time' as const,
101+
description: 'When the data was last updated',
102+
},
103+
},
104+
required: ['evaluations', 'cached', 'updatedAt'],
105+
},
106+
dashboardLink: DASHBOARD_LINK_PROPERTY,
107+
},
108+
required: ['result', 'dashboardLink'],
109+
}
110+
111+
// =============================================================================
112+
// TOOL DEFINITIONS
113+
// =============================================================================
114+
115+
export const resultsToolDefinitions: Tool[] = [
116+
{
117+
name: 'get_feature_total_evaluations',
118+
description:
119+
'Get total variable evaluations per time period for a specific feature. Include dashboard link in the response.',
120+
inputSchema: {
121+
type: 'object',
122+
properties: FEATURE_EVALUATION_QUERY_PROPERTIES,
123+
required: ['featureKey'],
124+
},
125+
outputSchema: FEATURE_EVALUATIONS_OUTPUT_SCHEMA,
126+
},
127+
{
128+
name: 'get_project_total_evaluations',
129+
description:
130+
'Get total variable evaluations per time period for the entire project. Include dashboard link in the response.',
131+
inputSchema: {
132+
type: 'object',
133+
properties: PROJECT_EVALUATION_QUERY_PROPERTIES,
134+
},
135+
outputSchema: PROJECT_EVALUATIONS_OUTPUT_SCHEMA,
136+
},
137+
]
138+
139+
export const resultsToolHandlers: Record<string, ToolHandler> = {
140+
get_feature_total_evaluations: async (
141+
args: unknown,
142+
apiClient: DevCycleApiClient,
143+
) => {
144+
const validatedArgs = GetFeatureTotalEvaluationsArgsSchema.parse(args)
145+
146+
return await apiClient.executeWithDashboardLink(
147+
'getFeatureTotalEvaluations',
148+
validatedArgs,
149+
async (authToken, projectKey) => {
150+
const { featureKey, ...apiQueries } = validatedArgs
151+
152+
return await handleZodiosValidationErrors(
153+
() =>
154+
fetchFeatureTotalEvaluations(
155+
authToken,
156+
projectKey,
157+
featureKey,
158+
apiQueries,
159+
),
160+
'fetchFeatureTotalEvaluations',
161+
)
162+
},
163+
(orgId, projectKey) =>
164+
generateFeatureAnalyticsDashboardLink(
165+
orgId,
166+
projectKey,
167+
validatedArgs.featureKey,
168+
),
169+
)
170+
},
171+
get_project_total_evaluations: async (
172+
args: unknown,
173+
apiClient: DevCycleApiClient,
174+
) => {
175+
const validatedArgs = GetProjectTotalEvaluationsArgsSchema.parse(args)
176+
177+
return await apiClient.executeWithDashboardLink(
178+
'getProjectTotalEvaluations',
179+
validatedArgs,
180+
async (authToken, projectKey) => {
181+
return await handleZodiosValidationErrors(
182+
() =>
183+
fetchProjectTotalEvaluations(
184+
authToken,
185+
projectKey,
186+
validatedArgs,
187+
),
188+
'fetchProjectTotalEvaluations',
189+
)
190+
},
191+
generateProjectAnalyticsDashboardLink,
192+
)
193+
},
194+
}

src/mcp/types.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,28 @@ export const GetFeatureAuditLogHistoryArgsSchema = z.object({
187187
feature_key: z.string(),
188188
days_back: z.number().min(1).max(365).default(30).optional(),
189189
})
190+
191+
// Base evaluation query schema (matches API camelCase naming)
192+
const BaseEvaluationQuerySchema = z.object({
193+
startDate: z.number().optional(),
194+
endDate: z.number().optional(),
195+
environment: z.string().optional(),
196+
period: z.enum(['day', 'hour', 'month']).optional(),
197+
sdkType: z.enum(['client', 'server', 'mobile', 'api']).optional(),
198+
})
199+
200+
// MCP argument schemas (using camelCase to match API)
201+
export const GetFeatureTotalEvaluationsArgsSchema =
202+
BaseEvaluationQuerySchema.extend({
203+
featureKey: z.string(),
204+
platform: z.string().optional(),
205+
variable: z.string().optional(),
206+
})
207+
208+
export const GetProjectTotalEvaluationsArgsSchema = BaseEvaluationQuerySchema
209+
210+
// API query schemas (same as MCP args since we use camelCase throughout)
211+
export const FeatureTotalEvaluationsQuerySchema =
212+
GetFeatureTotalEvaluationsArgsSchema.omit({ featureKey: true })
213+
export const ProjectTotalEvaluationsQuerySchema =
214+
GetProjectTotalEvaluationsArgsSchema

0 commit comments

Comments
 (0)