Module: Html2rss::MCP::Contract
- Defined in:
- lib/html2rss/mcp/contract.rb
Overview
Published MCP contract: strategy enum, input/output schemas, listing annotations, and the single compact JSON envelope response.
Defined Under Namespace
Classes: UnpublishedRequestError
Constant Summary collapse
- STRATEGIES =
Published MCP request strategies (excludes
local_file). %w[auto faraday botasaurus].freeze
- URL_PROPERTY =
JSON Schema property for a source page URL.
{ type: 'string', format: 'uri', description: 'Source page URL' }.freeze
- STRATEGY_PROPERTY =
JSON Schema property for scrape/capture
strategy. { type: 'string', enum: STRATEGIES, default: 'auto', description: 'Request strategy (auto runs faraday → botasaurus fallback chain)' }.freeze
- INSPECT_STRATEGY_PROPERTY =
JSON Schema property for inspect
strategy(auto stays on Faraday). STRATEGY_PROPERTY.merge( description: 'Request strategy (auto uses Faraday for cheap diagnostics; pin botasaurus when needed)' ).freeze
- XOR_ONE_OF =
JSON Schema
oneOfrequiring exactly one ofconfigoryaml. [ { required: %w[config], not: { required: %w[yaml] } }.freeze, { required: %w[yaml], not: { required: %w[config] } }.freeze ].freeze
- CONFIG_XOR_PROPERTIES =
JSON Schema properties for the config/yaml XOR pair.
{ config: { type: 'object', description: 'Feed configuration hash with channel and selectors (XOR yaml)' }.freeze, yaml: { type: 'string', pattern: '\\S', description: 'Feed configuration YAML string (XOR config)' }.freeze }.freeze
- CONFIG_XOR_SCHEMA =
Input schema for
validate_config(config XOR yaml). { type: 'object', properties: CONFIG_XOR_PROPERTIES, oneOf: XOR_ONE_OF }.freeze
- APPLY_INPUT_SCHEMA =
Input schema for
apply_config(required URL plus config XOR yaml). { type: 'object', properties: { url: URL_PROPERTY, **CONFIG_XOR_PROPERTIES }.freeze, required: %w[url], oneOf: XOR_ONE_OF }.freeze
- SCRAPE_INPUT_SCHEMA =
Input schema for
scrape_url. { type: 'object', properties: { url: URL_PROPERTY, strategy: STRATEGY_PROPERTY, limit: { type: 'integer', description: 'Max articles to keep (default 25)', default: 25 }, items_selector: { type: 'string', description: 'Optional CSS selector hint for items' } }.freeze, required: %w[url] }.freeze
- INSPECT_INPUT_SCHEMA =
Input schema for
inspect_url. { type: 'object', properties: { url: URL_PROPERTY, strategy: INSPECT_STRATEGY_PROPERTY }.freeze, required: %w[url] }.freeze
- CAPTURE_INPUT_SCHEMA =
Input schema for
capture_config. { type: 'object', properties: { url: URL_PROPERTY, strategy: STRATEGY_PROPERTY, items_selector: { type: 'string', description: 'Optional CSS selector hint for items' } }.freeze, required: %w[url] }.freeze
- ANNOTATIONS_OPEN_WORLD =
Tool annotations for open-world read-only tools.
{ read_only_hint: true, destructive_hint: false, idempotent_hint: true, open_world_hint: true }.freeze
- ANNOTATIONS_VALIDATE =
Tool annotations for
validate_config(closed world). ANNOTATIONS_OPEN_WORLD.merge(open_world_hint: false).freeze
- TITLES =
Human titles for
tools/list. { scrape_url: 'Scrape URL', inspect_url: 'Inspect URL', capture_config: 'Capture feed config', validate_config: 'Validate feed config', apply_config: 'Apply feed config' }.freeze
Class Method Summary collapse
-
.assert_published_request!(config) ⇒ void
Rejects unpublished MCP request adapters so apply/validate cannot File.read arbitrary paths.
-
.output_schema ⇒ Hash
Envelope JSON Schema.
-
.response(outcome) ⇒ ::MCP::Tool::Response
One envelope Hash, one compact JSON body, no
_meta.
Class Method Details
.assert_published_request!(config) ⇒ void
This method returns an undefined value.
Rejects unpublished MCP request adapters so apply/validate cannot
File.read arbitrary paths. CLI and Config still allow local_file.
160 161 162 163 164 165 166 167 168 169 |
# File 'lib/html2rss/mcp/contract.rb', line 160 def assert_published_request!(config) strategy = config[:strategy] unless strategy.nil? || STRATEGIES.include?(strategy.to_s) raise UnpublishedRequestError, "MCP does not accept strategy #{strategy} (published: #{STRATEGIES.join(', ')})" end return unless config.dig(:request, :local_file_path) raise UnpublishedRequestError, 'MCP does not accept request.local_file_path' end |
.output_schema ⇒ Hash
Envelope JSON Schema. Built lazily so Zeitwerk can load Contract before Outcome.
124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/html2rss/mcp/contract.rb', line 124 def output_schema # rubocop:disable Metrics/MethodLength -- schema document is one hash { type: 'object', additionalProperties: false, required: %w[ok next_step guidance payload], properties: { ok: { type: 'boolean' }, next_step: { type: 'string', enum: Outcome::NextStep::NAMES.map(&:to_s) }, guidance: { type: 'string' }, payload: { type: 'object' } } } end |
.response(outcome) ⇒ ::MCP::Tool::Response
One envelope Hash, one compact JSON body, no _meta.
143 144 145 146 147 148 149 150 |
# File 'lib/html2rss/mcp/contract.rb', line 143 def response(outcome) wire = outcome.to_h ::MCP::Tool::Response.new( [{ type: 'text', text: JSON.generate(wire) }], error: !outcome.ok, structured_content: wire ) end |