#!/usr/bin/env bun
/**
 * Remote MCP Server for Harmony
 *
 * Hosted MCP endpoint that any AI agent can connect to via HTTP.
 * Auth via API key passed as Bearer token, validated against the Harmony API.
 *
 * Usage:
 *   Claude.ai → POST https://mcp.gethmy.com/mcp (Bearer: hmy_xxx)
 *
 * Env vars:
 *   HARMONY_API_URL - Harmony API base URL (default: https://app.gethmy.com/api)
 *   PORT            - Listen port (default: 3002)
 */
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
import { serve } from "bun";
import { Hono } from "hono";
import { cors } from "hono/cors";
import { HarmonyApiClient } from "./api-client.js";
import { registerHandlers } from "./server.js";
// ---------------------------------------------------------------------------
// Config from env
// ---------------------------------------------------------------------------
const HARMONY_API_URL = process.env.HARMONY_API_URL || "https://app.gethmy.com/api";
const PORT = parseInt(process.env.PORT || "3002", 10);
async function validateApiKey(apiKey) {
    try {
        const response = await fetch(`${HARMONY_API_URL}/v1/workspaces`, {
            headers: { "X-API-Key": apiKey },
        });
        if (!response.ok)
            return null;
        const data = (await response.json());
        const firstWorkspace = data.workspaces?.[0];
        return { workspaceId: firstWorkspace?.id ?? null };
    }
    catch {
        return null;
    }
}
const sessions = new Map();
// Clean up stale sessions every 30 minutes
setInterval(() => {
    const now = Date.now();
    const maxAge = 60 * 60 * 1000; // 1 hour
    for (const [id, session] of sessions) {
        if (now - session.createdAt > maxAge) {
            session.transport.close().catch(() => { });
            sessions.delete(id);
        }
    }
}, 30 * 60 * 1000);
function createSession(apiKey, keyInfo) {
    const transport = new WebStandardStreamableHTTPServerTransport({
        sessionIdGenerator: () => crypto.randomUUID(),
        enableJsonResponse: true,
    });
    const server = new Server({ name: "harmony-mcp-remote", version: "1.0.0" }, { capabilities: { tools: {}, resources: {} } });
    const session = {
        transport,
        server,
        apiKey,
        activeWorkspaceId: keyInfo.workspaceId,
        activeProjectId: null,
        createdAt: Date.now(),
    };
    // Create per-session deps
    const client = new HarmonyApiClient({ apiKey, apiUrl: HARMONY_API_URL });
    const deps = {
        getClient: () => client,
        isConfigured: () => true,
        getActiveProjectId: () => session.activeProjectId,
        getActiveWorkspaceId: () => session.activeWorkspaceId,
        setActiveProject: (id) => {
            session.activeProjectId = id;
        },
        setActiveWorkspace: (id) => {
            session.activeWorkspaceId = id;
        },
        getApiUrl: () => HARMONY_API_URL,
        getMemoryDir: () => null, // No local filesystem in remote mode
        getUserEmail: () => null,
        saveConfig: () => { }, // No-op in remote mode
        resetClient: () => { }, // No-op in remote mode
    };
    registerHandlers(server, deps);
    // Clean up session when transport closes
    transport.onclose = () => {
        if (transport.sessionId) {
            sessions.delete(transport.sessionId);
        }
    };
    return session;
}
// ---------------------------------------------------------------------------
// Hono app
// ---------------------------------------------------------------------------
const app = new Hono();
app.use("/*", cors({
    origin: "*",
    allowMethods: ["GET", "POST", "DELETE", "OPTIONS"],
    allowHeaders: [
        "Content-Type",
        "Authorization",
        "Mcp-Session-Id",
        "Mcp-Protocol-Version",
    ],
    exposeHeaders: ["Mcp-Session-Id"],
}));
// Health check
app.get("/health", (c) => c.json({
    status: "ok",
    service: "harmony-mcp-remote",
    sessions: sessions.size,
}));
// MCP endpoint - handles POST (JSON-RPC), GET (SSE), DELETE (session close)
app.all("/mcp", async (c) => {
    const method = c.req.method;
    // Extract API key from Authorization header
    const authHeader = c.req.header("Authorization");
    if (!authHeader?.startsWith("Bearer ")) {
        return c.json({ error: "Missing Authorization: Bearer <api-key>" }, 401);
    }
    const apiKey = authHeader.slice(7);
    // Check for existing session
    const sessionId = c.req.header("Mcp-Session-Id");
    if (sessionId && sessions.has(sessionId)) {
        // Existing session - forward request
        const session = sessions.get(sessionId);
        return session.transport.handleRequest(c.req.raw);
    }
    if (method === "POST") {
        // Could be a new session (initialize) or an existing session we don't know about
        // Validate API key
        const keyInfo = await validateApiKey(apiKey);
        if (!keyInfo) {
            return c.json({ error: "Invalid API key" }, 401);
        }
        // Create new session
        const session = createSession(apiKey, keyInfo);
        // Connect server to transport
        await session.server.connect(session.transport);
        // Store session once transport has a session ID
        const origOnSessionInitialized = session.transport._onsessioninitialized;
        session.transport._onsessioninitialized = (sid) => {
            sessions.set(sid, session);
            origOnSessionInitialized?.(sid);
        };
        // Handle the initialize request
        return session.transport.handleRequest(c.req.raw);
    }
    // GET or DELETE without a valid session
    return c.json({ error: "Invalid or missing session" }, 404);
});
// ---------------------------------------------------------------------------
// Start server
// ---------------------------------------------------------------------------
console.log(`Starting Harmony Remote MCP server on port ${PORT}...`);
serve({
    fetch: app.fetch,
    port: PORT,
});
console.log(`Harmony Remote MCP server running at http://localhost:${PORT}`);
console.log(`MCP endpoint: http://localhost:${PORT}/mcp`);
console.log(`Health check: http://localhost:${PORT}/health`);
