Skip to main content
Glama
control-plane-app-config.ts15.6 kB
import { consumerConfigSchema, ConsumerConfig, createPermissionConsumerRequestSchema, Permissions, singleToolGroupSchema, ToolExtension, toolExtensionSchema, ToolExtensions, ToolGroup, toolGroupUpdateSchema, } from "@mcpx/shared-model"; import { loggableError } from "@mcpx/toolkit-core/logging"; import express, { Router } from "express"; import { Logger } from "winston"; import z from "zod/v4"; import { env } from "../env.js"; import { AlreadyExistsError, NotFoundError } from "../errors.js"; import { Services } from "../services/services.js"; export function buildControlPlaneAppConfigRouter( authGuard: express.RequestHandler, services: Services, logger: Logger, ): Router { const router = Router(); if (!env.ENABLE_CONTROL_PLANE_REST) { logger.debug( "Control Plane REST API is disabled. Skipping control plane routes.", ); return router; } // ==================== TOOL GROUPS ==================== router.get("/tool-groups", authGuard, async (_req, res) => { const toolGroups = services.controlPlane.config.getToolGroups(); res.status(200).json(toolGroups satisfies ToolGroup[]); }); router.get("/tool-groups/:name", authGuard, async (req, res) => { const name = req.params["name"]; if (!name) { res.status(400).json({ message: "Tool group name is required" }); return; } const toolGroup = services.controlPlane.config.getToolGroup({ name }); if (!toolGroup) { res.status(404).json({ message: `Tool group '${name}' not found` }); return; } res.status(200).json(toolGroup satisfies ToolGroup); }); router.post("/tool-groups", authGuard, async (req, res) => { const parsed = singleToolGroupSchema.safeParse(req.body); if (!parsed.success) { res.status(400).json({ message: "Invalid request schema", error: z.treeifyError(parsed.error), }); return; } try { const toolGroup = await services.controlPlane.config.addToolGroup({ group: parsed.data, }); res.status(201).json(toolGroup satisfies ToolGroup); } catch (e) { if (e instanceof AlreadyExistsError) { res.status(409).json({ message: e.message, error: loggableError(e) }); return; } logger.error("Error adding tool group", { error: loggableError(e) }); res.status(500).json({ message: "Internal server error" }); } }); router.put("/tool-groups/:name", authGuard, async (req, res) => { const name = req.params["name"]; if (!name) { res.status(400).json({ message: "Tool group name is required" }); return; } const parsed = toolGroupUpdateSchema.safeParse(req.body); if (!parsed.success) { res.status(400).json({ message: "Invalid request schema", error: z.treeifyError(parsed.error), }); return; } try { const toolGroup = await services.controlPlane.config.updateToolGroup({ name, updates: parsed.data, }); res.status(200).json(toolGroup satisfies ToolGroup); } catch (e) { if (e instanceof NotFoundError) { res.status(404).json({ message: e.message, error: loggableError(e) }); return; } if (e instanceof AlreadyExistsError) { res.status(409).json({ message: e.message, error: loggableError(e) }); return; } logger.error("Error updating tool group", { error: loggableError(e) }); res.status(500).json({ message: "Internal server error" }); } }); router.delete("/tool-groups/:name", authGuard, async (req, res) => { const name = req.params["name"]; if (!name) { res.status(400).json({ message: "Tool group name is required" }); return; } try { await services.controlPlane.config.deleteToolGroup({ name }); res.status(200).json({ message: `Tool group '${name}' deleted` }); } catch (e) { if (e instanceof NotFoundError) { res.status(404).json({ message: e.message, error: loggableError(e) }); return; } logger.error("Error deleting tool group", { error: loggableError(e) }); res.status(500).json({ message: "Internal server error" }); } }); // ==================== TOOL EXTENSIONS ==================== const toolExtensionUpdateSchema = toolExtensionSchema.omit({ name: true }); router.get("/tool-extensions", authGuard, async (_req, res) => { const extensions = services.controlPlane.config.getToolExtensions(); res.status(200).json(extensions satisfies ToolExtensions); }); router.get( "/tool-extensions/:serverName/:originalToolName/:customToolName", authGuard, async (req, res) => { const { serverName, originalToolName, customToolName } = req.params; if (!serverName || !originalToolName || !customToolName) { res.status(400).json({ message: "All path parameters are required" }); return; } const extension = services.controlPlane.config.getToolExtension({ serverName, originalToolName, customToolName, }); if (!extension) { res.status(404).json({ message: `Tool extension '${customToolName}' not found for ${serverName}/${originalToolName}`, }); return; } res.status(200).json(extension satisfies ToolExtension); }, ); router.post( "/tool-extensions/:serverName/:originalToolName", authGuard, async (req, res) => { const { serverName, originalToolName } = req.params; if (!serverName || !originalToolName) { res.status(400).json({ message: "All path parameters are required" }); return; } const parsed = toolExtensionSchema.safeParse(req.body); if (!parsed.success) { res.status(400).json({ message: "Invalid request schema", error: z.treeifyError(parsed.error), }); return; } try { const extension = await services.controlPlane.config.addToolExtension({ serverName, originalToolName, extension: parsed.data, }); res.status(201).json(extension satisfies ToolExtension); } catch (e) { if (e instanceof AlreadyExistsError) { res.status(409).json({ message: e.message, error: loggableError(e) }); return; } logger.error("Error adding tool extension", { error: loggableError(e), }); res.status(500).json({ message: "Internal server error" }); } }, ); router.put( "/tool-extensions/:serverName/:originalToolName/:customToolName", authGuard, async (req, res) => { const { serverName, originalToolName, customToolName } = req.params; if (!serverName || !originalToolName || !customToolName) { res.status(400).json({ message: "All path parameters are required" }); return; } const parsed = toolExtensionUpdateSchema.safeParse(req.body); if (!parsed.success) { res.status(400).json({ message: "Invalid request schema", error: z.treeifyError(parsed.error), }); return; } try { const extension = await services.controlPlane.config.updateToolExtension({ serverName, originalToolName, customToolName, updates: parsed.data, }); res.status(200).json(extension satisfies ToolExtension); } catch (e) { if (e instanceof NotFoundError) { res.status(404).json({ message: e.message, error: loggableError(e) }); return; } logger.error("Error updating tool extension", { error: loggableError(e), }); res.status(500).json({ message: "Internal server error" }); } }, ); router.delete( "/tool-extensions/:serverName/:originalToolName/:customToolName", authGuard, async (req, res) => { const { serverName, originalToolName, customToolName } = req.params; if (!serverName || !originalToolName || !customToolName) { res.status(400).json({ message: "All path parameters are required" }); return; } try { await services.controlPlane.config.deleteToolExtension({ serverName, originalToolName, customToolName, }); res.status(200).json({ message: `Tool extension '${customToolName}' deleted from ${serverName}/${originalToolName}`, }); } catch (e) { if (e instanceof NotFoundError) { res.status(404).json({ message: e.message, error: loggableError(e) }); return; } logger.error("Error deleting tool extension", { error: loggableError(e), }); res.status(500).json({ message: "Internal server error" }); } }, ); // ==================== PERMISSIONS ==================== router.get("/permissions", authGuard, async (_req, res) => { const permissions = services.controlPlane.config.getPermissions(); res.status(200).json(permissions satisfies Permissions); }); router.get("/permissions/default", authGuard, async (_req, res) => { const defaultPermission = services.controlPlane.config.getDefaultPermission(); res.status(200).json(defaultPermission satisfies ConsumerConfig); }); router.get("/permissions/consumers", authGuard, async (_req, res) => { const consumers = services.controlPlane.config.getPermissionConsumers(); res.status(200).json(consumers satisfies Record<string, ConsumerConfig>); }); router.get( "/permissions/consumers/:consumerName", authGuard, async (req, res) => { const name = req.params["consumerName"]; if (!name) { res.status(400).json({ message: "Consumer name is required" }); return; } const consumer = services.controlPlane.config.getPermissionConsumer({ name, }); if (!consumer) { res .status(404) .json({ message: `Permission consumer '${name}' not found` }); return; } res.status(200).json(consumer satisfies ConsumerConfig); }, ); router.post("/permissions/consumers", authGuard, async (req, res) => { const parsed = createPermissionConsumerRequestSchema.safeParse(req.body); if (!parsed.success) { res.status(400).json({ message: "Invalid request schema", error: z.treeifyError(parsed.error), }); return; } try { const consumer = await services.controlPlane.config.addPermissionConsumer( { name: parsed.data.name, config: parsed.data.config, }, ); res.status(201).json(consumer satisfies ConsumerConfig); } catch (e) { if (e instanceof AlreadyExistsError) { res.status(409).json({ message: e.message, error: loggableError(e) }); return; } logger.error("Error adding permission consumer", { error: loggableError(e), }); res.status(500).json({ message: "Internal server error" }); } }); router.put("/permissions/default", authGuard, async (req, res) => { const parsed = consumerConfigSchema.safeParse(req.body); if (!parsed.success) { res.status(400).json({ message: "Invalid request schema", error: z.treeifyError(parsed.error), }); return; } try { const defaultPermission = await services.controlPlane.config.updateDefaultPermission({ config: parsed.data, }); res.status(200).json(defaultPermission satisfies ConsumerConfig); } catch (e) { logger.error("Error updating default permission", { error: loggableError(e), }); res.status(500).json({ message: "Internal server error" }); } }); router.put( "/permissions/consumers/:consumerName", authGuard, async (req, res) => { const name = req.params["consumerName"]; if (!name) { res.status(400).json({ message: "Consumer name is required" }); return; } const parsed = consumerConfigSchema.safeParse(req.body); if (!parsed.success) { res.status(400).json({ message: "Invalid request schema", error: z.treeifyError(parsed.error), }); return; } try { const consumer = await services.controlPlane.config.updatePermissionConsumer({ name, config: parsed.data, }); res.status(200).json(consumer satisfies ConsumerConfig); } catch (e) { if (e instanceof NotFoundError) { res.status(404).json({ message: e.message, error: loggableError(e) }); return; } logger.error("Error updating permission consumer", { error: loggableError(e), }); res.status(500).json({ message: "Internal server error" }); } }, ); router.delete( "/permissions/consumers/:consumerName", authGuard, async (req, res) => { const name = req.params["consumerName"]; if (!name) { res.status(400).json({ message: "Consumer name is required" }); return; } try { await services.controlPlane.config.deletePermissionConsumer({ name }); res .status(200) .json({ message: `Permission consumer '${name}' deleted` }); } catch (e) { if (e instanceof NotFoundError) { res.status(404).json({ message: e.message, error: loggableError(e) }); return; } logger.error("Error deleting permission consumer", { error: loggableError(e), }); res.status(500).json({ message: "Internal server error" }); } }, ); // ==================== SERVER ATTRIBUTES ==================== router.put("/target-server/:name/activate", authGuard, async (req, res) => { const name = req.params["name"]; if (!name) { res.status(400).json({ message: "Target server name is required" }); return; } try { await services.controlPlane.config.activateTargetServer(name); logger.info("Activated target server", { name }); res.status(200).json({ message: "Target server activated successfully" }); } catch (e) { const error = loggableError(e); logger.error("Error activating target server", { error, name }); res.status(500).json(error); } }); router.put("/target-server/:name/deactivate", authGuard, async (req, res) => { const name = req.params["name"]; if (!name) { res.status(400).json({ message: "Target server name is required" }); return; } try { await services.controlPlane.config.deactivateTargetServer(name); logger.info("Deactivated target server", { name }); res .status(200) .json({ message: "Target server deactivated successfully" }); } catch (e) { const error = loggableError(e); logger.error("Error deactivating target server", { error, name }); res.status(500).json(error); } }); router.get("/target-servers/attributes", authGuard, async (_req, res) => { try { const targetServerAttributes = services.controlPlane.config.getTargetServerAttributes(); res.status(200).json({ targetServerAttributes }); } catch (e) { const error = loggableError(e); logger.error("Error fetching target server attributes", { error }); res.status(500).json(error); } }); return router; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/TheLunarCompany/lunar'

If you have feedback or need assistance with the MCP directory API, please join our Discord server