Repository navigation
Commit 72fad92
fix(mcp)!: the objectstack://objects resources read the stdio transport's merged object view, so a package's objects are listed and described (#25268)
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4Uu4BM6tVgxhg4qFQsbuRHiy9)_
Fixes #25245
Clause-②: no (narrowing)
Metadata-protocol: none
The stdio MCP transport's two object resources, `objectstack://objects`
and `objectstack://objects/{objectName}`, now read the same objects its
`list_objects` and `describe_object` tools answer from since #25243: the
protocol's merged listing, the one `GET /api/v1/meta/object` serves.
They read the `metadata` service until now, so on a kernel with an
`environmentId` they listed none of a package's boot-registered objects
and answered `404` for each, while the tools listed and described them.
The merged read is shared, not copied: `stdio-data-bridge.ts` exports
one view, the tools answer from it, and `plugin.ts` gives the same view
to its runtime, whose two object resources read it. No published
signature moves.
## The defect, measured on base `0b8bce40e0`
`bridgeResources` (`packages/mcp/src/mcp-server-runtime.ts`) built
`objectstack://objects` from `metadataService.listObjects()` and
`objectstack://objects/NAME` from `metadataService.getObject(NAME)`.
That service holds a copy of an object only where something put one
there. On a kernel with an `environmentId` (`createStandaloneStack`: `os
dev` as `env_local`, `os serve`, `os start`; and the cloud
single-environment runtime) nothing copies an object a package registers
in the `SchemaRegistry` during the boot.
Real kernels (`ObjectKernel` + `MetadataPlugin` + `ObjectQLPlugin`),
driven through the real `MCPServerPlugin` stdio session over an
in-process transport speaking a stdio client's JSON-RPC, with the
fixture package #25243's pins use (two objects, one declared
`apiEnabled: false`):
| shape, when the package registered | `resources/read
objectstack://objects` | `resources/read
objectstack://objects/qa_pkg_open` | `list_objects` / `describe_object`
on the same kernel |
| :-- | :-- | :-- | :-- |
| `env_local`, during the boot | **`{ objects: [], totalCount: 0 }`** |
**`{ error: 'Object "qa_pkg_open" not found', code:
'RESOURCE_NOT_FOUND', status: 404 }`** | both listed, both described |
| `env_local`, installed after the boot (`source: 'marketplace'`) | both
listed | described | both listed, both described |
| no `environmentId`, during the boot | both listed | described | both
listed, both described |
| no `environmentId`, installed after the boot | both listed | described
| both listed, both described |
**A disabled package (`registry.disablePackage`).** On the shape without
an `environmentId` the boot copied both objects into the `metadata`
service, and the copy outlives the disable: both resources still listed
and described them, while `list_objects` and `describe_object` omit them
since #25243 and `GET /api/v1/meta/object` omits them. On `env_local`
there is no copy, so the resources already omitted them.
## What changed
- **`packages/mcp/src/stdio-data-bridge.ts`.** New
`stdioObjectView(reads)` returns `{ listObjects, getObject }`: the
protocol's merged listing (`readMergedObjects`, unchanged from #25243),
and a name looked up in that same listing, so a name the listing omits
is absent on both reads. A host with no merged read gets the `metadata`
service's `listObjects()` / `getObject()`, as before. The bridge's
`listObjects` and `describeObject` now answer through this view; their
answers do not change (the #25243 unit pins pass unchanged). Exported
within the package only; `index.ts` does not publish it.
- **`packages/mcp/src/mcp-server-runtime.ts`.** New type
`McpObjectView`: the `listObjects` and `getObject` members of
`IMetadataService`, picked. `buildObjectListResource` and
`buildObjectSchemaResource` take it as an optional last parameter and
read their objects from it; it defaults to the `metadata` service, so
every existing caller reads exactly as before. New package-internal
`setRuntimeObjectView(runtime, view)` records a runtime's view in a
module-level `WeakMap`; the two object resources registered by
`bridgeResources` look it up at each `resources/read` and fall back to
the `metadata` service. `bridgeResources` keeps its published signature:
a third parameter would have made a new type reachable from the exported
`MCPServerRuntime`, a public-surface widening for wiring between this
package's own plugin and runtime. The completeness verdict is still
asked of the `metadata` service (`diagnoseListRead` on the listing,
`diagnoseEmptyRead` on a miss), as the `list_objects` tool asks it.
- **`packages/mcp/src/plugin.ts`.** The `protocol` accessor #25243 added
(resolved at each use, `undefined` when absent or throwing, never
captured in `start()`) moves out of the stdio branch, because the
resources are bridged whether or not this process starts the stdio
transport. Before `bridgeResources`, the plugin calls
`setRuntimeObjectView(this.runtime, stdioObjectView({ metadataService,
protocol }))`. The data bridge and the record resource's gate keep
reading the same accessor.
- **`packages/mcp/README.md`.** One paragraph under Resources saying
what the two object resources read.
- **`.changeset/25245-mcp-stdio-object-resources-merged.md`.** A `patch`
for `@objectstack/mcp`, with the BREAKING banner and the ADR-0087
disposition `not-required (no-migration-prescription)`; both declaration
lines are in the body.
## Contract
Every input whose answer changes, old to new, on the stdio transport:
1. **`resources/read objectstack://objects` on a kernel with an
`environmentId`, for an object a package or plugin registered during the
boot.** Old: omitted, `totalCount` short. New: listed; the non-`sys_`
set is what `list_objects` lists.
2. **`resources/read objectstack://objects/NAME` on such an object.**
Old: `{ error: 'Object "NAME" not found', code: 'RESOURCE_NOT_FOUND',
status: 404 }`. New: the schema body, equal to what `describe_object`
answers for NAME.
3. **Both resources on a disabled package's objects, where the
`metadata` service still holds a copy** (the narrowing). Old: listed,
and described from the copy. New: omitted, and
`objectstack://objects/NAME` answers the `404 RESOURCE_NOT_FOUND` body,
as `describe_object` answers "not found".
4. **An object whose served definition differs from the `metadata`
service's copy.** Old: the resources described the copy. New: they
describe the definition REST serves.
5. **Both resources while the protocol's merged listing throws.** Old:
the `metadata` service's answer. New: the protocol's error travels to
the client as a JSON-RPC error, as `list_objects` lets it travel since
#25243; nothing falls back. Pinned with a throwing double, not on a real
store outage.
Unchanged: objects that already had a copy (every object on a kernel
without an `environmentId`, a marketplace install on any kernel, a
runtime-authored object) are listed and described as before. A loader
outage still withholds `totalCount` (`partial: true`, `returnedCount`,
`503 SERVICE_UNAVAILABLE`), and a miss during an outage still answers
`503 SERVICE_UNAVAILABLE`. A host with no `protocol` service, or one
without the merged listing, reads the `metadata` service,
byte-identically (pinned). A host that builds its own `MCPServerRuntime`
and calls `bridgeResources` reads the `metadata` service, as before. The
published surface does not move: the built `dist/index.d.ts` declares
`bridgeResources` with the same two parameters, and names none of
`McpObjectView`, `setRuntimeObjectView` and `stdioObjectView` as
declarations. The record resource is untouched. Discovery stays ungated:
the resource lists and describes an object declared `apiEnabled: false`,
as `list_objects` and `describe_object` do.
## Pins
-
**`packages/qa/dogfood/test/mcp-stdio-package-object-discovery.test.ts`,
10 new cases on real kernels, beside #25243's tool pins (80 cases in the
file).** For each of the four rows above: `objectstack://objects` lists
both package objects, has no `partial`, has `totalCount` equal to its
length, and lists the same non-`sys_` names `list_objects` lists;
`objectstack://objects/NAME` answers both objects,
`enableFeatures.apiEnabled: false` included, each body EQUAL to
`describe_object`'s, and a name nothing serves answers exactly `{ error:
'Object "qa_pkg_nowhere" not found', code: 'RESOURCE_NOT_FOUND', status:
404 }`. For a disabled package on both shapes: neither object is listed,
and each answers that `404` body.
-
**`packages/mcp/src/mcp-server-runtime.merged-object-resources.test.ts`,
14 cases, doubles.** The listing from the merged read where the copy is
absent, the `metadata` service's `listObjects` never asked; a disabled
package's objects omitted from both resources although the copy holds
them; the `partial` verdict kept from `listDiagnosed`; the `503` on a
miss kept from `getDiagnosed`; a throwing merged read propagated by both
builders, the copy never asked; the schema body equal to
`describeObject`'s for both package objects; no protocol, and a protocol
without `getMetaItems`, byte-identical to the `metadata` service alone;
a protocol registered after the bridge read on the next call. Over a
real `resources/read` on an `MCPServerRuntime`: both resources answer
from the view `setRuntimeObjectView` gave, never the `metadata` service;
a view given after `bridgeResources` is read on the next call; a runtime
given no view reads the `metadata` service, and another runtime's view
is not its own.
**Red before, green after.** Each leg's direction was predicted before
it ran. The dogfood file consumes `@objectstack/mcp`'s `dist/`, so every
leg rebuilt it, and `scripts/ablation-dist-preflight.mjs` proved the
leg's marker reached `dist/` (exit 0) and, after the restore and a
rebuild at HEAD, that none remained (`--absent`, exit 0 for each).
Mutations were landed and restored by `scripts/ablation-replace.mjs`
(anchor 1 to 0, blob changed; restored blob equals HEAD, `git diff HEAD`
empty).
| leg | mutation | dogfood (80) | unit (14) |
| :-- | :-- | :-- | :-- |
| main | the base tree `0b8bce40e0` (source blobs `6ca41ee187`,
`f6d8800263`, `e71d1f1c81`) with the new dogfood pins | 3 failed, 77
passed: the `env_local` during-boot listing and schema; the
no-`environmentId` disabled package's resources | not run (the file
imports names absent on base) |
| bridge | the runtime's two resources ignore the view their host gave
(`runtimeObjectViews.get(this)` replaced) | the same 3 failed | 2
failed: both resources answer from the view; a view given after
`bridgeResources` is read |
| plugin | `plugin.ts` gives its runtime no view | the same 3 failed |
14 passed (this file drives the runtime, not the plugin) |
| viewcopy | the view's `getObject` falls back to the copy for a name
the listing omits | 2 failed: the no-`environmentId` disabled package's
resources, and its `describe_object` (#25243's row, which reads the same
view) | 1 failed: a name the listing omits is the coded miss |
| fix | `a697b07f34` | 80 passed | 14 passed |
The legs and the fix row were measured on `a697b07f34`. The final head
differs from it by one doc comment in `mcp-server-runtime.ts` (a
`{@link}` to the package-internal setter, which the published
declaration file would have carried as a dangling link, now plain code
text); both pin files are re-run on the final head by the unit suite and
the battery below. An earlier local revision passed the view as a third
parameter of `bridgeResources`; it was replaced before the first push
for the public-surface reason above, and its legs read the same way.
## Local verification
On `5cf1aa4ce3`, the final commit, over `origin/main` `0b8bce40e0` (it
has since moved two commits, #25258 and #25259, neither touching
`packages/mcp`, `packages/metadata-protocol` or this dogfood file), or
on `dc7bee12dc` where a line says so; the one commit between them
(`5cf1aa4ce3`) changes how the dogfood pin file's resource helper is
typed, not what any case asserts. Every heavy run went through the
verify lock, and each line cites its `VERDICT`.
- `@objectstack/mcp` unit suite (`pnpm --filter @objectstack/mcp test`),
on `dc7bee12dc`: `VERDICT command-exit 0`; Test Files 42 passed (42),
Tests 532 passed (532).
- `pnpm --filter @objectstack/mcp typecheck`, on `dc7bee12dc`: `VERDICT
command-exit 0` (`tsc --noEmit`, and `check:test-typecheck` OK: 6 files,
53 errors, 8 pinned signatures held in the ledger).
- `turbo run build --filter=!@objectstack/docs`, on `dc7bee12dc`:
`VERDICT command-exit 0`, 76 of 76 tasks.
- `pnpm --filter @objectstack/dogfood typecheck` with
`NODE_OPTIONS=--max-old-space-size=8192`: `VERDICT command-exit 0`. The
first run, on `dc7bee12dc`, answered exit 2 with one error (TS2339): the
pin's record reader called `this.readResource` inside the returned
object literal, whose `this` tsc types as `Booted` or a `PromiseLike` of
it. `5cf1aa4ce3` reads both through a local helper.
- `pnpm lint` (`eslint . --no-inline-config`), on `dc7bee12dc`: `VERDICT
command-exit 0`; `eslint --no-inline-config` on the one file
`5cf1aa4ce3` changes: `VERDICT command-exit 0`.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 70 families derived; each run, every one
`exit 0` (the second pass, on the final head; the first pass, on
`dc7bee12dc`, also answered 70 of 70 `exit 0`).
- `--ran` reconciliation: `70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN`,
exit 0.
- `node scripts/check-changeset-no-major.mjs --base origin/main --event
PAYLOAD`, with PAYLOAD a `pull_request` event carrying this title and
body: exit 0 (`✓ LEVEL AXIS`: `no (narrowing)`, `Metadata-protocol:
none`, a non-protocol change at `patch`).
- `node scripts/check-closing-keyword-parity.mjs --body` on this body:
exit 2, its "closing declarations found" answer: `Fixes #25245` and
nothing else, bound by the duplicate-fix guard's and the part-of guard's
parsers.
- The full dogfood battery, once (`pnpm --filter @objectstack/dogfood
test`, no shard), on `dc7bee12dc`: `VERDICT command-exit 0`; Test Files
289 passed, 3 skipped (292); Tests 2491 passed, 18 skipped (2509); no
failure. It was not re-run for `5cf1aa4ce3`, which touches that one pin
file's helper; the file was re-run on the final head: `VERDICT
command-exit 0`, 80 of 80.
## Acceptance notes
- **Same family, left as it was: the prompt bridge captures the protocol
once.** `bridgePrompts` reads skills through `mergedRead`, which
`plugin.ts` resolves once in `start()` (#8328), not at each use as the
object reads now are. Not this card's defect (skills, not objects), and
not measured here.
- **`objectstack://objects/NAME` reads the whole merged listing per
call**, as `describe_object` does since #25243 and the HTTP bridge since
#25136.
- **File surface.** Beside `mcp-server-runtime.ts` and `plugin.ts`, the
fix exports the shared view from `stdio-data-bridge.ts` (the card's "not
a second copy of the merge"), adds one README paragraph, and adds the
real-kernel pins to the file #25243's tool pins live in. Claim revision
6346855817 names this surface and the `(narrowing)` arm, which the first
claim (6346581465) wrote as a bare `no` before the disabled-package
reading was measured.
- **Branch name.** This PR's branch is `claude/project-thread-dkk3hd`,
the name the session was given, not `claude/issue-25245-…`; the
Duplicate Fix Guard may say so.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01TgbP2QERdKmh147ohacHbK)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01TgbP2QERdKmh147ohacHbK
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 0148071 commit 72fad92
7 files changed
Lines changed: 495 additions & 31 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
178 | 178 | | |
179 | 179 | | |
180 | 180 | | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
181 | 187 | | |
182 | 188 | | |
183 | 189 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 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 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
0 commit comments