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.
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 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/woods/mcp/server.rb', line 58 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 ) # Rewrite "Tool not found" into version-aware update guidance for agents # running against an older gem than the skill they're following assumes. server.singleton_class.prepend(VersionAwareToolDispatch) 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.
1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 |
# File 'lib/woods/mcp/server.rb', line 1461 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, update: Woods::UpdateCheck.status_hash }, 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 |