#!/usr/bin/env node
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import express from "express";
import cors from "cors";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";
import Database from "better-sqlite3";
import { validateInput, StartFrameSchema, AddAnchorSchema } from "./schemas.js";
import { readFileSync, existsSync, mkdirSync } from "fs";
import { randomUUID } from "crypto";
import { join, dirname } from "path";
import { execSync } from "child_process";
import { FrameManager } from "../../core/context/index.js";
import { logger } from "../../core/monitoring/logger.js";
import { isFeatureEnabled } from "../../core/config/feature-flags.js";
const DEFAULT_PORT = 3847;
class RemoteStackMemoryMCP {
  server;
  db;
  projectRoot;
  frameManager;
  taskStore = null;
  linearAuthManager = null;
  linearSync = null;
  projectId;
  contexts = /* @__PURE__ */ new Map();
  transport = null;
  constructor(projectRoot) {
    this.projectRoot = projectRoot || this.findProjectRoot();
    this.projectId = this.getProjectId();
    const dbDir = join(this.projectRoot, ".stackmemory");
    if (!existsSync(dbDir)) {
      mkdirSync(dbDir, { recursive: true });
    }
    const dbPath = join(dbDir, "context.db");
    this.db = new Database(dbPath);
    this.initDB();
    this.frameManager = new FrameManager(this.db, this.projectId);
    this.initLinearIfEnabled();
    this.server = new Server(
      {
        name: "stackmemory-remote",
        version: "0.1.0"
      },
      {
        capabilities: {
          tools: {}
        }
      }
    );
    this.setupHandlers();
    this.loadInitialContext();
    logger.info("StackMemory Remote MCP Server initialized", {
      projectRoot: this.projectRoot,
      projectId: this.projectId
    });
  }
  findProjectRoot() {
    let dir = process.cwd();
    while (dir !== "/") {
      if (existsSync(join(dir, ".git"))) {
        return dir;
      }
      dir = dirname(dir);
    }
    return process.cwd();
  }
  async initLinearIfEnabled() {
    if (!isFeatureEnabled("linear")) {
      return;
    }
    try {
      const { LinearTaskManager } = await import("../../features/tasks/linear-task-manager.js");
      const { LinearAuthManager } = await import("../linear/auth.js");
      const { LinearSyncEngine, DEFAULT_SYNC_CONFIG } = await import("../linear/sync.js");
      this.taskStore = new LinearTaskManager(this.projectRoot, this.db);
      this.linearAuthManager = new LinearAuthManager(this.projectRoot);
      this.linearSync = new LinearSyncEngine(
        this.taskStore,
        this.linearAuthManager,
        DEFAULT_SYNC_CONFIG
      );
    } catch (error) {
      logger.warn("Failed to initialize Linear integration", { error });
    }
  }
  initDB() {
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS contexts (
        id TEXT PRIMARY KEY,
        type TEXT NOT NULL,
        content TEXT NOT NULL,
        importance REAL DEFAULT 0.5,
        created_at INTEGER DEFAULT (unixepoch()),
        last_accessed INTEGER DEFAULT (unixepoch()),
        access_count INTEGER DEFAULT 1
      );

      CREATE TABLE IF NOT EXISTS attention_log (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        context_id TEXT,
        query TEXT,
        response TEXT,
        influence_score REAL,
        timestamp INTEGER DEFAULT (unixepoch())
      );
    `);
  }
  loadInitialContext() {
    const projectInfo = this.getProjectInfo();
    this.addContext(
      "project",
      `Project: ${projectInfo.name}
Path: ${projectInfo.path}`,
      0.9
    );
    try {
      const recentCommits = execSync("git log --oneline -10", {
        cwd: this.projectRoot
      }).toString();
      this.addContext("git_history", `Recent commits:
${recentCommits}`, 0.6);
    } catch {
    }
    const readmePath = join(this.projectRoot, "README.md");
    if (existsSync(readmePath)) {
      const readme = readFileSync(readmePath, "utf-8");
      const summary = readme.substring(0, 500);
      this.addContext("readme", `Project README:
${summary}...`, 0.8);
    }
    this.loadStoredContexts();
  }
  getProjectId() {
    let identifier;
    try {
      identifier = execSync("git config --get remote.origin.url", {
        cwd: this.projectRoot,
        stdio: "pipe",
        timeout: 5e3
      }).toString().trim();
    } catch {
      identifier = this.projectRoot;
    }
    const cleaned = identifier.replace(/\.git$/, "").replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
    return cleaned.substring(cleaned.length - 50) || "unknown";
  }
  getProjectInfo() {
    const packageJsonPath = join(this.projectRoot, "package.json");
    if (existsSync(packageJsonPath)) {
      const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
      return {
        name: pkg.name || "unknown",
        path: this.projectRoot
      };
    }
    return {
      name: this.projectRoot.split("/").pop() || "unknown",
      path: this.projectRoot
    };
  }
  addContext(type, content, importance = 0.5) {
    const id = `${type}_${Date.now()}`;
    this.db.prepare(
      `
      INSERT OR REPLACE INTO contexts (id, type, content, importance)
      VALUES (?, ?, ?, ?)
    `
    ).run(id, type, content, importance);
    this.contexts.set(id, { type, content, importance });
    return id;
  }
  loadStoredContexts() {
    const stored = this.db.prepare(
      `
      SELECT * FROM contexts
      ORDER BY importance DESC, last_accessed DESC
      LIMIT 50
    `
    ).all();
    stored.forEach((ctx) => {
      this.contexts.set(ctx.id, ctx);
    });
  }
  setupHandlers() {
    this.server.setRequestHandler(
      z.object({
        method: z.literal("tools/list")
      }),
      async () => {
        return {
          tools: [
            {
              name: "get_context",
              description: "Get current project context",
              inputSchema: {
                type: "object",
                properties: {
                  query: {
                    type: "string",
                    description: "What you want to know"
                  },
                  limit: {
                    type: "number",
                    description: "Max contexts to return"
                  }
                }
              }
            },
            {
              name: "add_decision",
              description: "Record a decision or important information",
              inputSchema: {
                type: "object",
                properties: {
                  content: {
                    type: "string",
                    description: "The decision or information"
                  },
                  type: {
                    type: "string",
                    enum: ["decision", "constraint", "learning"]
                  }
                },
                required: ["content", "type"]
              }
            },
            {
              name: "start_frame",
              description: "Start a new frame (task/subtask) on the call stack",
              inputSchema: {
                type: "object",
                properties: {
                  name: { type: "string", description: "Frame name/goal" },
                  type: {
                    type: "string",
                    enum: [
                      "task",
                      "subtask",
                      "tool_scope",
                      "review",
                      "write",
                      "debug"
                    ],
                    description: "Frame type"
                  },
                  constraints: {
                    type: "array",
                    items: { type: "string" },
                    description: "Constraints for this frame"
                  }
                },
                required: ["name", "type"]
              }
            },
            {
              name: "close_frame",
              description: "Close current frame and generate digest",
              inputSchema: {
                type: "object",
                properties: {
                  result: {
                    type: "string",
                    description: "Frame completion result"
                  },
                  outputs: {
                    type: "object",
                    description: "Final outputs from frame"
                  }
                }
              }
            },
            {
              name: "add_anchor",
              description: "Add anchored fact/decision/constraint to current frame",
              inputSchema: {
                type: "object",
                properties: {
                  type: {
                    type: "string",
                    enum: [
                      "FACT",
                      "DECISION",
                      "CONSTRAINT",
                      "INTERFACE_CONTRACT",
                      "TODO",
                      "RISK"
                    ],
                    description: "Anchor type"
                  },
                  text: { type: "string", description: "Anchor content" },
                  priority: {
                    type: "number",
                    description: "Priority (0-10)",
                    minimum: 0,
                    maximum: 10
                  }
                },
                required: ["type", "text"]
              }
            },
            {
              name: "get_hot_stack",
              description: "Get current active frames and context",
              inputSchema: {
                type: "object",
                properties: {
                  maxEvents: {
                    type: "number",
                    description: "Max recent events per frame",
                    default: 20
                  }
                }
              }
            },
            {
              name: "sm_search",
              description: "Search across StackMemory - frames, events, decisions, tasks",
              inputSchema: {
                type: "object",
                properties: {
                  query: { type: "string", description: "Search query" },
                  scope: {
                    type: "string",
                    enum: ["all", "frames", "events", "decisions", "tasks"],
                    description: "Scope of search"
                  },
                  limit: { type: "number", description: "Maximum results" }
                },
                required: ["query"]
              }
            }
          ]
        };
      }
    );
    this.server.setRequestHandler(
      z.object({
        method: z.literal("tools/call"),
        params: z.object({
          name: z.string(),
          arguments: z.record(z.unknown())
        })
      }),
      async (request) => {
        const { name, arguments: args } = request.params;
        try {
          switch (name) {
            case "get_context":
              return this.handleGetContext(args);
            case "add_decision":
              return this.handleAddDecision(args);
            case "start_frame":
              return this.handleStartFrame(args);
            case "close_frame":
              return this.handleCloseFrame(args);
            case "add_anchor":
              return this.handleAddAnchor(args);
            case "get_hot_stack":
              return this.handleGetHotStack(args);
            case "sm_search":
              return this.handleSmSearch(args);
            default:
              throw new Error(`Unknown tool: ${name}`);
          }
        } catch (error) {
          return {
            content: [{ type: "text", text: `Error: ${error.message}` }]
          };
        }
      }
    );
  }
  async handleGetContext(args) {
    const { query = "", limit = 10 } = args;
    const contexts = Array.from(this.contexts.values()).sort((a, b) => b.importance - a.importance).slice(0, limit);
    const response = contexts.map(
      (ctx) => `[${ctx.type.toUpperCase()}] (importance: ${ctx.importance.toFixed(2)})
${ctx.content}`
    ).join("\n\n---\n\n");
    return {
      content: [
        {
          type: "text",
          text: response || "No context available yet."
        }
      ]
    };
  }
  async handleAddDecision(args) {
    const { content, type = "decision" } = args;
    const id = this.addContext(type, content, 0.8);
    return {
      content: [
        {
          type: "text",
          text: `Added ${type}: ${content}
ID: ${id}`
        }
      ]
    };
  }
  async handleStartFrame(args) {
    const { name, type, constraints } = validateInput(
      StartFrameSchema,
      args,
      "start_frame"
    );
    const inputs = {};
    if (constraints) {
      inputs.constraints = constraints;
    }
    const frameId = this.frameManager.createFrame({
      type,
      name,
      inputs
    });
    this.addContext("active_frame", `Active frame: ${name} (${type})`, 0.9);
    return {
      content: [
        {
          type: "text",
          text: `Started ${type}: ${name}
Frame ID: ${frameId}
Stack depth: ${this.frameManager.getStackDepth()}`
        }
      ]
    };
  }
  async handleCloseFrame(args) {
    const { result, outputs } = args;
    const currentFrameId = this.frameManager.getCurrentFrameId();
    if (!currentFrameId) {
      return {
        content: [{ type: "text", text: "No active frame to close" }]
      };
    }
    this.frameManager.closeFrame(currentFrameId, outputs);
    return {
      content: [
        {
          type: "text",
          text: `Closed frame: ${result || "completed"}
Stack depth: ${this.frameManager.getStackDepth()}`
        }
      ]
    };
  }
  async handleAddAnchor(args) {
    const { type, text, priority } = validateInput(
      AddAnchorSchema,
      args,
      "add_anchor"
    );
    const anchorId = this.frameManager.addAnchor(type, text, priority);
    return {
      content: [
        {
          type: "text",
          text: `Added ${type}: ${text}
Anchor ID: ${anchorId}`
        }
      ]
    };
  }
  async handleGetHotStack(args) {
    const { maxEvents = 20 } = args;
    const hotStack = this.frameManager.getHotStackContext(maxEvents);
    const activePath = this.frameManager.getActiveFramePath();
    if (hotStack.length === 0) {
      return {
        content: [
          {
            type: "text",
            text: "No active frames. Start a frame with start_frame tool."
          }
        ]
      };
    }
    let response = "Active Call Stack:\n\n";
    activePath.forEach((frame, index) => {
      const indent = "  ".repeat(index);
      const context = hotStack[index];
      response += `${indent}${index + 1}. ${frame.name} (${frame.type})
`;
      if (context?.anchors?.length > 0) {
        response += `${indent}   Anchors: ${context.anchors.length}
`;
      }
      if (context?.recentEvents?.length > 0) {
        response += `${indent}   Events: ${context.recentEvents.length}
`;
      }
      response += "\n";
    });
    response += `Total stack depth: ${hotStack.length}`;
    return {
      content: [{ type: "text", text: response }]
    };
  }
  async handleSmSearch(args) {
    const { query, scope = "all", limit = 20 } = args;
    if (!query) {
      throw new Error("Query is required");
    }
    const results = [];
    if (scope === "all" || scope === "frames") {
      const frames = this.db.prepare(
        `
        SELECT frame_id, name, type, created_at
        FROM frames
        WHERE project_id = ? AND (name LIKE ? OR inputs LIKE ? OR outputs LIKE ?)
        ORDER BY created_at DESC
        LIMIT ?
      `
      ).all(
        this.projectId,
        `%${query}%`,
        `%${query}%`,
        `%${query}%`,
        limit
      );
      frames.forEach((f) => {
        results.push({
          type: "frame",
          id: f.frame_id,
          name: f.name,
          frameType: f.type
        });
      });
    }
    if (scope === "all" || scope === "decisions") {
      const anchors = this.db.prepare(
        `
        SELECT a.anchor_id, a.type, a.text, f.name as frame_name
        FROM anchors a
        JOIN frames f ON a.frame_id = f.frame_id
        WHERE f.project_id = ? AND a.text LIKE ?
        ORDER BY a.created_at DESC
        LIMIT ?
      `
      ).all(this.projectId, `%${query}%`, limit);
      anchors.forEach((a) => {
        results.push({
          type: "decision",
          id: a.anchor_id,
          decisionType: a.type,
          text: a.text,
          frame: a.frame_name
        });
      });
    }
    let response = `Search Results for "${query}"

`;
    response += `Found ${results.length} results

`;
    results.slice(0, 10).forEach((r) => {
      if (r.type === "frame") {
        response += `[Frame] ${r.name} (${r.frameType})
`;
      } else if (r.type === "decision") {
        response += `[${r.decisionType}] ${r.text.slice(0, 60)}...
`;
      }
    });
    return {
      content: [{ type: "text", text: response }]
    };
  }
  /**
   * Start the HTTP/SSE server
   */
  async startHttpServer(port = DEFAULT_PORT) {
    const app = express();
    app.use(
      cors({
        origin: [
          "https://claude.ai",
          "https://console.anthropic.com",
          /^http:\/\/localhost:\d+$/
        ],
        credentials: true
      })
    );
    app.use(express.json());
    app.get("/health", (req, res) => {
      res.json({
        status: "ok",
        server: "stackmemory-remote",
        projectId: this.projectId,
        projectRoot: this.projectRoot
      });
    });
    this.transport = new StreamableHTTPServerTransport({
      sessionIdGenerator: () => randomUUID()
    });
    await this.server.connect(this.transport);
    app.all("/mcp", async (req, res) => {
      logger.info("MCP request", { method: req.method });
      try {
        await this.transport.handleRequest(req, res, req.body);
      } catch (error) {
        logger.error("MCP request error", { error });
        if (!res.headersSent) {
          res.status(500).json({ error: "Internal server error" });
        }
      }
    });
    app.get("/sse", (req, res) => {
      res.redirect(307, "/mcp");
    });
    app.post("/message", (req, res) => {
      res.redirect(307, "/mcp");
    });
    app.get("/info", (req, res) => {
      res.json({
        name: "stackmemory-remote",
        version: "0.2.0",
        protocol: "mcp",
        transport: "streamable-http",
        endpoints: {
          mcp: "/mcp",
          health: "/health",
          info: "/info"
        },
        project: {
          id: this.projectId,
          root: this.projectRoot,
          name: this.getProjectInfo().name
        }
      });
    });
    return new Promise((resolve) => {
      app.listen(port, () => {
        console.log(
          `StackMemory Remote MCP Server running on http://localhost:${port}`
        );
        console.log(`
Endpoints:`);
        console.log(`  MCP:     http://localhost:${port}/mcp`);
        console.log(`  Health:  http://localhost:${port}/health`);
        console.log(`  Info:    http://localhost:${port}/info`);
        console.log(
          `
Project: ${this.getProjectInfo().name} (${this.projectId})`
        );
        console.log(
          `
For Claude.ai connector, use: http://localhost:${port}/sse`
        );
        resolve();
      });
    });
  }
}
var remote_server_default = RemoteStackMemoryMCP;
async function runRemoteMCPServer(port = DEFAULT_PORT, projectRoot) {
  const server = new RemoteStackMemoryMCP(projectRoot);
  await server.startHttpServer(port);
}
if (import.meta.url === `file://${process.argv[1]}`) {
  const port = parseInt(
    process.env.PORT || process.argv[2] || String(DEFAULT_PORT),
    10
  );
  const projectRoot = process.argv[3] || process.cwd();
  runRemoteMCPServer(port, projectRoot).catch((error) => {
    console.error("Failed to start remote MCP server:", error);
    process.exit(1);
  });
}
export {
  remote_server_default as default,
  runRemoteMCPServer
};
//# sourceMappingURL=remote-server.js.map
