Skip to content

Commit 231e4f9

Browse files
author
Lasim
committed
feat(all): Add shared types and components for configuration schema management
1 parent f34fef8 commit 231e4f9

File tree

24 files changed

+1914
-1256
lines changed

24 files changed

+1914
-1256
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ some-mcp configure --api-key=xxx
5858
{
5959
"mcpServers": {
6060
"deploystack": {
61+
"type": "http",
6162
"url": "https://satellite.deploystack.io/mcp"
6263
}
6364
}

services/backend/src/routes/mcp/servers/schemas.ts

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -964,8 +964,7 @@ export const LIST_SERVERS_SUCCESS_RESPONSE_SCHEMA = {
964964
} as const;
965965

966966
// Update request schema - all fields from CREATE_GLOBAL_SERVER_REQUEST_SCHEMA but optional
967-
export const UPDATE_GLOBAL_SERVER_REQUEST_SCHEMA = {
968-
type: 'object',
967+
export const UPDATE_GLOBAL_SERVER_REQUEST_SCHEMA = { type: 'object',
969968
properties: {
970969
// All fields from SERVER_FIELDS but optional
971970
name: SERVER_FIELDS.name,
@@ -1005,7 +1004,53 @@ export const UPDATE_GLOBAL_SERVER_REQUEST_SCHEMA = {
10051004
tags: SERVER_FIELDS.tags,
10061005
status: SERVER_FIELDS.status,
10071006
featured: SERVER_FIELDS.featured,
1008-
auto_install_new_default_team: SERVER_FIELDS.auto_install_new_default_team
1007+
auto_install_new_default_team: SERVER_FIELDS.auto_install_new_default_team,
1008+
// Three-tier configuration schema - CRITICAL FIX
1009+
template_args: {
1010+
type: 'array',
1011+
items: TEMPLATE_ARG_SCHEMA,
1012+
description: 'Template-level arguments'
1013+
},
1014+
template_env: {
1015+
type: 'array',
1016+
items: TEMPLATE_ENV_SCHEMA,
1017+
description: 'Template-level environment variables'
1018+
},
1019+
template_headers: {
1020+
type: 'array',
1021+
items: TEMPLATE_HEADER_SCHEMA,
1022+
description: 'Template-level headers'
1023+
},
1024+
team_args_schema: {
1025+
type: 'array',
1026+
items: TEAM_ARG_SCHEMA,
1027+
description: 'Team-level argument schema definitions'
1028+
},
1029+
team_env_schema: {
1030+
type: 'array',
1031+
items: TEAM_ENV_SCHEMA,
1032+
description: 'Team-level environment variable schema definitions'
1033+
},
1034+
team_headers_schema: {
1035+
type: 'array',
1036+
items: TEAM_HEADER_SCHEMA,
1037+
description: 'Team-level header schema definitions'
1038+
},
1039+
user_args_schema: {
1040+
type: 'array',
1041+
items: USER_ARG_SCHEMA,
1042+
description: 'User-level argument schema definitions'
1043+
},
1044+
user_env_schema: {
1045+
type: 'array',
1046+
items: USER_ENV_SCHEMA,
1047+
description: 'User-level environment variable schema definitions'
1048+
},
1049+
user_headers_schema: {
1050+
type: 'array',
1051+
items: USER_HEADER_SCHEMA,
1052+
description: 'User-level header schema definitions'
1053+
}
10091054
},
10101055
additionalProperties: false
10111056
} as const;

services/backend/src/routes/mcp/user-configurations/schemas.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ export const CREATE_USER_CONFIG_REQUEST_SCHEMA = {
5757
type: 'object',
5858
additionalProperties: { type: 'string' },
5959
description: 'User-specific environment variables'
60+
},
61+
user_headers: {
62+
type: 'object',
63+
additionalProperties: { type: 'string' },
64+
description: 'User-specific HTTP headers'
6065
}
6166
},
6267
additionalProperties: false
@@ -78,6 +83,11 @@ export const UPDATE_USER_CONFIG_REQUEST_SCHEMA = {
7883
type: 'object',
7984
additionalProperties: { type: 'string' },
8085
description: 'User-specific environment variables'
86+
},
87+
user_headers: {
88+
type: 'object',
89+
additionalProperties: { type: 'string' },
90+
description: 'User-specific HTTP headers'
8191
}
8292
},
8393
additionalProperties: false
@@ -127,6 +137,11 @@ export const USER_CONFIG_RESPONSE_SCHEMA = {
127137
additionalProperties: { type: 'string' },
128138
description: 'User-specific environment variables'
129139
},
140+
user_headers: {
141+
type: 'object',
142+
additionalProperties: { type: 'string' },
143+
description: 'User-specific HTTP headers'
144+
},
130145
created_at: { type: 'string', format: 'date-time', description: 'Creation timestamp' },
131146
updated_at: { type: 'string', format: 'date-time', description: 'Last update timestamp' },
132147
last_used_at: { type: 'string', format: 'date-time', description: 'Last used timestamp' }
@@ -227,12 +242,14 @@ export interface CreateUserConfigRequest {
227242
device_id?: string;
228243
user_args?: Record<string, string>;
229244
user_env?: Record<string, string>;
245+
user_headers?: Record<string, string>;
230246
}
231247

232248
export interface UpdateUserConfigRequest {
233249
device_id?: string;
234250
user_args?: Record<string, string>;
235251
user_env?: Record<string, string>;
252+
user_headers?: Record<string, string>;
236253
}
237254

238255
export interface UpdateUserArgsRequest {
@@ -250,6 +267,7 @@ export interface UserConfigData {
250267
device_id?: string;
251268
user_args?: Record<string, string>;
252269
user_env?: Record<string, string>;
270+
user_headers?: Record<string, string>;
253271
created_at: string;
254272
updated_at: string;
255273
last_used_at?: string;
@@ -415,6 +433,7 @@ export function formatUserConfigResponse(config: any): UserConfigData {
415433
device_id: config.device_id || undefined,
416434
user_args: safeJsonParse(config.user_args, undefined),
417435
user_env: safeJsonParse(config.user_env, undefined),
436+
user_headers: safeJsonParse(config.user_headers, undefined),
418437
created_at: config.created_at.toISOString(),
419438
updated_at: config.updated_at.toISOString(),
420439
last_used_at: config.last_used_at ? config.last_used_at.toISOString() : undefined

services/backend/src/services/mcpUserConfigurationService.ts

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface McpUserConfiguration {
1414
user_id: string;
1515
user_args?: Record<string, string>;
1616
user_env?: Record<string, string>;
17+
user_headers?: Record<string, string>;
1718
created_at: Date;
1819
updated_at: Date;
1920
last_used_at?: Date;
@@ -29,18 +30,21 @@ export interface McpUserConfiguration {
2930
description: string;
3031
user_args_schema?: any[];
3132
user_env_schema?: any[];
33+
user_headers_schema?: any[];
3234
};
3335
};
3436
}
3537

3638
export interface CreateUserConfigRequest {
3739
user_args?: Record<string, string>;
3840
user_env?: Record<string, string>;
41+
user_headers?: Record<string, string>;
3942
}
4043

4144
export interface UpdateUserConfigRequest {
4245
user_args?: Record<string, string>;
4346
user_env?: Record<string, string>;
47+
user_headers?: Record<string, string>;
4448
}
4549

4650
export class McpUserConfigurationService {
@@ -106,6 +110,7 @@ export class McpUserConfigurationService {
106110
for (const row of configurations) {
107111
const userArgsSchema = this.parseJsonField(row.server?.user_args_schema, []);
108112
const userEnvSchema = this.parseJsonField(row.server?.user_env_schema, []);
113+
const userHeadersSchema = this.parseJsonField(row.server?.user_headers_schema, []);
109114

110115
const userArgs = row.config.user_args
111116
? await McpArgsStorage.retrieveUserArgs(
@@ -125,10 +130,20 @@ export class McpUserConfigurationService {
125130
)
126131
: undefined;
127132

133+
const userHeaders = row.config.user_headers
134+
? await McpEnvStorage.retrieveUserEnv(
135+
row.config.user_headers,
136+
userHeadersSchema,
137+
{ maskSecrets: true },
138+
this.logger
139+
)
140+
: undefined;
141+
128142
processedConfigurations.push({
129143
...row.config,
130144
user_args: userArgs,
131145
user_env: userEnv,
146+
user_headers: userHeaders,
132147
installation: row.installation ? {
133148
id: row.installation.id,
134149
installation_name: row.installation.installation_name,
@@ -139,7 +154,8 @@ export class McpUserConfigurationService {
139154
name: row.server.name,
140155
description: row.server.description,
141156
user_args_schema: userArgsSchema,
142-
user_env_schema: userEnvSchema
157+
user_env_schema: userEnvSchema,
158+
user_headers_schema: userHeadersSchema
143159
} : undefined
144160
} : undefined
145161
});
@@ -192,6 +208,7 @@ export class McpUserConfigurationService {
192208

193209
const userArgsSchema = this.parseJsonField(server?.user_args_schema, []);
194210
const userEnvSchema = this.parseJsonField(server?.user_env_schema, []);
211+
const userHeadersSchema = this.parseJsonField(server?.user_headers_schema, []);
195212

196213
const userArgs = config.user_args
197214
? await McpArgsStorage.retrieveUserArgs(
@@ -211,10 +228,20 @@ export class McpUserConfigurationService {
211228
)
212229
: undefined;
213230

231+
const userHeaders = config.user_headers
232+
? await McpEnvStorage.retrieveUserEnv(
233+
config.user_headers,
234+
userHeadersSchema,
235+
{ maskSecrets: true },
236+
this.logger
237+
)
238+
: undefined;
239+
214240
return {
215241
...config,
216242
user_args: userArgs,
217243
user_env: userEnv,
244+
user_headers: userHeaders,
218245
installation: installation ? {
219246
id: installation.id,
220247
installation_name: installation.installation_name,
@@ -225,7 +252,8 @@ export class McpUserConfigurationService {
225252
name: server.name,
226253
description: server.description,
227254
user_args_schema: userArgsSchema,
228-
user_env_schema: userEnvSchema
255+
user_env_schema: userEnvSchema,
256+
user_headers_schema: userHeadersSchema
229257
} : undefined
230258
} : undefined
231259
};
@@ -275,6 +303,9 @@ export class McpUserConfigurationService {
275303
if (data.user_env) {
276304
this.validateUserEnv(data.user_env, this.parseJsonField(serverInfo.user_env_schema, []));
277305
}
306+
if (data.user_headers) {
307+
this.validateUserHeaders(data.user_headers, this.parseJsonField(serverInfo.user_headers_schema, []));
308+
}
278309
}
279310

280311
const configId = nanoid();
@@ -294,6 +325,11 @@ export class McpUserConfigurationService {
294325
this.parseJsonField(serverInfo?.user_env_schema, []),
295326
this.logger
296327
) : null,
328+
user_headers: data.user_headers ? await McpEnvStorage.storeUserEnv(
329+
data.user_headers,
330+
this.parseJsonField(serverInfo?.user_headers_schema, []),
331+
this.logger
332+
) : null,
297333
created_at: now,
298334
updated_at: now,
299335
last_used_at: null
@@ -344,6 +380,9 @@ export class McpUserConfigurationService {
344380
if (data.user_env !== undefined) {
345381
this.validateUserEnv(data.user_env, existing.installation.server.user_env_schema || []);
346382
}
383+
if (data.user_headers !== undefined) {
384+
this.validateUserHeaders(data.user_headers, existing.installation.server.user_headers_schema || []);
385+
}
347386
}
348387

349388

@@ -368,6 +407,14 @@ export class McpUserConfigurationService {
368407
) : null;
369408
}
370409

410+
if (data.user_headers !== undefined) {
411+
updateData.user_headers = data.user_headers ? await McpEnvStorage.storeUserEnv(
412+
data.user_headers,
413+
existing.installation?.server?.user_headers_schema || [],
414+
this.logger
415+
) : null;
416+
}
417+
371418
await this.db
372419
.update(mcpUserConfigurations)
373420
.set(updateData)
@@ -497,6 +544,24 @@ export class McpUserConfigurationService {
497544
}
498545
}
499546

547+
private validateUserHeaders(userHeaders: Record<string, string>, schema: any[]): void {
548+
// Validate user headers against schema
549+
// Only validate the fields that are actually being sent, not all required fields
550+
for (const [headerName, headerValue] of Object.entries(userHeaders)) {
551+
const schemaEntry = schema.find((header: any) => header.name === headerName)
552+
553+
if (schemaEntry) {
554+
// If this field is required and the sent value is empty, throw error
555+
if (schemaEntry.required && (!headerValue || headerValue.trim() === '')) {
556+
throw new Error(`Required header '${headerName}' is missing or empty`)
557+
}
558+
559+
// Additional type validation can be added here in the future
560+
}
561+
// Note: We don't validate fields that aren't in the schema - allowing flexibility
562+
}
563+
}
564+
500565
private parseJsonField(fieldValue: any, defaultValue: any): any {
501566
// Handle null, undefined, or empty values
502567
if (fieldValue === null || fieldValue === undefined || fieldValue === '') {

0 commit comments

Comments
 (0)