@stackmemoryai/stackmemory
Version:
Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a
1,420 lines (1,418 loc) • 110 kB
JavaScript
#!/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 { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import Database from "better-sqlite3";
import {
validateInput,
StartFrameSchema,
AddAnchorSchema,
CreateTaskSchema
} from "./schemas.js";
import {
readFileSync,
readdirSync,
existsSync,
mkdirSync,
writeFileSync,
appendFileSync
} from "fs";
import { homedir } from "os";
import { compactPlan } from "../../orchestrators/multimodal/utils.js";
import { filterPending } from "./pending-utils.js";
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";
import { BrowserMCPIntegration } from "../../features/browser/browser-mcp.js";
import { TraceDetector } from "../../core/trace/trace-detector.js";
import { LLMContextRetrieval } from "../../core/retrieval/index.js";
import { DiscoveryHandlers } from "./handlers/discovery-handlers.js";
import { DiffMemHandlers } from "./handlers/diffmem-handlers.js";
import { GreptileHandlers } from "./handlers/greptile-handlers.js";
import { CordHandlers } from "./handlers/cord-handlers.js";
import { TeamHandlers } from "./handlers/team-handlers.js";
import { SQLiteAdapter } from "../../core/database/sqlite-adapter.js";
import {
generateChronologicalDigest
} from "../../core/digest/chronological-digest.js";
import { fuzzyEdit } from "../../utils/fuzzy-edit.js";
import { v4 as uuidv4 } from "uuid";
import {
DEFAULT_PLANNER_MODEL,
DEFAULT_IMPLEMENTER,
DEFAULT_MAX_ITERS
} from "../../orchestrators/multimodal/constants.js";
function _getEnv(key, defaultValue) {
const value = process.env[key];
if (value === void 0) {
if (defaultValue !== void 0) return defaultValue;
throw new Error(`Environment variable ${key} is required`);
}
return value;
}
function _getOptionalEnv(key) {
return process.env[key];
}
class LocalStackMemoryMCP {
server;
db;
projectRoot;
frameManager;
taskStore = null;
linearAuthManager = null;
linearSync = null;
projectId;
contexts = /* @__PURE__ */ new Map();
browserMCP;
traceDetector;
contextRetrieval;
discoveryHandlers;
diffMemHandlers;
greptileHandlers;
providerHandlers = null;
cordHandlers = null;
teamHandlers = null;
pendingPlans = /* @__PURE__ */ new Map();
constructor() {
this.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.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())
);
CREATE TABLE IF NOT EXISTS edit_telemetry (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp INTEGER NOT NULL DEFAULT (unixepoch()),
session_id TEXT,
tool_name TEXT NOT NULL,
file_path TEXT,
success INTEGER NOT NULL DEFAULT 1,
error_type TEXT,
error_message TEXT
);
CREATE INDEX IF NOT EXISTS idx_edit_telemetry_ts ON edit_telemetry(timestamp);
`);
this.frameManager = new FrameManager(this.db, this.projectId);
this.initLinearIfEnabled();
this.server = new Server(
{
name: "stackmemory-local",
version: "0.1.0"
},
{
capabilities: {
tools: {}
}
}
);
this.browserMCP = new BrowserMCPIntegration({
headless: process.env["BROWSER_HEADLESS"] !== "false",
defaultViewport: { width: 1280, height: 720 }
});
this.traceDetector = new TraceDetector({}, void 0, this.db);
this.contextRetrieval = new LLMContextRetrieval(
this.db,
this.frameManager,
this.projectId
);
this.discoveryHandlers = new DiscoveryHandlers({
frameManager: this.frameManager,
contextRetrieval: this.contextRetrieval,
db: this.db,
projectRoot: this.projectRoot
});
this.diffMemHandlers = new DiffMemHandlers();
this.greptileHandlers = new GreptileHandlers();
this.initCordTeamHandlers();
this.initProviderHandlers();
this.setupHandlers();
this.loadInitialContext();
this.loadPendingPlans();
this.browserMCP.initialize(this.server).catch((error) => {
logger.error("Failed to initialize Browser MCP", error);
});
import("../../core/storage/obsidian-vault-adapter.js").then(({ initObsidianVault }) => initObsidianVault()).catch(() => {
});
logger.info("StackMemory 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();
}
/**
* Initialize Linear integration if enabled and credentials available
*/
async initLinearIfEnabled() {
if (!isFeatureEnabled("linear")) {
logger.info("Linear integration disabled (no API key or LOCAL mode)");
return;
}
try {
const { LinearTaskManager } = await import("../../features/tasks/linear-task-manager.js");
const { LinearAuthManager } = await import("../linear/auth.js");
const { LinearSyncEngine, DEFAULT_SYNC_CONFIG: DEFAULT_SYNC_CONFIG2 } = 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_CONFIG2
);
logger.info("Linear integration initialized");
} catch (error) {
logger.warn("Failed to initialize Linear integration", { error });
}
}
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);
});
}
async initCordTeamHandlers() {
try {
const dbPath = join(this.projectRoot, ".stackmemory", "context.db");
const adapter = new SQLiteAdapter(this.projectId, {
dbPath,
walMode: true
});
await adapter.connect();
this.cordHandlers = new CordHandlers({
frameManager: this.frameManager,
dbAdapter: adapter
});
this.teamHandlers = new TeamHandlers({
frameManager: this.frameManager,
dbAdapter: adapter
});
logger.info("Cord and Team handlers initialized");
} catch (error) {
logger.warn("Failed to initialize Cord/Team handlers", { error });
}
}
async initProviderHandlers() {
if (!isFeatureEnabled("multiProvider")) return;
try {
const { ProviderHandlers } = await import("./handlers/provider-handlers.js");
this.providerHandlers = new ProviderHandlers();
logger.info("Provider handlers initialized (multiProvider enabled)");
} catch (error) {
logger.warn("Failed to initialize provider handlers", { error });
}
}
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"
}
}
}
},
// Planning tools (only when ANTHROPIC_API_KEY is set)
...process.env.ANTHROPIC_API_KEY ? [
{
name: "plan_gate",
description: "Phase 1: Generate a plan and return an approvalId for later execution",
inputSchema: {
type: "object",
properties: {
task: {
type: "string",
description: "Task description"
},
plannerModel: {
type: "string",
description: "Claude model (optional)"
}
},
required: ["task"]
}
},
{
name: "approve_plan",
description: "Phase 2: Execute a previously generated plan by approvalId",
inputSchema: {
type: "object",
properties: {
approvalId: {
type: "string",
description: "Id from plan_gate"
},
implementer: {
type: "string",
enum: ["codex", "claude"],
default: "codex",
description: "Which agent implements code"
},
maxIters: { type: "number", default: 2 },
recordFrame: { type: "boolean", default: true },
execute: { type: "boolean", default: true }
},
required: ["approvalId"]
}
},
{
name: "pending_list",
description: "List pending approval-gated plans (supports filters)",
inputSchema: {
type: "object",
properties: {
taskContains: {
type: "string",
description: "Filter tasks containing this substring"
},
olderThanMs: {
type: "number",
description: "Only items older than this age (ms)"
},
newerThanMs: {
type: "number",
description: "Only items newer than this age (ms)"
},
sort: {
type: "string",
enum: ["asc", "desc"],
description: "Sort by createdAt"
},
limit: {
type: "number",
description: "Max items to return"
}
}
}
},
{
name: "pending_clear",
description: "Clear pending approval-gated plans (by id, all, or olderThanMs)",
inputSchema: {
type: "object",
properties: {
approvalId: {
type: "string",
description: "Clear a single approval by id"
},
all: {
type: "boolean",
description: "Clear all pending approvals",
default: false
},
olderThanMs: {
type: "number",
description: "Clear approvals older than this age (ms)"
}
}
}
},
{
name: "pending_show",
description: "Show a pending plan by approvalId",
inputSchema: {
type: "object",
properties: {
approvalId: {
type: "string",
description: "Approval id from plan_gate"
}
},
required: ["approvalId"]
}
},
{
name: "plan_only",
description: "Generate an implementation plan (Claude) and return JSON only",
inputSchema: {
type: "object",
properties: {
task: {
type: "string",
description: "Task description"
},
plannerModel: {
type: "string",
description: "Claude model for planning (optional)"
}
},
required: ["task"]
}
},
{
name: "call_codex",
description: "Invoke Codex via codex-sm with a prompt and args; dry-run by default",
inputSchema: {
type: "object",
properties: {
prompt: {
type: "string",
description: "Prompt for Codex"
},
args: {
type: "array",
items: { type: "string" },
description: "Additional CLI args for codex-sm"
},
execute: {
type: "boolean",
default: false,
description: "Actually run codex-sm (otherwise dry-run)"
}
},
required: ["prompt"]
}
},
{
name: "call_claude",
description: "Invoke Claude with a prompt (Anthropic SDK)",
inputSchema: {
type: "object",
properties: {
prompt: {
type: "string",
description: "Prompt for Claude"
},
model: {
type: "string",
description: "Claude model (optional)"
},
system: {
type: "string",
description: "System prompt (optional)"
}
},
required: ["prompt"]
}
}
] : [],
{
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: "create_task",
description: "Create a new task in git-tracked JSONL storage",
inputSchema: {
type: "object",
properties: {
title: { type: "string", description: "Task title" },
description: {
type: "string",
description: "Task description"
},
priority: {
type: "string",
enum: ["low", "medium", "high", "urgent"],
description: "Task priority"
},
estimatedEffort: {
type: "number",
description: "Estimated effort in minutes"
},
dependsOn: {
type: "array",
items: { type: "string" },
description: "Task IDs this depends on"
},
tags: {
type: "array",
items: { type: "string" },
description: "Tags for categorization"
}
},
required: ["title"]
}
},
{
name: "update_task_status",
description: "Update task status with automatic time tracking",
inputSchema: {
type: "object",
properties: {
taskId: { type: "string", description: "Task ID to update" },
status: {
type: "string",
enum: [
"pending",
"in_progress",
"completed",
"blocked",
"cancelled"
],
description: "New status"
},
reason: {
type: "string",
description: "Reason for status change (especially for blocked)"
}
},
required: ["taskId", "status"]
}
},
{
name: "get_active_tasks",
description: "Get currently active tasks synced from Linear",
inputSchema: {
type: "object",
properties: {
frameId: {
type: "string",
description: "Filter by specific frame ID"
},
status: {
type: "string",
enum: [
"pending",
"in_progress",
"completed",
"blocked",
"cancelled"
],
description: "Filter by status"
},
priority: {
type: "string",
enum: ["low", "medium", "high", "urgent"],
description: "Filter by priority"
},
search: {
type: "string",
description: "Search in task title or description"
},
limit: {
type: "number",
description: "Max number of tasks to return (default: 20)"
}
}
}
},
{
name: "get_task_metrics",
description: "Get project task metrics and analytics",
inputSchema: {
type: "object",
properties: {}
}
},
{
name: "linear_sync",
description: "Sync tasks with Linear",
inputSchema: {
type: "object",
properties: {
direction: {
type: "string",
enum: ["bidirectional", "to_linear", "from_linear"],
description: "Sync direction"
}
}
}
},
{
name: "linear_update_task",
description: "Update a Linear task status",
inputSchema: {
type: "object",
properties: {
issueId: {
type: "string",
description: "Linear issue ID or identifier (e.g., STA-34)"
},
status: {
type: "string",
enum: ["todo", "in-progress", "done", "canceled"],
description: "New status for the task"
},
title: {
type: "string",
description: "Update task title (optional)"
},
description: {
type: "string",
description: "Update task description (optional)"
},
priority: {
type: "number",
enum: [1, 2, 3, 4],
description: "Priority (1=urgent, 2=high, 3=medium, 4=low)"
}
},
required: ["issueId"]
}
},
{
name: "linear_get_tasks",
description: "Get Linear tasks",
inputSchema: {
type: "object",
properties: {
status: {
type: "string",
enum: ["todo", "in-progress", "done", "all"],
description: "Filter by status"
},
limit: {
type: "number",
description: "Maximum number of tasks to return"
}
}
}
},
{
name: "linear_status",
description: "Get Linear integration status",
inputSchema: {
type: "object",
properties: {}
}
},
{
name: "get_traces",
description: "Get detected traces (bundled tool call sequences)",
inputSchema: {
type: "object",
properties: {
type: {
type: "string",
enum: [
"search_driven",
"error_recovery",
"feature_implementation",
"refactoring",
"testing",
"exploration",
"debugging",
"documentation",
"build_deploy",
"unknown"
],
description: "Filter by trace type"
},
minScore: {
type: "number",
description: "Minimum importance score (0-1)"
},
limit: {
type: "number",
description: "Maximum number of traces to return"
}
}
}
},
{
name: "get_trace_statistics",
description: "Get statistics about detected traces",
inputSchema: {
type: "object",
properties: {}
}
},
{
name: "flush_traces",
description: "Flush any pending trace and finalize detection",
inputSchema: {
type: "object",
properties: {}
}
},
{
name: "compress_old_traces",
description: "Compress traces older than specified hours",
inputSchema: {
type: "object",
properties: {
ageHours: {
type: "number",
description: "Age threshold in hours (default: 24)"
}
}
}
},
{
name: "smart_context",
description: "LLM-driven context retrieval - intelligently selects relevant frames based on query",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Natural language query describing what context you need"
},
tokenBudget: {
type: "number",
description: "Maximum tokens to use for context (default: 4000)"
},
forceRefresh: {
type: "boolean",
description: "Force refresh of cached summaries"
}
},
required: ["query"]
}
},
{
name: "get_summary",
description: "Get compressed summary of project memory for analysis",
inputSchema: {
type: "object",
properties: {
forceRefresh: {
type: "boolean",
description: "Force refresh of cached summary"
}
}
}
},
// Discovery tools
{
name: "sm_discover",
description: "Discover relevant files based on current context. Extracts keywords from active frames and searches codebase.",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Optional query to focus the discovery"
},
depth: {
type: "string",
enum: ["shallow", "medium", "deep"],
description: "Search depth"
},
maxFiles: {
type: "number",
description: "Maximum files to return"
}
}
}
},
{
name: "sm_related_files",
description: "Find files related to a specific file or concept",
inputSchema: {
type: "object",
properties: {
file: {
type: "string",
description: "File path to find related files for"
},
concept: {
type: "string",
description: "Concept to search for"
},
maxFiles: {
type: "number",
description: "Maximum files to return"
}
}
}
},
{
name: "sm_session_summary",
description: "Get summary of current session with active tasks, files, and decisions",
inputSchema: {
type: "object",
properties: {
includeFiles: {
type: "boolean",
description: "Include recently accessed files"
},
includeDecisions: {
type: "boolean",
description: "Include recent decisions"
}
}
}
},
{
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"]
}
},
// DiffMem tools (only when DIFFMEM_ENDPOINT or DIFFMEM_ENABLED is set)
...process.env.DIFFMEM_ENDPOINT || process.env.DIFFMEM_ENABLED === "true" ? this.diffMemHandlers.getToolDefinitions() : [],
// Cord task orchestration tools
{
name: "cord_spawn",
description: "Create a subtask with clean context (spawn). Child sees only its prompt and completed blocker results.",
inputSchema: {
type: "object",
properties: {
goal: {
type: "string",
description: "What this task should accomplish"
},
prompt: {
type: "string",
description: "Detailed instructions for the task"
},
blocked_by: {
type: "array",
items: { type: "string" },
description: "Task IDs that must complete first"
},
parent_id: { type: "string", description: "Parent task ID" }
},
required: ["goal"]
}
},
{
name: "cord_fork",
description: "Create a subtask with full sibling context (fork). Child sees prompt, blocker results, AND completed sibling results.",
inputSchema: {
type: "object",
properties: {
goal: {
type: "string",
description: "What this task should accomplish"
},
prompt: {
type: "string",
description: "Detailed instructions for the task"
},
blocked_by: {
type: "array",
items: { type: "string" },
description: "Task IDs that must complete first"
},
parent_id: { type: "string", description: "Parent task ID" }
},
required: ["goal"]
}
},
{
name: "cord_complete",
description: "Mark a cord task as completed with a result. Automatically unblocks dependent tasks.",
inputSchema: {
type: "object",
properties: {
task_id: {
type: "string",
description: "Task ID to complete"
},
result: {
type: "string",
description: "The result/output of this task"
}
},
required: ["task_id", "result"]
}
},
{
name: "cord_ask",
description: "Create an ask task \u2014 a question that needs an answer before dependent tasks can proceed.",
inputSchema: {
type: "object",
properties: {
question: {
type: "string",
description: "The question to ask"
},
options: {
type: "array",
items: { type: "string" },
description: "Optional list of answer choices"
},
parent_id: { type: "string", description: "Parent task ID" }
},
required: ["question"]
}
},
{
name: "cord_tree",
description: "View the cord task tree with context scoping. Shows active, blocked, or completed tasks.",
inputSchema: {
type: "object",
properties: {
task_id: {
type: "string",
description: "Root task ID to show subtree (omit for full tree)"
},
include_results: {
type: "boolean",
default: false,
description: "Include task results in output"
}
}
}
},
// Team collaboration tools
{
name: "team_context_get",
description: "Get context from other agents working on the same project. Returns recent frames and shared anchors.",
inputSchema: {
type: "object",
properties: {
limit: {
type: "number",
default: 10,
description: "Max frames to return"
},
types: {
type: "array",
items: { type: "string" },
description: "Filter by frame types"
},
since: {
type: "number",
description: "Only frames created after this timestamp (epoch ms)"
}
}
}
},
{
name: "team_context_share",
description: "Share a piece of context with other agents. Creates a high-priority anchor visible to team_context_get.",
inputSchema: {
type: "object",
properties: {
content: {
type: "string",
description: "The context to share"
},
type: {
type: "string",
enum: [
"FACT",
"DECISION",
"CONSTRAINT",
"INTERFACE_CONTRACT",
"TODO",
"RISK"
],
default: "FACT",
description: "Type of context"
},
priority: {
type: "number",
minimum: 1,
maximum: 10,
default: 8,
description: "Priority level (1-10)"
}
},
required: ["content"]
}
},
{
name: "team_search",
description: "Search across all agents' context in the project. Uses full-text search across all sessions.",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Search query" },
limit: {
type: "number",
default: 20,
description: "Maximum results to return"
},
include_events: {
type: "boolean",
default: false,
description: "Include events in results"
}
},
required: ["query"]
}
},
// Greptile tools (only active when GREPTILE_API_KEY is set)
...process.env.GREPTILE_API_KEY ? this.greptileHandlers.getToolDefinitions() : [],
// Provider tools (only active when STACKMEMORY_MULTI_PROVIDER=true)
...isFeatureEnabled("multiProvider") ? [
{
name: "delegate_to_model",
description: "Route a prompt to a specific provider/model. Uses smart cost-based routing by default.",
inputSchema: {
type: "object",
properties: {
prompt: {
type: "string",
description: "The prompt to send"
},
provider: {
type: "string",
enum: [
"anthropic",
"cerebras",
"deepinfra",
"openai",
"openrouter"
],
description: "Override provider (auto-routes if omitted)"
},
model: {
type: "string",
description: "Override model name"
},
taskType: {
type: "string",
enum: [
"linting",
"context",
"code",
"testing",
"review",
"plan"
],
description: "Task type for auto-routing"
},
maxTokens: {
type: "number",
description: "Max tokens"
},
temperature: { type: "number" },
system: {
type: "string",
description: "System prompt"
}
},
required: ["prompt"]
}
},
{
name: "batch_submit",
description: "Submit prompts to Anthropic Batch API (50% discount, async)",
inputSchema: {
type: "object",
properties: {
prompts: {
type: "array",
items: {
type: "object",
properties: {
id: { type: "string" },
prompt: { type: "string" },
model: { type: "string" },
maxTokens: { type: "number" },
system: { type: "string" }
},
required: ["id", "prompt"]
},
description: "Array of prompts to batch"
},
description: {
type: "string",
description: "Batch job description"
}
},
required: ["prompts"]
}
},
{
name: "batch_check",
description: "Check status or retrieve results for a batch job",
inputSchema: {
type: "object",
properties: {
batchId: {
type: "string",
description: "Batch job ID"
},
retrieve: {
type: "boolean",
description: "Retrieve results if complete",
default: false
}
},
required: ["batchId"]
}
}
] : [],
// Digest tool
{
name: "sm_digest",
description: "Generate a chronological activity digest for a time period (today/yesterday/week)",
inputSchema: {
type: "object",
properties: {
period: {
type: "string",
enum: ["today", "yesterday", "week"],
description: "Time period for the digest"
}
},
required: ["period"]
}
}
]
};
}
);
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;
const callId = uuidv4();
const startTime = Date.now();
const currentFrameId = this.frameManager.getCurrentFrameId();
if (currentFrameId) {
this.frameManager.addEvent("tool_call", {
tool_name: name,
arguments: args,
timestamp: startTime
});
}
const toolCall = {
id: callId,
tool: name,
arguments: args,
timestamp: startTime
};
let result;
let error;
try {
switch (name) {
case "get_context":
result = await this.handleGetContext(args);
break;
case "add_decision":
result = await this.handleAddDecision(args);
break;
case "start_frame":
result = await this.handleStartFrame(args);
break;
case "close_frame":
result = await this.handleCloseFrame(args);
break;
case "add_anchor":
result = await this.handleAddAnchor(args);
break;
case "get_hot_stack":
result = await this.handleGetHotStack(args);
break;
case "create_task":
result = await this.handleCreateTask(args);
break;
case "update_task_status":
result = await this.handleUpdateTaskStatus(args);
break;
case "get_active_tasks":
result = await this.handleGetActiveTasks(args);
break;
case "get_task_metrics":
result = await this.handleGetTaskMetrics(args);
break;
case "linear_sync":
result = await this.handleLinearSync(args);
break;
case "linear_update_task":
result = await this.handleLinearUpdateTask(args);
break;
case "linear_get_tasks":
result = await this.handleLinearGetTasks(args);
break;
case "linear_status":
result = await this.handleLinearStatus(args);
break;
case "linear_create_comment":
result = await this.handleLinearCreateComment(args);
break;
case "linear_update_comment":
result = await this.handleLinearUpdateComment(args);
break;
case "linear_list_comments":
result = await this.handleLinearListComments(args);
break;
case "get_traces":
result = await this.handleGetTraces(args);
break;
case "get_trace_statistics":
result = await this.handleGetTraceStatistics(args);
break;
case "flush_traces":
result = await this.handleFlushTraces(args);
break;
case "compress_old_traces":
result = await this.handleCompressOldTraces(args);
break;
case "plan_only":
result = await this.handlePlanOnly(args);
break;
case "call_codex":
result = await this.handleCallCodex(args);
break;
case "call_claude":
result = await this.handleCallClaude(args);
break;
case "plan_gate":
result = await this.handlePlanGate(args);
break;
case "approve_plan":
result = await this.handleApprovePlan(args);
break;
case "pending_list":
result = await this.handlePendingList();
break;
case "pending_clear":
result = await this.handlePendingClear(args);
break;
case "pending_show":
result = await this.handlePendingShow(args);
break;
case "smart_context":
result = await this.handleSmartContext(args);
break;
case "get_summary":
result = await this.handleGetSummary(args);
break;
// Discovery tools
case "sm_discover":
result = await this.handleSmDiscover(args);
break;
case "sm_related_files":
result = await this.handleSmRelatedFiles(args);
break;
case "sm_session_summary":
result = await this.handleSmSessionSummary(args);
break;
case "sm_search":
result = await this.handleSmSearch(args);
break;
// DiffMem handlers
case "diffmem_get_user_context":
result = await this.diffMemHandlers.hand