mirror of
https://github.com/lukaszraczylo/compaction-mcp.git
synced 2026-06-05 23:14:02 +00:00
dded4ec04c
- Dockerfile: distroless container for MCP server - GoReleaser: multi-platform binary and Docker builds with cosign signing - GitHub Actions: release workflow using shared actions - Semver config for automatic version calculation - Persistence layer, content indexing, and improved tool handlers
120 lines
3.2 KiB
Go
120 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/mark3labs/mcp-go/mcp"
|
|
"github.com/mark3labs/mcp-go/server"
|
|
)
|
|
|
|
const serverInstructions = `Context compactor — your working memory. Use it to avoid losing information when your context window compresses.
|
|
|
|
MANDATORY: Call 'recall' at the start of every session to restore previous context.
|
|
|
|
WHEN TO STORE (call 'store' with a summary):
|
|
- After making a decision or choosing an approach
|
|
- After encountering and understanding an error
|
|
- After reading a file you'll need to reference later
|
|
- After the user explains requirements or constraints
|
|
- Before your context is likely to compress (long sessions, large outputs)
|
|
|
|
WHEN TO QUERY (call 'query' instead of re-reading):
|
|
- Before reading a file you may have stored previously
|
|
- When you need to recall a decision, error, or requirement
|
|
- When the user references something from earlier in the session
|
|
|
|
WHEN TO COMPACT (call 'compact'):
|
|
- When 'status' shows >80% budget usage
|
|
- After 'compact', use 'update' to summarize items it flags
|
|
|
|
TIPS:
|
|
- Always include a summary when storing — enables efficient compaction
|
|
- Tag items for easy retrieval: error, decision, code, requirement
|
|
- Pin critical items (architecture decisions, user preferences) with 'pin'
|
|
- Higher importance (7-10) for decisions and requirements, lower (1-4) for tool output`
|
|
|
|
func main() {
|
|
budget := flag.Int("budget", 100000, "Token budget for context storage")
|
|
stateDir := flag.String("state-dir", "", "Directory for persistent state (empty = ephemeral)")
|
|
flag.Parse()
|
|
|
|
budgetExplicit := false
|
|
flag.Visit(func(f *flag.Flag) {
|
|
if f.Name == "budget" {
|
|
budgetExplicit = true
|
|
}
|
|
})
|
|
|
|
store := NewStore(*budget)
|
|
|
|
var persister *Persister
|
|
if *stateDir != "" {
|
|
var err error
|
|
persister, err = NewPersister(*stateDir, store)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "persistence error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
if err := persister.Load(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "load state error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
persister.Start(30 * time.Second)
|
|
}
|
|
|
|
hooks := &server.Hooks{}
|
|
if !budgetExplicit {
|
|
hooks.OnAfterInitialize = append(hooks.OnAfterInitialize,
|
|
func(ctx context.Context, id any, req *mcp.InitializeRequest, res *mcp.InitializeResult) {
|
|
name := strings.ToLower(req.Params.ClientInfo.Name)
|
|
switch {
|
|
case strings.Contains(name, "claude"):
|
|
// Claude models: 200K context → 40% = 80K budget
|
|
store.Configure(80000, nil, 0)
|
|
case strings.Contains(name, "cursor"):
|
|
store.Configure(60000, nil, 0)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
s := server.NewMCPServer(
|
|
"compactor",
|
|
"0.1.0",
|
|
server.WithToolCapabilities(false),
|
|
server.WithRecovery(),
|
|
server.WithInstructions(serverInstructions),
|
|
server.WithHooks(hooks),
|
|
)
|
|
|
|
registerTools(s, store)
|
|
|
|
if persister != nil {
|
|
sigCh := make(chan os.Signal, 1)
|
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
go func() {
|
|
<-sigCh
|
|
persister.Stop()
|
|
os.Exit(0)
|
|
}()
|
|
}
|
|
|
|
if err := server.ServeStdio(s); err != nil {
|
|
fmt.Fprintf(os.Stderr, "server error: %v\n", err)
|
|
if persister != nil {
|
|
persister.Stop()
|
|
}
|
|
os.Exit(1)
|
|
}
|
|
if persister != nil {
|
|
persister.Stop()
|
|
}
|
|
}
|