@@ -4,7 +4,8 @@ import { mcpServerInstallations, mcpServers } from '../db/schema.sqlite';
44import type { AnyDatabase } from '../db' ;
55import type { FastifyBaseLogger } from 'fastify' ;
66import { nanoid } from 'nanoid' ;
7- import { encrypt , decrypt } from '../utils/encryption' ;
7+ import { McpArgsStorage } from '../utils/mcpArgsStorage' ;
8+ import { McpEnvStorage } from '../utils/mcpEnvStorage' ;
89
910// Types
1011export interface McpInstallation {
@@ -92,35 +93,53 @@ export class McpInstallationService {
9293 installationsFound : installations . length
9394 } , 'Retrieved MCP installations for team' ) ;
9495
95- return installations . map ( ( row : any ) => ( {
96- ...row . installation ,
97- team_args : row . installation . team_args
98- ? this . parseJsonField ( row . installation . team_args , [ ] )
99- : null ,
100- team_env : row . installation . team_env
101- ? this . decryptEnvironmentVariables ( row . installation . team_env )
102- : null ,
103- server : row . server ? {
104- id : row . server . id ,
105- name : row . server . name ,
106- description : row . server . description ,
107- language : row . server . language ,
108- runtime : row . server . runtime ,
109- status : row . server . status ,
110- author_name : row . server . author_name ,
111- homepage_url : row . server . homepage_url ,
112- github_url : row . server . github_url ,
113- tags : this . parseJsonField ( row . server . tags , [ ] ) ,
114- installation_methods : this . parseJsonField ( row . server . installation_methods , [ ] ) ,
115- template_args : this . parseJsonField ( row . server . template_args , [ ] ) ,
116- template_env : this . parseJsonField ( row . server . template_env , { } ) ,
117- team_args_schema : this . parseJsonField ( row . server . team_args_schema , [ ] ) ,
118- team_env_schema : this . parseJsonField ( row . server . team_env_schema , [ ] ) ,
119- user_args_schema : this . parseJsonField ( row . server . user_args_schema , [ ] ) ,
120- user_env_schema : this . parseJsonField ( row . server . user_env_schema , [ ] ) ,
121- transport_type : row . server . transport_type
122- } : undefined
123- } ) ) ;
96+ const processedInstallations = [ ] ;
97+
98+ for ( const row of installations ) {
99+ const teamEnv = row . installation . team_env
100+ ? await this . maskEnvironmentVariables (
101+ row . installation . team_env ,
102+ this . parseJsonField ( row . server ?. team_env_schema , [ ] )
103+ )
104+ : null ;
105+
106+ const teamArgs = row . installation . team_args
107+ ? await McpArgsStorage . retrieveTeamArgs (
108+ row . installation . team_args ,
109+ this . parseJsonField ( row . server ?. team_args_schema , [ ] ) ,
110+ { maskSecrets : true , decryptSecrets : false } ,
111+ this . logger
112+ )
113+ : null ;
114+
115+ processedInstallations . push ( {
116+ ...row . installation ,
117+ team_args : teamArgs ,
118+ team_env : teamEnv ,
119+ server : row . server ? {
120+ id : row . server . id ,
121+ name : row . server . name ,
122+ description : row . server . description ,
123+ language : row . server . language ,
124+ runtime : row . server . runtime ,
125+ status : row . server . status ,
126+ author_name : row . server . author_name ,
127+ homepage_url : row . server . homepage_url ,
128+ github_url : row . server . github_url ,
129+ tags : this . parseJsonField ( row . server . tags , [ ] ) ,
130+ installation_methods : this . parseJsonField ( row . server . installation_methods , [ ] ) ,
131+ template_args : this . parseJsonField ( row . server . template_args , [ ] ) ,
132+ template_env : this . parseJsonField ( row . server . template_env , { } ) ,
133+ team_args_schema : this . parseJsonField ( row . server . team_args_schema , [ ] ) ,
134+ team_env_schema : this . parseJsonField ( row . server . team_env_schema , [ ] ) ,
135+ user_args_schema : this . parseJsonField ( row . server . user_args_schema , [ ] ) ,
136+ user_env_schema : this . parseJsonField ( row . server . user_env_schema , [ ] ) ,
137+ transport_type : row . server . transport_type
138+ } : undefined
139+ } ) ;
140+ }
141+
142+ return processedInstallations ;
124143 }
125144
126145 async getInstallationById ( installationId : string , teamId : string ) : Promise < McpInstallation | null > {
@@ -156,13 +175,23 @@ export class McpInstallationService {
156175
157176 const { installation, server } = result [ 0 ] ;
158177
178+ const teamArgs = installation . team_args
179+ ? await McpArgsStorage . retrieveTeamArgs (
180+ installation . team_args ,
181+ this . parseJsonField ( server ?. team_args_schema , [ ] ) ,
182+ { maskSecrets : true , decryptSecrets : false } ,
183+ this . logger
184+ )
185+ : null ;
186+
159187 return {
160188 ...installation ,
161- team_args : installation . team_args
162- ? this . parseJsonField ( installation . team_args , [ ] )
163- : null ,
189+ team_args : teamArgs ,
164190 team_env : installation . team_env
165- ? this . decryptEnvironmentVariables ( installation . team_env )
191+ ? await this . maskEnvironmentVariables (
192+ installation . team_env ,
193+ this . parseJsonField ( server ?. team_env_schema , [ ] )
194+ )
166195 : null ,
167196 server : server ? {
168197 id : server . id ,
@@ -246,10 +275,16 @@ export class McpInstallationService {
246275 installation_name : data . installation_name ,
247276 installation_type : data . installation_type || 'local' ,
248277 team_args : data . team_args
249- ? JSON . stringify ( data . team_args )
278+ ? await this . encryptArguments (
279+ data . team_args ,
280+ this . parseJsonField ( server [ 0 ] . team_args_schema , [ ] )
281+ )
250282 : null ,
251283 team_env : data . team_env
252- ? this . encryptEnvironmentVariables ( data . team_env )
284+ ? await this . encryptEnvironmentVariables (
285+ data . team_env ,
286+ this . parseJsonField ( server [ 0 ] . team_env_schema , [ ] )
287+ )
253288 : null ,
254289 created_at : now ,
255290 updated_at : now ,
@@ -328,13 +363,19 @@ export class McpInstallationService {
328363 }
329364
330365 updateData . team_env = data . team_env
331- ? this . encryptEnvironmentVariables ( data . team_env )
366+ ? await this . encryptEnvironmentVariables (
367+ data . team_env ,
368+ existing . server ?. team_env_schema || [ ]
369+ )
332370 : null ;
333371 }
334372
335373 if ( data . team_args !== undefined ) {
336374 updateData . team_args = data . team_args
337- ? JSON . stringify ( data . team_args )
375+ ? await this . encryptArguments (
376+ data . team_args ,
377+ existing . server ?. team_args_schema || [ ]
378+ )
338379 : null ;
339380 }
340381
@@ -391,30 +432,55 @@ export class McpInstallationService {
391432 clientType
392433 } , 'Generating client configuration' ) ;
393434
394- const installation = await this . getInstallationById ( installationId , teamId ) ;
395- if ( ! installation || ! installation . server ) {
435+ // Get installation data directly from database to access encrypted values
436+ const result = await this . db
437+ . select ( {
438+ installation : mcpServerInstallations ,
439+ server : mcpServers
440+ } )
441+ . from ( mcpServerInstallations )
442+ . leftJoin ( mcpServers , eq ( mcpServerInstallations . server_id , mcpServers . id ) )
443+ . where (
444+ and (
445+ eq ( mcpServerInstallations . id , installationId ) ,
446+ eq ( mcpServerInstallations . team_id , teamId )
447+ )
448+ )
449+ . limit ( 1 ) ;
450+
451+ if ( result . length === 0 || ! result [ 0 ] . server ) {
396452 throw new Error ( 'Installation not found' ) ;
397453 }
398454
455+ const { installation, server } = result [ 0 ] ;
456+
399457 // Update last_used_at
400458 await this . db
401459 . update ( mcpServerInstallations )
402460 . set ( { last_used_at : new Date ( ) } )
403461 . where ( eq ( mcpServerInstallations . id , installationId ) ) ;
404462
405463 // Get Claude Desktop config from server's installation_methods
406- const claudeDesktopMethod = installation . server . installation_methods . find (
464+ const claudeDesktopMethod = this . parseJsonField ( server . installation_methods , [ ] ) . find (
407465 ( method : any ) => method . client === 'claude-desktop'
408466 ) ;
409467
410468 if ( ! claudeDesktopMethod ) {
411469 throw new Error ( 'Server does not support Claude Desktop installation' ) ;
412470 }
413471
414- // Merge template with team environment variables
472+ // For gateway config generation, we need to decrypt secrets (authorized use case)
473+ const decryptedTeamEnv = installation . team_env
474+ ? await this . decryptEnvironmentVariables (
475+ installation . team_env ,
476+ this . parseJsonField ( server . team_env_schema , [ ] )
477+ )
478+ : null ;
479+
480+ // Merge template with team environment variables (with decrypted secrets)
415481 const mergedEnv = { ...claudeDesktopMethod . env } ;
416- if ( installation . team_env ) {
417- Object . assign ( mergedEnv , installation . team_env ) ;
482+ if ( decryptedTeamEnv ) {
483+ Object . assign ( mergedEnv , decryptedTeamEnv ) ;
418484 }
419485
420486 const baseConfig = {
@@ -493,21 +559,42 @@ export class McpInstallationService {
493559 }
494560 }
495561
496- private encryptEnvironmentVariables ( vars : Record < string , string > ) : string {
497- return encrypt ( JSON . stringify ( vars ) ) ;
562+ private async encryptArguments (
563+ args : string [ ] ,
564+ schema ?: any [ ]
565+ ) : Promise < string > {
566+ return await McpArgsStorage . storeTeamArgs ( args , schema || [ ] , this . logger ) ;
498567 }
499568
500- private decryptEnvironmentVariables ( encryptedVars : string ) : Record < string , string > {
501- try {
502- const decrypted = decrypt ( encryptedVars ) ;
503- return JSON . parse ( decrypted ) ;
504- } catch ( error ) {
505- this . logger . error ( {
506- operation : 'decrypt_environment_variables' ,
507- error
508- } , 'Failed to decrypt environment variables' ) ;
509- return { } ;
510- }
569+ private async encryptEnvironmentVariables (
570+ vars : Record < string , string > ,
571+ schema ?: any [ ]
572+ ) : Promise < string > {
573+ return await McpEnvStorage . storeTeamEnv ( vars , schema || [ ] , this . logger ) ;
574+ }
575+
576+ private async decryptEnvironmentVariables (
577+ encryptedVars : string ,
578+ schema ?: any [ ]
579+ ) : Promise < Record < string , string > > {
580+ return await McpEnvStorage . retrieveTeamEnv (
581+ encryptedVars ,
582+ schema || [ ] ,
583+ { maskSecrets : false , decryptSecrets : true } ,
584+ this . logger
585+ ) ;
586+ }
587+
588+ private async maskEnvironmentVariables (
589+ encryptedVars : string ,
590+ schema ?: any [ ]
591+ ) : Promise < Record < string , string > > {
592+ return await McpEnvStorage . retrieveTeamEnv (
593+ encryptedVars ,
594+ schema || [ ] ,
595+ { maskSecrets : true , decryptSecrets : false } ,
596+ this . logger
597+ ) ;
511598 }
512599
513600 private parseJsonField ( fieldValue : any , defaultValue : any ) : any {
0 commit comments