Path: blob/main/extensions/copilot/src/extension/mcp/test/vscode-node/nuget.mapping.spec.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45// Copied from https://github.com/microsoft/vscode/blob/d49049e5263a64cba8c9ca33f89bb0ad198f3391/src/vs/platform/mcp/test/common/mcpManagementService.test.ts6// Refactored to use vitest78import { beforeEach, describe, expect, it } from 'vitest';9import { IGalleryMcpServerConfiguration, IMcpServerVariable, McpMappingUtility, McpServerType, McpServerVariableType, RegistryType, TransportType } from '../../vscode-node/nuget';1011describe('McpManagementService - getMcpServerConfigurationFromManifest', () => {12let service: McpMappingUtility;1314beforeEach(() => {15service = new McpMappingUtility();16});1718describe('NPM Package Tests', () => {19it('basic NPM package configuration', () => {20const manifest: IGalleryMcpServerConfiguration = {21packages: [{22registryType: RegistryType.NODE,23registryBaseUrl: 'https://registry.npmjs.org',24identifier: '@modelcontextprotocol/server-brave-search',25version: '1.0.2',26environmentVariables: [{27name: 'BRAVE_API_KEY',28value: 'test-key'29}]30}]31};3233const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);3435expect(result.mcpServerConfiguration.config.type).toBe(McpServerType.LOCAL);36if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {37expect(result.mcpServerConfiguration.config.command).toBe('npx');38expect(result.mcpServerConfiguration.config.args).toEqual(['@modelcontextprotocol/[email protected]']);39expect(result.mcpServerConfiguration.config.env).toEqual({ 'BRAVE_API_KEY': 'test-key' });40}41expect(result.mcpServerConfiguration.inputs).toBe(undefined);42});4344it('NPM package without version', () => {45const manifest: IGalleryMcpServerConfiguration = {46packages: [{47registryType: RegistryType.NODE,48registryBaseUrl: 'https://registry.npmjs.org',49identifier: '@modelcontextprotocol/everything',50version: ''51}]52};5354const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);5556expect(result.mcpServerConfiguration.config.type).toBe(McpServerType.LOCAL);57if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {58expect(result.mcpServerConfiguration.config.command).toBe('npx');59expect(result.mcpServerConfiguration.config.args).toEqual(['@modelcontextprotocol/everything']);60}61});6263it('NPM package with environment variables containing variables', () => {64const manifest: IGalleryMcpServerConfiguration = {65packages: [{66registryType: RegistryType.NODE,67identifier: 'test-server',68version: '1.0.0',69environmentVariables: [{70name: 'API_KEY',71value: 'key-{api_token}',72variables: {73api_token: {74description: 'Your API token',75isSecret: true,76isRequired: true77}78}79}]80}]81};8283const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);8485expect(result.mcpServerConfiguration.config.type).toBe(McpServerType.LOCAL);86if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {87expect(result.mcpServerConfiguration.config.env).toEqual({ 'API_KEY': 'key-${input:api_token}' });88}89expect(result.mcpServerConfiguration.inputs?.length).toBe(1);90expect(result.mcpServerConfiguration.inputs?.[0].id).toBe('api_token');91expect(result.mcpServerConfiguration.inputs?.[0].type).toBe(McpServerVariableType.PROMPT);92expect(result.mcpServerConfiguration.inputs?.[0].description).toBe('Your API token');93expect(result.mcpServerConfiguration.inputs?.[0].password).toBe(true);94});9596it('environment variable with empty value should create input variable (GitHub issue #266106)', () => {97const manifest: IGalleryMcpServerConfiguration = {98packages: [{99registryType: RegistryType.NODE,100identifier: '@modelcontextprotocol/server-brave-search',101version: '1.0.2',102environmentVariables: [{103name: 'BRAVE_API_KEY',104value: '', // Empty value should create input variable105description: 'Brave Search API Key',106isRequired: true,107isSecret: true108}]109}]110};111112const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);113114// BUG: Currently this creates env with empty string instead of input variable115// Should create an input variable since no meaningful value is provided116expect(result.mcpServerConfiguration.inputs?.length).toBe(1);117expect(result.mcpServerConfiguration.inputs?.[0].id).toBe('BRAVE_API_KEY');118expect(result.mcpServerConfiguration.inputs?.[0].description).toBe('Brave Search API Key');119expect(result.mcpServerConfiguration.inputs?.[0].password).toBe(true);120expect(result.mcpServerConfiguration.inputs?.[0].type).toBe(McpServerVariableType.PROMPT);121122// Environment should use input variable interpolation123if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {124expect(result.mcpServerConfiguration.config.env).toEqual({ 'BRAVE_API_KEY': '${input:BRAVE_API_KEY}' });125}126});127128it('environment variable with choices but empty value should create pick input (GitHub issue #266106)', () => {129const manifest: IGalleryMcpServerConfiguration = {130packages: [{131registryType: RegistryType.NODE,132identifier: 'test-server',133version: '1.0.0',134environmentVariables: [{135name: 'SSL_MODE',136value: '', // Empty value should create input variable137description: 'SSL connection mode',138default: 'prefer',139choices: ['disable', 'prefer', 'require']140}]141}]142};143144const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);145146// BUG: Currently this creates env with empty string instead of input variable147// Should create a pick input variable since choices are provided148expect(result.mcpServerConfiguration.inputs?.length).toBe(1);149expect(result.mcpServerConfiguration.inputs?.[0].id).toBe('SSL_MODE');150expect(result.mcpServerConfiguration.inputs?.[0].description).toBe('SSL connection mode');151expect(result.mcpServerConfiguration.inputs?.[0].default).toBe('prefer');152expect(result.mcpServerConfiguration.inputs?.[0].type).toBe(McpServerVariableType.PICK);153expect(result.mcpServerConfiguration.inputs?.[0].options).toEqual(['disable', 'prefer', 'require']);154155// Environment should use input variable interpolation156if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {157expect(result.mcpServerConfiguration.config.env).toEqual({ 'SSL_MODE': '${input:SSL_MODE}' });158}159});160161it('NPM package with package arguments', () => {162const manifest: IGalleryMcpServerConfiguration = {163packages: [{164registryType: RegistryType.NODE,165identifier: 'snyk',166version: '1.1298.0',167packageArguments: [168{ type: 'positional', value: 'mcp', valueHint: 'command', isRepeated: false },169{170type: 'named',171name: '-t',172value: 'stdio',173isRepeated: false174}175]176}]177};178179const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);180181expect(result.mcpServerConfiguration.config.type).toBe(McpServerType.LOCAL);182if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {183expect(result.mcpServerConfiguration.config.args).toEqual(['[email protected]', 'mcp', '-t', 'stdio']);184}185});186});187188describe('Python Package Tests', () => {189it('basic Python package configuration', () => {190const manifest: IGalleryMcpServerConfiguration = {191packages: [{192registryType: RegistryType.PYTHON,193registryBaseUrl: 'https://pypi.org',194identifier: 'weather-mcp-server',195version: '0.5.0',196environmentVariables: [{197name: 'WEATHER_API_KEY',198value: 'test-key'199}, {200name: 'WEATHER_UNITS',201value: 'celsius'202}]203}]204};205206const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.PYTHON);207208expect(result.mcpServerConfiguration.config.type).toBe(McpServerType.LOCAL);209if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {210expect(result.mcpServerConfiguration.config.command).toBe('uvx');211expect(result.mcpServerConfiguration.config.args).toEqual(['weather-mcp-server==0.5.0']);212expect(result.mcpServerConfiguration.config.env).toEqual({213'WEATHER_API_KEY': 'test-key',214'WEATHER_UNITS': 'celsius'215});216}217});218219it('Python package without version', () => {220const manifest: IGalleryMcpServerConfiguration = {221packages: [{222registryType: RegistryType.PYTHON,223identifier: 'weather-mcp-server',224version: ''225}]226};227228const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.PYTHON);229230if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {231expect(result.mcpServerConfiguration.config.args).toEqual(['weather-mcp-server']);232}233});234});235236describe('Docker Package Tests', () => {237it('basic Docker package configuration', () => {238const manifest: IGalleryMcpServerConfiguration = {239packages: [{240registryType: RegistryType.DOCKER,241registryBaseUrl: 'https://docker.io',242identifier: 'mcp/filesystem',243version: '1.0.2',244runtimeArguments: [{245type: 'named',246name: '--mount',247value: 'type=bind,src=/host/path,dst=/container/path',248isRepeated: false249}],250environmentVariables: [{251name: 'LOG_LEVEL',252value: 'info'253}],254packageArguments: [{255type: 'positional',256value: '/project',257valueHint: 'directory',258isRepeated: false259}]260}]261};262263const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER);264265expect(result.mcpServerConfiguration.config.type).toBe(McpServerType.LOCAL);266if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {267expect(result.mcpServerConfiguration.config.command).toBe('docker');268expect(result.mcpServerConfiguration.config.args).toEqual([269'run', '-i', '--rm',270'--mount', 'type=bind,src=/host/path,dst=/container/path',271'-e', 'LOG_LEVEL',272'mcp/filesystem:1.0.2',273'/project'274]);275expect(result.mcpServerConfiguration.config.env).toEqual({ 'LOG_LEVEL': 'info' });276}277});278279it('Docker package with variables in runtime arguments', () => {280const manifest: IGalleryMcpServerConfiguration = {281packages: [{282registryType: RegistryType.DOCKER,283identifier: 'example/database-manager-mcp',284version: '3.1.0',285runtimeArguments: [{286type: 'named',287name: '-e',288value: 'DB_TYPE={db_type}',289isRepeated: false,290variables: {291db_type: {292description: 'Type of database',293choices: ['postgres', 'mysql', 'mongodb', 'redis'],294isRequired: true295}296}297}]298}]299};300301const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER);302303expect(result.mcpServerConfiguration.config.type).toBe(McpServerType.LOCAL);304if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {305expect(result.mcpServerConfiguration.config.args).toEqual([306'run', '-i', '--rm',307'-e', 'DB_TYPE=${input:db_type}',308'example/database-manager-mcp:3.1.0'309]);310}311expect(result.mcpServerConfiguration.inputs?.length).toBe(1);312expect(result.mcpServerConfiguration.inputs?.[0].id).toBe('db_type');313expect(result.mcpServerConfiguration.inputs?.[0].type).toBe(McpServerVariableType.PICK);314expect(result.mcpServerConfiguration.inputs?.[0].options).toEqual(['postgres', 'mysql', 'mongodb', 'redis']);315});316317it('Docker package arguments without values should create input variables (GitHub issue #266106)', () => {318const manifest: IGalleryMcpServerConfiguration = {319packages: [{320registryType: RegistryType.DOCKER,321identifier: 'example/database-manager-mcp',322version: '3.1.0',323packageArguments: [{324type: 'named',325name: '--host',326description: 'Database host',327default: 'localhost',328isRequired: true,329isRepeated: false330// Note: No 'value' field - should create input variable331}, {332type: 'positional',333valueHint: 'database_name',334description: 'Name of the database to connect to',335isRequired: true,336isRepeated: false337// Note: No 'value' field - should create input variable338}]339}]340};341342const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER);343344// BUG: Currently named args without value are ignored, positional uses value_hint as literal345// Should create input variables for both arguments346expect(result.mcpServerConfiguration.inputs?.length).toBe(2);347348const hostInput = result.mcpServerConfiguration.inputs?.find((i: IMcpServerVariable) => i.id === 'host');349expect(hostInput?.description).toBe('Database host');350expect(hostInput?.default).toBe('localhost');351expect(hostInput?.type).toBe(McpServerVariableType.PROMPT);352353const dbNameInput = result.mcpServerConfiguration.inputs?.find((i: IMcpServerVariable) => i.id === 'database_name');354expect(dbNameInput?.description).toBe('Name of the database to connect to');355expect(dbNameInput?.type).toBe(McpServerVariableType.PROMPT);356357// Args should use input variable interpolation358if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {359expect(result.mcpServerConfiguration.config.args).toEqual([360'run', '-i', '--rm',361'example/database-manager-mcp:3.1.0',362'--host', '${input:host}',363'${input:database_name}'364]);365}366});367368it('Docker Hub backward compatibility', () => {369const manifest: IGalleryMcpServerConfiguration = {370packages: [{371registryType: RegistryType.DOCKER,372identifier: 'example/test-image',373version: '1.0.0'374}]375};376377const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER);378379expect(result.mcpServerConfiguration.config.type).toBe(McpServerType.LOCAL);380if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {381expect(result.mcpServerConfiguration.config.command).toBe('docker');382expect(result.mcpServerConfiguration.config.args).toEqual([383'run', '-i', '--rm',384'example/test-image:1.0.0'385]);386}387});388});389390describe('NuGet Package Tests', () => {391it('basic NuGet package configuration', () => {392const manifest: IGalleryMcpServerConfiguration = {393packages: [{394registryType: RegistryType.NUGET,395registryBaseUrl: 'https://api.nuget.org',396identifier: 'Knapcode.SampleMcpServer',397version: '0.5.0',398environmentVariables: [{399name: 'WEATHER_CHOICES',400value: 'sunny,cloudy,rainy'401}]402}]403};404405const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NUGET);406407expect(result.mcpServerConfiguration.config.type).toBe(McpServerType.LOCAL);408if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {409expect(result.mcpServerConfiguration.config.command).toBe('dnx');410expect(result.mcpServerConfiguration.config.args).toEqual(['[email protected]', '--yes']);411expect(result.mcpServerConfiguration.config.env).toEqual({ 'WEATHER_CHOICES': 'sunny,cloudy,rainy' });412}413});414415it('NuGet package with package arguments', () => {416const manifest: IGalleryMcpServerConfiguration = {417packages: [{418registryType: RegistryType.NUGET,419identifier: 'Knapcode.SampleMcpServer',420version: '0.4.0-beta',421packageArguments: [{422type: 'positional',423value: 'mcp',424valueHint: 'command',425isRepeated: false426}, {427type: 'positional',428value: 'start',429valueHint: 'action',430isRepeated: false431}]432}]433};434435const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NUGET);436437if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {438expect(result.mcpServerConfiguration.config.args).toEqual([439'[email protected]',440'--yes',441'--',442'mcp',443'start'444]);445}446});447});448449describe('Remote Server Tests', () => {450it('SSE remote server configuration', () => {451const manifest: IGalleryMcpServerConfiguration = {452remotes: [{453type: TransportType.SSE,454url: 'http://mcp-fs.anonymous.modelcontextprotocol.io/sse'455}]456};457458const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.REMOTE);459460expect(result.mcpServerConfiguration.config.type).toBe(McpServerType.REMOTE);461if (result.mcpServerConfiguration.config.type === McpServerType.REMOTE) {462expect(result.mcpServerConfiguration.config.url).toBe('http://mcp-fs.anonymous.modelcontextprotocol.io/sse');463expect(result.mcpServerConfiguration.config.headers).toBe(undefined);464}465});466467it('SSE remote server with headers and variables', () => {468const manifest: IGalleryMcpServerConfiguration = {469remotes: [{470type: TransportType.SSE,471url: 'https://mcp.anonymous.modelcontextprotocol.io/sse',472headers: [{473name: 'X-API-Key',474value: '{api_key}',475variables: {476api_key: {477description: 'API key for authentication',478isRequired: true,479isSecret: true480}481}482}, {483name: 'X-Region',484value: 'us-east-1'485}]486}]487};488489const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.REMOTE);490491expect(result.mcpServerConfiguration.config.type).toBe(McpServerType.REMOTE);492if (result.mcpServerConfiguration.config.type === McpServerType.REMOTE) {493expect(result.mcpServerConfiguration.config.headers).toEqual({494'X-API-Key': '${input:api_key}',495'X-Region': 'us-east-1'496});497}498expect(result.mcpServerConfiguration.inputs?.length).toBe(1);499expect(result.mcpServerConfiguration.inputs?.[0].id).toBe('api_key');500expect(result.mcpServerConfiguration.inputs?.[0].password).toBe(true);501});502503it('streamable HTTP remote server', () => {504const manifest: IGalleryMcpServerConfiguration = {505remotes: [{506type: TransportType.STREAMABLE_HTTP,507url: 'https://mcp.anonymous.modelcontextprotocol.io/http'508}]509};510511const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.REMOTE);512513expect(result.mcpServerConfiguration.config.type).toBe(McpServerType.REMOTE);514if (result.mcpServerConfiguration.config.type === McpServerType.REMOTE) {515expect(result.mcpServerConfiguration.config.url).toBe('https://mcp.anonymous.modelcontextprotocol.io/http');516}517});518519it('remote headers without values should create input variables', () => {520const manifest: IGalleryMcpServerConfiguration = {521remotes: [{522type: TransportType.SSE,523url: 'https://api.example.com/mcp',524headers: [{525name: 'Authorization',526description: 'API token for authentication',527isSecret: true,528isRequired: true529// Note: No 'value' field - should create input variable530}, {531name: 'X-Custom-Header',532description: 'Custom header value',533default: 'default-value',534choices: ['option1', 'option2', 'option3']535// Note: No 'value' field - should create input variable with choices536}]537}]538};539540const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.REMOTE);541542expect(result.mcpServerConfiguration.config.type).toBe(McpServerType.REMOTE);543if (result.mcpServerConfiguration.config.type === McpServerType.REMOTE) {544expect(result.mcpServerConfiguration.config.url).toBe('https://api.example.com/mcp');545expect(result.mcpServerConfiguration.config.headers).toEqual({546'Authorization': '${input:Authorization}',547'X-Custom-Header': '${input:X-Custom-Header}'548});549}550551// Should create input variables for headers without values552expect(result.mcpServerConfiguration.inputs?.length).toBe(2);553554const authInput = result.mcpServerConfiguration.inputs?.find((i: IMcpServerVariable) => i.id === 'Authorization');555expect(authInput?.description).toBe('API token for authentication');556expect(authInput?.password).toBe(true);557expect(authInput?.type).toBe(McpServerVariableType.PROMPT);558559const customInput = result.mcpServerConfiguration.inputs?.find((i: IMcpServerVariable) => i.id === 'X-Custom-Header');560expect(customInput?.description).toBe('Custom header value');561expect(customInput?.default).toBe('default-value');562expect(customInput?.type).toBe(McpServerVariableType.PICK);563expect(customInput?.options).toEqual(['option1', 'option2', 'option3']);564});565});566567describe('Variable Interpolation Tests', () => {568it('multiple variables in single value', () => {569const manifest: IGalleryMcpServerConfiguration = {570packages: [{571registryType: RegistryType.NODE,572identifier: 'test-server',573version: '1.0.0',574environmentVariables: [{575name: 'CONNECTION_STRING',576value: 'server={host};port={port};database={db_name}',577variables: {578host: {579description: 'Database host',580default: 'localhost'581},582port: {583description: 'Database port',584format: 'number',585default: '5432'586},587db_name: {588description: 'Database name',589isRequired: true590}591}592}]593}]594};595596const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);597598if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {599expect(result.mcpServerConfiguration.config.env).toEqual({600'CONNECTION_STRING': 'server=${input:host};port=${input:port};database=${input:db_name}'601});602}603expect(result.mcpServerConfiguration.inputs?.length).toBe(3);604605const hostInput = result.mcpServerConfiguration.inputs?.find((i: IMcpServerVariable) => i.id === 'host');606expect(hostInput?.default).toBe('localhost');607expect(hostInput?.type).toBe(McpServerVariableType.PROMPT);608609const portInput = result.mcpServerConfiguration.inputs?.find((i: IMcpServerVariable) => i.id === 'port');610expect(portInput?.default).toBe('5432');611612const dbNameInput = result.mcpServerConfiguration.inputs?.find((i: IMcpServerVariable) => i.id === 'db_name');613expect(dbNameInput?.description).toBe('Database name');614});615616it('variable with choices creates pick input', () => {617const manifest: IGalleryMcpServerConfiguration = {618packages: [{619registryType: RegistryType.NODE,620identifier: 'test-server',621version: '1.0.0',622runtimeArguments: [{623type: 'named',624name: '--log-level',625value: '{level}',626isRepeated: false,627variables: {628level: {629description: 'Log level',630choices: ['debug', 'info', 'warn', 'error'],631default: 'info'632}633}634}]635}]636};637638const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);639640expect(result.mcpServerConfiguration.inputs?.length).toBe(1);641expect(result.mcpServerConfiguration.inputs?.[0].type).toBe(McpServerVariableType.PICK);642expect(result.mcpServerConfiguration.inputs?.[0].options).toEqual(['debug', 'info', 'warn', 'error']);643expect(result.mcpServerConfiguration.inputs?.[0].default).toBe('info');644});645646it('variables in package arguments', () => {647const manifest: IGalleryMcpServerConfiguration = {648packages: [{649registryType: RegistryType.DOCKER,650identifier: 'test-image',651version: '1.0.0',652packageArguments: [{653type: 'named',654name: '--host',655value: '{db_host}',656isRepeated: false,657variables: {658db_host: {659description: 'Database host',660default: 'localhost'661}662}663}, {664type: 'positional',665value: '{database_name}',666valueHint: 'database_name',667isRepeated: false,668variables: {669database_name: {670description: 'Name of the database to connect to',671isRequired: true672}673}674}]675}]676};677678const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER);679680if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {681expect(result.mcpServerConfiguration.config.args).toEqual([682'run', '-i', '--rm',683'test-image:1.0.0',684'--host', '${input:db_host}',685'${input:database_name}'686]);687}688expect(result.mcpServerConfiguration.inputs?.length).toBe(2);689});690691it('positional arguments with value_hint should create input variables (GitHub issue #266106)', () => {692const manifest: IGalleryMcpServerConfiguration = {693packages: [{694registryType: RegistryType.NODE,695identifier: '@example/math-tool',696version: '2.0.1',697packageArguments: [{698type: 'positional',699valueHint: 'calculation_type',700description: 'Type of calculation to enable',701isRequired: true,702isRepeated: false703// Note: No 'value' field, only value_hint - should create input variable704}]705}]706};707708const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);709710// BUG: Currently value_hint is used as literal value instead of creating input variable711// Should create input variable instead712expect(result.mcpServerConfiguration.inputs?.length).toBe(1);713expect(result.mcpServerConfiguration.inputs?.[0].id).toBe('calculation_type');714expect(result.mcpServerConfiguration.inputs?.[0].description).toBe('Type of calculation to enable');715expect(result.mcpServerConfiguration.inputs?.[0].type).toBe(McpServerVariableType.PROMPT);716717// Args should use input variable interpolation718if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {719expect(result.mcpServerConfiguration.config.args).toEqual([720'@example/[email protected]',721'${input:calculation_type}'722]);723}724});725});726727describe('Edge Cases and Error Handling', () => {728it('empty manifest should throw error', () => {729const manifest: IGalleryMcpServerConfiguration = {};730731expect(() => {732service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);733}).toThrow();734});735736it('manifest with no matching package type should use first package', () => {737const manifest: IGalleryMcpServerConfiguration = {738packages: [{739registryType: RegistryType.PYTHON,740identifier: 'python-server',741version: '1.0.0'742}]743};744745const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);746747expect(result.mcpServerConfiguration.config.type).toBe(McpServerType.LOCAL);748if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {749expect(result.mcpServerConfiguration.config.command).toBe('uvx'); // Python command since that's the package type750expect(result.mcpServerConfiguration.config.args).toEqual(['python-server==1.0.0']);751}752});753754it('manifest with matching package type should use that package', () => {755const manifest: IGalleryMcpServerConfiguration = {756packages: [{757registryType: RegistryType.PYTHON,758identifier: 'python-server',759version: '1.0.0'760}, {761registryType: RegistryType.NODE,762identifier: 'node-server',763version: '2.0.0'764}]765};766767const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);768769if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {770expect(result.mcpServerConfiguration.config.command).toBe('npx');771expect(result.mcpServerConfiguration.config.args).toEqual(['[email protected]']);772}773});774775it('undefined environment variables should be omitted', () => {776const manifest: IGalleryMcpServerConfiguration = {777packages: [{778registryType: RegistryType.NODE,779identifier: 'test-server',780version: '1.0.0'781}]782};783784const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);785786if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {787expect(result.mcpServerConfiguration.config.env).toBe(undefined);788}789});790791it('named argument without value should only add name', () => {792const manifest: IGalleryMcpServerConfiguration = {793packages: [{794registryType: RegistryType.NODE,795identifier: 'test-server',796version: '1.0.0',797runtimeArguments: [{798type: 'named',799name: '--verbose',800isRepeated: false801}]802}]803};804805const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);806807if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {808expect(result.mcpServerConfiguration.config.args).toEqual(['--verbose', '[email protected]']);809}810});811812it('positional argument with undefined value should use value_hint', () => {813const manifest: IGalleryMcpServerConfiguration = {814packages: [{815registryType: RegistryType.NODE,816identifier: 'test-server',817version: '1.0.0',818packageArguments: [{819type: 'positional',820valueHint: 'target_directory',821isRepeated: false822}]823}]824};825826const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);827828if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {829expect(result.mcpServerConfiguration.config.args).toEqual(['[email protected]', 'target_directory']);830}831});832833it('named argument with no name should generate notice', () => {834const manifest = {835packages: [{836registryType: RegistryType.NODE,837identifier: 'test-server',838version: '1.0.0',839runtimeArguments: [{840type: 'named',841value: 'some-value',842isRepeated: false843}]844}]845};846847const result = service.getMcpServerConfigurationFromManifest(manifest as IGalleryMcpServerConfiguration, RegistryType.NODE);848849// Should generate a notice about the missing name850expect(result.notices.length).toBe(1);851expect(result.notices[0].includes('Named argument is missing a name')).toBeTruthy();852expect(result.notices[0].includes('some-value')).toBeTruthy(); // Should include the argument details in JSON format853854if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {855expect(result.mcpServerConfiguration.config.args).toEqual(['[email protected]']);856}857});858859it('named argument with empty name should generate notice', () => {860const manifest: IGalleryMcpServerConfiguration = {861packages: [{862registryType: RegistryType.NODE,863identifier: 'test-server',864version: '1.0.0',865runtimeArguments: [{866type: 'named',867name: '',868value: 'some-value',869isRepeated: false870}]871}]872};873874const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);875876// Should generate a notice about the missing name877expect(result.notices.length).toBe(1);878expect(result.notices[0].includes('Named argument is missing a name')).toBeTruthy();879expect(result.notices[0].includes('some-value')).toBeTruthy(); // Should include the argument details in JSON format880881if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {882expect(result.mcpServerConfiguration.config.args).toEqual(['[email protected]']);883}884});885});886887describe('Variable Processing Order', () => {888it('should use explicit variables instead of auto-generating when both are possible', () => {889const manifest: IGalleryMcpServerConfiguration = {890packages: [{891registryType: RegistryType.NODE,892identifier: 'test-server',893version: '1.0.0',894environmentVariables: [{895name: 'API_KEY',896value: 'Bearer {api_key}',897description: 'Should not be used', // This should be ignored since we have explicit variables898variables: {899api_key: {900description: 'Your API key',901isSecret: true902}903}904}]905}]906};907908const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE);909910expect(result.mcpServerConfiguration.inputs?.length).toBe(1);911expect(result.mcpServerConfiguration.inputs?.[0].id).toBe('api_key');912expect(result.mcpServerConfiguration.inputs?.[0].description).toBe('Your API key');913expect(result.mcpServerConfiguration.inputs?.[0].password).toBe(true);914915if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) {916expect(result.mcpServerConfiguration.config.env?.['API_KEY']).toBe('Bearer ${input:api_key}');917}918});919});920});921922923