Skip to content

Commit 69f4829

Browse files
committed
feat(backend): enhance MCP server creation and update descriptions
1 parent af7661a commit 69f4829

File tree

5 files changed

+110
-55
lines changed

5 files changed

+110
-55
lines changed

services/backend/api-spec.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14487,7 +14487,7 @@
1448714487
"tags": [
1448814488
"MCP Servers"
1448914489
],
14490-
"description": "Create a new global MCP server - requires global admin permissions. Global servers are visible to all users. Requires Content-Type: application/json header when sending request body.",
14490+
"description": "Create a new global MCP server - requires global admin permissions. Global servers are visible to all users. If transport_type is not provided, it will be automatically extracted from claude_desktop_config (CLI commands like npx, node, python = stdio). Requires Content-Type: application/json header when sending request body.",
1449114491
"requestBody": {
1449214492
"content": {
1449314493
"application/json": {
@@ -14670,7 +14670,6 @@
1467014670
"description",
1467114671
"language",
1467214672
"runtime",
14673-
"transport_type",
1467414673
"claude_desktop_config"
1467514674
],
1467614675
"additionalProperties": false
@@ -15143,7 +15142,7 @@
1514315142
"tags": [
1514415143
"MCP Servers"
1514515144
],
15146-
"description": "Update an existing global MCP server - requires global admin permissions. Only global servers can be updated through this endpoint. Requires Content-Type: application/json header when sending request body.",
15145+
"description": "Update an existing global MCP server - requires global admin permissions. Only global servers can be updated through this endpoint. If transport_type is not provided but claude_desktop_config is, transport_type will be automatically extracted. Requires Content-Type: application/json header when sending request body.",
1514715146
"parameters": [
1514815147
{
1514915148
"schema": {

services/backend/api-spec.yaml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9974,8 +9974,11 @@ paths:
99749974
tags:
99759975
- MCP Servers
99769976
description: "Create a new global MCP server - requires global admin
9977-
permissions. Global servers are visible to all users. Requires
9978-
Content-Type: application/json header when sending request body."
9977+
permissions. Global servers are visible to all users. If transport_type
9978+
is not provided, it will be automatically extracted from
9979+
claude_desktop_config (CLI commands like npx, node, python = stdio).
9980+
Requires Content-Type: application/json header when sending request
9981+
body."
99799982
requestBody:
99809983
content:
99819984
application/json:
@@ -10107,7 +10110,6 @@ paths:
1010710110
- description
1010810111
- language
1010910112
- runtime
10110-
- transport_type
1011110113
- claude_desktop_config
1011210114
additionalProperties: false
1011310115
required: true
@@ -10392,8 +10394,9 @@ paths:
1039210394
- MCP Servers
1039310395
description: "Update an existing global MCP server - requires global admin
1039410396
permissions. Only global servers can be updated through this endpoint.
10395-
Requires Content-Type: application/json header when sending request
10396-
body."
10397+
If transport_type is not provided but claude_desktop_config is,
10398+
transport_type will be automatically extracted. Requires Content-Type:
10399+
application/json header when sending request body."
1039710400
parameters:
1039810401
- schema:
1039910402
type: object

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

Lines changed: 8 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,7 @@ import { createSchema } from 'zod-openapi';
44
import { requireGlobalAdmin } from '../../../middleware/roleMiddleware';
55
import { McpCatalogService } from '../../../services/mcpCatalogService';
66
import { getDb } from '../../../db';
7-
8-
// Claude Desktop configuration schema
9-
const claudeDesktopConfigSchema = z.object({
10-
mcpServers: z.record(z.string(), z.object({
11-
command: z.string().min(1, 'Command is required'),
12-
args: z.array(z.string()),
13-
env: z.record(z.string(), z.string()).optional()
14-
}))
15-
}).refine(
16-
(config) => Object.keys(config.mcpServers).length === 1,
17-
{ message: "Claude Desktop config must contain exactly one MCP server" }
18-
);
7+
import { claudeDesktopConfigSchema, extractMcpConfigData } from '../../../utils/mcpConfigExtractor';
198

209
// Request schema for creating global MCP servers
2110
const createGlobalServerRequestSchema = z.object({
@@ -25,7 +14,7 @@ const createGlobalServerRequestSchema = z.object({
2514
language: z.string().min(1, 'Language is required'),
2615
runtime: z.string().min(1, 'Runtime is required'),
2716
claude_desktop_config: claudeDesktopConfigSchema,
28-
transport_type: z.enum(['stdio', 'http', 'sse']).default('stdio'),
17+
transport_type: z.enum(['stdio', 'http', 'sse']).optional(),
2918
tools: z.array(z.object({
3019
name: z.string().min(1, 'Tool name is required'),
3120
description: z.string().min(1, 'Tool description is required')
@@ -55,32 +44,6 @@ const createGlobalServerRequestSchema = z.object({
5544
featured: z.boolean().default(false)
5645
});
5746

58-
// Utility function to extract MCP configuration data from Claude Desktop config
59-
function extractMcpConfigData(claudeConfig: z.infer<typeof claudeDesktopConfigSchema>) {
60-
const serverKey = Object.keys(claudeConfig.mcpServers)[0];
61-
const serverConfig = claudeConfig.mcpServers[serverKey];
62-
63-
// Extract installation_methods (Claude Desktop format)
64-
const installation_methods = [{
65-
client: "claude-desktop",
66-
command: serverConfig.command,
67-
args: serverConfig.args,
68-
env: serverConfig.env || {}
69-
}];
70-
71-
// Extract environment_variables metadata
72-
const environment_variables = Object.keys(serverConfig.env || {}).map(envKey => ({
73-
name: envKey,
74-
description: `${envKey} environment variable`,
75-
required: true,
76-
type: "password",
77-
validation: "",
78-
placeholder: serverConfig.env![envKey]
79-
}));
80-
81-
return { installation_methods, environment_variables };
82-
}
83-
8447
// Response schema for successful creation
8548
const createGlobalServerResponseSchema = z.object({
8649
success: z.boolean(),
@@ -133,7 +96,7 @@ export default async function createGlobalServer(server: FastifyInstance) {
13396
schema: {
13497
tags: ['MCP Servers'],
13598
summary: 'Create global MCP server (Global Admin only)',
136-
description: 'Create a new global MCP server - requires global admin permissions. Global servers are visible to all users. Requires Content-Type: application/json header when sending request body.',
99+
description: 'Create a new global MCP server - requires global admin permissions. Global servers are visible to all users. If transport_type is not provided, it will be automatically extracted from claude_desktop_config (CLI commands like npx, node, python = stdio). Requires Content-Type: application/json header when sending request body.',
137100
security: [{ cookieAuth: [] }],
138101
// Plain JSON Schema for Fastify validation
139102
body: {
@@ -214,7 +177,7 @@ export default async function createGlobalServer(server: FastifyInstance) {
214177
tags: { type: 'array', items: { type: 'string' } },
215178
featured: { type: 'boolean' }
216179
},
217-
required: ['name', 'description', 'language', 'runtime', 'transport_type', 'claude_desktop_config'],
180+
required: ['name', 'description', 'language', 'runtime', 'claude_desktop_config'],
218181
additionalProperties: false
219182
},
220183
// createSchema() for OpenAPI documentation
@@ -258,14 +221,15 @@ export default async function createGlobalServer(server: FastifyInstance) {
258221
claudeConfig: requestData.claude_desktop_config
259222
}, 'Extracting MCP configuration data from Claude Desktop config');
260223

261-
const { installation_methods, environment_variables } = extractMcpConfigData(requestData.claude_desktop_config);
224+
const { installation_methods, environment_variables, transport_type: extractedTransportType } = extractMcpConfigData(requestData.claude_desktop_config);
262225

263226
request.log.debug({
264227
operation: 'create_global_mcp_server',
265228
userId: request.user?.id,
266229
extractedData: {
267230
installation_methods,
268-
environment_variables
231+
environment_variables,
232+
extractedTransportType
269233
}
270234
}, 'Successfully extracted MCP configuration data');
271235

@@ -289,7 +253,7 @@ export default async function createGlobalServer(server: FastifyInstance) {
289253
author_contact: requestData.author_contact,
290254
organization: requestData.organization,
291255
license: requestData.license,
292-
transport_type: requestData.transport_type,
256+
transport_type: requestData.transport_type || extractedTransportType,
293257
environment_variables,
294258
dependencies: requestData.dependencies,
295259
category_id: requestData.category_id,

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createSchema } from 'zod-openapi';
44
import { requireGlobalAdmin } from '../../../middleware/roleMiddleware';
55
import { McpCatalogService } from '../../../services/mcpCatalogService';
66
import { getDb } from '../../../db';
7+
import { claudeDesktopConfigSchema, extractTransportTypeFromClaudeConfig } from '../../../utils/mcpConfigExtractor';
78

89
// Path parameters schema
910
const updateGlobalServerParamsSchema = z.object({
@@ -43,6 +44,7 @@ const updateGlobalServerRequestSchema = z.object({
4344
author_contact: z.string().optional(),
4445
organization: z.string().optional(),
4546
license: z.string().optional(),
47+
claude_desktop_config: claudeDesktopConfigSchema.optional(),
4648
transport_type: z.enum(['stdio', 'http', 'sse']).optional(),
4749
environment_variables: z.array(z.object({
4850
name: z.string().min(1, 'Environment variable name is required'),
@@ -109,7 +111,7 @@ export default async function updateGlobalServer(server: FastifyInstance) {
109111
schema: {
110112
tags: ['MCP Servers'],
111113
summary: 'Update global MCP server (Global Admin only)',
112-
description: 'Update an existing global MCP server - requires global admin permissions. Only global servers can be updated through this endpoint. Requires Content-Type: application/json header when sending request body.',
114+
description: 'Update an existing global MCP server - requires global admin permissions. Only global servers can be updated through this endpoint. If transport_type is not provided but claude_desktop_config is, transport_type will be automatically extracted. Requires Content-Type: application/json header when sending request body.',
113115
security: [{ cookieAuth: [] }],
114116
params: createSchema(updateGlobalServerParamsSchema),
115117
requestBody: {
@@ -189,6 +191,20 @@ export default async function updateGlobalServer(server: FastifyInstance) {
189191
});
190192
}
191193

194+
// Extract transport_type from claude_desktop_config if provided but transport_type is not
195+
let finalUpdateData = { ...updateData };
196+
if (updateData.claude_desktop_config && !updateData.transport_type) {
197+
const extractedTransportType = extractTransportTypeFromClaudeConfig(updateData.claude_desktop_config);
198+
finalUpdateData.transport_type = extractedTransportType;
199+
200+
request.log.debug({
201+
operation: 'update_global_mcp_server',
202+
userId: request.user?.id,
203+
serverId,
204+
extractedTransportType
205+
}, 'Extracted transport_type from claude_desktop_config');
206+
}
207+
192208
request.log.info({
193209
operation: 'update_global_mcp_server',
194210
step: 'calling_service',
@@ -200,7 +216,7 @@ export default async function updateGlobalServer(server: FastifyInstance) {
200216
serverId,
201217
request.user!.id,
202218
'global_admin', // We know user is global admin due to middleware
203-
updateData
219+
finalUpdateData
204220
);
205221

206222
request.log.info({
@@ -222,7 +238,7 @@ export default async function updateGlobalServer(server: FastifyInstance) {
222238
userId: request.user?.id,
223239
serverId,
224240
serverName: updatedServer.name,
225-
updatedFields: Object.keys(updateData)
241+
updatedFields: Object.keys(finalUpdateData)
226242
}, 'Global MCP server updated successfully');
227243

228244
// Safe JSON parsing helper function
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { z } from 'zod';
2+
3+
// Claude Desktop configuration schema (shared)
4+
export const claudeDesktopConfigSchema = z.object({
5+
mcpServers: z.record(z.string(), z.object({
6+
command: z.string().min(1, 'Command is required'),
7+
args: z.array(z.string()),
8+
env: z.record(z.string(), z.string()).optional()
9+
}))
10+
}).refine(
11+
(config) => Object.keys(config.mcpServers).length === 1,
12+
{ message: "Claude Desktop config must contain exactly one MCP server" }
13+
);
14+
15+
export type ClaudeDesktopConfig = z.infer<typeof claudeDesktopConfigSchema>;
16+
17+
/**
18+
* Extract transport_type from Claude Desktop configuration
19+
* @param claudeConfig - The claude desktop configuration object
20+
* @returns Transport type: 'stdio' | 'http' | 'sse'
21+
*/
22+
export function extractTransportTypeFromClaudeConfig(claudeConfig: ClaudeDesktopConfig): 'stdio' | 'http' | 'sse' {
23+
const serverKey = Object.keys(claudeConfig.mcpServers)[0];
24+
const serverConfig = claudeConfig.mcpServers[serverKey];
25+
const command = serverConfig.command?.toLowerCase() || '';
26+
27+
// Standard CLI commands indicate stdio transport
28+
const stdioCommands = ['npx', 'node', 'python', 'python3', 'pip', 'poetry', 'cargo', 'go', 'java', 'dotnet'];
29+
if (stdioCommands.some(cmd => command.includes(cmd))) {
30+
return 'stdio';
31+
}
32+
33+
// Future: Could add logic for http/sse detection if needed
34+
// if (command.includes('http') || command.includes('serve')) {
35+
// return 'http';
36+
// }
37+
38+
// Default to stdio as it's the most common
39+
return 'stdio';
40+
}
41+
42+
/**
43+
* Extract complete MCP configuration data from Claude Desktop config
44+
* @param claudeConfig - The claude desktop configuration object
45+
* @returns Object containing installation_methods, environment_variables, and transport_type
46+
*/
47+
export function extractMcpConfigData(claudeConfig: ClaudeDesktopConfig) {
48+
const serverKey = Object.keys(claudeConfig.mcpServers)[0];
49+
const serverConfig = claudeConfig.mcpServers[serverKey];
50+
51+
// Extract installation_methods (Claude Desktop format)
52+
const installation_methods = [{
53+
client: "claude-desktop",
54+
command: serverConfig.command,
55+
args: serverConfig.args,
56+
env: serverConfig.env || {}
57+
}];
58+
59+
// Extract environment_variables metadata
60+
const environment_variables = Object.keys(serverConfig.env || {}).map(envKey => ({
61+
name: envKey,
62+
description: `${envKey} environment variable`,
63+
required: true,
64+
type: "password",
65+
validation: "",
66+
placeholder: serverConfig.env![envKey]
67+
}));
68+
69+
// Extract transport_type
70+
const transport_type = extractTransportTypeFromClaudeConfig(claudeConfig);
71+
72+
return { installation_methods, environment_variables, transport_type };
73+
}

0 commit comments

Comments
 (0)