okf-mcp 1.2.0 → 1.3.0

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,56 @@
1
+ ---
2
+ type: Component
3
+ title: The HTTP bridge
4
+ description: WEBrick to Rack in one file — buffered responses, the streaming adapter that parks a handler thread until the SDK ends a stream, and the teardown order that stops it hanging.
5
+ tags: [mcp, http, webrick, streaming, teardown]
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-08-19T12:00:00Z
9
+ resource: lib/okf/mcp/http.rb
10
+ ---
11
+
12
+ # The file
13
+
14
+ | file | what it owns |
15
+ | ---- | ------------ |
16
+ | `lib/okf/mcp/http.rb` | `OKF::MCP::HTTP` — `prepare`, `stop`, `app_for`, `build`, `handle`, and the two stream classes `Stream` and `Streams` |
17
+
18
+ This is the only file that knows WEBrick exists. `--http` goes through it;
19
+ `OKF::MCP.app` under a Rack 3 server does not, which is why the streaming
20
+ subtleties below are scoped to this bridge and not to the gem.
21
+
22
+ # The subtlety the whole file is shaped around
23
+
24
+ `subscriptions/listen` is answered by the SDK with a **Rack streaming body
25
+ whose callable returns immediately**. WEBrick ends a proc-body response when
26
+ the proc returns. Composed naively, every listen would close the instant it
27
+ opened.
28
+
29
+ So `Stream#wait` parks the handler thread until the SDK ends the stream, and
30
+ `Streams` is the bounded set of live ones. Three consequences are load-bearing,
31
+ and `test/integration/http_listen_test.rb` pins each:
32
+
33
+ - **Teardown closes the transport before WEBrick.** `HTTP.stop` does them in
34
+ that order because WEBrick's shutdown joins its connection threads and hangs
35
+ on any open stream.
36
+ - **The signal trap hands teardown to a thread.** A mutex in trap context is a
37
+ `ThreadError` on 2.7, which is the floor.
38
+ - **Listens are capped at 32, on this bridge only.** Each holds a WEBrick
39
+ thread and a connection token. That is not true under a Rack server, where
40
+ the SDK's own default stands, so the cap belongs here rather than in the
41
+ server definition.
42
+
43
+ **`EPIPE` must propagate.** A dead peer is noticed by `EPIPE` raising out of a
44
+ keepalive write, and that propagation *is* the SDK's cleanup signal. An adapter
45
+ that rescues it leaks the stream instead of closing it — so the rescue that
46
+ looks defensive is the bug.
47
+
48
+ # Host and origin checking
49
+
50
+ `allowed_hosts_for` and `local_hosts` derive the default allowlist from the
51
+ bind address. `app_for` is where `allowed_hosts` and `allowed_origins` reach
52
+ the transport, and it is called from [`App`](doors.md) rather than duplicated —
53
+ one construction site, so a new option cannot land on half the callers.
54
+
55
+ `read_body` bounds the request body and `oversized` is its refusal;
56
+ `not_found` answers anything off the MCP path.
@@ -0,0 +1,13 @@
1
+ # Structure
2
+
3
+ Every file under `lib/`, grouped by the layer that owns it. One concept owns
4
+ each file, and `test/unit/bundle_catalog_test.rb` fails if that stops being
5
+ true in either direction — a file no concept names, or a concept naming a file
6
+ that is gone.
7
+
8
+ Read them bottom-up: each layer depends only on the one below it.
9
+
10
+ * [The doors](doors.md) - `lib/okf/mcp.rb`, `lib/okf/plugin.rb`, `lib/okf/mcp/cli.rb`, `lib/okf/mcp/app.rb`, `lib/okf/mcp/version.rb` — the three ways in, and the load contract that keeps a bare require cheap.
11
+ * [The served set](served-set.md) - `lib/okf/mcp/registry.rb`, `filters.rb`, `backend.rb`, `memory_backend.rb` — which bundles exist, and the cache in front of them.
12
+ * [The server definition](server-definition.md) - `lib/okf/mcp/server.rb`, `output_schemas.rb`, `resources.rb` — the fourteen tools, their declared shapes, and concepts as resources.
13
+ * [The HTTP bridge](http-bridge.md) - `lib/okf/mcp/http.rb` — WEBrick to Rack, and the streaming adapter that the rest of the gem does not need to know about.
@@ -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,92 @@ 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.3.0] - 2026-08-22
9
+
10
+ ### Fixed
11
+
12
+ - **A long-running server now sees writes inside a *linked* registry.** okf gained
13
+ `okf registry link`, which folds another registry file's bundles into the served
14
+ set; the freshness stamp still watched only this server's own registry file, so
15
+ an `okf registry set` in the linked file never moved it and the server kept
16
+ answering about the set it booted with. The stamp now covers every file the
17
+ registry reads. The link list comes from the kernel, so adding or dropping a
18
+ link moves the first path's stamp and the next pass watches the new set.
19
+
20
+ The two kinds of file keep different rules. This server's own registry going
21
+ unreadable still answers `nil` and holds the last good set — a file caught
22
+ mid-write must not empty what is being served. A linked file is a pointer's
23
+ target, and okf already reports a missing one as resolving to nothing, so a
24
+ vanished target drops its bundles instead of freezing them.
25
+
26
+ ### Changed
27
+
28
+ - **The `mcp` floor moves to `~> 1.3`.** `Gemfile.lock` is not committed, so the
29
+ suite resolves the newest SDK the requirement admits and proves itself against
30
+ that one; the floor tracks what the suite proves, and 1.3.0 shipped. Nothing in
31
+ this gem changed for it — all 324 tests pass against 1.3.0 — but a floor that
32
+ admits an SDK the suite never ran against is the claim the floor test exists to
33
+ refuse.
34
+
35
+ - **The `okf` floor moves to 2.2.0**, the kernel that ships `registry link` and
36
+ `Registry#links_listing`. The stamp reads that method, and against 2.1.1 every
37
+ refresh would raise NoMethodError straight past the SystemCallError rescue.
38
+ `rake verify_okf_floor` gates `release` on exactly this and now passes.
39
+
40
+ - `list_bundles` groups now carry a `link` key — `null` for a group this registry
41
+ owns, the link's name for one that arrived through a link (okf ≥ 2.2). Linked
42
+ groups were already resolvable as a `bundle` argument and are now listed too.
43
+
44
+ ## [1.2.1] - 2026-08-20
45
+
46
+ ### Fixed
47
+
48
+ - **The package metadata follows the `gems/` move and the repository rename.**
49
+ Two URLs on the package page were about to be wrong at once. `changelog_uri`
50
+ named `blob/main/okf-mcp/CHANGELOG.md`, a path that stopped existing when the
51
+ four gems moved under `gems/`; `homepage` named `serradura/okf-gem`, and the
52
+ repository is now **`serradura/okf`** — the name the command has had all
53
+ along. rubygems.org serves whatever the last release published, so neither
54
+ corrects itself: only a release republishes metadata, and this is that
55
+ release. Both land in one round rather than two, which is the whole reason
56
+ the rename waited for the paths to stop moving.
57
+
58
+ `homepage_uri`, `source_code_uri` and `changelog_uri` are all derived from
59
+ `homepage`, so one line moves four pieces of this gem's metadata. GitHub
60
+ redirects the old URLs permanently, so anything already published keeps
61
+ resolving.
62
+
63
+ ### Added
64
+
65
+ - **The gem ships its own knowledge bundle.** `.okf/` is in `spec.files`, so an
66
+ installed okf-mcp carries a real bundle — its own — that a host can read
67
+ through the very tools it serves. It documents the gem's structure: every
68
+ file under `lib/` grouped by the layer that owns it, the catalog of the
69
+ fourteen tools, the four design rules, and how to add a tool or a test.
70
+ `AGENTS.md` now relies on it rather than restating it, and
71
+ `test/unit/bundle_catalog_test.rb` fails when the two disagree with the tree
72
+ — a file no concept names, a concept naming a file that is gone, or a tool
73
+ catalog out of step with `server.rb`.
74
+
75
+ ### Fixed
76
+
77
+ - **`rake test` on the 2.7 floor.** The Rakefile set
78
+ `Bundler::GemHelper#tag_prefix=` unguarded, and that accessor arrived in
79
+ Bundler 2.2 — Ruby 2.7, this gem's floor, ships 2.1.4. Loading the Rakefile
80
+ there raised `NoMethodError` and took the whole task down before a single
81
+ test ran. CI never saw it, because `ruby/setup-ruby` installs the newest
82
+ Bundler each Ruby accepts; only a floor run against the stock image
83
+ surfaces it. okf-tui and okf-pro have carried the `respond_to?` guard since
84
+ they were cut, and this gem now does too — on a Bundler without the
85
+ accessor, `release` aborts rather than pushing a bare `vX.Y.Z` that would
86
+ trigger the okf image build.
87
+
88
+ ### Changed
89
+
90
+ - **The okf floor moves to `>= 2.1.1, < 3`** — the kernel this shell develops
91
+ against released 2.1.1, and `test/unit/gemspec_test.rb` holds the floor to the
92
+ version the suite actually resolves against. It may lead the kernel; it may
93
+ never lag. The ceiling is unchanged.
9
94
 
10
95
  ## [1.2.0] - 2026-08-18
11
96
 
@@ -344,7 +429,8 @@ rather than pretending to be changes somebody could have seen.
344
429
 
345
430
  The name reservation on RubyGems: an empty gem, no functionality.
346
431
 
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
432
+ [1.2.1]: https://github.com/serradura/okf/compare/okf-mcp/v1.2.0...okf-mcp/v1.2.1
433
+ [1.2.0]: https://github.com/serradura/okf/compare/okf-mcp/v1.1.0...okf-mcp/v1.2.0
434
+ [1.1.0]: https://github.com/serradura/okf/compare/okf-mcp/v1.0.0...okf-mcp/v1.1.0
435
+ [1.0.0]: https://github.com/serradura/okf/releases/tag/okf-mcp/v1.0.0
350
436
  [0.0.0]: https://rubygems.org/gems/okf-mcp/versions/0.0.0
data/README.md CHANGED
@@ -77,8 +77,9 @@ bundles only when the registry itself is what is being served.
77
77
 
78
78
  With no arguments the registry *is* what is served, so it is followed rather
79
79
  than snapshotted: `okf registry set`, `rename` or `del` in another terminal
80
- shows up on the next tool call, without a restart. The file is re-read only
81
- when its fingerprint moves, so the cost is a `stat`. Bundle contents are never
80
+ shows up on the next tool call, without a restart. A write inside a registry the
81
+ first one *links* counts too. The files are re-read only when a fingerprint
82
+ moves, so the cost is a `stat` each. Bundle contents are never
82
83
  snapshotted either — bodies are read live, and a bundle is re-parsed whenever
83
84
  one of its files changes.
84
85
 
@@ -206,6 +207,16 @@ bundle exec rake # tests + RuboCop — what CI runs
206
207
  bundle exec rake test:integration # the critical layer alone + coverage/integration/
207
208
  ```
208
209
 
210
+ The gem carries its own OKF bundle in `.okf/` — what each file under `lib/`
211
+ does, the catalog of the fourteen tools, the design rules, and how to add a
212
+ tool. It ships inside the gem, so it is there after `gem install` too, and the
213
+ quickest way to read it is through this server:
214
+
215
+ ```bash
216
+ okf mcp .okf # serve okf-mcp's own knowledge, over MCP
217
+ okf server .okf # or read it as a graph
218
+ ```
219
+
209
220
  ## License
210
221
 
211
222
  Apache-2.0, see [LICENSE.txt](LICENSE.txt).
@@ -15,7 +15,7 @@ module OKF
15
15
  # names a registered bundle or group, whose slug is reserved before any
16
16
  # plain-dir basename is deduped — the server verb's rule). No argv means
17
17
  # the active kernel registry, resolved exactly as the CLI resolves it:
18
- # a project-local .okf-registry.json discovered from cwd, else
18
+ # a project-local .okf.json discovered from cwd, else
19
19
  # $OKF_HOME/registry.json, OKF_NO_DISCOVERY=1 forcing global.
20
20
  class Registry
21
21
  # One served bundle: the +slug+ tools name it by, its absolute +root+ on
@@ -297,10 +297,34 @@ module OKF
297
297
  # mtime *and* size, the pair the residency layer already uses: a second
298
298
  # write inside one filesystem timestamp tick moves the size when it does
299
299
  # not move the clock.
300
+ #
301
+ # Every file the registry reads, not just its own. A link puts bundles in a
302
+ # second file, and watching only the first meant a `registry set` over there
303
+ # was never seen: this server would keep answering about the set it booted
304
+ # with, which is the one failure a stamp exists to prevent. The link list
305
+ # itself comes from the kernel, so adding or dropping a link moves the first
306
+ # entry and the next pass watches the new set.
307
+ # The two files are watched under different rules, and the asymmetry is the
308
+ # point. This server's *own* registry going unreadable answers nil, which
309
+ # holds the last good set — a file caught mid-write must not empty what is
310
+ # being served. A *linked* file is only a pointer's target, and the kernel
311
+ # already treats a missing one as "resolves to nothing, reported", so its
312
+ # absence is a state change to follow rather than an error to ride out.
300
313
  def registry_stamp
301
314
  return nil if @kernel.nil?
302
315
 
303
- stat = ::File.stat(@kernel.path)
316
+ own = file_stamp(@kernel.path)
317
+ return nil if own.nil?
318
+
319
+ [ own ] + @kernel.links_listing.map { |row| file_stamp(row[:registry]) }
320
+ rescue OKF::Error, SystemCallError
321
+ nil
322
+ end
323
+
324
+ # nil for a file that is not there — a state in its own right for a link, so
325
+ # a target appearing or vanishing moves the stamp exactly as an edit does.
326
+ def file_stamp(path)
327
+ stat = ::File.stat(path)
304
328
  [ stat.mtime.to_f, stat.size ]
305
329
  rescue SystemCallError
306
330
  nil
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OKF
4
4
  module MCP
5
- VERSION = "1.2.0"
5
+ VERSION = "1.3.0"
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.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Serradura
@@ -15,21 +15,21 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '1.2'
18
+ version: '1.3'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '1.2'
25
+ version: '1.3'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: okf
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: '2.1'
32
+ version: 2.2.0
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.2.0
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: