feat: add HTTP transport (CF-3081)

This commit is contained in:
Infra
2026-04-13 09:34:39 +00:00
parent 1349f3b0ce
commit 18defcc9d5
7 changed files with 609 additions and 3376 deletions

5
.dockerignore Normal file
View File

@@ -0,0 +1,5 @@
node_modules
dist
.env
.git
*.log

15
Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json tsconfig.json ./
RUN npm install
COPY src ./src
RUN npm run build
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm install --omit=dev && npm cache clean --force
COPY --from=build /app/dist ./dist
USER node
EXPOSE 9216
CMD ["node", "dist/http-server.js"]

2811
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "session-mcp",
"version": "1.0.0",
"version": "1.1.0",
"description": "MCP server for session/memory/archive management with PostgreSQL/pgvector. Forked from task-mcp (CF-762).",
"main": "dist/index.js",
"type": "module",
@@ -8,19 +8,22 @@
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx src/index.ts",
"clean": "rm -rf dist"
"clean": "rm -rf dist",
"start:http": "node dist/http-server.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4",
"@sentry/node": "^10.39.0",
"@sentry/profiling-node": "^10.39.0",
"dotenv": "^17.2.3",
"pg": "^8.11.3"
"pg": "^8.11.3",
"express": "^4.19.2"
},
"devDependencies": {
"@types/node": "^20.11.0",
"@types/pg": "^8.10.9",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
"typescript": "^5.3.3",
"@types/express": "^4.17.21"
}
}

44
src/http-server.ts Normal file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env node
import dotenv from "dotenv";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
dotenv.config({ path: join(__dirname, "..", ".env"), override: true });
import { initSentry } from "./sentry.js";
initSentry(process.env.SENTRY_ENVIRONMENT || "production");
import express from "express";
import { randomUUID } from "crypto";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { testConnection, close } from "./db.js";
import { createServer } from "./server.js";
const PORT = parseInt(process.env.MCP_HTTP_PORT || "9216");
const HOST = process.env.MCP_HTTP_HOST || "0.0.0.0";
const transports = new Map<string, StreamableHTTPServerTransport>();
const sessionServers = new Map<string, Server>();
const app = express();
app.use(express.json());
app.post("/mcp", async (req, res) => {
try {
const sid0 = req.headers["mcp-session-id"] as string | undefined;
if (sid0 && transports.has(sid0)) { await transports.get(sid0)!.handleRequest(req, res, req.body); return; }
const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (sid) => { transports.set(sid, transport); },
});
transport.onclose = () => { const sid = transport.sessionId; if (sid) { transports.delete(sid); sessionServers.delete(sid); } };
const ss = createServer(); await ss.connect(transport);
const sid = transport.sessionId; if (sid) sessionServers.set(sid, ss);
await transport.handleRequest(req, res, req.body);
} catch (err) { console.error("[session-mcp]", err); if (!res.headersSent) res.status(500).json({ error: "Internal" }); }
});
app.get("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"] as string|undefined; if(!sid||!transports.has(sid)){res.status(400).json({error:"bad"});return;} await transports.get(sid)!.handleRequest(req,res); });
app.delete("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"] as string|undefined; if(!sid||!transports.has(sid)){res.status(400).json({error:"bad"});return;} await transports.get(sid)!.handleRequest(req,res); });
app.get("/health", (_req, res) => res.json({ status: "ok", server: "session-mcp", activeSessions: transports.size }));
(async () => {
if (!(await testConnection())) { console.error("DB connect failed"); process.exit(1); }
app.listen(PORT, HOST, () => console.error(`session-mcp: HTTP on http://${HOST}:${PORT}/mcp`));
})();
process.on("SIGINT", async () => { await close(); process.exit(0); });
process.on("SIGTERM", async () => { await close(); process.exit(0); });

View File

@@ -1,566 +1,22 @@
#!/usr/bin/env node
/**
* Session MCP Server
*
* Forked from task-mcp (CF-762): Sessions, memory, archives, infrastructure.
* Task management now handled by Jira Cloud via mcp-atlassian.
*
* Uses PostgreSQL with pgvector for semantic search on sessions/memories.
*/
// Load environment variables from .env file
import dotenv from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import dotenv from "dotenv";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const envPath = join(__dirname, '..', '.env');
const result = dotenv.config({ path: envPath, override: true });
// Initialize Sentry for error tracking
import { initSentry, withSentryTransaction } from './sentry.js';
initSentry(process.env.SENTRY_ENVIRONMENT || 'production');
if (result.error) {
console.error('Failed to load .env from:', envPath, result.error);
} else {
console.error('Loaded .env from:', envPath);
}
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { testConnection, close } from './db.js';
import { toolDefinitions } from './tools/index.js';
// Kept tools (sessions, archives, infrastructure, docs, delegations, commits)
import { taskDelegations, taskDelegationQuery } from './tools/delegations.js';
import { projectLock, projectUnlock, projectLockStatus, projectContext } from './tools/locks.js';
import { taskCommitAdd, taskCommitRemove, taskCommitsList, taskLinkCommits, sessionTasks } from './tools/commits.js';
import { changelogAdd, changelogSinceSession, changelogList } from './tools/changelog.js';
import { timeline } from './tools/timeline.js';
import {
componentRegister,
componentList,
componentAddDependency,
componentAddFile,
componentAddCheck,
impactAnalysis,
impactLearn,
componentGraph,
} from './tools/impact.js';
import { toolDocAdd, toolDocSearch, toolDocGet, toolDocList, toolDocExport } from './tools/tool-docs.js';
import {
sessionStart,
sessionUpdate,
sessionEnd,
sessionList,
sessionSearch,
sessionContext,
buildRecord,
sessionCommitLink,
sessionRecoverOrphaned,
sessionRecoverTempNotes,
} from './tools/sessions.js';
import {
sessionNoteAdd,
sessionNotesList,
sessionPlanSave,
sessionPlanUpdateStatus,
sessionPlanList,
projectDocUpsert,
projectDocGet,
projectDocList,
sessionDocumentationGenerate,
sessionSemanticSearch,
sessionProductivityAnalytics,
sessionPatternDetection,
} from './tools/session-docs.js';
import { archiveAdd, archiveSearch, archiveList, archiveGet } from './tools/archives.js';
import { transcriptSearch } from './tools/transcripts.js';
import { projectArchive } from './tools/project-archive.js';
// Create MCP server
const server = new Server(
{ name: 'session-mcp', version: '1.0.0' },
{ capabilities: { tools: {} } }
);
// Register tool list handler
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: toolDefinitions,
}));
// Register tool call handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
return withSentryTransaction(name, async () => {
try {
let result: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const a = args as any;
switch (name) {
// Delegations
case 'task_delegations':
result = await taskDelegations({ task_id: a.task_id });
break;
case 'task_delegation_query':
result = await taskDelegationQuery({
status: a.status,
backend: a.backend,
limit: a.limit,
});
break;
// Project Locks
case 'project_lock':
result = await projectLock({
project: a.project,
session_id: a.session_id,
duration_minutes: a.duration_minutes,
reason: a.reason,
});
break;
case 'project_unlock':
result = await projectUnlock({
project: a.project,
session_id: a.session_id,
force: a.force,
});
break;
case 'project_lock_status':
result = await projectLockStatus({
project: a.project,
});
break;
case 'project_context':
result = await projectContext();
break;
// Commits
case 'task_commit_add':
result = await taskCommitAdd({
task_id: a.task_id,
commit_sha: a.commit_sha,
repo: a.repo,
source: a.source,
});
break;
case 'task_commit_remove':
result = await taskCommitRemove({
task_id: a.task_id,
commit_sha: a.commit_sha,
});
break;
case 'task_commits_list':
result = await taskCommitsList(a.task_id);
break;
case 'task_link_commits':
result = await taskLinkCommits({
repo: a.repo,
commits: a.commits,
dry_run: a.dry_run,
});
break;
case 'session_tasks':
result = await sessionTasks({
session_id: a.session_id,
limit: a.limit,
});
break;
// Infrastructure Changelog
case 'changelog_add':
result = await changelogAdd(a as any);
break;
case 'changelog_since_session':
result = await changelogSinceSession(a as any);
break;
case 'changelog_list':
result = await changelogList(a.days_back, a.limit);
break;
// Event Timeline (CF-2885)
case 'timeline':
result = await timeline(a as any);
break;
// Impact Analysis
case 'component_register':
result = JSON.stringify(await componentRegister(a.id, a.name, a.type, {
path: a.path,
repo: a.repo,
description: a.description,
health_check: a.health_check,
}), null, 2);
break;
case 'component_list':
result = JSON.stringify(await componentList(a.type), null, 2);
break;
case 'component_add_dependency':
result = JSON.stringify(await componentAddDependency(
a.component_id,
a.depends_on,
a.dependency_type,
a.description
), null, 2);
break;
case 'component_add_file':
result = JSON.stringify(await componentAddFile(a.component_id, a.file_pattern), null, 2);
break;
case 'component_add_check':
result = JSON.stringify(await componentAddCheck(a.component_id, a.name, a.check_type, a.check_command, {
expected_result: a.expected_result,
timeout_seconds: a.timeout_seconds,
}), null, 2);
break;
case 'impact_analysis':
result = JSON.stringify(await impactAnalysis(a.changed_files), null, 2);
break;
case 'impact_learn':
result = JSON.stringify(await impactLearn(
a.changed_component,
a.affected_component,
a.impact_description,
{ error_id: a.error_id, task_id: a.task_id }
), null, 2);
break;
case 'component_graph':
result = JSON.stringify(await componentGraph(a.component_id), null, 2);
break;
// Tool Documentation
case 'tool_doc_add':
result = await toolDocAdd({
tool_name: a.tool_name,
category: a.category,
title: a.title,
description: a.description,
usage_example: a.usage_example,
parameters: a.parameters,
notes: a.notes,
tags: a.tags,
source_file: a.source_file,
});
break;
case 'tool_doc_search':
result = await toolDocSearch({
query: a.query,
category: a.category,
tags: a.tags,
limit: a.limit,
});
break;
case 'tool_doc_get':
result = await toolDocGet({
tool_name: a.tool_name,
});
break;
case 'tool_doc_list':
result = await toolDocList({
category: a.category,
tag: a.tag,
limit: a.limit,
});
break;
case 'tool_doc_export':
result = await toolDocExport();
break;
// Sessions
case 'session_start':
result = await sessionStart({
session_id: a.session_id,
project: a.project,
working_directory: a.working_directory,
git_branch: a.git_branch,
initial_prompt: a.initial_prompt,
jira_issue_key: a.jira_issue_key,
});
break;
case 'session_update':
result = await sessionUpdate({
session_id: a.session_id,
message_count: a.message_count,
token_count: a.token_count,
tools_used: a.tools_used,
});
break;
case 'session_end':
result = await sessionEnd({
session_id: a.session_id,
summary: a.summary,
status: a.status,
});
break;
case 'session_list':
result = await sessionList({
project: a.project,
status: a.status,
since: a.since,
limit: a.limit,
});
break;
case 'session_search':
result = await sessionSearch({
query: a.query,
project: a.project,
limit: a.limit,
search_mode: a.search_mode,
});
break;
case 'session_context':
result = await sessionContext(a.session_id);
break;
case 'build_record':
result = await buildRecord(
a.session_id,
a.version_id,
a.build_number,
a.git_commit_sha,
a.status,
a.started_at
);
break;
case 'session_commit_link':
result = await sessionCommitLink(
a.session_id,
a.commit_sha,
a.repo,
a.commit_message,
a.committed_at
);
break;
case 'session_recover_orphaned':
result = await sessionRecoverOrphaned({
project: a.project,
});
break;
case 'session_recover_temp_notes':
result = await sessionRecoverTempNotes({
session_id: a.session_id,
temp_file_path: a.temp_file_path,
});
break;
// Session Documentation
case 'session_note_add':
result = await sessionNoteAdd({
session_id: a.session_id,
note_type: a.note_type,
content: a.content,
});
break;
case 'session_notes_list':
result = JSON.stringify(
await sessionNotesList({
session_id: a.session_id,
note_type: a.note_type,
}),
null,
2
);
break;
case 'session_plan_save':
result = await sessionPlanSave({
session_id: a.session_id,
plan_content: a.plan_content,
plan_file_name: a.plan_file_name,
status: a.status,
});
break;
case 'session_plan_update_status':
result = await sessionPlanUpdateStatus({
plan_id: a.plan_id,
status: a.status,
});
break;
case 'session_plan_list':
result = JSON.stringify(
await sessionPlanList({
session_id: a.session_id,
status: a.status,
}),
null,
2
);
break;
case 'project_doc_upsert':
result = await projectDocUpsert({
project: a.project,
doc_type: a.doc_type,
title: a.title,
content: a.content,
session_id: a.session_id,
});
break;
case 'project_doc_get':
result = JSON.stringify(
await projectDocGet({
project: a.project,
doc_type: a.doc_type,
}),
null,
2
);
break;
case 'project_doc_list':
result = JSON.stringify(
await projectDocList({
project: a.project,
}),
null,
2
);
break;
case 'session_documentation_generate':
result = await sessionDocumentationGenerate({
session_id: a.session_id,
});
break;
case 'session_semantic_search':
result = JSON.stringify(
await sessionSemanticSearch({
query: a.query,
project: a.project,
limit: a.limit,
search_mode: a.search_mode,
filter_topics: a.filter_topics,
filter_projects: a.filter_projects,
filter_issue_keys: a.filter_issue_keys,
}),
null,
2
);
break;
case 'session_productivity_analytics':
result = JSON.stringify(
await sessionProductivityAnalytics({
project: a.project,
time_period: a.time_period,
}),
null,
2
);
break;
case 'session_pattern_detection':
result = JSON.stringify(
await sessionPatternDetection({
project: a.project,
pattern_type: a.pattern_type,
}),
null,
2
);
break;
// Transcripts (CF-2394)
case 'session_transcript_search':
result = await transcriptSearch({
query: a.query,
project: a.project,
session_issue_key: a.session_issue_key,
limit: a.limit,
search_mode: a.search_mode,
});
break;
// Archives
case 'archive_add':
result = await archiveAdd({
project: a.project,
archive_type: a.archive_type,
title: a.title,
content: a.content,
original_path: a.original_path,
file_size: a.file_size,
archived_by_session: a.archived_by_session,
metadata: a.metadata,
});
break;
case 'archive_search':
result = await archiveSearch({
query: a.query,
project: a.project,
archive_type: a.archive_type,
limit: a.limit,
search_mode: a.search_mode,
});
break;
case 'archive_list':
result = await archiveList({
project: a.project,
archive_type: a.archive_type,
since: a.since,
limit: a.limit,
});
break;
case 'archive_get':
result = await archiveGet({
id: a.id,
});
break;
// Project archival
case 'project_archive':
result = await projectArchive({
project_key: a.project_key,
project_path: a.project_path,
delete_local: a.delete_local,
session_id: a.session_id,
});
break;
default:
throw new Error(`Unknown tool: ${name}`);
}
return {
content: [{ type: 'text', text: result }],
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
content: [{ type: 'text', text: `Error: ${message}` }],
isError: true,
};
}
});
});
// Main entry point
dotenv.config({ path: join(__dirname, "..", ".env"), override: true });
import { initSentry } from "./sentry.js";
initSentry(process.env.SENTRY_ENVIRONMENT || "production");
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { testConnection, close } from "./db.js";
import { createServer } from "./server.js";
async function main() {
process.on('SIGINT', async () => {
await close();
process.exit(0);
});
process.on('SIGTERM', async () => {
await close();
process.exit(0);
});
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('session-mcp: Server started');
testConnection().then((connected) => {
if (connected) {
console.error('session-mcp: Connected to database');
} else {
console.error('session-mcp: Warning - database not reachable, will retry on tool calls');
if (!(await testConnection())) { console.error("DB connect failed"); process.exit(1); }
const server = createServer();
const t = new StdioServerTransport();
await server.connect(t);
console.error("session-mcp: stdio started");
}
});
}
main().catch((error) => {
console.error('session-mcp: Fatal error:', error);
process.exit(1);
});
main().catch(e => { console.error(e); process.exit(1); });
process.on("SIGINT", async () => { await close(); process.exit(0); });
process.on("SIGTERM", async () => { await close(); process.exit(0); });

521
src/server.ts Normal file
View File

@@ -0,0 +1,521 @@
#!/usr/bin/env node
/**
* Session MCP Server
*
* Forked from task-mcp (CF-762): Sessions, memory, archives, infrastructure.
* Task management now handled by Jira Cloud via mcp-atlassian.
*
* Uses PostgreSQL with pgvector for semantic search on sessions/memories.
*/
import { withSentryTransaction } from "./sentry.js";
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { testConnection, close } from './db.js';
import { toolDefinitions } from './tools/index.js';
// Kept tools (sessions, archives, infrastructure, docs, delegations, commits)
import { taskDelegations, taskDelegationQuery } from './tools/delegations.js';
import { projectLock, projectUnlock, projectLockStatus, projectContext } from './tools/locks.js';
import { taskCommitAdd, taskCommitRemove, taskCommitsList, taskLinkCommits, sessionTasks } from './tools/commits.js';
import { changelogAdd, changelogSinceSession, changelogList } from './tools/changelog.js';
import { timeline } from './tools/timeline.js';
import {
componentRegister,
componentList,
componentAddDependency,
componentAddFile,
componentAddCheck,
impactAnalysis,
impactLearn,
componentGraph,
} from './tools/impact.js';
import { toolDocAdd, toolDocSearch, toolDocGet, toolDocList, toolDocExport } from './tools/tool-docs.js';
import {
sessionStart,
sessionUpdate,
sessionEnd,
sessionList,
sessionSearch,
sessionContext,
buildRecord,
sessionCommitLink,
sessionRecoverOrphaned,
sessionRecoverTempNotes,
} from './tools/sessions.js';
import {
sessionNoteAdd,
sessionNotesList,
sessionPlanSave,
sessionPlanUpdateStatus,
sessionPlanList,
projectDocUpsert,
projectDocGet,
projectDocList,
sessionDocumentationGenerate,
sessionSemanticSearch,
sessionProductivityAnalytics,
sessionPatternDetection,
} from './tools/session-docs.js';
import { archiveAdd, archiveSearch, archiveList, archiveGet } from './tools/archives.js';
import { transcriptSearch } from './tools/transcripts.js';
import { projectArchive } from './tools/project-archive.js';
// Create MCP server
export function createServer(): Server {
const server = new Server(
{ name: 'session-mcp', version: '1.0.0' },
{ capabilities: { tools: {} } }
);
// Register tool list handler
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: toolDefinitions,
}));
// Register tool call handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
return withSentryTransaction(name, async () => {
try {
let result: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const a = args as any;
switch (name) {
// Delegations
case 'task_delegations':
result = await taskDelegations({ task_id: a.task_id });
break;
case 'task_delegation_query':
result = await taskDelegationQuery({
status: a.status,
backend: a.backend,
limit: a.limit,
});
break;
// Project Locks
case 'project_lock':
result = await projectLock({
project: a.project,
session_id: a.session_id,
duration_minutes: a.duration_minutes,
reason: a.reason,
});
break;
case 'project_unlock':
result = await projectUnlock({
project: a.project,
session_id: a.session_id,
force: a.force,
});
break;
case 'project_lock_status':
result = await projectLockStatus({
project: a.project,
});
break;
case 'project_context':
result = await projectContext();
break;
// Commits
case 'task_commit_add':
result = await taskCommitAdd({
task_id: a.task_id,
commit_sha: a.commit_sha,
repo: a.repo,
source: a.source,
});
break;
case 'task_commit_remove':
result = await taskCommitRemove({
task_id: a.task_id,
commit_sha: a.commit_sha,
});
break;
case 'task_commits_list':
result = await taskCommitsList(a.task_id);
break;
case 'task_link_commits':
result = await taskLinkCommits({
repo: a.repo,
commits: a.commits,
dry_run: a.dry_run,
});
break;
case 'session_tasks':
result = await sessionTasks({
session_id: a.session_id,
limit: a.limit,
});
break;
// Infrastructure Changelog
case 'changelog_add':
result = await changelogAdd(a as any);
break;
case 'changelog_since_session':
result = await changelogSinceSession(a as any);
break;
case 'changelog_list':
result = await changelogList(a.days_back, a.limit);
break;
// Event Timeline (CF-2885)
case 'timeline':
result = await timeline(a as any);
break;
// Impact Analysis
case 'component_register':
result = JSON.stringify(await componentRegister(a.id, a.name, a.type, {
path: a.path,
repo: a.repo,
description: a.description,
health_check: a.health_check,
}), null, 2);
break;
case 'component_list':
result = JSON.stringify(await componentList(a.type), null, 2);
break;
case 'component_add_dependency':
result = JSON.stringify(await componentAddDependency(
a.component_id,
a.depends_on,
a.dependency_type,
a.description
), null, 2);
break;
case 'component_add_file':
result = JSON.stringify(await componentAddFile(a.component_id, a.file_pattern), null, 2);
break;
case 'component_add_check':
result = JSON.stringify(await componentAddCheck(a.component_id, a.name, a.check_type, a.check_command, {
expected_result: a.expected_result,
timeout_seconds: a.timeout_seconds,
}), null, 2);
break;
case 'impact_analysis':
result = JSON.stringify(await impactAnalysis(a.changed_files), null, 2);
break;
case 'impact_learn':
result = JSON.stringify(await impactLearn(
a.changed_component,
a.affected_component,
a.impact_description,
{ error_id: a.error_id, task_id: a.task_id }
), null, 2);
break;
case 'component_graph':
result = JSON.stringify(await componentGraph(a.component_id), null, 2);
break;
// Tool Documentation
case 'tool_doc_add':
result = await toolDocAdd({
tool_name: a.tool_name,
category: a.category,
title: a.title,
description: a.description,
usage_example: a.usage_example,
parameters: a.parameters,
notes: a.notes,
tags: a.tags,
source_file: a.source_file,
});
break;
case 'tool_doc_search':
result = await toolDocSearch({
query: a.query,
category: a.category,
tags: a.tags,
limit: a.limit,
});
break;
case 'tool_doc_get':
result = await toolDocGet({
tool_name: a.tool_name,
});
break;
case 'tool_doc_list':
result = await toolDocList({
category: a.category,
tag: a.tag,
limit: a.limit,
});
break;
case 'tool_doc_export':
result = await toolDocExport();
break;
// Sessions
case 'session_start':
result = await sessionStart({
session_id: a.session_id,
project: a.project,
working_directory: a.working_directory,
git_branch: a.git_branch,
initial_prompt: a.initial_prompt,
jira_issue_key: a.jira_issue_key,
});
break;
case 'session_update':
result = await sessionUpdate({
session_id: a.session_id,
message_count: a.message_count,
token_count: a.token_count,
tools_used: a.tools_used,
});
break;
case 'session_end':
result = await sessionEnd({
session_id: a.session_id,
summary: a.summary,
status: a.status,
});
break;
case 'session_list':
result = await sessionList({
project: a.project,
status: a.status,
since: a.since,
limit: a.limit,
});
break;
case 'session_search':
result = await sessionSearch({
query: a.query,
project: a.project,
limit: a.limit,
search_mode: a.search_mode,
});
break;
case 'session_context':
result = await sessionContext(a.session_id);
break;
case 'build_record':
result = await buildRecord(
a.session_id,
a.version_id,
a.build_number,
a.git_commit_sha,
a.status,
a.started_at
);
break;
case 'session_commit_link':
result = await sessionCommitLink(
a.session_id,
a.commit_sha,
a.repo,
a.commit_message,
a.committed_at
);
break;
case 'session_recover_orphaned':
result = await sessionRecoverOrphaned({
project: a.project,
});
break;
case 'session_recover_temp_notes':
result = await sessionRecoverTempNotes({
session_id: a.session_id,
temp_file_path: a.temp_file_path,
});
break;
// Session Documentation
case 'session_note_add':
result = await sessionNoteAdd({
session_id: a.session_id,
note_type: a.note_type,
content: a.content,
});
break;
case 'session_notes_list':
result = JSON.stringify(
await sessionNotesList({
session_id: a.session_id,
note_type: a.note_type,
}),
null,
2
);
break;
case 'session_plan_save':
result = await sessionPlanSave({
session_id: a.session_id,
plan_content: a.plan_content,
plan_file_name: a.plan_file_name,
status: a.status,
});
break;
case 'session_plan_update_status':
result = await sessionPlanUpdateStatus({
plan_id: a.plan_id,
status: a.status,
});
break;
case 'session_plan_list':
result = JSON.stringify(
await sessionPlanList({
session_id: a.session_id,
status: a.status,
}),
null,
2
);
break;
case 'project_doc_upsert':
result = await projectDocUpsert({
project: a.project,
doc_type: a.doc_type,
title: a.title,
content: a.content,
session_id: a.session_id,
});
break;
case 'project_doc_get':
result = JSON.stringify(
await projectDocGet({
project: a.project,
doc_type: a.doc_type,
}),
null,
2
);
break;
case 'project_doc_list':
result = JSON.stringify(
await projectDocList({
project: a.project,
}),
null,
2
);
break;
case 'session_documentation_generate':
result = await sessionDocumentationGenerate({
session_id: a.session_id,
});
break;
case 'session_semantic_search':
result = JSON.stringify(
await sessionSemanticSearch({
query: a.query,
project: a.project,
limit: a.limit,
search_mode: a.search_mode,
filter_topics: a.filter_topics,
filter_projects: a.filter_projects,
filter_issue_keys: a.filter_issue_keys,
}),
null,
2
);
break;
case 'session_productivity_analytics':
result = JSON.stringify(
await sessionProductivityAnalytics({
project: a.project,
time_period: a.time_period,
}),
null,
2
);
break;
case 'session_pattern_detection':
result = JSON.stringify(
await sessionPatternDetection({
project: a.project,
pattern_type: a.pattern_type,
}),
null,
2
);
break;
// Transcripts (CF-2394)
case 'session_transcript_search':
result = await transcriptSearch({
query: a.query,
project: a.project,
session_issue_key: a.session_issue_key,
limit: a.limit,
search_mode: a.search_mode,
});
break;
// Archives
case 'archive_add':
result = await archiveAdd({
project: a.project,
archive_type: a.archive_type,
title: a.title,
content: a.content,
original_path: a.original_path,
file_size: a.file_size,
archived_by_session: a.archived_by_session,
metadata: a.metadata,
});
break;
case 'archive_search':
result = await archiveSearch({
query: a.query,
project: a.project,
archive_type: a.archive_type,
limit: a.limit,
search_mode: a.search_mode,
});
break;
case 'archive_list':
result = await archiveList({
project: a.project,
archive_type: a.archive_type,
since: a.since,
limit: a.limit,
});
break;
case 'archive_get':
result = await archiveGet({
id: a.id,
});
break;
// Project archival
case 'project_archive':
result = await projectArchive({
project_key: a.project_key,
project_path: a.project_path,
delete_local: a.delete_local,
session_id: a.session_id,
});
break;
default:
throw new Error(`Unknown tool: ${name}`);
}
return {
content: [{ type: 'text', text: result }],
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
content: [{ type: 'text', text: `Error: ${message}` }],
isError: true,
};
}
});
});
return server;
}