okf-mcp 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ ---
2
+ type: Component
3
+ title: The served set, and the cache in front of it
4
+ description: Which bundles this server will answer about — resolved once at boot as the tools' allowlist — plus the folder cache, the engine detection and the one place the `dir` vocabulary lives.
5
+ tags: [mcp, registry, cache, backend, filters]
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-08-19T12:00:00Z
9
+ resource: lib/okf/mcp/registry.rb
10
+ ---
11
+
12
+ # The files
13
+
14
+ | file | what it owns |
15
+ | ---- | ------------ |
16
+ | `lib/okf/mcp/registry.rb` | `OKF::MCP::Registry` — the served set: argv refs (dirs and `@slug`s) or the kernel registry, resolved once at boot |
17
+ | `lib/okf/mcp/filters.rb` | `OKF::MCP::Filters` — the `dir` vocabulary: folding, normalizing, containment, depth |
18
+ | `lib/okf/mcp/backend.rb` | `OKF::MCP::Backend` — engine detection: the in-memory backend, or `okf-sqlite3` when it is installed and suitable |
19
+ | `lib/okf/mcp/memory_backend.rb` | `OKF::MCP::MemoryBackend` — the always-present folder cache: residency, fingerprints, catalog rows, search pairs |
20
+
21
+ # The registry is an allowlist, not a lookup
22
+
23
+ `Registry.from_argv` and `Registry.from_kernel` resolve **once, at boot**. After
24
+ that the entry list is fixed, and `#root!` is the only way a tool turns a bundle
25
+ name into a path — it raises for anything not in the set.
26
+
27
+ That is the containment property the whole server rests on: a host cannot ask
28
+ this process to read a bundle nobody served it. `#refresh!` re-reads the same
29
+ sources; it does not widen the set.
30
+
31
+ Identity comes from the kernel's registry, never minted here when a kernel slug
32
+ exists — `resolve_arg`, `ref_entry` and `group_roots` all defer to it, so
33
+ `@handbook` means the same bundle to `okf lint` and to a host. Groups expand to
34
+ their member roots, recursively, at resolve time.
35
+
36
+ # The `dir` vocabulary lives in exactly one file
37
+
38
+ `Filters` exists because it did not. The folding, normalizing and containment
39
+ rules lived in two places and **the two copies disagreed about the root** —
40
+ one treated `""` as "everything", the other as "the root directory only". Every
41
+ tool that takes a `dir` now reads them from here.
42
+
43
+ `normalize_dir`, `within?`, `dir_depth` and `known_dir?` are the whole surface.
44
+ A new tool taking a `dir` uses them rather than writing a third opinion.
45
+
46
+ # The cache is per-root and fingerprinted
47
+
48
+ `MemoryBackend#folder` caches an `OKF::Bundle::Folder` per root;
49
+ `#fingerprint` is what decides whether a cached one is still good. `#retain`
50
+ prunes roots that are no longer served, and `#during_request` memoizes
51
+ fingerprints for the span of one request so a single frame does not stat the
52
+ same tree repeatedly.
53
+
54
+ `Backend.detect` is the seam for a faster engine: if `okf-sqlite3` is installed
55
+ and `suitable?`, it is used; otherwise the memory backend, which is always
56
+ present. Nothing above this layer knows which one answered:
57
+ [the server definition](server-definition.md) asks the backend, never the
58
+ engine.
@@ -0,0 +1,72 @@
1
+ ---
2
+ type: Component
3
+ title: The server definition
4
+ description: The MCP::Server subclass, the fourteen tool builders and the private helpers they share, one declared output shape per tool, and concepts as resources with a URI parser the SDK's matcher cannot supply.
5
+ tags: [mcp, server, tools, schemas, resources]
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-08-19T12:00:00Z
9
+ resource: lib/okf/mcp/server.rb
10
+ ---
11
+
12
+ # The files
13
+
14
+ | file | what it owns |
15
+ | ---- | ------------ |
16
+ | `lib/okf/mcp/server.rb` | `OKF::MCP::Server` — `Server::Definition < ::MCP::Server`, `Server.build`, the fourteen `*_tool` builders, the two prompts, and the shared private helpers |
17
+ | `lib/okf/mcp/output_schemas.rb` | `OKF::MCP::OutputSchemas` — one declared result shape per tool, looked up by name |
18
+ | `lib/okf/mcp/resources.rb` | `OKF::MCP::Resources` — bundles and concepts as MCP resources, and the URI grammar |
19
+
20
+ # server.rb is one file on purpose, in three bands
21
+
22
+ At ~1,300 lines it is the largest file in the gem, and splitting it has been
23
+ considered and declined: the fourteen builders are near-identical in shape, and
24
+ what makes them safe is that they sit next to each other where a divergence is
25
+ visible. Read it as three bands.
26
+
27
+ **`Server.build` and `Definition`.** `build` assembles the definition from a
28
+ registry and an engine. `Definition` subclasses the SDK's server and adds the
29
+ per-request wrap: `#in_request` memoizes fingerprints and `#retain_served`
30
+ prunes cache residency, so a frame's repeated reads cost one stat. `#handle`
31
+ and `#handle_json` are what the tests drive — real JSON-RPC frames, no
32
+ transport involved.
33
+
34
+ **The fourteen builders**, one per tool: `tags_tool`, `types_tool`,
35
+ `stats_tool`, `list_bundles_tool`, `dirs_tool`, `index_tool`, `search_tool`,
36
+ `read_concept_tool`, `catalog_tool`, `log_tool`, `validate_tool`, `lint_tool`,
37
+ `graph_tool`, `references_tool`. Each is a `define_tool` call and a body that
38
+ calls the kernel. The [tool catalog](../capabilities/tools.md) is the reader's
39
+ map of them.
40
+
41
+ **The shared helpers**, which are where the contracts actually live:
42
+ `define_tool` (the one place `readOnlyHint`, `title` and the output-schema
43
+ lookup are attached — which is why a tool cannot forget them), `respond_json`
44
+ and `respond_error`, the projection and validation helpers (`check_fields`,
45
+ `check_projection`, `check_asked`, `project_rows`), the bounded-log arithmetic
46
+ (`bounded_log`, `sized_log`), and `check_dir!`, which is the refusal every
47
+ `dir`-taking tool shares.
48
+
49
+ # One declared shape per tool, looked up by name
50
+
51
+ `OutputSchemas[name]` is a lookup, not a registry a tool writes into. An
52
+ omission is therefore deliberate and visible: `read_concept` has no shape,
53
+ because it answers markdown rather than a structured row, and that is the only
54
+ one. `test/integration/output_schema_test.rb` walks the wire and checks the
55
+ declared shape against the answer actually sent.
56
+
57
+ # Resources own the URI grammar because the SDK cannot
58
+
59
+ `Resources.parse` exists for one reason worth knowing before touching it: **OKF
60
+ concept ids carry slashes** (`capabilities/graph-server.md`), and the SDK's
61
+ resource-template matcher stops at `[^/]+`. So a templated match would truncate
62
+ every nested id. This file parses the URI itself.
63
+
64
+ `list`, `templates`, `read` and `complete` are the surface; `concept_ids` and
65
+ `prefixed` back the completion. `test/integration/resources_test.rb` and
66
+ `completions_test.rb` drive them.
67
+
68
+ # The prompts are read at get-time
69
+
70
+ `Server::PROMPTS` names two markdown files shipped in `lib/okf/mcp/prompts/`,
71
+ and `Server.prompt_text` reads one when a host asks for it. Booting never pays
72
+ for prompt bodies, and editing a prompt does not need a code change.
@@ -0,0 +1,65 @@
1
+ ---
2
+ type: Playbook
3
+ title: Adding a tool, end to end
4
+ description: The walk a fifteenth tool owes — the question to answer before writing it, where the code goes, the four files that gain a test, and the three pins that will refuse it if a step is skipped.
5
+ tags: [testing, tools, playbook, contribution]
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-08-19T12:00:00Z
9
+ resource: lib/okf/mcp/server.rb
10
+ ---
11
+
12
+ # First, the question that usually ends it
13
+
14
+ **Do the [fourteen](../capabilities/tools.md) already compose to this?**
15
+ `dirs` + `index` + `search` answer most retrieval between them, and tool-list
16
+ weight is a real cost on every host that connects. A tool that duplicates a
17
+ composition is a permanent tax for a one-time convenience.
18
+
19
+ Second question: **could the kernel answer it?** If it could, it should — see
20
+ [kernel-first](../design/kernel-first.md). A kernel method serves the CLI, the
21
+ graph server, the TUI and this shell; a tool here serves one.
22
+
23
+ If both answers are no, the walk is below.
24
+
25
+ # The walk
26
+
27
+ 1. **Write the failing test first**, in the folder that matches how the bundle
28
+ is named — `test/integration/by_dir/<tool>_test.rb` at minimum, and
29
+ `by_registry/` and `across_bundles/` if the tool takes a bundle ref or
30
+ several. Drive real JSON-RPC through `handle_json`. Run it: it must fail
31
+ because the tool does not exist, not because a fixture is missing.
32
+ 2. **Add the kernel call**, if the analysis is not already there. That is a
33
+ change to `okf`, with its own test, landing first.
34
+ 3. **Add the builder** — `<name>_tool(context)` in `lib/okf/mcp/server.rb`,
35
+ beside its neighbours, and list it in `tools_for`. Use `define_tool`, which
36
+ is what attaches `readOnlyHint`, the `title` and the output-schema lookup;
37
+ nothing else may construct a tool.
38
+ 4. **Declare the output shape** in `lib/okf/mcp/output_schemas.rb`, keyed by
39
+ the tool's name. Omit it only for a markdown answer, and expect to defend
40
+ the omission — `read_concept` is the only one today.
41
+ 5. **Carry `total`** if the answer is a list: how many rows matched *before*
42
+ any `limit`. No silent truncation, ever.
43
+ 6. **Reuse the `dir` vocabulary** from `lib/okf/mcp/filters.rb` if the tool
44
+ takes a `dir`, and `check_dir!` for the refusal. Do not write a third
45
+ opinion about what the root means — that bug has already happened once.
46
+ 7. **Update [the catalog](../capabilities/tools.md)** with the tool's row.
47
+ 8. **Run the suite.** The same test from step 1 passes, unedited.
48
+
49
+ # The three pins that refuse a skipped step
50
+
51
+ - `test/integration/capabilities_test.rb` reads the wire and fails a tool
52
+ missing `readOnlyHint` or `title`.
53
+ - `test/integration/output_schema_test.rb` checks each declared shape against
54
+ the answer actually sent.
55
+ - `test/unit/bundle_catalog_test.rb` fails when the catalog and `server.rb`
56
+ disagree about which tools exist — so step 7 is not optional, and forgetting
57
+ it is a red suite rather than a stale document.
58
+
59
+ # Adding a file, not a tool
60
+
61
+ A new file under `lib/` needs a line in whichever
62
+ [structure](../structure/) concept owns its layer — or a concept of its own if
63
+ it is a new layer. `bundle_catalog_test.rb` fails on an unowned file, which is
64
+ the point: the Map used to live in `AGENTS.md` where nothing checked it, and it
65
+ went stale silently.
@@ -0,0 +1,9 @@
1
+ # Testing
2
+
3
+ How this gem is tested, and what a change owes before it lands. The root
4
+ `AGENTS.md`'s rule applies here as written — a change starts with a failing
5
+ test, red for the reason you predicted, then the code, then the same test green
6
+ and unedited — and what follows is that rule's shape for this gem's surfaces.
7
+
8
+ * [The layers](layers.md) - What each layer of the suite proves, and the claims it deliberately does not spend a process on.
9
+ * [Adding a tool](adding-a-tool.md) - The walk a new tool owes: where the code goes, which files gain a test, and what the pins will refuse.
@@ -0,0 +1,56 @@
1
+ ---
2
+ type: Constraint
3
+ title: What each test layer proves
4
+ description: Integration-first over real JSON-RPC frames, with process-spawning confined to the two files that prove the process — plus the wire-reading rules the SSE tests cannot be written without.
5
+ tags: [testing, integration, http, sse]
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-08-19T12:00:00Z
9
+ resource: test/integration
10
+ ---
11
+
12
+ # The layers
13
+
14
+ | layer | proves | files |
15
+ | ----- | ------ | ----- |
16
+ | tool integration | a tool's real answer, driven as JSON-RPC through `handle_json` | `test/integration/{by_dir,by_registry,across_bundles}/` |
17
+ | protocol surface | capabilities, output schemas, resources, completions, prompts | `capabilities_test.rb`, `output_schema_test.rb`, `resources_test.rb`, `completions_test.rb`, `prompts_test.rb` |
18
+ | the process | `okf mcp` spawned for real, WEBrick on a real socket | `cli_plugin_test.rb`, `http_test.rb`, `http_modern_test.rb`, `http_listen_test.rb` |
19
+ | the argv shell | flags and exit codes, in-process, spawning nothing | `cli_test.rb` |
20
+ | units | the pieces with contracts of their own | `test/unit/` |
21
+
22
+ The three tool directories mirror the kernel's own — the three ways a bundle is
23
+ named: by directory, through the registry, and several at once.
24
+
25
+ # One claim, one place
26
+
27
+ A claim about **argv** is proven once, cheaply, in `cli_test.rb`, which drives
28
+ the shell in-process and deliberately spawns nothing. A claim about **the
29
+ process** is proven once, in the spawning files. The split is deliberate: every
30
+ spawn is seconds, and a suite that spawns for argv claims spends minutes
31
+ proving something a method call already settled.
32
+
33
+ The three HTTP files share one harness, `test/integration/http_harness.rb`, so
34
+ they cannot drift in how they compose the bridge — the same reason
35
+ [`App`](../structure/doors.md) is a single construction site in `lib/`.
36
+
37
+ # Reading the wire, which is where SSE tests go wrong
38
+
39
+ Two rules, both learned the hard way, and neither optional:
40
+
41
+ **Use a raw `TCPSocket`, not `Net::HTTP`.** Net::HTTP holds a chunked body
42
+ until EOF, so a stream that stays open — which is the whole point of
43
+ `subscriptions/listen` — reads as a hang rather than as a passing test.
44
+
45
+ **Thread `read_until`'s returned buffer back in.** One `readpartial` can carry
46
+ the next frame along with the awaited one, so a sequential read that starts
47
+ from an empty buffer drops a frame and then blocks waiting for it.
48
+
49
+ # The bundle is pinned too
50
+
51
+ `test/unit/bundle_catalog_test.rb` checks this bundle against the tree: every
52
+ `.rb` under `lib/` named by exactly one concept in
53
+ [`structure/`](../structure/), every path a concept names still present, and
54
+ the [tool catalog](../capabilities/tools.md) agreeing with the tools
55
+ `server.rb` defines. Structural documentation is code-derived, so it is pinned
56
+ like code rather than trusted like prose.
data/CHANGELOG.md CHANGED
@@ -5,7 +5,56 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [1.2.1] - 2026-08-20
9
+
10
+ ### Fixed
11
+
12
+ - **The package metadata follows the `gems/` move and the repository rename.**
13
+ Two URLs on the package page were about to be wrong at once. `changelog_uri`
14
+ named `blob/main/okf-mcp/CHANGELOG.md`, a path that stopped existing when the
15
+ four gems moved under `gems/`; `homepage` named `serradura/okf-gem`, and the
16
+ repository is now **`serradura/okf`** — the name the command has had all
17
+ along. rubygems.org serves whatever the last release published, so neither
18
+ corrects itself: only a release republishes metadata, and this is that
19
+ release. Both land in one round rather than two, which is the whole reason
20
+ the rename waited for the paths to stop moving.
21
+
22
+ `homepage_uri`, `source_code_uri` and `changelog_uri` are all derived from
23
+ `homepage`, so one line moves four pieces of this gem's metadata. GitHub
24
+ redirects the old URLs permanently, so anything already published keeps
25
+ resolving.
26
+
27
+ ### Added
28
+
29
+ - **The gem ships its own knowledge bundle.** `.okf/` is in `spec.files`, so an
30
+ installed okf-mcp carries a real bundle — its own — that a host can read
31
+ through the very tools it serves. It documents the gem's structure: every
32
+ file under `lib/` grouped by the layer that owns it, the catalog of the
33
+ fourteen tools, the four design rules, and how to add a tool or a test.
34
+ `AGENTS.md` now relies on it rather than restating it, and
35
+ `test/unit/bundle_catalog_test.rb` fails when the two disagree with the tree
36
+ — a file no concept names, a concept naming a file that is gone, or a tool
37
+ catalog out of step with `server.rb`.
38
+
39
+ ### Fixed
40
+
41
+ - **`rake test` on the 2.7 floor.** The Rakefile set
42
+ `Bundler::GemHelper#tag_prefix=` unguarded, and that accessor arrived in
43
+ Bundler 2.2 — Ruby 2.7, this gem's floor, ships 2.1.4. Loading the Rakefile
44
+ there raised `NoMethodError` and took the whole task down before a single
45
+ test ran. CI never saw it, because `ruby/setup-ruby` installs the newest
46
+ Bundler each Ruby accepts; only a floor run against the stock image
47
+ surfaces it. okf-tui and okf-pro have carried the `respond_to?` guard since
48
+ they were cut, and this gem now does too — on a Bundler without the
49
+ accessor, `release` aborts rather than pushing a bare `vX.Y.Z` that would
50
+ trigger the okf image build.
51
+
52
+ ### Changed
53
+
54
+ - **The okf floor moves to `>= 2.1.1, < 3`** — the kernel this shell develops
55
+ against released 2.1.1, and `test/unit/gemspec_test.rb` holds the floor to the
56
+ version the suite actually resolves against. It may lead the kernel; it may
57
+ never lag. The ceiling is unchanged.
9
58
 
10
59
  ## [1.2.0] - 2026-08-18
11
60
 
@@ -344,7 +393,8 @@ rather than pretending to be changes somebody could have seen.
344
393
 
345
394
  The name reservation on RubyGems: an empty gem, no functionality.
346
395
 
347
- [1.2.0]: https://github.com/serradura/okf-gem/compare/okf-mcp/v1.1.0...okf-mcp/v1.2.0
348
- [1.1.0]: https://github.com/serradura/okf-gem/compare/okf-mcp/v1.0.0...okf-mcp/v1.1.0
349
- [1.0.0]: https://github.com/serradura/okf-gem/releases/tag/okf-mcp/v1.0.0
396
+ [1.2.1]: https://github.com/serradura/okf/compare/okf-mcp/v1.2.0...okf-mcp/v1.2.1
397
+ [1.2.0]: https://github.com/serradura/okf/compare/okf-mcp/v1.1.0...okf-mcp/v1.2.0
398
+ [1.1.0]: https://github.com/serradura/okf/compare/okf-mcp/v1.0.0...okf-mcp/v1.1.0
399
+ [1.0.0]: https://github.com/serradura/okf/releases/tag/okf-mcp/v1.0.0
350
400
  [0.0.0]: https://rubygems.org/gems/okf-mcp/versions/0.0.0
data/README.md CHANGED
@@ -206,6 +206,16 @@ bundle exec rake # tests + RuboCop — what CI runs
206
206
  bundle exec rake test:integration # the critical layer alone + coverage/integration/
207
207
  ```
208
208
 
209
+ The gem carries its own OKF bundle in `.okf/` — what each file under `lib/`
210
+ does, the catalog of the fourteen tools, the design rules, and how to add a
211
+ tool. It ships inside the gem, so it is there after `gem install` too, and the
212
+ quickest way to read it is through this server:
213
+
214
+ ```bash
215
+ okf mcp .okf # serve okf-mcp's own knowledge, over MCP
216
+ okf server .okf # or read it as a graph
217
+ ```
218
+
209
219
  ## License
210
220
 
211
221
  Apache-2.0, see [LICENSE.txt](LICENSE.txt).
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OKF
4
4
  module MCP
5
- VERSION = "1.2.0"
5
+ VERSION = "1.2.1"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: okf-mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Serradura
@@ -29,7 +29,7 @@ dependencies:
29
29
  requirements:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: '2.1'
32
+ version: 2.1.1
33
33
  - - "<"
34
34
  - !ruby/object:Gem::Version
35
35
  version: '3'
@@ -39,7 +39,7 @@ dependencies:
39
39
  requirements:
40
40
  - - ">="
41
41
  - !ruby/object:Gem::Version
42
- version: '2.1'
42
+ version: 2.1.1
43
43
  - - "<"
44
44
  - !ruby/object:Gem::Version
45
45
  version: '3'
@@ -56,6 +56,27 @@ executables: []
56
56
  extensions: []
57
57
  extra_rdoc_files: []
58
58
  files:
59
+ - ".okf/capabilities/index.md"
60
+ - ".okf/capabilities/resources-and-prompts.md"
61
+ - ".okf/capabilities/tools.md"
62
+ - ".okf/capabilities/transports.md"
63
+ - ".okf/design/index.md"
64
+ - ".okf/design/kernel-first.md"
65
+ - ".okf/design/one-entry-point.md"
66
+ - ".okf/design/ruby-floor.md"
67
+ - ".okf/design/runtime-dependencies.md"
68
+ - ".okf/design/the-tool-set.md"
69
+ - ".okf/index.md"
70
+ - ".okf/log.md"
71
+ - ".okf/overview.md"
72
+ - ".okf/structure/doors.md"
73
+ - ".okf/structure/http-bridge.md"
74
+ - ".okf/structure/index.md"
75
+ - ".okf/structure/served-set.md"
76
+ - ".okf/structure/server-definition.md"
77
+ - ".okf/testing/adding-a-tool.md"
78
+ - ".okf/testing/index.md"
79
+ - ".okf/testing/layers.md"
59
80
  - CHANGELOG.md
60
81
  - LICENSE.txt
61
82
  - NOTICE
@@ -75,14 +96,14 @@ files:
75
96
  - lib/okf/mcp/server.rb
76
97
  - lib/okf/mcp/version.rb
77
98
  - lib/okf/plugin.rb
78
- homepage: https://github.com/serradura/okf-gem
99
+ homepage: https://github.com/serradura/okf
79
100
  licenses:
80
101
  - Apache-2.0
81
102
  metadata:
82
103
  allowed_push_host: https://rubygems.org
83
- homepage_uri: https://github.com/serradura/okf-gem
84
- source_code_uri: https://github.com/serradura/okf-gem
85
- changelog_uri: https://github.com/serradura/okf-gem/blob/main/okf-mcp/CHANGELOG.md
104
+ homepage_uri: https://github.com/serradura/okf
105
+ source_code_uri: https://github.com/serradura/okf
106
+ changelog_uri: https://github.com/serradura/okf/blob/main/gems/okf-mcp/CHANGELOG.md
86
107
  rubygems_mfa_required: 'true'
87
108
  rdoc_options: []
88
109
  require_paths: