Module: OKF::MCP::Resources
- Defined in:
- lib/okf/mcp/resources.rb
Overview
Bundles and concepts as MCP resources — the affordance no tool call provides: a host can attach a document to the context itself, without the model having to decide to fetch it.
Two shapes, for two different costs. A static resource per bundle,
derived from the registry alone, so listing costs one stat per bundle
and never a parse. A template for concepts, resolved live, because
enumerating every concept would mean reading every bundle at boot — which
is exactly the eager work the residency layer exists to avoid, and would
freeze a list that the fingerprint check is meant to keep honest.
A bundle is listed only when it actually has a root index.md (the spec
makes it optional), so listed implies readable — no synthesized stand-in
that would put invented prose behind a real URI.
Constant Summary collapse
- SCHEME =
"okf://"- TEMPLATE =
"#{SCHEME}{bundle}/{id}"- MIME =
"text/markdown"- ROOT_INDEX =
"index.md"
Class Method Summary collapse
-
.complete(context, params) ⇒ Object
Argument completion for the template above — the half that makes a template browsable instead of a shape you have to already know.
-
.list(registry) ⇒ Object
One resource per served bundle that has a root index — the "start here" document, and the only bundle-level file whose meaning is fixed by the spec.
-
.read(context, uri) ⇒ Object
The whole read surface, for both shapes.
-
.templates ⇒ Object
Published for discovery only.
Class Method Details
.complete(context, params) ⇒ Object
Argument completion for the template above — the half that makes a template browsable instead of a shape you have to already know. Only the template's own two arguments answer; our prompts take none.
Never an error and never a partial leak: an unserved bundle, an unknown argument and a missing context all complete to nothing, so a completion request cannot be used to probe what argv did not serve.
91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/okf/mcp/resources.rb', line 91 def complete(context, params) ref = params[:ref] || {} return [] unless ref[:type] == "ref/resource" && ref[:uri] == TEMPLATE argument = params[:argument] || {} value = argument[:value].to_s case argument[:name].to_s when "bundle" then prefixed(context.registry.slugs, value) when "id" then prefixed(concept_ids(context, params.dig(:context, :arguments)), value) else [] end end |
.list(registry) ⇒ Object
One resource per served bundle that has a root index — the "start here" document, and the only bundle-level file whose meaning is fixed by the spec.
31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/okf/mcp/resources.rb', line 31 def list(registry) registry.entries.select { |entry| root_index?(entry.root) }.map do |entry| ::MCP::Resource.new( uri: SCHEME + entry.slug, name: entry.slug, title: entry.title, description: "The #{entry.slug} bundle's root index — its map, read live from disk.", mime_type: MIME ) end end |
.read(context, uri) ⇒ Object
The whole read surface, for both shapes. Returns the contents array
the SDK puts straight into the response.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/okf/mcp/resources.rb', line 63 def read(context, uri) slug, id = parse(uri) not_found(uri) if slug.nil? # Raises the kernel's own refusal for a bundle argv did not serve: a # URI is not a path, and the allowlist is the only door. root = context.root!(slug) text = id ? concept_text(context, slug, id, uri) : root_index_text(root) [ ::MCP::Resource::TextContents.new(uri: uri, mime_type: MIME, text: text).to_h ] rescue OKF::Path::Error, SystemCallError # A containment refusal reads exactly as an absent file, never as # invalid_params: a concept symlinked out of the root must be # indistinguishable from a missing one (Path::Error), the same not-found # a vanished file gets (SystemCallError), and the internal "escapes # bundle root" reason never reaches the client. Ordered ahead of the # OKF::Error branch because Path::Error is one. not_found(uri) rescue Error, OKF::Error => e invalid_params(e., uri) end |
.templates ⇒ Object
Published for discovery only. The SDK binds {id} to [^/]+, and
every OKF id below the root carries a slash, so this template can
advertise the shape but must never be the thing that parses it — see
#read, which owns the parsing.
47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/okf/mcp/resources.rb', line 47 def templates [ ::MCP::ResourceTemplate.new( uri_template: TEMPLATE, name: "okf-concept", title: "OKF concept", description: "One concept's markdown, verbatim and live from disk. " \ "`bundle` is a slug from list_bundles; `id` is a concept id " \ "(e.g. runbooks/billing-restart) and may contain slashes.", mime_type: MIME ) ] end |