Class: Insika::McpStore

Inherits:
Object
  • Object
show all
Includes:
Coercion
Defined in:
lib/insika/mcp_store.rb

Overview

MCP instances authored at runtime. One record per instance in the ConfigStore (scope "mcp"), keyed by the name slug (tavily, github, ...). Holds transport/command/url, the enabled flag and a Hash of env credentials (tokens/keys the instance injects into the server).

The credentials (env) NEVER leave here in plaintext to the UI: the display reads (get/all) mask EACH value with the __OCULTO__ sentinel. Only get_raw/all_raw (consumed by an MCP client, never by the screen) return the real values. On write, the sentinel coming back preserves the value; a new string replaces it; "" clears it (see Insika::SecretMasking, the same pattern as the LLM api_keys).

Current scope: durable config CRUD (the instances UI). Running an MCP client against these instances is later runtime work — the store is the editable source from now on.

Constant Summary collapse

SCOPE =
"mcp"

Instance Method Summary collapse

Methods included from Coercion

blank?, deep_stringify, presence, present?, utf8

Constructor Details

#initialize(config_store:) ⇒ McpStore

Returns a new instance of McpStore.



24
25
26
# File 'lib/insika/mcp_store.rb', line 24

def initialize(config_store:)
  @cs = config_store
end

Instance Method Details

#allObject

-> [Hash] all MASKED (for the UI).



42
43
44
# File 'lib/insika/mcp_store.rb', line 42

def all
  names.filter_map { |n| get(n) }
end

#all_rawObject

-> [Hash] all with REAL env (for an MCP client). Never goes to the screen.



47
48
49
# File 'lib/insika/mcp_store.rb', line 47

def all_raw
  names.filter_map { |n| raw(n) }
end

#delete(name) ⇒ Object

-> bool (did it exist?).



76
# File 'lib/insika/mcp_store.rb', line 76

def delete(name) = @cs.delete(SCOPE, name.to_s)

#get(name) ⇒ Object

-> MASKED Hash (env with sentinel) | nil.



29
30
31
# File 'lib/insika/mcp_store.rb', line 29

def get(name)
  mask(raw(name))
end

#get_raw(name) ⇒ Object

-> Hash with REAL env | nil. Internal use (MCP client), never the screen.



34
35
36
# File 'lib/insika/mcp_store.rb', line 34

def get_raw(name)
  raw(name)
end

#namesObject

-> [String] slugs, lexicographic order.



39
# File 'lib/insika/mcp_store.rb', line 39

def names = @cs.keys(SCOPE)

#upsert(attrs) ⇒ Object

Upsert with per-env-key secret reconciliation. attrs (string|symbol keys):

name (required), transport, command, url, description,
enabled (bool), env ({ "KEY" => value|sentinel|"" })

-> MASKED Hash (the stored record).



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/insika/mcp_store.rb', line 56

def upsert(attrs)
  h = symbolize(attrs)
  name = presence(h[:name])
  raise Insika::ValidationError, "name is required" if name.nil?

  existing = raw(name)
  record = {
    "name" => name,
    "transport" => presence(h[:transport]) || "stdio",
    "command" => presence(h[:command]),
    "url" => presence(h[:url]),
    "description" => presence(h[:description]),
    "enabled" => h.fetch(:enabled, true) ? true : false,
    "env" => reconcile_env(h[:env], existing && existing["env"])
  }
  @cs.put(SCOPE, name, record)
  mask(record)
end