#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import {
getCurrentSession,
getContextStats,
findSessionFile,
getSessionId,
} from "./session.js";
import {
createCheckpoint,
listCheckpoints,
getCheckpoint,
restoreCheckpoint,
deleteCheckpoint,
} from "./checkpoints.js";
// Tool definitions
const tools: Tool[] = [
{
name: "checkpoint_context",
description: `Save your current context state as a named checkpoint. Use this when you're well-oriented and want to be able to return to this mental state later.
Good moments to checkpoint:
- After fully understanding a codebase or task
- Before starting a large, context-heavy operation
- When you feel sharp and well-oriented
The checkpoint captures your entire conversation history up to this point.`,
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description:
'Checkpoint name (e.g., "oriented", "pre-refactor", "task-complete")',
},
note: {
type: "string",
description:
"Optional note about what state this checkpoint represents",
},
},
required: ["name"],
},
},
{
name: "reset_to_checkpoint",
description: `Reset your context back to a saved checkpoint, injecting a handoff message to your future self.
The message_to_self will appear as if you wrote it just before the reset. Use it to brief your future self on:
- What was accomplished since the checkpoint
- Key findings and decisions
- Clear next steps
- Critical details (file paths, variable names, gotchas)
After calling this, your context will be restored to the checkpoint state plus your handoff message.`,
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of checkpoint to restore",
},
message_to_self: {
type: "string",
description:
"Briefing message for your future self after the reset. Write as if handing off to a colleague.",
},
},
required: ["name", "message_to_self"],
},
},
{
name: "list_checkpoints",
description:
"List all available checkpoints for the current session. Shows when each was created and any notes attached.",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "get_context_stats",
description: `Get statistics about your current context window health.
Returns:
- Number of turns (user and assistant messages)
- Estimated token count
- Number of compaction/summary events
- Context age
Use this to decide when you might want to checkpoint or reset.`,
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "delete_checkpoint",
description: "Delete a checkpoint you no longer need.",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of checkpoint to delete",
},
},
required: ["name"],
},
},
];
// Create server
const server = new Server(
{
name: "context-travel",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
}
);
// Handle tool listing
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools };
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "checkpoint_context": {
const session = getCurrentSession();
if (!session) {
return {
content: [
{
type: "text",
text: "Error: Could not find current session. Make sure CLAUDE_SESSION_ID is set or Claude Code is running.",
},
],
isError: true,
};
}
const checkpointName = (args as any).name as string;
const note = (args as any).note as string | undefined;
const checkpoint = createCheckpoint(session, checkpointName, note);
return {
content: [
{
type: "text",
text: `Checkpoint "${checkpointName}" created successfully.
Session: ${session.sessionId}
Stats at checkpoint:
- User turns: ${checkpoint.metadata.stats.userTurns}
- Assistant turns: ${checkpoint.metadata.stats.assistantTurns}
- Estimated tokens: ${checkpoint.metadata.stats.estimatedTokens.toLocaleString()}
${note ? `\nNote: ${note}` : ""}
You can return to this state later with reset_to_checkpoint("${checkpointName}", "your handoff message").`,
},
],
};
}
case "reset_to_checkpoint": {
const sessionId = getSessionId();
if (!sessionId) {
return {
content: [
{
type: "text",
text: "Error: Could not determine current session ID.",
},
],
isError: true,
};
}
const checkpointName = (args as any).name as string;
const messageToSelf = (args as any).message_to_self as string;
const checkpoint = getCheckpoint(sessionId, checkpointName);
if (!checkpoint) {
const available = listCheckpoints(sessionId);
const availableNames =
available.length > 0
? available.map((c) => ` - ${c.metadata.name}`).join("\n")
: " (none)";
return {
content: [
{
type: "text",
text: `Error: Checkpoint "${checkpointName}" not found.
Available checkpoints:
${availableNames}`,
},
],
isError: true,
};
}
const result = restoreCheckpoint(checkpoint, messageToSelf);
return {
content: [
{
type: "text",
text: `Context reset to checkpoint "${checkpointName}".
Your handoff message has been injected. On your next turn, you'll see:
"${messageToSelf.slice(0, 200)}${messageToSelf.length > 200 ? "..." : ""}"
The context window has been restored to the checkpoint state (${checkpoint.metadata.stats.userTurns} user turns, ~${checkpoint.metadata.stats.estimatedTokens.toLocaleString()} tokens).`,
},
],
};
}
case "list_checkpoints": {
const sessionId = getSessionId();
if (!sessionId) {
return {
content: [
{
type: "text",
text: "Error: Could not determine current session ID.",
},
],
isError: true,
};
}
const checkpoints = listCheckpoints(sessionId);
if (checkpoints.length === 0) {
return {
content: [
{
type: "text",
text: `No checkpoints found for session ${sessionId}.
Create one with checkpoint_context("name") when you're well-oriented.`,
},
],
};
}
const list = checkpoints
.map((c) => {
const age = getRelativeTime(new Date(c.metadata.createdAt));
return `- **${c.metadata.name}** (${age})
Turns: ${c.metadata.stats.userTurns} user, ${c.metadata.stats.assistantTurns} assistant
Tokens: ~${c.metadata.stats.estimatedTokens.toLocaleString()}${c.metadata.note ? `\n Note: ${c.metadata.note}` : ""}`;
})
.join("\n\n");
return {
content: [
{
type: "text",
text: `Checkpoints for session ${sessionId}:\n\n${list}`,
},
],
};
}
case "get_context_stats": {
const session = getCurrentSession();
if (!session) {
return {
content: [
{
type: "text",
text: "Error: Could not find current session.",
},
],
isError: true,
};
}
const stats = getContextStats(session.sessionFilePath);
if (!stats) {
return {
content: [
{
type: "text",
text: "Error: Could not read session statistics.",
},
],
isError: true,
};
}
const checkpoints = listCheckpoints(session.sessionId);
const contextAge = stats.createdAt
? getRelativeTime(new Date(stats.createdAt))
: "unknown";
// Estimate context health
let healthIndicator = "Fresh";
if (stats.summaryCount > 3) {
healthIndicator = "Fragmented (multiple compactions)";
} else if (stats.summaryCount > 0) {
healthIndicator = "Compacted";
} else if (stats.estimatedTokens > 150000) {
healthIndicator = "Heavy";
} else if (stats.estimatedTokens > 80000) {
healthIndicator = "Moderate";
}
return {
content: [
{
type: "text",
text: `Context Statistics
Session: ${stats.sessionId}
Health: ${healthIndicator}
Turns:
- User: ${stats.userTurns}
- Assistant: ${stats.assistantTurns}
- Tool calls: ${stats.toolUseCalls}
Size:
- Estimated tokens: ~${stats.estimatedTokens.toLocaleString()}
- File size: ${(stats.fileSizeBytes / 1024).toFixed(1)} KB
Context age: ${contextAge}
Compaction events: ${stats.summaryCount}
Available checkpoints: ${checkpoints.length}
${stats.summaryCount > 2 ? "⚠️ Multiple compactions detected. Consider resetting to a checkpoint to restore full fidelity." : ""}${stats.estimatedTokens > 120000 ? "⚠️ Context is getting heavy. Good time to checkpoint if you haven't." : ""}`,
},
],
};
}
case "delete_checkpoint": {
const sessionId = getSessionId();
if (!sessionId) {
return {
content: [
{
type: "text",
text: "Error: Could not determine current session ID.",
},
],
isError: true,
};
}
const checkpointName = (args as any).name as string;
const deleted = deleteCheckpoint(sessionId, checkpointName);
if (deleted) {
return {
content: [
{
type: "text",
text: `Checkpoint "${checkpointName}" deleted.`,
},
],
};
} else {
return {
content: [
{
type: "text",
text: `Error: Could not delete checkpoint "${checkpointName}". It may not exist.`,
},
],
isError: true,
};
}
}
default:
return {
content: [
{
type: "text",
text: `Unknown tool: ${name}`,
},
],
isError: true,
};
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
});
// Helper function for relative time
function getRelativeTime(date: Date): string {
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1) return "just now";
if (diffMins < 60) return `${diffMins}m ago`;
if (diffHours < 24) return `${diffHours}h ago`;
return `${diffDays}d ago`;
}
// Start server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Context Travel MCP server running on stdio");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});