Skip to main content
Glama
app-config.it.test.ts8.85 kB
import { parse } from "yaml"; import { getTestHarness, TestHarness, transportTypes } from "./utils.js"; import { Config } from "../src/model/config/config.js"; import { appConfigSchema } from "@mcpx/shared-model"; const MCPX_BASE_URL = "http://localhost:9000"; describe("App Config", () => { describe.each(transportTypes)("API over %s Router", (transportType) => { describe("config operations", () => { const harness = getTestHarness(); beforeAll(async () => { await harness.initialize(transportType); }); afterAll(async () => { await harness.shutdown(); }); it("can write config and read it back", async () => { const config = { permissions: { default: { _type: "default-allow", block: ["dangerous-tools"], }, consumers: { guests: { _type: "default-block", allow: ["safe-tools"], consumerGroupKey: "guest-group", }, }, }, toolGroups: [ { name: "safe-tools", services: { "echo-service": ["echo"], }, }, { name: "dangerous-tools", services: { "calculator-service": ["powerOfTwo"], }, }, ], }; // PATCH config const patchResponse = await fetch(`${MCPX_BASE_URL}/app-config`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(config), }); expect(patchResponse.status).toBe(200); // GET should return the config const getResponse = await fetch(`${MCPX_BASE_URL}/app-config`); expect(getResponse.status).toBe(200); const responseData = await getResponse.json(); const parsedConfig = parse(responseData.yaml); expect(parsedConfig.permissions).toEqual({ default: { _type: "default-allow", block: ["dangerous-tools"], }, consumers: { guests: { _type: "default-block", allow: ["safe-tools"], consumerGroupKey: "guest-group", }, }, }); }); it("handles full config with all fields correctly", async () => { const fullConfig = { permissions: { default: { _type: "default-allow", block: [], }, consumers: { testers: { _type: "default-block", allow: ["test-tools"], consumerGroupKey: "test-group", }, }, }, toolGroups: [ { name: "test-tools", services: { "echo-service": ["echo"], "calculator-service": ["add"], }, }, ], auth: { enabled: false, }, toolExtensions: { services: { "echo-service": { echo: { childTools: [ { name: "echo_custom", description: { action: "rewrite", text: "Custom echo tool for testing", }, overrideParams: {}, }, ], }, }, }, }, }; // PATCH with full config const patchResponse = await fetch(`${MCPX_BASE_URL}/app-config`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(fullConfig), }); expect(patchResponse.status).toBe(200); // GET and verify all fields are preserved const getResponse = await fetch(`${MCPX_BASE_URL}/app-config`); expect(getResponse.status).toBe(200); const responseData = await getResponse.json(); const parsedConfig = parse(responseData.yaml); expect(parsedConfig.permissions).toEqual({ default: { _type: "default-allow", block: [], }, consumers: { testers: { _type: "default-block", allow: ["test-tools"], consumerGroupKey: "test-group", }, }, }); expect(parsedConfig.toolGroups).toEqual(fullConfig.toolGroups); expect(parsedConfig.auth).toEqual(fullConfig.auth); expect(parsedConfig.toolExtensions).toEqual(fullConfig.toolExtensions); }); it("preserves _type discriminating tags", async () => { const config = { permissions: { default: { _type: "default-allow", block: ["restricted"], }, consumers: { "api-users": { _type: "default-block", allow: ["public-tools"], consumerGroupKey: "api-group", }, }, }, toolGroups: [ { name: "public-tools", services: { "echo-service": ["echo"], }, }, { name: "restricted", services: { "calculator-service": ["powerOfTwo"], }, }, ], }; // PATCH config const patchResponse = await fetch(`${MCPX_BASE_URL}/app-config`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(config), }); expect(patchResponse.status).toBe(200); // GET should preserve _type fields const getResponse = await fetch(`${MCPX_BASE_URL}/app-config`); expect(getResponse.status).toBe(200); const responseData = await getResponse.json(); const parsedConfig = parse(responseData.yaml); // Verify _type fields are preserved expect(parsedConfig.permissions.default).toHaveProperty( "_type", "default-allow", ); expect(parsedConfig.permissions.consumers["api-users"]).toHaveProperty( "_type", "default-block", ); }); }); }); describe.each(transportTypes)( "Validations over %s Router", (transportType) => { describe("config updates", () => { let testHarness: TestHarness; beforeAll(async () => { testHarness = getTestHarness(); await testHarness.initialize(transportType); }); afterAll(async () => { await testHarness.shutdown(); }); it("rejects config with invalid schema", async () => { const invalidConfig = { permissions: { default: { _type: "default-allow", block: "*", // This is a mistake! must be an array }, consumers: {}, }, toolGroups: [ { name: "foo", services: ["echoService"], // This is a mistake! must be an object with service names as keys }, ], }; const patchResponse = await fetch(`${MCPX_BASE_URL}/app-config`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(invalidConfig), }); expect(patchResponse.status).toBe(400); }); it("rejects config with valid schema but not-workable values", async () => { const invalidConfig: Config = appConfigSchema.parse({ permissions: { default: { _type: "default-allow", block: ["foo-with-typo"], }, consumers: {}, }, toolGroups: [ { name: "foo", services: { "echo-service": ["echo"], }, }, ], auth: { enabled: false }, toolExtensions: { services: {} }, targetServerAttributes: {}, }); const patchResponse = await fetch(`${MCPX_BASE_URL}/app-config`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(invalidConfig), }); expect(patchResponse.status).toBe(400); const errorData = await patchResponse.json(); expect(errorData.error).toContain("Config update rejected"); }); }); }, ); });

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