Class: SmartBrain::Server::McpServer

Inherits:
Object
  • Object
show all
Defined in:
lib/smart_brain/server/mcp_server.rb

Overview

Model Context Protocol server over stdio (newline-delimited JSON-RPC 2.0).

No external MCP gem dependency: implements the subset clients actually use (initialize / notifications/initialized / tools/list / tools/call) so that Claude Code, Cursor, etc. can drive SmartBrain as a native tool.

Constant Summary collapse

PROTOCOL_VERSION =
'2024-11-05'
INSTRUCTIONS =
<<~TXT.strip
  SmartBrain memory runtime. Available tools:
  - commit_turn: persist a turn (messages + structured events) and extract durable memory.
  - compose_context: assemble the minimal sufficient context (summary + recent + evidence) for a user message.
  - search_memory: full-text recall over the session's persistent memory.
  - knowledge_distill / knowledge_gate / knowledge_promote / knowledge_demote: the Stage-1 knowledge lifecycle (evidence → candidate → promoted → demoted). Distill reusable rules at tier dao_ren or qi; promote only after enough supporting evidence; demote with a reason_type (contradicted|obsolete|superseded).
  - kg_add / kg_query / kg_invalidate: knowledge-graph triples (subject-predicate-object) with temporal validity. Run fact_check before asserting entity relations to catch name/contradiction/stale issues.
  - fact_check: offline, zero-LLM contradiction scan (SimilarNameConflict / RelationContradiction / StaleFact) of a text against the session's entities + KG.
  - brief / wake_up: deterministic citation-first cognitive snapshot and L0/L1 resume payload.
  - status: backend, metrics, counts.
  Always pass a stable session_id per conversation. Prefer compose_context before answering, and commit_turn after each assistant reply.
TXT
SCOPE_PROPERTIES =
{
  domain_id: { type: 'string' },
  scope_context: { type: 'object' }
}.freeze
TOOLS =
[
  {
    name: 'commit_turn',
    description: 'Persist a conversation turn and extract structured memory items (decisions, tasks, goals, entities, preferences).',
    inputSchema: {
      type: 'object',
      properties: SCOPE_PROPERTIES.merge(
        session_id: { type: 'string' },
        turn_events: {
          type: 'object',
          description: 'messages: [{role, content}], plus optional tasks/decisions/goals/entities/preferences/refs arrays.'
        }
      ),
      required: %w[session_id turn_events]
    }
  },
  {
    name: 'compose_context',
    description: 'Retrieve evidence and assemble a context package for the latest user message.',
    inputSchema: {
      type: 'object',
      properties: SCOPE_PROPERTIES.merge(
        session_id: { type: 'string' },
        user_message: { type: 'string' },
        agent_state: { type: 'object' }
      ),
      required: %w[session_id user_message]
    }
  },
  {
    name: 'search_memory',
    description: 'Full-text search the session\'s persistent memory; returns ranked evidence.',
    inputSchema: {
      type: 'object',
      properties: SCOPE_PROPERTIES.merge(
        session_id: { type: 'string' },
        query: { type: 'string' },
        limit: { type: 'integer' }
      ),
      required: %w[session_id query]
    }
  },
  {
    name: 'status',
    description: 'SmartBrain diagnostics: storage backend, P95, memory/resource ratio, counts.',
    inputSchema: { type: 'object', properties: {}, additionalProperties: false }
  },
  {
    name: 'knowledge_distill',
    description: 'Distill a candidate knowledge item (tier dao_ren or qi) from supporting evidence refs.',
    inputSchema: {
      type: 'object',
      properties: SCOPE_PROPERTIES.merge(
        session_id: { type: 'string' },
        statement: { type: 'string' },
        content: { type: 'string' },
        tier: { type: 'string', enum: %w[dao_ren qi] },
        supporting_refs: { type: 'array', items: { type: 'string' } },
        domain: { type: 'string' }, field: { type: 'string' }, reviewer: { type: 'string' }
      ),
      required: %w[session_id statement tier]
    }
  },
  {
    name: 'knowledge_gate',
    description: 'Read-only check: is a candidate knowledge item ready for promotion?',
    inputSchema: { type: 'object', properties: SCOPE_PROPERTIES.merge(memory_item_id: { type: 'string' }, session_id: { type: 'string' }), required: %w[memory_item_id] }
  },
  {
    name: 'knowledge_promote',
    description: 'Gate-enforced promotion of a candidate knowledge item to promoted.',
    inputSchema: {
      type: 'object',
      properties: SCOPE_PROPERTIES.merge(
        session_id: { type: 'string' },
        memory_item_id: { type: 'string' },
        verification_refs: { type: 'array', items: { type: 'string' } },
        reason: { type: 'string' }, reviewer: { type: 'string' }, force: { type: 'boolean' }
      ),
      required: %w[memory_item_id verification_refs reason]
    }
  },
  {
    name: 'knowledge_demote',
    description: 'Evidence-backed demotion of a knowledge item (reason_type: contradicted|obsolete|superseded).',
    inputSchema: {
      type: 'object',
      properties: SCOPE_PROPERTIES.merge(
        session_id: { type: 'string' },
        memory_item_id: { type: 'string' },
        evidence_refs: { type: 'array', items: { type: 'string' } },
        reason: { type: 'string' },
        reason_type: { type: 'string', enum: %w[contradicted obsolete superseded] }
      ),
      required: %w[memory_item_id evidence_refs reason reason_type]
    }
  },
  {
    name: 'knowledge_promote_to_scope',
    description: 'Copy a verified memory item into a writable target scope while preserving provenance.',
    inputSchema: {
      type: 'object',
      properties: SCOPE_PROPERTIES.merge(
        session_id: { type: 'string' }, memory_item_id: { type: 'string' }, target_scope: { type: 'object' },
        verification_refs: { type: 'array', items: { type: 'string' } }, reason: { type: 'string' }, reviewer: { type: 'string' }
      ),
      required: %w[domain_id session_id scope_context memory_item_id target_scope verification_refs reason]
    }
  },
  {
    name: 'knowledge_retract',
    description: 'Retract a memory item with evidence and an audit event.',
    inputSchema: {
      type: 'object',
      properties: SCOPE_PROPERTIES.merge(
        session_id: { type: 'string' }, memory_item_id: { type: 'string' },
        evidence_refs: { type: 'array', items: { type: 'string' } }, reason: { type: 'string' }, reviewer: { type: 'string' }
      ),
      required: %w[memory_item_id evidence_refs reason]
    }
  },
  {
    name: 'knowledge_lineage',
    description: 'Return cross-scope promotion lineage for a memory item.',
    inputSchema: {
      type: 'object', properties: SCOPE_PROPERTIES.merge(session_id: { type: 'string' }, memory_item_id: { type: 'string' }),
      required: %w[memory_item_id]
    }
  },
  {
    name: 'kg_add',
    description: 'Add a knowledge-graph triple (subject-predicate-object) with temporal validity.',
    inputSchema: {
      type: 'object',
      properties: SCOPE_PROPERTIES.merge(
        session_id: { type: 'string' },
        subject: { type: 'string' },
        predicate: { type: 'string' },
        object: { type: 'string' },
        confidence: { type: 'number' },
        scope_ref: { type: 'object' }
      ),
      required: %w[session_id subject predicate object]
    }
  },
  {
    name: 'kg_query',
    description: 'Query KG triples (any of subject/predicate/object; include_invalid to see invalidated).',
    inputSchema: {
      type: 'object',
      properties: SCOPE_PROPERTIES.merge(
        session_id: { type: 'string' },
        subject: { type: 'string' }, predicate: { type: 'string' }, object: { type: 'string' },
        include_invalid: { type: 'boolean' }
      ),
      required: %w[session_id]
    }
  },
  {
    name: 'kg_invalidate',
    description: 'Invalidate a KG triple (sets valid_to=now, status=invalidated).',
    inputSchema: {
      type: 'object',
      properties: SCOPE_PROPERTIES.merge(
        edge_id: { type: 'string' }, reason: { type: 'string' }, session_id: { type: 'string' }
      ),
      required: %w[edge_id]
    }
  },
  {
    name: 'kg_timeline',
    description: 'Return the temporal history of KG edges for a subject across readable scopes.',
    inputSchema: {
      type: 'object', properties: SCOPE_PROPERTIES.merge(session_id: { type: 'string' }, subject: { type: 'string' }),
      required: %w[session_id subject]
    }
  },
  {
    name: 'kg_stats',
    description: 'Return KG active and invalidated counts across readable scopes.',
    inputSchema: {
      type: 'object', properties: SCOPE_PROPERTIES.merge(session_id: { type: 'string' }), required: %w[session_id]
    }
  },
  {
    name: 'fact_check',
    description: 'Offline zero-LLM contradiction scan of text vs session entities + KG (SimilarName/Relation/StaleFact).',
    inputSchema: {
      type: 'object',
      properties: SCOPE_PROPERTIES.merge(session_id: { type: 'string' }, text: { type: 'string' }),
      required: %w[session_id text]
    }
  },
  {
    name: 'brief',
    description: 'Deterministic citation-first cognitive snapshot (summary/key_facts/evidence/entities/unresolved/uncertainty/next_actions).',
    inputSchema: {
      type: 'object',
      properties: SCOPE_PROPERTIES.merge(session_id: { type: 'string' }, query: { type: 'string' }),
      required: %w[session_id]
    }
  },
  {
    name: 'wake_up',
    description: 'Minimal L0/L1 importance-ordered resume payload for a session.',
    inputSchema: { type: 'object', properties: SCOPE_PROPERTIES.merge(session_id: { type: 'string' }), required: %w[session_id] }
  }
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(service:, in_io: $stdin, out_io: $stdout, err_io: $stderr) ⇒ McpServer

Returns a new instance of McpServer.



246
247
248
249
250
251
# File 'lib/smart_brain/server/mcp_server.rb', line 246

def initialize(service:, in_io: $stdin, out_io: $stdout, err_io: $stderr)
  @service = service
  @in = in_io
  @out = out_io
  @err = err_io
end

Instance Attribute Details

#serviceObject (readonly)

Returns the value of attribute service.



244
245
246
# File 'lib/smart_brain/server/mcp_server.rb', line 244

def service
  @service
end

Instance Method Details

#runObject



253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/smart_brain/server/mcp_server.rb', line 253

def run
  @out.sync = true
  @in.each_line do |raw|
    line = raw.to_s.strip
    next if line.empty?

    request = JSON.parse(line)
    handle(request)
  rescue JSON::ParserError => e
    log("ignoring malformed line: #{e.message}")
  rescue StandardError => e
    log("handler error: #{e.class}: #{e.message}")
  end
end