Module: Woods::MCP::Server
- Defined in:
- lib/woods/mcp/server.rb
Overview
Builds an MCP::Server with up to 29 tools, 2 resources, and 2 resource templates for querying Woods extraction output, managing pipelines, and collecting feedback. 14 tools are always registered; 15 more register conditionally based on wiring: 5 operator tools, 4 feedback tools, 4 snapshot tools, 1 session_trace tool, 1 Notion sync tool.
All tools are defined inline via closures over an IndexReader instance. No Rails required at runtime — reads JSON files from disk.
Class Method Summary collapse
-
.build(index_dir:, retriever: nil, operator: nil, feedback_store: nil, snapshot_store: nil, bootstrap_state: nil, response_format: nil, warmup: true, retriever_reloader: nil) ⇒ MCP::Server
Build a configured MCP::Server with all tools and resources.
-
.build_status(reader:, retriever:, index_dir:, bootstrap_state: nil) ⇒ Object
Build the woods_status payload.
Class Method Details
.build(index_dir:, retriever: nil, operator: nil, feedback_store: nil, snapshot_store: nil, bootstrap_state: nil, response_format: nil, warmup: true, retriever_reloader: nil) ⇒ MCP::Server
Build a configured MCP::Server with all tools and resources.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/woods/mcp/server.rb', line 46 def build(index_dir:, retriever: nil, operator: nil, feedback_store: nil, snapshot_store: nil, bootstrap_state: nil, response_format: nil, warmup: true, retriever_reloader: nil) reader = IndexReader.new(index_dir) reader.warmup! if warmup config = Woods.configuration format = response_format || (config.respond_to?(:context_format) ? config.context_format : nil) || :markdown renderer = ToolResponseRenderer.for(format) resources = build_resources resource_templates = build_resource_templates # Lambda captured by all tool blocks for building responses. respond = method(:text_response) respond_err = method(:error_response) op_missing = lambda do |tool| error_response( 'Pipeline operator is not configured. Pass `operator:` to Woods::MCP::Server.build ' \ 'or use Woods::MCP::Bootstrapper to wire StatusReporter, ErrorEscalator, and PipelineGuard.', code: :not_configured, config_key: 'operator', doc_link: 'docs/OPERATOR_GUIDE.md', tool: tool ) end fb_missing = lambda do |tool| error_response( 'Feedback store is not configured. Pass `feedback_store:` to Woods::MCP::Server.build ' \ 'to enable retrieval feedback capture.', code: :not_configured, config_key: 'feedback_store', doc_link: 'docs/FEEDBACK_STORE.md', tool: tool ) end snap_missing = lambda do |tool| error_response( 'Snapshot store is not configured. Set `enable_snapshots: true` in Woods.configure ' \ 'and pass `snapshot_store:` to Woods::MCP::Server.build.', code: :not_configured, config_key: 'enable_snapshots', doc_link: 'docs/TEMPORAL_SNAPSHOTS.md', tool: tool ) end server = ::MCP::Server.new( name: 'woods', version: Woods::VERSION, resources: resources, resource_templates: resource_templates ) define_lookup_tool(server, reader, respond, respond_err, renderer) define_search_tool(server, reader, respond, respond_err, renderer) define_traversal_tool(server, reader, respond, renderer, name: 'dependencies', description: 'Traverse forward dependencies of a unit (what it depends on). Returns a BFS tree with depth.', reader_method: :traverse_dependencies, render_key: :dependencies) define_traversal_tool(server, reader, respond, renderer, name: 'dependents', description: 'Traverse reverse dependencies of a unit (what depends on it). Returns a BFS tree with depth.', reader_method: :traverse_dependents, render_key: :dependents) define_structure_tool(server, reader, respond, renderer) define_graph_analysis_tool(server, reader, respond, renderer) define_domain_clusters_tool(server, reader, respond, renderer) define_pagerank_tool(server, reader, respond, renderer) define_framework_tool(server, reader, respond, renderer) define_recent_changes_tool(server, reader, respond, renderer) define_reload_tool(server, reader, respond, retriever_reloader) define_retrieve_tool(server, retriever, respond, respond_err) define_trace_flow_tool(server, reader, index_dir, respond, respond_err, renderer) # Conditionally register collaborator-dependent tools. Historically # all 15 stubs were registered unconditionally and returned # isError: true when the wiring was missing — that added token # noise to every LLM turn's tool catalog and invited the model to # try tools guaranteed to fail. Only register when the collaborator # is wired, so tools/list reflects what the server can actually do. define_session_trace_tool(server, reader, respond, respond_err) if session_tracer_wired? define_operator_tools(server, operator, respond, respond_err, op_missing) if operator define_feedback_tools(server, feedback_store, respond, respond_err, fb_missing) if feedback_store define_snapshot_tools(server, snapshot_store, respond, respond_err, snap_missing) if snapshot_store define_notion_sync_tool(server, reader, index_dir, respond, respond_err) if notion_wired? define_woods_status_tool(server, reader, retriever, index_dir, bootstrap_state, respond) register_resource_handler(server, reader) server end |
.build_status(reader:, retriever:, index_dir:, bootstrap_state: nil) ⇒ Object
Build the woods_status payload. Exposed at module level so specs (and future console/unified-server entry points) can assemble the same shape without reaching through the MCP::Server internals.
features.embedding_model / features.embedding_provider / features.vector_store prefer the ResolvedConfig captured at embed time (bootstrap_state.resolved_config, which is read back from woods.json) over Woods.configuration, whose defaults can contradict the actual provider in use. Without this, operators debugging “wrong provider” see status claiming embedding_model: “text-embedding-3-small” next to embedding_provider: “ollama” and reasonably distrust every field.
1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 |
# File 'lib/woods/mcp/server.rb', line 1409 def build_status(reader:, retriever:, index_dir:, bootstrap_state: nil) manifest = safe_manifest(reader) extracted_at = manifest && manifest['extracted_at'] staleness = staleness_seconds(extracted_at) # Tolerate a nil Woods.configuration — specs that reset it between # runs can leave a transient nil window, and build_status should # still produce a readable payload during that window. config = Woods.configuration || Woods::Configuration.new resolved = bootstrap_state&.resolved_config { ready: manifest && !manifest['counts'].to_h.empty?, server: { name: 'woods', version: Woods::VERSION, index_dir: index_dir.to_s }, index: index_section(manifest, extracted_at, staleness, index_dir), retriever: { configured: !retriever.nil?, class: retriever&.class&.name }, bootstrap: bootstrap_state&.to_h, features: features_from(config, resolved) } end |