From 49b305d2b9a604957123746cb177dfc59dca3aa9 Mon Sep 17 00:00:00 2001 From: ThisIsDemetrio Date: Fri, 10 Jul 2026 19:52:09 +0200 Subject: [PATCH] fix: add refType parameter to configuration_get tool so version tags resolve correctly --- src/apis/backendClient.test.ts | 28 ++++++++++ src/apis/backendClient.ts | 14 +++-- src/apis/client.ts | 17 +++--- src/apis/types/configuration.ts | 2 + src/tools/configuration/index.test.ts | 75 +++++++++++++++++++++++++++ src/tools/configuration/index.ts | 8 ++- 6 files changed, 131 insertions(+), 13 deletions(-) diff --git a/src/apis/backendClient.test.ts b/src/apis/backendClient.test.ts index b233430..11c4ebb 100644 --- a/src/apis/backendClient.test.ts +++ b/src/apis/backendClient.test.ts @@ -323,6 +323,20 @@ suite('Backend Internal Client', () => { t.assert.deepStrictEqual(environmentResult, mockedResult) }) + test('get configuration by version', async (t: TestContext) => { + const mockedResult = { + projectId, + } + + agent.get(internalEndpoint).intercept({ + path: `/api/backend/projects/${projectId}/versions/${refId}/configuration`, + method: 'GET', + }).reply(200, mockedResult) + + const versionResult = await client.getRevisionBasedConfiguration(projectId, refId, 'versions') + t.assert.deepStrictEqual(versionResult, mockedResult) + }) + test('get configuration must thrown if the API call fails', async (t: TestContext) => { agent.get(internalEndpoint).intercept({ path: `/api/backend/projects/${projectId}/revisions/${refId}/configuration`, @@ -571,6 +585,20 @@ suite('Backend Client', () => { t.assert.deepStrictEqual(environmentResult, mockedResult) }) + test('get configuration by version', async (t: TestContext) => { + const mockedResult = { + projectId, + } + + agent.get(mockedEndpoint).intercept({ + path: `/api/backend/projects/${projectId}/versions/${refId}/configuration`, + method: 'GET', + }).reply(200, mockedResult) + + const versionResult = await client.getRevisionBasedConfiguration(projectId, refId, 'versions') + t.assert.deepStrictEqual(versionResult, mockedResult) + }) + test('save configuration', async (t: TestContext) => { const data = { title: 'title', diff --git a/src/apis/backendClient.ts b/src/apis/backendClient.ts index e82ce3f..a43a1e7 100644 --- a/src/apis/backendClient.ts +++ b/src/apis/backendClient.ts @@ -20,7 +20,7 @@ import { } from '@mia-platform/console-types' import { HTTPClient } from './http-client' -import { ConfigToSave, RetrievedConfiguration, SaveResponse } from './types/configuration' +import { ConfigToSave, ConfigurationRefType, RetrievedConfiguration, SaveResponse } from './types/configuration' import { PostProject, ProjectDraft, Template } from './types/governance' export const internalEndpoint = process.env.BACKEND_INTERNAL_ENDPOINT || 'http://internal.local:3000' @@ -92,8 +92,12 @@ export class BackendClient { ) } - getRevisionBasedConfiguration (prjID: string, refID: string): Promise { - return this.#client.get(this.#revisionConfigurationPath(prjID, refID)) + getRevisionBasedConfiguration ( + prjID: string, + refID: string, + refType: ConfigurationRefType = 'revisions', + ): Promise { + return this.#client.get(this.#revisionConfigurationPath(prjID, refID, refType)) } getEnvironmentBasedConfiguration (prjID: string, refID: string): Promise { @@ -150,8 +154,8 @@ export class BackendClient { return this.#client.get>(this.#companyRulesPath(tenantID), new URLSearchParams({})) } - #revisionConfigurationPath (prjID: string, refID: string): string { - return `/api/backend/projects/${prjID}/revisions/${encodeURIComponent(refID)}/configuration` + #revisionConfigurationPath (prjID: string, refID: string, refType: ConfigurationRefType = 'revisions'): string { + return `/api/backend/projects/${prjID}/${refType}/${encodeURIComponent(refID)}/configuration` } #environmentConfigurationPath (prjID: string, refID: string): string { diff --git a/src/apis/client.ts b/src/apis/client.ts index f572c2d..83a55ce 100644 --- a/src/apis/client.ts +++ b/src/apis/client.ts @@ -41,6 +41,7 @@ import { CompareForDeployResponse, PipelineStatus, TriggerDeployResponse } from import { Config, ConfigToSave, + ConfigurationRefType, DockerSuggestionPrefix, ResourcesToCreate, RetrievedConfiguration, @@ -89,7 +90,7 @@ export interface IAPIClient { // #region Configuration Methods getConfigurationRevisions(projectId: string): Promise> - getConfiguration(projectId: string, refId: string): Promise + getConfiguration(projectId: string, refId: string, refType?: ConfigurationRefType): Promise saveConfiguration( projectId: string, refId: string, @@ -294,13 +295,17 @@ export class APIClient implements IAPIClient { return await this.saveConfiguration(projectID, refID, resourcesToCreate) } - async getConfiguration (prjID: string, refID: string): Promise { + async getConfiguration ( + prjID: string, + refID: string, + refType: ConfigurationRefType = 'revisions', + ): Promise { const ft = await this.#featureFlagsClient.getToggles(prjID, [ ENABLE_ENVIRONMENT_BASED_CONFIGURATION_MANAGEMENT ]) if (ft[ENABLE_ENVIRONMENT_BASED_CONFIGURATION_MANAGEMENT] || false) { return this.#backendClient.getEnvironmentBasedConfiguration(prjID, refID) } - return this.#backendClient.getRevisionBasedConfiguration(prjID, refID) + return this.#backendClient.getRevisionBasedConfiguration(prjID, refID, refType) } async saveConfiguration ( @@ -808,7 +813,7 @@ export interface APIClientMockFunctions { // #region Configuration Methods getConfigurationRevisionsMockFn?: (projectId: string) => Promise> - getConfigurationMockFn?: (projectId: string, refId: string) => Promise + getConfigurationMockFn?: (projectId: string, refId: string, refType?: ConfigurationRefType) => Promise saveConfigurationMockFn?: (projectId: string) => Promise createServiceFromMarketplaceItemMockFn?: (projectID: string) => Promise createEndpointsMockFn?: (projectID: string) => Promise @@ -945,12 +950,12 @@ export class APIClientMock implements IAPIClient { } - async getConfiguration (projectId: string, refId: string): Promise { + async getConfiguration (projectId: string, refId: string, refType?: ConfigurationRefType): Promise { if (!this.mocks.getConfigurationMockFn) { throw new Error('getConfigurationMockFn not mocked') } - return this.mocks.getConfigurationMockFn(projectId, refId) + return this.mocks.getConfigurationMockFn(projectId, refId, refType) } async saveConfiguration ( diff --git a/src/apis/types/configuration.ts b/src/apis/types/configuration.ts index 43eb92f..2e3109c 100644 --- a/src/apis/types/configuration.ts +++ b/src/apis/types/configuration.ts @@ -26,6 +26,8 @@ import { export type Config = ConfigType +export type ConfigurationRefType = 'revisions' | 'versions' + export type RetrievedConfiguration = ConfigType & { fastDataConfig: unknown microfrontendPluginsConfig: unknown diff --git a/src/tools/configuration/index.test.ts b/src/tools/configuration/index.test.ts index a74583d..0655e92 100644 --- a/src/tools/configuration/index.test.ts +++ b/src/tools/configuration/index.test.ts @@ -385,6 +385,81 @@ suite('get configuration tool', () => { ]) }) + it('should default refType to "revisions" when not provided', async (t: it.TestContext) => { + const testTenantId = 'tenant123' + const testProjectId = 'project123' + const refId = 'main' + + const getProjectInfoMockFn = mock.fn(async (projectId: string) => { + return { + id: projectId, + tenantId: testTenantId, + } as unknown as IProject + }) + const aiFeaturesMockFn = mock.fn(async () => true) + const refTypeAwareGetConfigurationMockFn = + mock.fn(async (_projectId: string, _refId: string, _refType?: string): Promise => { + return mockConfiguration as unknown as RetrievedConfiguration + }) + + const client = await getTestMCPServerClient({ + getConfigurationMockFn: refTypeAwareGetConfigurationMockFn, + getProjectInfoMockFn, + isAiFeaturesEnabledForTenantMockFn: aiFeaturesMockFn, + }) + await client.request({ + method: 'tools/call', + params: { + name: 'configuration_get', + arguments: { + projectId: testProjectId, + refId, + }, + }, + }, CallToolResultSchema) + + t.assert.equal(refTypeAwareGetConfigurationMockFn.mock.callCount(), 1) + t.assert.deepEqual(refTypeAwareGetConfigurationMockFn.mock.calls[0].arguments, [ testProjectId, refId, 'revisions' ]) + }) + + it('should map refType "version" to "versions" when retrieving configuration from a version', async (t: it.TestContext) => { + const testTenantId = 'tenant123' + const testProjectId = 'project123' + const refId = 'v1.0.0' + + const getProjectInfoMockFn = mock.fn(async (projectId: string) => { + return { + id: projectId, + tenantId: testTenantId, + } as unknown as IProject + }) + const aiFeaturesMockFn = mock.fn(async () => true) + const refTypeAwareGetConfigurationMockFn = + mock.fn(async (_projectId: string, _refId: string, _refType?: string): Promise => { + return mockConfiguration as unknown as RetrievedConfiguration + }) + + const client = await getTestMCPServerClient({ + getConfigurationMockFn: refTypeAwareGetConfigurationMockFn, + getProjectInfoMockFn, + isAiFeaturesEnabledForTenantMockFn: aiFeaturesMockFn, + }) + await client.request({ + method: 'tools/call', + params: { + name: 'configuration_get', + arguments: { + projectId: testProjectId, + refId, + refType: 'version', + }, + }, + }, CallToolResultSchema) + + t.assert.equal(refTypeAwareGetConfigurationMockFn.mock.callCount(), 1) + t.assert.deepEqual(refTypeAwareGetConfigurationMockFn.mock.calls[0].arguments, [ testProjectId, refId, 'versions' ]) + }) + it('should return error message if API request fails', async (t: it.TestContext) => { const testTenantId = 'tenant123' const testProjectId = 'error-project' diff --git a/src/tools/configuration/index.ts b/src/tools/configuration/index.ts index b79acb3..ffb8d28 100644 --- a/src/tools/configuration/index.ts +++ b/src/tools/configuration/index.ts @@ -67,13 +67,17 @@ export function addConfigurationCapabilities (server: McpServer, client: IAPICli { projectId: z.string().describe(paramsDescriptions.PROJECT_ID), refId: z.string().describe(paramsDescriptions.REF_ID), + refType: z.enum([ 'revision', 'version' ]).optional().default('revision').describe(paramsDescriptions.REF_TYPE), }, - async ({ projectId, refId }): Promise => { + async ({ projectId, refId, refType }): Promise => { try { const project = await client.projectInfo(projectId) await assertAiFeaturesEnabledForProject(client, project) - const config = await client.getConfiguration(projectId, refId) + const backendRefType = refType === 'version' + ? 'versions' + : 'revisions' + const config = await client.getConfiguration(projectId, refId, backendRefType) return { structuredContent: config, content: [