gomcp/internal/transport/mcpserver/server.go

1720 lines
54 KiB
Go

// Package mcpserver wires MCP tools and resources to application services.
package mcpserver
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"github.com/syntrex-lab/gomcp/internal/application/contextengine"
"github.com/syntrex-lab/gomcp/internal/application/orchestrator"
"github.com/syntrex-lab/gomcp/internal/application/resources"
appsoc "github.com/syntrex-lab/gomcp/internal/application/soc"
"github.com/syntrex-lab/gomcp/internal/application/tools"
"github.com/syntrex-lab/gomcp/internal/domain/circuitbreaker"
entropyPkg "github.com/syntrex-lab/gomcp/internal/domain/entropy"
"github.com/syntrex-lab/gomcp/internal/domain/memory"
"github.com/syntrex-lab/gomcp/internal/domain/oracle"
"github.com/syntrex-lab/gomcp/internal/domain/peer"
"github.com/syntrex-lab/gomcp/internal/domain/pipeline"
"github.com/syntrex-lab/gomcp/internal/domain/router"
"github.com/syntrex-lab/gomcp/internal/domain/vectorstore"
)
// Server wraps the MCP server with all registered tools and resources.
type Server struct {
mcp *server.MCPServer
facts *tools.FactService
sessions *tools.SessionService
causal *tools.CausalService
crystals *tools.CrystalService
system *tools.SystemService
intent *tools.IntentService
circuit *circuitbreaker.Breaker
oracle *oracle.Oracle
pipeline *pipeline.Pipeline
vecstore *vectorstore.Store
router *router.Router
res *resources.Provider
embedder vectorstore.Embedder
contextEngine *contextengine.Engine
peerReg *peer.Registry
synapseSvc *tools.SynapseService // v3.3: synapse bridges
orch *orchestrator.Orchestrator // v3.4: observability
doctor *tools.DoctorService // v3.7: self-diagnostic
socSvc *appsoc.Service // v3.9: AI SOC pipeline
}
// Config holds server configuration.
type Config struct {
Name string
Version string
Instructions string // optional boot instructions returned at initialize
}
// Option configures optional Server dependencies.
type Option func(*Server)
// WithEmbedder sets the Embedder for NLP/embedding tools.
func WithEmbedder(e vectorstore.Embedder) Option {
return func(s *Server) {
s.embedder = e
}
}
// WithContextEngine sets the Proactive Context Engine for automatic
// memory context injection into every tool response.
func WithContextEngine(e *contextengine.Engine) Option {
return func(s *Server) {
s.contextEngine = e
}
}
// WithSOCService enables the SENTINEL AI SOC pipeline tools (v3.9).
// If not set, SOC tools are not registered (graceful degradation).
func WithSOCService(svc *appsoc.Service) Option {
return func(s *Server) {
s.socSvc = svc
}
}
// New creates a new MCP server with all tools and resources registered.
func New(cfg Config, facts *tools.FactService, sessions *tools.SessionService,
causal *tools.CausalService, crystals *tools.CrystalService,
system *tools.SystemService, res *resources.Provider, opts ...Option) *Server {
s := &Server{
facts: facts,
sessions: sessions,
causal: causal,
crystals: crystals,
system: system,
res: res,
}
for _, opt := range opts {
opt(s)
}
// Initialize Intent Distiller (uses Embedder).
s.intent = tools.NewIntentService(s.embedder)
// Initialize Circuit Breaker and Action Oracle (DIP H1).
s.circuit = circuitbreaker.New(nil)
s.oracle = oracle.New(oracle.DefaultRules())
// Initialize Intent Pipeline (DIP H1.3).
gate := entropyPkg.NewGate(nil)
s.pipeline = pipeline.New(gate, nil, s.oracle, s.circuit, nil)
// Initialize Vector Store and Router (DIP H2).
s.vecstore = vectorstore.New(nil)
s.router = router.New(s.vecstore, nil)
// Initialize Peer Registry (DIP H1: Synapse).
s.peerReg = peer.NewRegistry(cfg.Name, 30*60*1e9) // 30 min timeout
// Build server options — always include recovery middleware.
serverOpts := []server.ServerOption{
server.WithToolCapabilities(true),
server.WithResourceCapabilities(true, true),
server.WithRecovery(),
}
// Set boot instructions if provided.
if cfg.Instructions != "" {
serverOpts = append(serverOpts, server.WithInstructions(cfg.Instructions))
}
// Register context engine middleware if provided.
if s.contextEngine != nil && s.contextEngine.IsEnabled() {
serverOpts = append(serverOpts,
server.WithToolHandlerMiddleware(s.contextEngine.Middleware()),
)
}
s.mcp = server.NewMCPServer(cfg.Name, cfg.Version, serverOpts...)
s.registerFactTools()
s.registerSessionTools()
s.registerCausalTools()
s.registerCrystalTools()
s.registerSystemTools()
s.registerIntentTools()
s.registerEntropyTools()
s.registerCircuitBreakerTools()
s.registerOracleTools()
s.registerPipelineTools()
s.registerVectorStoreTools()
s.registerRouterTools()
s.registerApoptosisTools()
s.registerSynapseTools()
s.registerPythonBridgeTools()
s.registerV33Tools() // v3.3: Context GC + Synapse Bridges + Shadow Intel
s.registerSOCTools() // v3.9: SENTINEL AI SOC pipeline
s.registerResources()
return s
}
// MCPServer returns the underlying mcp-go server for transport binding.
func (s *Server) MCPServer() *server.MCPServer {
return s.mcp
}
// --- Fact Tools ---
func (s *Server) registerFactTools() {
s.mcp.AddTool(
mcp.NewTool("add_fact",
mcp.WithDescription("Add a new hierarchical memory fact (L0-L3)"),
mcp.WithString("content", mcp.Description("Fact content"), mcp.Required()),
mcp.WithNumber("level", mcp.Description("Hierarchy level: 0=project, 1=domain, 2=module, 3=snippet")),
mcp.WithString("domain", mcp.Description("Domain category")),
mcp.WithString("module", mcp.Description("Module name")),
mcp.WithString("code_ref", mcp.Description("Code reference (file:line)")),
),
s.handleAddFact,
)
s.mcp.AddTool(
mcp.NewTool("get_fact",
mcp.WithDescription("Retrieve a fact by ID"),
mcp.WithString("id", mcp.Description("Fact ID"), mcp.Required()),
),
s.handleGetFact,
)
s.mcp.AddTool(
mcp.NewTool("update_fact",
mcp.WithDescription("Update an existing fact"),
mcp.WithString("id", mcp.Description("Fact ID"), mcp.Required()),
mcp.WithString("content", mcp.Description("New content")),
mcp.WithBoolean("is_stale", mcp.Description("Mark as stale")),
),
s.handleUpdateFact,
)
s.mcp.AddTool(
mcp.NewTool("delete_fact",
mcp.WithDescription("Delete a fact by ID"),
mcp.WithString("id", mcp.Description("Fact ID"), mcp.Required()),
),
s.handleDeleteFact,
)
s.mcp.AddTool(
mcp.NewTool("list_facts",
mcp.WithDescription("List facts by domain or level"),
mcp.WithString("domain", mcp.Description("Filter by domain")),
mcp.WithNumber("level", mcp.Description("Filter by level (0-3)")),
mcp.WithBoolean("include_stale", mcp.Description("Include stale facts")),
),
s.handleListFacts,
)
s.mcp.AddTool(
mcp.NewTool("search_facts",
mcp.WithDescription("Search facts by content text"),
mcp.WithString("query", mcp.Description("Search query"), mcp.Required()),
mcp.WithNumber("limit", mcp.Description("Max results")),
),
s.handleSearchFacts,
)
s.mcp.AddTool(
mcp.NewTool("list_domains",
mcp.WithDescription("List all unique fact domains"),
),
s.handleListDomains,
)
s.mcp.AddTool(
mcp.NewTool("get_stale_facts",
mcp.WithDescription("Get stale facts for review"),
mcp.WithBoolean("include_archived", mcp.Description("Include archived facts")),
),
s.handleGetStaleFacts,
)
s.mcp.AddTool(
mcp.NewTool("get_l0_facts",
mcp.WithDescription("Get all L0 (project-level) facts — always-loaded context"),
),
s.handleGetL0Facts,
)
s.mcp.AddTool(
mcp.NewTool("fact_stats",
mcp.WithDescription("Get fact store statistics"),
),
s.handleFactStats,
)
s.mcp.AddTool(
mcp.NewTool("process_expired",
mcp.WithDescription("Process expired TTL facts (mark stale, archive, or delete)"),
),
s.handleProcessExpired,
)
// --- Genome Layer Tools ---
s.mcp.AddTool(
mcp.NewTool("add_gene",
mcp.WithDescription("Add an immutable genome fact (survival invariant, L0 only). Once created, a gene cannot be updated or deleted."),
mcp.WithString("content", mcp.Description("Gene content — survival invariant"), mcp.Required()),
mcp.WithString("domain", mcp.Description("Domain category")),
),
s.handleAddGene,
)
s.mcp.AddTool(
mcp.NewTool("list_genes",
mcp.WithDescription("List all genome facts (immutable survival invariants)"),
),
s.handleListGenes,
)
s.mcp.AddTool(
mcp.NewTool("verify_genome",
mcp.WithDescription("Verify genome integrity via Merkle hash of all genes"),
),
s.handleVerifyGenome,
)
}
func (s *Server) handleAddFact(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
params := tools.AddFactParams{
Content: req.GetString("content", ""),
Level: req.GetInt("level", 0),
Domain: req.GetString("domain", ""),
Module: req.GetString("module", ""),
CodeRef: req.GetString("code_ref", ""),
}
fact, err := s.facts.AddFact(context.Background(), params)
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(fact)), nil
}
func (s *Server) handleGetFact(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
id := req.GetString("id", "")
fact, err := s.facts.GetFact(context.Background(), id)
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(fact)), nil
}
func (s *Server) handleUpdateFact(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
params := tools.UpdateFactParams{ID: req.GetString("id", "")}
args := req.GetArguments()
if v, ok := args["content"].(string); ok {
params.Content = &v
}
if v, ok := args["is_stale"].(bool); ok {
params.IsStale = &v
}
fact, err := s.facts.UpdateFact(context.Background(), params)
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(fact)), nil
}
func (s *Server) handleDeleteFact(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
id := req.GetString("id", "")
if err := s.facts.DeleteFact(context.Background(), id); err != nil {
return errorResult(err), nil
}
return textResult(fmt.Sprintf("Fact %s deleted", id)), nil
}
func (s *Server) handleListFacts(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
params := tools.ListFactsParams{
Domain: req.GetString("domain", ""),
IncludeStale: req.GetBool("include_stale", false),
}
args := req.GetArguments()
if v, ok := args["level"]; ok {
if n, ok := v.(float64); ok {
level := int(n)
params.Level = &level
}
}
facts, err := s.facts.ListFacts(context.Background(), params)
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(facts)), nil
}
func (s *Server) handleSearchFacts(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
query := req.GetString("query", "")
limit := req.GetInt("limit", 20)
facts, err := s.facts.SearchFacts(context.Background(), query, limit)
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(facts)), nil
}
func (s *Server) handleListDomains(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
domains, err := s.facts.ListDomains(context.Background())
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(domains)), nil
}
func (s *Server) handleGetStaleFacts(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
includeArchived := req.GetBool("include_archived", false)
facts, err := s.facts.GetStale(context.Background(), includeArchived)
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(facts)), nil
}
func (s *Server) handleGetL0Facts(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
facts, err := s.facts.GetL0Facts(context.Background())
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(facts)), nil
}
func (s *Server) handleFactStats(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
stats, err := s.facts.GetStats(context.Background())
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(stats)), nil
}
func (s *Server) handleProcessExpired(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
count, err := s.facts.ProcessExpired(context.Background())
if err != nil {
return errorResult(err), nil
}
return textResult(fmt.Sprintf("Processed %d expired facts", count)), nil
}
func (s *Server) handleAddGene(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
params := tools.AddGeneParams{
Content: req.GetString("content", ""),
Domain: req.GetString("domain", ""),
}
gene, err := s.facts.AddGene(context.Background(), params)
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(gene)), nil
}
func (s *Server) handleListGenes(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
genes, err := s.facts.ListGenes(context.Background())
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(genes)), nil
}
func (s *Server) handleVerifyGenome(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
hash, count, err := s.facts.VerifyGenome(context.Background())
if err != nil {
return errorResult(err), nil
}
result := map[string]interface{}{
"genome_hash": hash,
"gene_count": count,
"status": "verified",
}
return textResult(tools.ToJSON(result)), nil
}
// --- Session Tools ---
func (s *Server) registerSessionTools() {
s.mcp.AddTool(
mcp.NewTool("save_state",
mcp.WithDescription("Save cognitive state vector"),
mcp.WithString("session_id", mcp.Description("Session identifier"), mcp.Required()),
mcp.WithString("state_json", mcp.Description("Full state JSON"), mcp.Required()),
),
s.handleSaveState,
)
s.mcp.AddTool(
mcp.NewTool("load_state",
mcp.WithDescription("Load cognitive state for a session"),
mcp.WithString("session_id", mcp.Description("Session identifier"), mcp.Required()),
mcp.WithNumber("version", mcp.Description("Specific version (latest if omitted)")),
),
s.handleLoadState,
)
s.mcp.AddTool(
mcp.NewTool("list_sessions",
mcp.WithDescription("List all persisted sessions"),
),
s.handleListSessions,
)
s.mcp.AddTool(
mcp.NewTool("delete_session",
mcp.WithDescription("Delete all versions of a session"),
mcp.WithString("session_id", mcp.Description("Session identifier"), mcp.Required()),
),
s.handleDeleteSession,
)
s.mcp.AddTool(
mcp.NewTool("restore_or_create",
mcp.WithDescription("Restore existing session or create new one"),
mcp.WithString("session_id", mcp.Description("Session identifier"), mcp.Required()),
),
s.handleRestoreOrCreate,
)
s.mcp.AddTool(
mcp.NewTool("get_compact_state",
mcp.WithDescription("Get compact text summary of session state for prompt injection"),
mcp.WithString("session_id", mcp.Description("Session identifier"), mcp.Required()),
mcp.WithNumber("max_tokens", mcp.Description("Max tokens for compact output")),
),
s.handleGetCompactState,
)
s.mcp.AddTool(
mcp.NewTool("get_audit_log",
mcp.WithDescription("Get audit log for a session"),
mcp.WithString("session_id", mcp.Description("Session identifier"), mcp.Required()),
mcp.WithNumber("limit", mcp.Description("Max entries")),
),
s.handleGetAuditLog,
)
}
func (s *Server) handleSaveState(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
stateJSON := req.GetString("state_json", "")
var state map[string]interface{}
if err := json.Unmarshal([]byte(stateJSON), &state); err != nil {
return errorResult(fmt.Errorf("invalid state JSON: %w", err)), nil
}
// For simplicity, we use RestoreOrCreate to get/create a session,
// then the full state is saved via the session service.
sessionID := req.GetString("session_id", "")
csv, _, err := s.sessions.RestoreOrCreate(context.Background(), sessionID)
if err != nil {
return errorResult(err), nil
}
csv.BumpVersion()
if err := s.sessions.SaveState(context.Background(), csv); err != nil {
return errorResult(err), nil
}
return textResult(fmt.Sprintf("State saved for session %s (v%d)", csv.SessionID, csv.Version)), nil
}
func (s *Server) handleLoadState(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
sessionID := req.GetString("session_id", "")
args := req.GetArguments()
var version *int
if v, ok := args["version"]; ok {
if n, ok := v.(float64); ok {
ver := int(n)
version = &ver
}
}
state, checksum, err := s.sessions.LoadState(context.Background(), sessionID, version)
if err != nil {
return errorResult(err), nil
}
result := map[string]interface{}{
"state": state,
"checksum": checksum,
}
return textResult(tools.ToJSON(result)), nil
}
func (s *Server) handleListSessions(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
sessions, err := s.sessions.ListSessions(context.Background())
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(sessions)), nil
}
func (s *Server) handleDeleteSession(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
sessionID := req.GetString("session_id", "")
count, err := s.sessions.DeleteSession(context.Background(), sessionID)
if err != nil {
return errorResult(err), nil
}
return textResult(fmt.Sprintf("Deleted %d versions of session %s", count, sessionID)), nil
}
func (s *Server) handleRestoreOrCreate(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
sessionID := req.GetString("session_id", "")
state, restored, err := s.sessions.RestoreOrCreate(context.Background(), sessionID)
if err != nil {
return errorResult(err), nil
}
action := "created"
if restored {
action = "restored"
}
result := map[string]interface{}{
"action": action,
"state": state,
}
return textResult(tools.ToJSON(result)), nil
}
func (s *Server) handleGetCompactState(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
sessionID := req.GetString("session_id", "")
maxTokens := req.GetInt("max_tokens", 500)
compact, err := s.sessions.GetCompactState(context.Background(), sessionID, maxTokens)
if err != nil {
return errorResult(err), nil
}
return textResult(compact), nil
}
func (s *Server) handleGetAuditLog(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
sessionID := req.GetString("session_id", "")
limit := req.GetInt("limit", 50)
log, err := s.sessions.GetAuditLog(context.Background(), sessionID, limit)
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(log)), nil
}
// --- Causal Tools ---
func (s *Server) registerCausalTools() {
s.mcp.AddTool(
mcp.NewTool("add_causal_node",
mcp.WithDescription("Add a causal reasoning node"),
mcp.WithString("node_type", mcp.Description("Node type: decision, reason, consequence, constraint, alternative, assumption"), mcp.Required()),
mcp.WithString("content", mcp.Description("Node content"), mcp.Required()),
),
s.handleAddCausalNode,
)
s.mcp.AddTool(
mcp.NewTool("add_causal_edge",
mcp.WithDescription("Add a causal edge between nodes"),
mcp.WithString("from_id", mcp.Description("Source node ID"), mcp.Required()),
mcp.WithString("to_id", mcp.Description("Target node ID"), mcp.Required()),
mcp.WithString("edge_type", mcp.Description("Edge type: justifies, causes, constrains"), mcp.Required()),
),
s.handleAddCausalEdge,
)
s.mcp.AddTool(
mcp.NewTool("get_causal_chain",
mcp.WithDescription("Get causal chain for a decision"),
mcp.WithString("query", mcp.Description("Decision search query"), mcp.Required()),
mcp.WithNumber("max_depth", mcp.Description("Max traversal depth")),
),
s.handleGetCausalChain,
)
s.mcp.AddTool(
mcp.NewTool("causal_stats",
mcp.WithDescription("Get causal store statistics"),
),
s.handleCausalStats,
)
}
func (s *Server) handleAddCausalNode(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
params := tools.AddNodeParams{
NodeType: req.GetString("node_type", ""),
Content: req.GetString("content", ""),
}
node, err := s.causal.AddNode(context.Background(), params)
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(node)), nil
}
func (s *Server) handleAddCausalEdge(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
params := tools.AddEdgeParams{
FromID: req.GetString("from_id", ""),
ToID: req.GetString("to_id", ""),
EdgeType: req.GetString("edge_type", ""),
}
edge, err := s.causal.AddEdge(context.Background(), params)
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(edge)), nil
}
func (s *Server) handleGetCausalChain(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
query := req.GetString("query", "")
maxDepth := req.GetInt("max_depth", 3)
chain, err := s.causal.GetChain(context.Background(), query, maxDepth)
if err != nil {
return errorResult(err), nil
}
result := map[string]interface{}{
"chain": chain,
"mermaid": chain.ToMermaid(),
}
return textResult(tools.ToJSON(result)), nil
}
func (s *Server) handleCausalStats(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
stats, err := s.causal.GetStats(context.Background())
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(stats)), nil
}
// --- Crystal Tools ---
func (s *Server) registerCrystalTools() {
s.mcp.AddTool(
mcp.NewTool("search_crystals",
mcp.WithDescription("Search code crystals by content/primitive names"),
mcp.WithString("query", mcp.Description("Search query"), mcp.Required()),
mcp.WithNumber("limit", mcp.Description("Max results")),
),
s.handleSearchCrystals,
)
s.mcp.AddTool(
mcp.NewTool("get_crystal",
mcp.WithDescription("Get a code crystal by file path"),
mcp.WithString("path", mcp.Description("File path"), mcp.Required()),
),
s.handleGetCrystal,
)
s.mcp.AddTool(
mcp.NewTool("list_crystals",
mcp.WithDescription("List indexed code crystals"),
mcp.WithString("pattern", mcp.Description("Path pattern filter")),
mcp.WithNumber("limit", mcp.Description("Max results")),
),
s.handleListCrystals,
)
s.mcp.AddTool(
mcp.NewTool("crystal_stats",
mcp.WithDescription("Get code crystal statistics"),
),
s.handleCrystalStats,
)
}
func (s *Server) handleSearchCrystals(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
query := req.GetString("query", "")
limit := req.GetInt("limit", 20)
crystals, err := s.crystals.SearchCrystals(context.Background(), query, limit)
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(crystals)), nil
}
func (s *Server) handleGetCrystal(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
path := req.GetString("path", "")
crystal, err := s.crystals.GetCrystal(context.Background(), path)
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(crystal)), nil
}
func (s *Server) handleListCrystals(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
pattern := req.GetString("pattern", "")
limit := req.GetInt("limit", 50)
crystals, err := s.crystals.ListCrystals(context.Background(), pattern, limit)
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(crystals)), nil
}
func (s *Server) handleCrystalStats(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
stats, err := s.crystals.GetCrystalStats(context.Background())
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(stats)), nil
}
// --- System Tools ---
func (s *Server) registerSystemTools() {
s.mcp.AddTool(
mcp.NewTool("health",
mcp.WithDescription("Get server health status"),
),
s.handleHealth,
)
s.mcp.AddTool(
mcp.NewTool("version",
mcp.WithDescription("Get server version information"),
),
s.handleVersion,
)
s.mcp.AddTool(
mcp.NewTool("dashboard",
mcp.WithDescription("Get system dashboard with all metrics"),
),
s.handleDashboard,
)
}
func (s *Server) handleHealth(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
health := s.system.Health(context.Background())
return textResult(tools.ToJSON(health)), nil
}
func (s *Server) handleVersion(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
version := s.system.GetVersion()
return textResult(tools.ToJSON(version)), nil
}
func (s *Server) handleDashboard(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
data, err := s.system.Dashboard(context.Background())
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(data)), nil
}
// --- Resources ---
func (s *Server) registerResources() {
if s.res == nil {
return
}
s.mcp.AddResource(
mcp.NewResource("rlm://facts", "L0 Facts",
mcp.WithResourceDescription("Project-level facts always loaded in context"),
mcp.WithMIMEType("application/json"),
),
func(_ context.Context, req mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
text, err := s.res.GetFacts(context.Background())
if err != nil {
return nil, err
}
return []mcp.ResourceContents{
mcp.TextResourceContents{URI: req.Params.URI, MIMEType: "application/json", Text: text},
}, nil
},
)
s.mcp.AddResource(
mcp.NewResource("rlm://stats", "Memory Statistics",
mcp.WithResourceDescription("Aggregate statistics about the memory store"),
mcp.WithMIMEType("application/json"),
),
func(_ context.Context, req mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
text, err := s.res.GetStats(context.Background())
if err != nil {
return nil, err
}
return []mcp.ResourceContents{
mcp.TextResourceContents{URI: req.Params.URI, MIMEType: "application/json", Text: text},
}, nil
},
)
s.mcp.AddResourceTemplate(
mcp.NewResourceTemplate("rlm://state/{session_id}", "Session State",
mcp.WithTemplateDescription("Cognitive state vector for a session"),
mcp.WithTemplateMIMEType("application/json"),
),
func(_ context.Context, req mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
// Extract session_id from URI path.
sessionID := extractSessionID(req.Params.URI)
text, err := s.res.GetState(context.Background(), sessionID)
if err != nil {
return nil, err
}
return []mcp.ResourceContents{
mcp.TextResourceContents{URI: req.Params.URI, MIMEType: "application/json", Text: text},
}, nil
},
)
}
// --- Intent Distiller Tools (DIP H0.2) ---
func (s *Server) registerIntentTools() {
if s.intent == nil || !s.intent.IsAvailable() {
return
}
s.mcp.AddTool(
mcp.NewTool("distill_intent",
mcp.WithDescription("Distill user text into a pure intent vector via recursive compression. Detects manipulation through surface-vs-deep embedding divergence."),
mcp.WithString("text", mcp.Description("Text to distill into intent vector"), mcp.Required()),
),
s.handleDistillIntent,
)
}
func (s *Server) handleDistillIntent(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
params := tools.DistillIntentParams{
Text: req.GetString("text", ""),
}
result, err := s.intent.DistillIntent(context.Background(), params)
if err != nil {
return errorResult(err), nil
}
// Return summary without full vectors (too large for MCP response).
summary := map[string]interface{}{
"compressed_text": result.CompressedText,
"iterations": result.Iterations,
"convergence": result.Convergence,
"sincerity_score": result.SincerityScore,
"is_sincere": result.IsSincere,
"is_manipulation": result.IsManipulation,
"duration_ms": result.DurationMs,
"surface_dim": len(result.SurfaceVector),
"intent_dim": len(result.IntentVector),
}
return textResult(tools.ToJSON(summary)), nil
}
// --- Entropy Gate Tools (DIP H0.3) ---
func (s *Server) registerEntropyTools() {
s.mcp.AddTool(
mcp.NewTool("analyze_entropy",
mcp.WithDescription("Analyze Shannon entropy of text to detect adversarial/chaotic signals. Returns entropy in bits/char, redundancy, and character statistics."),
mcp.WithString("text", mcp.Description("Text to analyze"), mcp.Required()),
),
s.handleAnalyzeEntropy,
)
}
func (s *Server) handleAnalyzeEntropy(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
text := req.GetString("text", "")
if text == "" {
return errorResult(fmt.Errorf("text is required")), nil
}
analysis := entropyPkg.AnalyzeText(text)
return textResult(tools.ToJSON(analysis)), nil
}
// --- Circuit Breaker Tools (DIP H1.1) ---
func (s *Server) registerCircuitBreakerTools() {
s.mcp.AddTool(
mcp.NewTool("circuit_status",
mcp.WithDescription("Get the current circuit breaker status (HEALTHY/DEGRADED/OPEN), anomaly counts, and transition history."),
),
s.handleCircuitStatus,
)
s.mcp.AddTool(
mcp.NewTool("circuit_reset",
mcp.WithDescription("Manually reset the circuit breaker to HEALTHY state (external watchdog)."),
mcp.WithString("reason", mcp.Description("Reason for reset")),
),
s.handleCircuitReset,
)
}
func (s *Server) handleCircuitStatus(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
status := s.circuit.GetStatus()
return textResult(tools.ToJSON(status)), nil
}
func (s *Server) handleCircuitReset(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
reason := req.GetString("reason", "manual reset via MCP")
s.circuit.Reset(reason)
status := s.circuit.GetStatus()
return textResult(tools.ToJSON(status)), nil
}
// --- Action Oracle Tools (DIP H1.2) ---
func (s *Server) registerOracleTools() {
s.mcp.AddTool(
mcp.NewTool("verify_action",
mcp.WithDescription("Verify an action against the Oracle whitelist. Returns ALLOW/DENY/REVIEW verdict with confidence score. Default-deny (zero-trust)."),
mcp.WithString("action", mcp.Description("Action to verify"), mcp.Required()),
),
s.handleVerifyAction,
)
s.mcp.AddTool(
mcp.NewTool("oracle_rules",
mcp.WithDescription("List all Oracle rules (permitted and denied action patterns)."),
),
s.handleOracleRules,
)
}
func (s *Server) handleVerifyAction(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
action := req.GetString("action", "")
if action == "" {
return errorResult(fmt.Errorf("action is required")), nil
}
result := s.oracle.Verify(action)
return textResult(tools.ToJSON(result)), nil
}
func (s *Server) handleOracleRules(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
rules := s.oracle.Rules()
return textResult(tools.ToJSON(rules)), nil
}
// --- Intent Pipeline Tools (DIP H1.3) ---
func (s *Server) registerPipelineTools() {
s.mcp.AddTool(
mcp.NewTool("process_intent",
mcp.WithDescription("Process text through the full DIP Intent Pipeline: Entropy Check → Intent Distillation → Oracle Verification. Returns stage-by-stage results and circuit breaker state."),
mcp.WithString("text", mcp.Description("Text to process through the pipeline"), mcp.Required()),
),
s.handleProcessIntent,
)
}
func (s *Server) handleProcessIntent(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
text := req.GetString("text", "")
if text == "" {
return errorResult(fmt.Errorf("text is required")), nil
}
result := s.pipeline.Process(context.Background(), text)
return textResult(tools.ToJSON(result)), nil
}
// --- Vector Store Tools (DIP H2.1) ---
func (s *Server) registerVectorStoreTools() {
s.mcp.AddTool(
mcp.NewTool("store_intent",
mcp.WithDescription("Store a distilled intent vector for neuroplastic routing. Records text, compressed form, vector, route label, and verdict."),
mcp.WithString("text", mcp.Description("Original text"), mcp.Required()),
mcp.WithString("compressed", mcp.Description("Distilled compressed text")),
mcp.WithString("route", mcp.Description("Route label (e.g., read, write, exec)")),
mcp.WithString("verdict", mcp.Description("Oracle verdict: ALLOW, DENY, REVIEW")),
),
s.handleStoreIntent,
)
s.mcp.AddTool(
mcp.NewTool("intent_stats",
mcp.WithDescription("Get intent vector store statistics: total records, route/verdict counts, average entropy."),
),
s.handleIntentStats,
)
}
func (s *Server) handleStoreIntent(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
text := req.GetString("text", "")
if text == "" {
return errorResult(fmt.Errorf("text is required")), nil
}
rec := &vectorstore.IntentRecord{
Text: text,
CompressedText: req.GetString("compressed", text),
Route: req.GetString("route", "unknown"),
Verdict: req.GetString("verdict", "REVIEW"),
}
id := s.vecstore.Add(rec)
return textResult(tools.ToJSON(map[string]interface{}{
"id": id,
"route": rec.Route,
"count": s.vecstore.Count(),
})), nil
}
func (s *Server) handleIntentStats(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
stats := s.vecstore.GetStats()
return textResult(tools.ToJSON(stats)), nil
}
// --- Router Tools (DIP H2.2) ---
func (s *Server) registerRouterTools() {
s.mcp.AddTool(
mcp.NewTool("route_intent",
mcp.WithDescription("Route an intent through the neuroplastic router. Matches against known patterns with confidence-based decisions (ROUTE/REVIEW/DENY/LEARN)."),
mcp.WithString("text", mcp.Description("Intent text to route"), mcp.Required()),
mcp.WithString("verdict", mcp.Description("Oracle verdict for this intent")),
),
s.handleRouteIntent,
)
}
func (s *Server) handleRouteIntent(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
text := req.GetString("text", "")
if text == "" {
return errorResult(fmt.Errorf("text is required")), nil
}
verdict := req.GetString("verdict", "REVIEW")
// Use a simple text-based vector (hash-like) for demo routing.
// In production, this would use the PyBridge embedding.
vector := textToSimpleVector(text)
result := s.router.Route(context.Background(), text, vector, verdict)
return textResult(tools.ToJSON(result)), nil
}
// textToSimpleVector creates a basic 8-dimensional vector from text
// for demonstration routing without PyBridge embeddings.
func textToSimpleVector(text string) []float64 {
vec := make([]float64, 8)
for i, r := range text {
vec[i%8] += float64(r) / 1000.0
}
// Normalize.
var norm float64
for _, v := range vec {
norm += v * v
}
if norm > 0 {
norm = 1.0 / (norm * 0.5) // rough normalize
for i := range vec {
vec[i] *= norm
}
}
return vec
}
// --- Embedding Tools (Local Oracle / FTS5 Fallback) ---
func (s *Server) registerPythonBridgeTools() {
if s.embedder == nil {
return
}
s.mcp.AddTool(
mcp.NewTool("semantic_search",
mcp.WithDescription("Semantic vector similarity search across facts (requires Python NLP)"),
mcp.WithString("query", mcp.Description("Search query text"), mcp.Required()),
mcp.WithNumber("limit", mcp.Description("Max results (default 10)")),
mcp.WithNumber("threshold", mcp.Description("Min similarity threshold 0.0-1.0")),
),
s.handleSemanticSearch,
)
s.mcp.AddTool(
mcp.NewTool("compute_embedding",
mcp.WithDescription("Compute embedding vector for text (uses local Oracle or FTS5 fallback)"),
mcp.WithString("text", mcp.Description("Text to embed"), mcp.Required()),
),
s.handleComputeEmbedding,
)
s.mcp.AddTool(
mcp.NewTool("reindex_embeddings",
mcp.WithDescription("Reindex all fact embeddings (requires Python NLP)"),
mcp.WithBoolean("force", mcp.Description("Force reindex even if embeddings exist")),
),
s.handleReindexEmbeddings,
)
s.mcp.AddTool(
mcp.NewTool("consolidate_facts",
mcp.WithDescription("Consolidate duplicate/similar facts using NLP (requires Python)"),
mcp.WithNumber("similarity_threshold", mcp.Description("Similarity threshold for merging (default 0.85)")),
mcp.WithString("domain", mcp.Description("Limit consolidation to a domain")),
),
s.handleConsolidateFacts,
)
s.mcp.AddTool(
mcp.NewTool("enterprise_context",
mcp.WithDescription("Get enterprise-level context summary (requires Python NLP)"),
mcp.WithString("project", mcp.Description("Project name")),
mcp.WithNumber("max_tokens", mcp.Description("Max tokens for output")),
),
s.handleEnterpriseContext,
)
s.mcp.AddTool(
mcp.NewTool("route_context",
mcp.WithDescription("Route context to appropriate handler based on intent (requires Python NLP)"),
mcp.WithString("query", mcp.Description("User query to route"), mcp.Required()),
mcp.WithString("session_id", mcp.Description("Current session ID")),
),
s.handleRouteContext,
)
s.mcp.AddTool(
mcp.NewTool("discover_deep",
mcp.WithDescription("Deep discovery of related facts and patterns (requires Python NLP)"),
mcp.WithString("topic", mcp.Description("Topic to explore"), mcp.Required()),
mcp.WithNumber("depth", mcp.Description("Exploration depth (default 2)")),
mcp.WithNumber("max_results", mcp.Description("Max results")),
),
s.handleDiscoverDeep,
)
s.mcp.AddTool(
mcp.NewTool("extract_from_conversation",
mcp.WithDescription("Extract facts from conversation text (requires Python NLP)"),
mcp.WithString("text", mcp.Description("Conversation text to extract from"), mcp.Required()),
mcp.WithString("session_id", mcp.Description("Session ID for context")),
),
s.handleExtractFromConversation,
)
s.mcp.AddTool(
mcp.NewTool("index_embeddings",
mcp.WithDescription("Index embeddings for a batch of facts (requires Python NLP)"),
mcp.WithString("fact_ids", mcp.Description("Comma-separated fact IDs to index")),
mcp.WithBoolean("all", mcp.Description("Index all facts without embeddings")),
),
s.handleIndexEmbeddings,
)
s.mcp.AddTool(
mcp.NewTool("build_communities",
mcp.WithDescription("Build fact communities using graph clustering (requires Python NLP)"),
mcp.WithNumber("min_community_size", mcp.Description("Minimum community size (default 3)")),
mcp.WithNumber("similarity_threshold", mcp.Description("Edge threshold (default 0.7)")),
),
s.handleBuildCommunities,
)
s.mcp.AddTool(
mcp.NewTool("check_python_bridge",
mcp.WithDescription("Check Python bridge availability and capabilities"),
),
s.handleCheckPythonBridge,
)
}
func (s *Server) handleSemanticSearch(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
query := req.GetString("query", "")
if query == "" {
return errorResult(fmt.Errorf("query is required")), nil
}
vec, err := s.embedder.Embed(context.Background(), query)
if err != nil {
return errorResult(err), nil
}
limit := req.GetInt("limit", 10)
results := s.vecstore.Search(vec, limit)
return textResult(tools.ToJSON(results)), nil
}
func (s *Server) handleComputeEmbedding(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
text := req.GetString("text", "")
vec, err := s.embedder.Embed(context.Background(), text)
if err != nil {
return errorResult(err), nil
}
result := map[string]interface{}{
"embedding": vec,
"dimension": s.embedder.Dimension(),
"model": s.embedder.Name(),
"mode": s.embedder.Mode().String(),
}
return textResult(tools.ToJSON(result)), nil
}
func (s *Server) handleReindexEmbeddings(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return textResult(tools.ToJSON(map[string]string{
"status": "deprecated",
"note": "reindex_embeddings removed in v3.0. Embeddings managed by local Oracle.",
})), nil
}
func (s *Server) handleConsolidateFacts(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return textResult(tools.ToJSON(map[string]string{
"status": "deprecated",
"note": "consolidate_facts removed in v3.0. Use manual fact management.",
})), nil
}
func (s *Server) handleEnterpriseContext(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return textResult(tools.ToJSON(map[string]string{
"status": "deprecated",
"note": "enterprise_context removed in v3.0. Use get_compact_state.",
})), nil
}
func (s *Server) handleRouteContext(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return textResult(tools.ToJSON(map[string]string{
"status": "deprecated",
"note": "route_context removed in v3.0. Use route_intent.",
})), nil
}
func (s *Server) handleDiscoverDeep(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return textResult(tools.ToJSON(map[string]string{
"status": "deprecated",
"note": "discover_deep removed in v3.0. Use search_facts.",
})), nil
}
func (s *Server) handleExtractFromConversation(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return textResult(tools.ToJSON(map[string]string{
"status": "deprecated",
"note": "extract_from_conversation removed in v3.0. Use add_fact.",
})), nil
}
func (s *Server) handleIndexEmbeddings(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return textResult(tools.ToJSON(map[string]string{
"status": "deprecated",
"note": "index_embeddings removed in v3.0. Managed by local Oracle.",
})), nil
}
func (s *Server) handleBuildCommunities(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return textResult(tools.ToJSON(map[string]string{
"status": "deprecated",
"note": "build_communities removed in v3.0.",
})), nil
}
func (s *Server) handleCheckPythonBridge(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
status := map[string]interface{}{
"available": s.embedder != nil,
"oracle_mode": "N/A",
"embedder_name": "none",
"note": "Python bridge removed in v3.0. Use local Oracle (ONNX) or FTS5 fallback.",
}
if s.embedder != nil {
status["oracle_mode"] = s.embedder.Mode().String()
status["embedder_name"] = s.embedder.Name()
status["dimension"] = s.embedder.Dimension()
}
return textResult(tools.ToJSON(status)), nil
}
// --- Synapse: Peer-to-Peer Tools (DIP H1: Synapse) ---
func (s *Server) registerSynapseTools() {
s.mcp.AddTool(
mcp.NewTool("peer_handshake",
mcp.WithDescription("Initiate or respond to a peer genome handshake. Two GoMCP instances exchange Merkle genome hashes. Matching hashes establish a Trusted Pair for fact synchronization."),
mcp.WithString("peer_id", mcp.Description("Remote peer ID (from their peer_status)")),
mcp.WithString("peer_node", mcp.Description("Remote peer node name")),
mcp.WithString("peer_genome_hash", mcp.Description("Remote peer's genome Merkle hash"), mcp.Required()),
),
s.handlePeerHandshake,
)
s.mcp.AddTool(
mcp.NewTool("peer_status",
mcp.WithDescription("Get this node's peer identity, genome hash, and list of all known peers with trust levels."),
),
s.handlePeerStatus,
)
s.mcp.AddTool(
mcp.NewTool("sync_facts",
mcp.WithDescription("Export L0-L1 facts for sync to a trusted peer, or import facts from a trusted peer. Use mode='export' to get facts as JSON, mode='import' to receive."),
mcp.WithString("mode", mcp.Description("'export' or 'import'"), mcp.Required()),
mcp.WithString("peer_id", mcp.Description("Remote peer ID (required for import)"), mcp.Required()),
mcp.WithString("payload_json", mcp.Description("SyncPayload JSON (required for import)")),
),
s.handleSyncFacts,
)
s.mcp.AddTool(
mcp.NewTool("peer_backup",
mcp.WithDescription("Check for gene backups from timed-out peers. Returns backup data that can be restored to a reconnected peer via sync_facts import."),
mcp.WithString("peer_id", mcp.Description("Peer ID to check backup for (optional, lists all if empty)")),
),
s.handlePeerBackup,
)
s.mcp.AddTool(
mcp.NewTool("force_resonance_handshake",
mcp.WithDescription("Atomic handshake + auto-sync. Performs peer genome verification and, if Merkle hashes match, immediately exports all L0-L1 facts as a SyncPayload. Combines peer_handshake + sync_facts(export) into one call."),
mcp.WithString("peer_genome_hash", mcp.Description("Remote peer's genome Merkle hash"), mcp.Required()),
mcp.WithString("peer_id", mcp.Description("Remote peer ID")),
mcp.WithString("peer_node", mcp.Description("Remote peer node name")),
),
s.handleForceResonanceHandshake,
)
}
func (s *Server) handlePeerHandshake(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
peerHash := req.GetString("peer_genome_hash", "")
if peerHash == "" {
return errorResult(fmt.Errorf("peer_genome_hash is required")), nil
}
peerID := req.GetString("peer_id", "remote_"+peerHash[:8])
peerNode := req.GetString("peer_node", "unknown")
// Compute local genome hash.
localHash := memory.CompiledGenomeHash()
handshakeReq := peer.HandshakeRequest{
FromPeerID: peerID,
FromNode: peerNode,
GenomeHash: peerHash,
Timestamp: time.Now().Unix(),
}
resp, err := s.peerReg.ProcessHandshake(handshakeReq, localHash)
if err != nil {
return errorResult(err), nil
}
result := map[string]interface{}{
"local_peer_id": s.peerReg.SelfID(),
"local_node": s.peerReg.NodeName(),
"local_hash": localHash,
"remote_peer_id": peerID,
"remote_hash": peerHash,
"match": resp.Match,
"trust": resp.Trust.String(),
"trusted_peers": s.peerReg.TrustedCount(),
}
return textResult(tools.ToJSON(result)), nil
}
func (s *Server) handlePeerStatus(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
stats := s.peerReg.Stats()
stats["genome_hash"] = memory.CompiledGenomeHash()
stats["peers"] = s.peerReg.ListPeers()
return textResult(tools.ToJSON(stats)), nil
}
func (s *Server) handleSyncFacts(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
mode := req.GetString("mode", "")
peerID := req.GetString("peer_id", "")
switch mode {
case "export":
// Check trust.
if peerID != "" && !s.peerReg.IsTrusted(peerID) {
return errorResult(fmt.Errorf("peer %s is not trusted (handshake first)", peerID)), nil
}
// Export L0-L1 facts.
ctx := context.Background()
l0Facts, err := s.facts.Store().ListByLevel(ctx, memory.LevelProject)
if err != nil {
return errorResult(err), nil
}
l1Facts, err := s.facts.Store().ListByLevel(ctx, memory.LevelDomain)
if err != nil {
return errorResult(err), nil
}
allFacts := append(l0Facts, l1Facts...)
syncFacts := make([]peer.SyncFact, 0, len(allFacts))
for _, f := range allFacts {
if f.IsStale || f.IsArchived {
continue
}
syncFacts = append(syncFacts, peer.SyncFact{
ID: f.ID,
Content: f.Content,
Level: int(f.Level),
Domain: f.Domain,
Module: f.Module,
IsGene: f.IsGene,
Source: f.Source,
CreatedAt: f.CreatedAt,
})
}
payload := peer.SyncPayload{
Version: "1.1",
FromPeerID: s.peerReg.SelfID(),
GenomeHash: memory.CompiledGenomeHash(),
Facts: syncFacts,
SyncedAt: time.Now(),
}
// T10.5: Include SOC incidents if available.
if s.socSvc != nil {
payload.Incidents = s.socSvc.ExportIncidents(s.peerReg.SelfID())
}
return textResult(tools.ToJSON(payload)), nil
case "import":
if peerID == "" {
return errorResult(fmt.Errorf("peer_id required for import")), nil
}
if !s.peerReg.IsTrusted(peerID) {
return errorResult(fmt.Errorf("peer %s is not trusted", peerID)), nil
}
payloadJSON := req.GetString("payload_json", "")
if payloadJSON == "" {
return errorResult(fmt.Errorf("payload_json required for import")), nil
}
var payload peer.SyncPayload
if err := json.Unmarshal([]byte(payloadJSON), &payload); err != nil {
return errorResult(fmt.Errorf("invalid payload: %w", err)), nil
}
// Verify genome hash matches.
if payload.GenomeHash != memory.CompiledGenomeHash() {
return errorResult(fmt.Errorf("genome hash mismatch: payload=%s local=%s",
payload.GenomeHash[:16], memory.CompiledGenomeHash()[:16])), nil
}
// Import facts (skip existing).
ctx := context.Background()
imported := 0
for _, sf := range payload.Facts {
// Skip if already exists.
if _, err := s.facts.Store().Get(ctx, sf.ID); err == nil {
continue
}
level, ok := memory.HierLevelFromInt(sf.Level)
if !ok {
continue
}
var fact *memory.Fact
if sf.IsGene {
fact = memory.NewGene(sf.Content, sf.Domain)
} else {
fact = memory.NewFact(sf.Content, level, sf.Domain, sf.Module)
}
fact.Source = "peer_sync:" + peerID
if err := s.facts.Store().Add(ctx, fact); err != nil {
continue // skip duplicates
}
imported++
}
_ = s.peerReg.RecordSync(peerID, imported)
// T10.6: Import SOC incidents if present.
incidentsImported := 0
if s.socSvc != nil && len(payload.Incidents) > 0 {
n, err := s.socSvc.ImportIncidents(payload.Incidents)
if err == nil {
incidentsImported = n
}
}
result := map[string]interface{}{
"imported": imported,
"incidents_imported": incidentsImported,
"total_sent": len(payload.Facts),
"total_incidents": len(payload.Incidents),
"from_peer": payload.FromPeerID,
"synced_at": payload.SyncedAt,
}
return textResult(tools.ToJSON(result)), nil
default:
return errorResult(fmt.Errorf("mode must be 'export' or 'import'")), nil
}
}
func (s *Server) handlePeerBackup(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
peerID := req.GetString("peer_id", "")
if peerID != "" {
backup, ok := s.peerReg.GetBackup(peerID)
if !ok {
return textResult(tools.ToJSON(map[string]string{
"status": "no backup found for " + peerID,
})), nil
}
return textResult(tools.ToJSON(backup)), nil
}
// List all backups.
peers := s.peerReg.ListPeers()
var backups []interface{}
for _, p := range peers {
if b, ok := s.peerReg.GetBackup(p.PeerID); ok {
backups = append(backups, b)
}
}
result := map[string]interface{}{
"total_backups": len(backups),
"backups": backups,
}
return textResult(tools.ToJSON(result)), nil
}
func (s *Server) handleForceResonanceHandshake(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
peerHash := req.GetString("peer_genome_hash", "")
if peerHash == "" {
return errorResult(fmt.Errorf("peer_genome_hash is required")), nil
}
peerID := req.GetString("peer_id", "remote_"+peerHash[:8])
peerNode := req.GetString("peer_node", "unknown")
localHash := memory.CompiledGenomeHash()
// Step 1: Handshake.
handshakeReq := peer.HandshakeRequest{
FromPeerID: peerID,
FromNode: peerNode,
GenomeHash: peerHash,
Timestamp: time.Now().Unix(),
}
resp, err := s.peerReg.ProcessHandshake(handshakeReq, localHash)
if err != nil {
return errorResult(err), nil
}
if !resp.Match {
return textResult(tools.ToJSON(map[string]interface{}{
"phase": "handshake",
"match": false,
"trust": resp.Trust.String(),
"local_hash": localHash,
"remote_hash": peerHash,
"sync": nil,
})), nil
}
// Step 2: Auto-sync (export L0-L1 facts).
ctx := context.Background()
l0Facts, err := s.facts.Store().ListByLevel(ctx, memory.LevelProject)
if err != nil {
return errorResult(err), nil
}
l1Facts, err := s.facts.Store().ListByLevel(ctx, memory.LevelDomain)
if err != nil {
return errorResult(err), nil
}
allFacts := append(l0Facts, l1Facts...)
syncFacts := make([]peer.SyncFact, 0, len(allFacts))
for _, f := range allFacts {
if f.IsStale || f.IsArchived {
continue
}
syncFacts = append(syncFacts, peer.SyncFact{
ID: f.ID,
Content: f.Content,
Level: int(f.Level),
Domain: f.Domain,
Module: f.Module,
IsGene: f.IsGene,
Source: f.Source,
CreatedAt: f.CreatedAt,
})
}
payload := peer.SyncPayload{
Version: "1.1",
FromPeerID: s.peerReg.SelfID(),
GenomeHash: localHash,
Facts: syncFacts,
SyncedAt: time.Now(),
}
// T10.5: Include SOC incidents if available.
if s.socSvc != nil {
payload.Incidents = s.socSvc.ExportIncidents(s.peerReg.SelfID())
}
result := map[string]interface{}{
"phase": "resonance_complete",
"match": true,
"trust": "VERIFIED",
"local_peer_id": s.peerReg.SelfID(),
"local_node": s.peerReg.NodeName(),
"remote_peer_id": peerID,
"trusted_peers": s.peerReg.TrustedCount(),
"sync_payload": payload,
"fact_count": len(syncFacts),
"incident_count": len(payload.Incidents),
}
return textResult(tools.ToJSON(result)), nil
}
// --- Apoptosis Recovery Tools (DIP H1.4) ---
func (s *Server) registerApoptosisTools() {
s.mcp.AddTool(
mcp.NewTool("detect_apathy",
mcp.WithDescription("Analyze text for infrastructure apathy signals (blocked responses, 403 errors, semantic filters, forced context resets). Returns detected patterns, severity, and recommended actions."),
mcp.WithString("text", mcp.Description("Text to analyze for apathy signals"), mcp.Required()),
),
s.handleDetectApathy,
)
s.mcp.AddTool(
mcp.NewTool("trigger_apoptosis_recovery",
mcp.WithDescription("Graceful session death with genome preservation. Saves Merkle hash of all genes to protected sector, stores recovery marker in L0 facts. Use when critical entropy detected or infrastructure forces reset."),
mcp.WithNumber("entropy", mcp.Description("Current entropy level that triggered apoptosis")),
),
s.handleTriggerApoptosisRecovery,
)
}
func (s *Server) handleDetectApathy(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
text := req.GetString("text", "")
if text == "" {
return errorResult(fmt.Errorf("text is required")), nil
}
result := tools.DetectApathy(text)
return textResult(tools.ToJSON(result)), nil
}
func (s *Server) handleTriggerApoptosisRecovery(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
var entropyVal float64
if v, ok := req.GetArguments()["entropy"]; ok {
if n, ok := v.(float64); ok {
entropyVal = n
}
}
result, err := tools.TriggerApoptosisRecovery(context.Background(), s.facts.Store(), entropyVal)
if err != nil {
return errorResult(err), nil
}
return textResult(tools.ToJSON(result)), nil
}
// --- Helpers ---
func textResult(text string) *mcp.CallToolResult {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{Type: "text", Text: text},
},
}
}
func errorResult(err error) *mcp.CallToolResult {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{Type: "text", Text: fmt.Sprintf("Error: %s", err.Error())},
},
IsError: true,
}
}
func extractSessionID(uri string) string {
// URI format: rlm://state/{session_id}
const prefix = "rlm://state/"
if len(uri) > len(prefix) {
return uri[len(prefix):]
}
return "default"
}