Skip to content

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Jul 11, 2025

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:

{
  "type": "number",
  "enum": [0, 1, 2, 3, 4]
}

But the MCP protocol expects:

{
  "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):

queryOrder: z.nativeEnum(DefinitionQueryOrder).optional()

After (generates string schema):

queryOrder: z.enum(getEnumKeys(DefinitionQueryOrder) as [string, ...string[]]).optional()

The tool handlers now properly convert string values back to numeric for API calls:

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


💡 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 in the docs.

@Copilot Copilot AI changed the title [WIP] z.nativeEnum generates numeric values for enums, but API expects strings Fix enum schema generation to use string values instead of numeric values Jul 11, 2025
@Copilot Copilot AI requested a review from nikolapeja6 July 11, 2025 08:00
Copilot finished work on behalf of nikolapeja6 July 11, 2025 08:00
@nikolapeja6 nikolapeja6 requested a review from mkonjikovac July 14, 2025 13:10
@kboom
Copy link
Contributor

kboom commented Jul 14, 2025

@nikolapeja6 this looks good, but we must resolve the conflicts. Also, I think that it doesn't necessarily handle all cases of enums we already have. In some places, we explicitly define strings which is not great, and that should be chnged to. In different, we do this correctly much like we do this in utils, but we didn't update the code to reuse the same logic. Let's fix that.

Copilot AI and others added 2 commits July 14, 2025 15:56
- Updated repos.ts to use PullRequestStatus enum with getEnumKeys()
- Updated workitems.ts to use Operation enum for patch operations
- Updated workitems.ts to use QueryExpand enum for query expansion
- Added conversion functions to handle enum value transformations
- All enum schemas now generate string types instead of numeric types
- Updated handlers to properly convert enum values for API calls

Co-authored-by: kboom <[email protected]>
Copy link
Contributor Author

Copilot AI commented Jul 14, 2025

@nikolapeja6 this looks good, but we must resolve the conflicts. Also, I think that it doesn't necessarily handle all cases of enums we already have. In some places, we explicitly define strings which...

✅ Done! I've resolved the merge conflicts and replaced hardcoded string enums with proper Azure DevOps API enums:

Merge conflicts resolved:

  • Successfully merged latest changes from main branch

Enum standardization completed:

  • repos.ts: Replaced hardcoded ["abandoned", "active", "all", "completed", "notSet"] with PullRequestStatus enum
  • workitems.ts: Replaced hardcoded ["add", "replace", "remove"] with Operation enum
  • workitems.ts: Replaced hardcoded ["all", "clauses", "minimal", "none", "wiql"] with QueryExpand enum

All enum schemas now consistently generate string types using the getEnumKeys() pattern. The remaining hardcoded enums (project state filters, repository types, timeframes) don't have corresponding Azure DevOps API enums and are appropriate as string literals.

Commit: 6e1737f

Copilot finished work on behalf of kboom July 14, 2025 16:09
@Copilot Copilot AI requested a review from kboom July 14, 2025 16:09
@nikolapeja6 nikolapeja6 reopened this Jul 16, 2025
Copy link

github-actions bot commented Jul 16, 2025

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@Copilot Copilot AI requested a review from nikolapeja6 July 16, 2025 18:32
Copilot finished work on behalf of nikolapeja6 July 16, 2025 18:32
Copilot finished work on behalf of nikolapeja6 July 17, 2025 15:04
@Copilot Copilot AI requested a review from nikolapeja6 July 17, 2025 15:04
@nikolapeja6 nikolapeja6 self-assigned this Jul 18, 2025
@nikolapeja6
Copy link
Member

PR is ready to be merged - @microsoft/azure-devops-mcp-maintainers please approve.

… undefined values

- Added safeEnumConvert utility function to validate enum keys before conversion
- Updated all enum conversions in builds.ts and releases.ts to use safe validation
- Fixed test cases to use correct string enum keys instead of numeric values
- Ensures API calls receive valid enum values or undefined instead of accessing invalid keys

Co-authored-by: aaudzei <[email protected]>
@Copilot Copilot AI requested a review from aaudzei July 21, 2025 12:57
Copilot finished work on behalf of aaudzei July 21, 2025 12:57
@Copilot Copilot AI requested a review from aaudzei July 21, 2025 14:04
Copilot finished work on behalf of aaudzei July 21, 2025 14:04
buildId: 123,
stageName: "Build",
status: StageUpdateType.Retry,
status: "Retry" as keyof typeof StageUpdateType,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot , handler accepts status: string. There is no need for type casting.
Apply to the rest of changes in this file as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed unnecessary type casting from all instances of status parameter in the builds test file. The handler accepts status: string so no type casting to keyof typeof StageUpdateType is needed.

Commit: 944fd41

@Copilot Copilot AI requested a review from aaudzei July 21, 2025 15:19
Copilot finished work on behalf of aaudzei July 21, 2025 15:19
Copy link
Contributor

@aaudzei aaudzei left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested the branch. And although it is easy to make model mix enums:

Image

I don't think it is "our fault". The description of tool and params must be good enough to correct the execution when necessary:

Image

.slice(0, top);
}

function pullRequestStatusStringToInt(status: string): number {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we do this automatically? Do we really have to enumerate all of enums values?

@nikolapeja6 nikolapeja6 merged commit 14a25b4 into main Jul 21, 2025
7 checks passed
@nikolapeja6 nikolapeja6 deleted the copilot/fix-183-2 branch July 21, 2025 16:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

z.nativeEnum generates numeric values for enums, but API expects strings

5 participants