You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix enum schema generation to use string values instead of numeric values (#248)
## Problem
When using the Azure DevOps MCP server, enum parameters in tool function
declarations were generating numeric values (e.g., `0, 1, 2`) in the
JSON schema, but the MCP API expects string representations (e.g.,
`"None", "LastModifiedAscending", "LastModifiedDescending"`).
This resulted in API errors like:
```
Invalid value at 'request.tools[0].function_declarations[X].parameters.properties[Y].value.enum[Z]' (TYPE_STRING), [numeric_value]
```
The issue affected several tools including:
- `build_get_definitions` (queryOrder parameter)
- `build_get_builds` (queryOrder parameter)
- `build_update_build_stage` (status parameter)
- `release_get_definitions` (expand, queryOrder parameters)
- `release_get_releases` (statusFilter, queryOrder, expand parameters)
## Root Cause
The issue was caused by using `z.nativeEnum()` with TypeScript numeric
enums from the `azure-devops-node-api` package. When
`zod-to-json-schema` processes `z.nativeEnum()`, it generates:
```json
{
"type": "number",
"enum": [0, 1, 2, 3, 4]
}
```
But the MCP protocol expects:
```json
{
"type": "string",
"enum": ["None", "LastModifiedAscending", "LastModifiedDescending", "DefinitionNameAscending", "DefinitionNameDescending"]
}
```
## Solution
1. **Added utility function**: Created `getEnumKeys()` in `utils.ts` to
extract string keys from TypeScript numeric enums
2. **Replaced z.nativeEnum**: Updated all enum parameters in `builds.ts`
and `releases.ts` to use `z.enum(getEnumKeys(EnumType))` instead of
`z.nativeEnum(EnumType)`
3. **Maintained API compatibility**: Updated tool handlers to convert
string enum values back to numeric values when calling Azure DevOps APIs
4. **Added comprehensive tests**: Created tests to verify enum schemas
generate the correct string types and values
## Changes
### Files Modified:
- `src/utils.ts` - Added `getEnumKeys()` utility function
- `src/tools/builds.ts` - Replaced 3 instances of `z.nativeEnum()` with
string-based enums
- `src/tools/releases.ts` - Replaced 5 instances of `z.nativeEnum()`
with string-based enums
- `test/src/tools/builds.test.ts` - Updated tests to use string enum
values
- `test/src/enum-schema.test.ts` - Added comprehensive enum schema
validation tests
### Before/After Comparison:
**Before (generates numeric schema):**
```typescript
queryOrder: z.nativeEnum(DefinitionQueryOrder).optional()
```
**After (generates string schema):**
```typescript
queryOrder: z.enum(getEnumKeys(DefinitionQueryOrder) as [string, ...string[]]).optional()
```
The tool handlers now properly convert string values back to numeric for
API calls:
```typescript
queryOrder ? DefinitionQueryOrder[queryOrder as keyof typeof DefinitionQueryOrder] : undefined
```
## Testing
- All existing tests pass
- New tests verify enum schemas generate string types with correct
values
- Manual verification confirms schemas now generate `"type": "string"`
instead of `"type": "number"`
- Build and linting pass successfully
Fixes#183
<!-- START COPILOT CODING AGENT TIPS -->
---
💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.
---------
Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: nikolapeja6 <[email protected]>
Co-authored-by: kboom <[email protected]>
Co-authored-by: Nikola Pejic <[email protected]>
.describe("Maximum created time for releases (default: now)"),
104
-
queryOrder: z.nativeEnum(ReleaseQueryOrder).optional().default(ReleaseQueryOrder.Ascending).describe("Order in which to return releases (default: Ascending)"),
op: z.enum(["add","replace","remove"]).default("add").describe("The operation to perform on the field."),
467
+
op: z.enum(["Add","Replace","Remove"]).default("Add").describe("The operation to perform on the field."),
458
468
path: z.string().describe("The path of the field to update, e.g., '/fields/System.Title'."),
459
-
value: z.string().describe("The new value for the field. This is required for 'add' and 'replace' operations, and should be omitted for 'remove' operations."),
469
+
value: z.string().describe("The new value for the field. This is required for 'Add' and 'Replace' operations, and should be omitted for 'Remove' operations."),
460
470
})
461
471
)
462
472
.describe("An array of field updates to apply to the work item."),
project: z.string().describe("The name or ID of the Azure DevOps project."),
559
576
query: z.string().describe("The ID or path of the query to retrieve."),
560
-
expand: z.enum(["all","clauses","minimal","none","wiql"]).optional().describe("Optional expand parameter to include additional details in the response. Defaults to 'none'."),
op: z.enum(["add","replace","remove"]).default("add").describe("The operation to perform on the field."),
626
+
op: z.enum(["Add","Replace","Remove"]).default("Add").describe("The operation to perform on the field."),
607
627
id: z.number().describe("The ID of the work item to update."),
608
628
path: z.string().describe("The path of the field to update, e.g., '/fields/System.Title'."),
609
629
value: z.string().describe("The new value for the field. This is required for 'add' and 'replace' operations, and should be omitted for 'remove' operations."),
0 commit comments