Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@missionsquad/mcp-api",
"version": "1.7.0",
"version": "1.8.0",
"description": "MCP Servers exposed via HTTP API",
"main": "dist/index.js",
"repository": "missionsquad/mcp-api",
Expand Down
9 changes: 7 additions & 2 deletions src/controllers/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export interface AddServerRequest {
command: string
args?: string[]
env?: Record<string, string>
secretName?: string // ← KEEP for backward compatibility
secretNames?: string[] // ← ADD new property
enabled?: boolean
startupTimeout?: number
}
Expand All @@ -38,6 +40,8 @@ export interface UpdateServerRequest {
command?: string
args?: string[]
env?: Record<string, string>
secretName?: string // ← KEEP for backward compatibility
secretNames?: string[] // ← ADD new property
enabled?: boolean
startupTimeout?: number
}
Expand Down Expand Up @@ -88,12 +92,13 @@ export class MCPController implements Resource {
private getServers(req: Request, res: Response, next: NextFunction): void {
try {
const servers = Object.values(this.mcpService.servers).map(
({ name, command, args, env, secretName, status, enabled, toolsList, logs }) => ({
({ name, command, args, env, secretNames, status, enabled, toolsList, logs }) => ({
name,
command,
args,
env,
secretName,
secretNames, // Only return new format (migration already happened)
// secretName is intentionally excluded from response
status,
enabled,
toolsList,
Expand Down
73 changes: 68 additions & 5 deletions src/services/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export interface MCPServer {
command: string
args: string[]
env: Record<string, string>
secretName?: string
secretName?: string // ← KEEP for backward compatibility
secretNames?: string[] // ← ADD new property
status: 'connected' | 'connecting' | 'disconnected' | 'error'
enabled: boolean
startupTimeout?: number
Expand Down Expand Up @@ -85,6 +86,45 @@ export class MCPService implements Resource {
this.packageService = packageService
}

/**
* Migrates old secretName format to new secretNames format
* Also persists the migrated version back to database
* This enables seamless backward compatibility during transition
*/
private async migrateServerSecrets(server: MCPServer): Promise<MCPServer> {
// If already using new format, return as-is
if (server.secretNames && server.secretNames.length > 0) {
return server
}

// If has old format, migrate
if (server.secretName && !server.secretNames) {
const migratedServer = {
...server,
secretNames: [server.secretName],
secretName: undefined // Remove old property from in-memory object
}

// Persist migration to DB asynchronously (don't block)
this.mcpDBClient.update(migratedServer, { name: server.name }).catch(err => {
log({
level: 'warn',
msg: `Failed to auto-migrate server ${server.name}: ${err.message}`
})
})

log({
level: 'info',
msg: `Auto-migrated server ${server.name} from secretName to secretNames`
})

return migratedServer
}

// No secrets configured
return server
}

/**
* Convert a built-in server to MCPServer format for API consistency
*/
Expand Down Expand Up @@ -130,6 +170,12 @@ export class MCPService implements Resource {
status: 'disconnected',
enabled: server.enabled !== false // Default to true if not set
}))

// Auto-migrate old format to new format
for (let i = 0; i < this.list.length; i++) {
this.list[i] = await this.migrateServerSecrets(this.list[i])
}

for (const server of this.list) {
try {
await this.connectToServer(server)
Expand Down Expand Up @@ -407,11 +453,18 @@ export class MCPService implements Resource {
command: string
args?: string[]
env?: Record<string, string>
secretName?: string
secretName?: string // ← KEEP for backward compatibility
secretNames?: string[] // ← ADD new property
enabled?: boolean
startupTimeout?: number
}): Promise<MCPServer> {
const { name, command, args = [], env = {}, secretName, enabled = true, startupTimeout } = serverData
const { name, command, args = [], env = {}, secretName, secretNames, enabled = true, startupTimeout } = serverData

// Normalize: prefer secretNames, but handle secretName for backward compat
let finalSecretNames = secretNames
if (!finalSecretNames && secretName) {
finalSecretNames = [secretName]
}

// Prevent adding servers that conflict with built-in external names
const builtInRegistry = BuiltInServerRegistry.getInstance()
Expand All @@ -430,7 +483,8 @@ export class MCPService implements Resource {
command,
args,
env,
secretName,
secretNames: finalSecretNames, // Use normalized value
secretName: undefined, // Don't save old format for new servers
status: 'disconnected',
enabled,
startupTimeout
Expand All @@ -449,7 +503,8 @@ export class MCPService implements Resource {
command?: string
args?: string[]
env?: Record<string, string>
secretName?: string
secretName?: string // ← KEEP for backward compatibility
secretNames?: string[] // ← ADD new property
enabled?: boolean
startupTimeout?: number
}
Expand All @@ -465,13 +520,21 @@ export class MCPService implements Resource {
throw new Error(`Server with name ${name} not found`)
}

// Normalize secrets if provided in update
let finalSecretNames = serverData.secretNames
if (!finalSecretNames && serverData.secretName) {
finalSecretNames = [serverData.secretName]
}

// Check if enabled state is changing
const enabledChanged = serverData.enabled !== undefined && serverData.enabled !== existingServer.enabled

const updatedServer: MCPServer = {
...existingServer,
...serverData,
name, // Ensure name doesn't change
secretNames: finalSecretNames ?? existingServer.secretNames,
secretName: undefined, // Remove old format when updating
startupTimeout: serverData.startupTimeout ?? existingServer.startupTimeout
}

Expand Down