mcp/discovery.jsjavascript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mcpDiscoveryRouter = exports.SERVER_CARD_URL = exports.RESOURCE_METADATA_URL = void 0;
const express_1 = require("express");
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
const { version } = require("../../package.json");
const BASE_URL = (process.env.BACKEND_DOMAIN || "https://api.choreo-planer.de").replace(/\/+$/, "");
const MCP_RESOURCE_URL = `${BASE_URL}/mcp`;
const SERVER_CARD_URL = `${BASE_URL}/mcp/server-card`;
exports.SERVER_CARD_URL = SERVER_CARD_URL;
const AUTH_DOC_URL = `${BASE_URL}/auth.md`;
const RESOURCE_METADATA_URL = `${BASE_URL}/.well-known/oauth-protected-resource`;
exports.RESOURCE_METADATA_URL = RESOURCE_METADATA_URL;
const SERVER_CARD_SCHEMA = "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json";
const SERVER_CARD_MEDIA_TYPE = "application/mcp-server-card+json";
const AI_CATALOG_MEDIA_TYPE = "application/ai-catalog+json";
const MCP_IDENTIFIER = "urn:air:choreo-planer.de:mcp:cheer-choreo-tool";
const DESCRIPTION = "Create and manage cheerleading choreographies as structured data: clubs, teams, seasons, members, choreos, hits, lineups and positions.";
function buildServerCard() {
    return {
        $schema: SERVER_CARD_SCHEMA,
        name: "de.choreo-planer/cheer-choreo-tool",
        version,
        title: "Choreo Planer MCP Server",
        description: DESCRIPTION,
        websiteUrl: "https://www.choreo-planer.de",
        repository: {
            url: "https://github.com/andreasnicklaus/cheer-choreo-tool",
            source: "github",
            subfolder: "server",
        },
        remotes: [
            {
                type: "streamable-http",
                url: MCP_RESOURCE_URL,
                supportedProtocolVersions: types_js_1.SUPPORTED_PROTOCOL_VERSIONS,
            },
        ],
    };
}
function buildAiCatalog() {
    return {
        specVersion: "1.0",
        entries: [
            {
                identifier: MCP_IDENTIFIER,
                type: SERVER_CARD_MEDIA_TYPE,
                url: SERVER_CARD_URL,
            },
        ],
    };
}
/**
 * RFC 9728 protected resource metadata.
 *
 * `authorization_servers` is intentionally omitted: Choreo Planer issues MCP
 * access tokens through its own `POST /auth/mcp-token` endpoint and does not
 * operate an OAuth 2.0 authorization server. RFC 9728 marks the parameter
 * OPTIONAL for exactly this case.
 */
function buildProtectedResourceMetadata() {
    return {
        resource: MCP_RESOURCE_URL,
        bearer_methods_supported: ["header"],
        resource_name: "Choreo Planer MCP Server",
        resource_documentation: AUTH_DOC_URL,
    };
}
function sendPublicJson(res, body, mediaType) {
    res.setHeader("Content-Type", mediaType);
    res.setHeader("Cache-Control", "public, max-age=3600");
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
    res.status(200).json(body);
}
const router = (0, express_1.Router)();
exports.mcpDiscoveryRouter = router;
const sendServerCard = (_req, res) => sendPublicJson(res, buildServerCard(), SERVER_CARD_MEDIA_TYPE);
const sendAiCatalog = (_req, res) => sendPublicJson(res, buildAiCatalog(), AI_CATALOG_MEDIA_TYPE);
const sendProtectedResourceMetadata = (_req, res) => sendPublicJson(res, buildProtectedResourceMetadata(), "application/json");
// Reserved single-server location (SEP-2127) plus widely used aliases.
router.get("/server-card", sendServerCard);
router.get("/mcp/server-card", sendServerCard);
router.get("/mcp-server-card.json", sendServerCard);
// Domain-level discovery entrypoints.
router.get("/.well-known/ai-catalog.json", sendAiCatalog);
router.get("/.well-known/mcp", sendAiCatalog);
// RFC 9728. The path-aware variant is the location derived from the
// resource identifier, per RFC 9728 section 3.1.
router.get("/.well-known/oauth-protected-resource", sendProtectedResourceMetadata);
router.get("/.well-known/oauth-protected-resource/mcp", sendProtectedResourceMetadata);
router.get("/auth.md", (_req, res) => {
    res.setHeader("Content-Type", "text/markdown; charset=utf-8");
    res.setHeader("Cache-Control", "public, max-age=3600");
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
    res.sendFile("auth.md", { root: `${__dirname}/resources` });
});