Skip to content

Commit 463e580

Browse files
author
Lasim
committed
feat(all): Add translations for 'secret' data type in mcp-catalog
1 parent c9abb46 commit 463e580

File tree

17 files changed

+653
-111
lines changed

17 files changed

+653
-111
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

services/backend/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## <small>0.31.3 (2025-08-25)</small>
4+
5+
* chore(backend): release v0.31.2 ([77608e7](https://github.com/deploystackio/deploystack/commit/77608e7))
6+
* feat(all): implement storage-first architecture in BasicInfoStepEdit component ([c9abb46](https://github.com/deploystackio/deploystack/commit/c9abb46))
7+
8+
## <small>0.31.2 (2025-08-25)</small>
9+
10+
* feat(all): implement storage-first architecture in BasicInfoStepEdit component ([c9abb46](https://github.com/deploystackio/deploystack/commit/c9abb46))
11+
312
## <small>0.31.1 (2025-08-24)</small>
413

514
* chore(backend): release v0.31.0 ([5f7a5da](https://github.com/deploystackio/deploystack/commit/5f7a5da))

services/backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@deploystack/backend",
3-
"version": "0.31.1",
3+
"version": "0.31.3",
44
"scripts": {
55
"dev": "nodemon",
66
"build": "tsc && webpack --mode=production",

services/backend/src/config/version.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ export interface VersionInfo {
99

1010
// This will be replaced by the build script
1111
let versionData: VersionInfo = {
12-
version: '0.31.0',
13-
buildTime: '2025-08-24T21:26:47.922Z',
12+
version: '0.31.2',
13+
buildTime: '2025-08-25T19:31:55.874Z',
1414
source: 'release'
1515
};
1616

services/backend/src/routes/mcp/servers/create-global.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const templateEnvSchema = z.object({
2525

2626
const teamArgSchema = z.object({
2727
name: z.string(),
28-
type: z.string(),
28+
type: z.enum(['string', 'number', 'boolean', 'secret']),
2929
description: z.string(),
3030
required: z.boolean(),
3131
locked: z.boolean(),
@@ -36,7 +36,7 @@ const teamArgSchema = z.object({
3636

3737
const teamEnvSchema = z.object({
3838
name: z.string(),
39-
type: z.string(),
39+
type: z.enum(['string', 'number', 'boolean', 'secret']),
4040
description: z.string(),
4141
required: z.boolean(),
4242
locked: z.boolean(),
@@ -46,7 +46,7 @@ const teamEnvSchema = z.object({
4646

4747
const userArgSchema = z.object({
4848
name: z.string(),
49-
type: z.string(),
49+
type: z.enum(['string', 'number', 'boolean', 'secret']),
5050
description: z.string(),
5151
required: z.boolean(),
5252
locked: z.boolean(),
@@ -56,7 +56,7 @@ const userArgSchema = z.object({
5656

5757
const userEnvSchema = z.object({
5858
name: z.string(),
59-
type: z.string(),
59+
type: z.enum(['string', 'number', 'boolean', 'secret']),
6060
description: z.string(),
6161
required: z.boolean(),
6262
locked: z.boolean()

services/backend/src/routes/mcp/servers/update-global.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const templateEnvSchema = z.object({
2828

2929
const teamArgSchema = z.object({
3030
name: z.string(),
31-
type: z.string(),
31+
type: z.enum(['string', 'number', 'boolean', 'secret']),
3232
description: z.string(),
3333
required: z.boolean(),
3434
locked: z.boolean(),
@@ -39,7 +39,7 @@ const teamArgSchema = z.object({
3939

4040
const teamEnvSchema = z.object({
4141
name: z.string(),
42-
type: z.string(),
42+
type: z.enum(['string', 'number', 'boolean', 'secret']),
4343
description: z.string(),
4444
required: z.boolean(),
4545
locked: z.boolean(),
@@ -49,7 +49,7 @@ const teamEnvSchema = z.object({
4949

5050
const userArgSchema = z.object({
5151
name: z.string(),
52-
type: z.string(),
52+
type: z.enum(['string', 'number', 'boolean', 'secret']),
5353
description: z.string(),
5454
required: z.boolean(),
5555
locked: z.boolean(),
@@ -59,7 +59,7 @@ const userArgSchema = z.object({
5959

6060
const userEnvSchema = z.object({
6161
name: z.string(),
62-
type: z.string(),
62+
type: z.enum(['string', 'number', 'boolean', 'secret']),
6363
description: z.string(),
6464
required: z.boolean(),
6565
locked: z.boolean()

services/backend/src/services/mcpInstallationService.ts

Lines changed: 144 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { mcpServerInstallations, mcpServers } from '../db/schema.sqlite';
44
import type { AnyDatabase } from '../db';
55
import type { FastifyBaseLogger } from 'fastify';
66
import { 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
1011
export 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

Comments
 (0)