Skip to content
Kward Search API index

Class: Kward::RPC::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/kward/rpc/server.rb

Overview

JSON-RPC backend for trusted local UI clients.

The server speaks LSP-style Content-Length framing over stdin/stdout, exposes capabilities during initialize, redacts secrets in errors and notifications, and coordinates auth, config, sessions, turns, tools, memory, commands, and startup resources.

Server should stay focused on protocol concerns: framing, JSON-RPC error codes, method dispatch, capability reporting, and redaction at the wire boundary. Delegate stateful product behavior to manager objects such as SessionManager, AuthManager, and ConfigManager. When adding an RPC feature, update dispatch, capabilities, docs, and tests together so clients can trust initialize as the source of supported behavior.

Constant Summary collapse

PROTOCOL_VERSION =
1
JSONRPC_VERSION =
"2.0"
BUILTIN_SLASH_COMMAND_NAMES =
PromptCommands::BUILTIN_RESERVED_COMMAND_NAMES
ERROR_CODES =
{
  parse_error: -32_700,
  invalid_request: -32_600,
  method_not_found: -32_601,
  invalid_params: -32_602,
  internal_error: -32_603
}.freeze
PROTOCOL_METHODS =
["initialize", "shutdown"].freeze
WORKSPACE_METHODS =
["workspace/validate", "workspace/info"].freeze
TOOL_METHODS =
["tools/list"].freeze
MCP_METHODS =
["mcp/status"].freeze
PROMPT_METHODS =
["prompts/list", "prompts/expand"].freeze
SESSION_METHODS =
[
  "sessions/create", "sessions/resume", "sessions/list", "sessions/rename",
  "sessions/clone", "sessions/compact", "sessions/forkMessages", "sessions/fork",
  "sessions/tree", "sessions/tree/setLabel", "sessions/tree/navigate",
  "sessions/export", "sessions/delete", "sessions/close", "sessions/transcript", "sessions/active"
].freeze
TURN_METHODS =
["turns/start", "turns/cancel", "turns/status", "turns/events", "turns/list", "turns/listActive"].freeze
PLUGIN_CHAT_METHODS =
["pluginChats/list", "pluginChats/open", "pluginChats/transcript", "pluginChats/subscribe", "pluginChats/unsubscribe", "pluginChats/turns/start", "pluginChats/turns/cancel", "pluginChats/turns/status", "pluginChats/turns/events", "pluginChats/turns/list", "pluginChats/turns/listActive"].freeze
MODEL_METHODS =
["models/list", "models/current", "models/set", "reasoning/set", "models/refresh"].freeze
RUNTIME_METHODS =
["runtime/state", "runtime/stats"].freeze
RUNTIME_SETTING_METHODS =
["runtime/updateSetting", "runtime/reload"].freeze
AUTH_METHODS =
[
  "auth/status", "auth/providers", "auth/loginWithApiKey", "auth/logoutProvider",
  "auth/loginWithOAuth", "auth/startOpenAILogin", "auth/submitOpenAICode", "auth/loginStatus"
].freeze
MEMORY_METHODS =
[
  "memory/status", "memory/enable", "memory/disable", "memory/autoSummary/enable",
  "memory/autoSummary/disable", "memory/list", "memory/add", "memory/addCore",
  "memory/forget", "memory/promote", "memory/relax", "memory/inspect",
  "memory/why", "memory/summarize"
].freeze
COMMAND_METHODS =
["commands/list", "commands/run"].freeze
PLUGIN_ACTION_METHODS =
["pluginActions/list", "pluginActions/run"].freeze
SKILL_CAPTURE_METHODS =
["skills/captureSessions", "skills/captureDraft", "skills/saveCapturedDraft"].freeze
STARTUP_RESOURCE_METHODS =
["resources/startup"].freeze
CONFIG_METHODS =
["config/read", "config/update"].freeze
LOGGING_METHODS =
["logging/stats", "logging/tokenCsv"].freeze
LIFECYCLE_HOOK_METHODS =
["hooks/logs"].freeze
UI_METHODS =
["ui/answerQuestion", "ui/answerRequest"].freeze
TOOL_APPROVAL_METHODS =
["tool/answerApproval"].freeze
TRANSPORT_METHODS =
["transports/list", "transports/status"].freeze
SESSION_EVENT_NOTIFICATION =
"session/event"
SESSION_UPDATED_NOTIFICATION =
"session/updated"
TURN_EVENT_NOTIFICATION =
"turn/event"
UI_QUESTION_NOTIFICATION =
"ui/question"
UI_REQUEST_NOTIFICATION =
"ui/request"
UI_PROGRESS_NOTIFICATION =
"ui/progress"
UI_NOTIFICATION =
"ui/notification"
"ui/footer"
TOOL_APPROVAL_NOTIFICATION =
"tool/approvalRequested"
HOOK_EVENT_NOTIFICATION =
"hook/event"
METHOD_GROUPS =
{
  protocol: PROTOCOL_METHODS,
  workspace: WORKSPACE_METHODS,
  tools: TOOL_METHODS,
  mcp: MCP_METHODS,
  prompts: PROMPT_METHODS,
  sessions: SESSION_METHODS,
  turns: TURN_METHODS,
  plugin_chats: PLUGIN_CHAT_METHODS,
  models: MODEL_METHODS,
  runtime: RUNTIME_METHODS,
  runtime_settings: RUNTIME_SETTING_METHODS,
  auth: AUTH_METHODS,
  memory: MEMORY_METHODS,
  commands: COMMAND_METHODS,
  plugin_actions: PLUGIN_ACTION_METHODS,
  skill_capture: SKILL_CAPTURE_METHODS,
  startup_resources: STARTUP_RESOURCE_METHODS,
  config: CONFIG_METHODS,
  logging: LOGGING_METHODS,
  lifecycle_hooks: LIFECYCLE_HOOK_METHODS,
  ui: UI_METHODS,
  tool_approval: TOOL_APPROVAL_METHODS
}.freeze
RPC_METHODS =
METHOD_GROUPS.values.flatten.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input: $stdin, output: $stdout, error_output: $stderr, client: Client.new) ⇒ Server

Creates the RPC server and its stateful managers.

Parameters:

  • input (IO) (defaults to: $stdin) —

    framed JSON-RPC input stream

  • output (IO) (defaults to: $stdout) —

    framed JSON-RPC output stream

  • error_output (IO, nil) (defaults to: $stderr) —

    redacted diagnostic stream

  • client (Client) (defaults to: Client.new) —

    model-provider client shared by sessions



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/kward/rpc/server.rb', line 129

def initialize(input: $stdin, output: $stdout, error_output: $stderr, client: Client.new)
  @transport = Transport.new(input: input, output: output)
  @error_output = error_output
  @client = client
  @config_manager = ConfigManager.new
  @session_manager = SessionManager.new(server: self, client: client, config_manager: @config_manager)
  @plugin_chat_manager = PluginChatManager.new(
    server: self,
    client: client,
    plugin_registry_provider: -> { @session_manager.plugin_registry }
  )
  @transport_manager = Kward::Transport::Manager.new(
    registry: @session_manager.plugin_registry,
    gateway: lambda { |transport_id|
      Kward::Transport::Gateway.new(session_manager: @session_manager, transport_id: transport_id)
    },
    plugin_chat_gateway: lambda { |transport_id|
      Kward::Transport::PluginChatGateway.new(runtime: @plugin_chat_manager.runtime, transport_id: transport_id)
    },
    config_provider: ->(transport_id) { ConfigFiles.transport_config(transport_id) }
  )
  @auth_manager = AuthManager.new(server: self, config_manager: @config_manager)
  @shutdown = false
  @shutdown_complete = false
end

Instance Attribute Details

#plugin_chat_manager ⇒ Object (readonly)



155
156
157
# File 'lib/kward/rpc/server.rb', line 155

def plugin_chat_manager
  @plugin_chat_manager
end

#session_manager ⇒ Object (readonly)



155
156
157
# File 'lib/kward/rpc/server.rb', line 155

def session_manager
  @session_manager
end

Instance Method Details

#error_payload(error) ⇒ Hash

Builds redacted diagnostics suitable for JSON-RPC error data.

Parameters:

  • error (Exception)

Returns:

  • (Hash)


200
201
202
203
204
205
206
# File 'lib/kward/rpc/server.rb', line 200

def error_payload(error)
  Redactor.redact({
    code: error.class.name,
    message: error.message,
    backtrace: Array(error.backtrace).first(8)
  })
end

#log_error(error) ⇒ void

This method returns an undefined value.

Writes a redacted server diagnostic without exposing error payload data.

Parameters:

  • error (Exception)


212
213
214
# File 'lib/kward/rpc/server.rb', line 212

def log_error(error)
  @error_output.puts("Kward RPC error: #{Redactor.redact_string(error.message)}") if @error_output
end

#notify(method, params = {}) ⇒ Object

Sends a redacted JSON-RPC notification to the client.

Parameters:

  • method (String) —

    notification method name

  • params (Hash) (defaults to: {}) —

    notification params



192
193
194
# File 'lib/kward/rpc/server.rb', line 192

def notify(method, params = {})
  @transport.write_message({ jsonrpc: JSONRPC_VERSION, method: method, params: Redactor.redact(params) })
end

#run ⇒ void

This method returns an undefined value.

Reads framed JSON-RPC messages until shutdown or EOF.



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/kward/rpc/server.rb', line 160

def run
  until @shutdown
    begin
      message = @transport.read_message
      break unless message

      handle_message(message)
    rescue JSON::ParserError => e
      write_error(nil, ERROR_CODES[:parse_error], "Parse error", e)
    rescue StandardError => e
      write_error(nil, ERROR_CODES[:invalid_request], e.message, e)
    end
  end
ensure
  shutdown
end

#shutdown ⇒ Object



177
178
179
180
181
182
183
184
185
186
# File 'lib/kward/rpc/server.rb', line 177

def shutdown
  return if @shutdown_complete

  @shutdown = true
  @transport_manager.shutdown
  @plugin_chat_manager.shutdown
  @session_manager.shutdown_sessions
  @session_manager.shutdown_plugins
  @shutdown_complete = true
end