diff --git a/package-lock.json b/package-lock.json index 1cba085..52578e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "axios": "^1.7.0", "form-data": "^4.0.5", "md-to-adf": "^0.5.0", - "zod": "^3.22.4" + "zod": "^4.4.2" }, "bin": { "mcp-jira-stdio": "dist/index.js" @@ -5136,9 +5136,9 @@ } }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.2.tgz", + "integrity": "sha512-IynmDyxsEsb9RKzO3J9+4SxXnl2FTFSzNBaKKaMV6tsSk0rw9gYw9gs+JFCq/qk2LCZ78KDwyj+Z289TijSkUw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 2f08b18..a7164b0 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "axios": "^1.7.0", "form-data": "^4.0.5", "md-to-adf": "^0.5.0", - "zod": "^3.22.4" + "zod": "^4.4.2" }, "devDependencies": { "@types/node": "^25.3.3", diff --git a/src/types/tools.ts b/src/types/tools.ts index 4d505b9..31d211e 100644 --- a/src/types/tools.ts +++ b/src/types/tools.ts @@ -61,7 +61,7 @@ export const CreateIssueInputSchema = z.object({ labels: z.array(z.string()).optional().describe('Issue labels'), components: z.array(z.string()).optional().describe('Component names'), customFields: z - .record(z.any()) + .record(z.string(), z.any()) .optional() .describe( 'Additional Jira field mappings, e.g. { "customfield_12345": value }. Use for required custom fields.' diff --git a/src/utils/validators.ts b/src/utils/validators.ts index 8b1ac90..7c22dc9 100644 --- a/src/utils/validators.ts +++ b/src/utils/validators.ts @@ -6,7 +6,7 @@ export function validateInput(schema: ZodSchema, input: unknown): T { return schema.parse(input); } catch (error) { if (error instanceof z.ZodError) { - const errorMessages = error.errors.map((err) => `${err.path.join('.')}: ${err.message}`); + const errorMessages = error.issues.map((err) => `${err.path.join('.')}: ${err.message}`); throw new Error(`${ERROR_MESSAGES.VALIDATION_ERROR}\n${errorMessages.join('\n')}`); } throw error; diff --git a/tests/unit/tools/create-issue-link.test.ts b/tests/unit/tools/create-issue-link.test.ts index 8de338c..1880d4f 100644 --- a/tests/unit/tools/create-issue-link.test.ts +++ b/tests/unit/tools/create-issue-link.test.ts @@ -4,6 +4,7 @@ import { createIssueLinkTool, } from '../../../src/tools/create-issue-link.js'; import { validateInput } from '../../../src/utils/validators.js'; +import { CreateIssueLinkInputSchema } from '../../../src/types/tools.js'; import { createIssueLink } from '../../../src/utils/api-helpers.js'; import { formatSuccessResponse } from '../../../src/utils/formatters.js'; import { handleError } from '../../../src/utils/error-handler.js'; @@ -194,14 +195,7 @@ describe('create-issue-link tool', () => { await handleCreateIssueLink(input); - expect(mockedValidateInput).toHaveBeenCalledWith( - expect.objectContaining({ - _def: expect.objectContaining({ - typeName: 'ZodObject', - }), - }), - input - ); + expect(mockedValidateInput).toHaveBeenCalledWith(CreateIssueLinkInputSchema, input); }); it('should handle validation errors for missing fromIssue', async () => { diff --git a/tests/unit/tools/create-issue.test.ts b/tests/unit/tools/create-issue.test.ts index cc5639d..504833a 100644 --- a/tests/unit/tools/create-issue.test.ts +++ b/tests/unit/tools/create-issue.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { handleCreateIssue, createIssueTool } from '../../../src/tools/create-issue.js'; import { validateInput } from '../../../src/utils/validators.js'; +import { CreateIssueInputSchema } from '../../../src/types/tools.js'; import { createIssue } from '../../../src/utils/api-helpers.js'; import { formatIssueResponse } from '../../../src/utils/formatters.js'; import { handleError } from '../../../src/utils/error-handler.js'; @@ -219,14 +220,7 @@ describe('create-issue tool', () => { await handleCreateIssue(input); - expect(mockedValidateInput).toHaveBeenCalledWith( - expect.objectContaining({ - _def: expect.objectContaining({ - typeName: 'ZodObject', - }), - }), - input - ); + expect(mockedValidateInput).toHaveBeenCalledWith(CreateIssueInputSchema, input); }); it('should handle validation errors for missing required fields', async () => { diff --git a/tests/unit/tools/get-issue.test.ts b/tests/unit/tools/get-issue.test.ts index 26f6fc9..e628196 100644 --- a/tests/unit/tools/get-issue.test.ts +++ b/tests/unit/tools/get-issue.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { handleGetIssue, getIssueTool } from '../../../src/tools/get-issue.js'; import { validateInput, extractIssueKey } from '../../../src/utils/validators.js'; +import { GetIssueInputSchema } from '../../../src/types/tools.js'; import { getIssue } from '../../../src/utils/api-helpers.js'; import { formatIssueResponse } from '../../../src/utils/formatters.js'; import { handleError } from '../../../src/utils/error-handler.js'; @@ -201,14 +202,7 @@ describe('get-issue tool', () => { await handleGetIssue(input); - expect(mockedValidateInput).toHaveBeenCalledWith( - expect.objectContaining({ - _def: expect.objectContaining({ - typeName: 'ZodObject', - }), - }), - input - ); + expect(mockedValidateInput).toHaveBeenCalledWith(GetIssueInputSchema, input); }); it('should handle validation errors', async () => { diff --git a/tests/unit/tools/search-issues.test.ts b/tests/unit/tools/search-issues.test.ts index 11c83ca..288e9de 100644 --- a/tests/unit/tools/search-issues.test.ts +++ b/tests/unit/tools/search-issues.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { handleSearchIssues, searchIssuesTool } from '../../../src/tools/search-issues.js'; import { validateInput } from '../../../src/utils/validators.js'; +import { SearchIssuesInputSchema } from '../../../src/types/tools.js'; import { searchIssues } from '../../../src/utils/api-helpers.js'; import { formatSearchResultsResponse } from '../../../src/utils/formatters.js'; import { handleError } from '../../../src/utils/error-handler.js'; @@ -207,14 +208,7 @@ describe('search-issues tool', () => { await handleSearchIssues(input); - expect(mockedValidateInput).toHaveBeenCalledWith( - expect.objectContaining({ - _def: expect.objectContaining({ - typeName: 'ZodObject', - }), - }), - input - ); + expect(mockedValidateInput).toHaveBeenCalledWith(SearchIssuesInputSchema, input); }); it('should handle validation errors for missing JQL', async () => { diff --git a/tests/unit/utils/validators.test.ts b/tests/unit/utils/validators.test.ts index b47db45..f4bb1c6 100644 --- a/tests/unit/utils/validators.test.ts +++ b/tests/unit/utils/validators.test.ts @@ -37,8 +37,8 @@ describe('validators', () => { it('should handle missing required fields', () => { const input = { age: 30 }; - expect(() => validateInput(testSchema, input)).toThrow('name: Required'); - expect(() => validateInput(testSchema, input)).toThrow('email: Required'); + expect(() => validateInput(testSchema, input)).toThrow('name: Invalid input'); + expect(() => validateInput(testSchema, input)).toThrow('email: Invalid input'); }); it('should rethrow non-zod errors', () => {