Commit 983ea31
authored
* feat(mcp): serve the discovery surfaces and publish server.json
The server card and the agent-tools trio are COMPUTED at request time from the contract
registry. metagraphed committed its card and paid for it: every concurrent tool PR
conflicted on the same generated file. There is nothing here to regenerate and nothing a
tool PR can collide on.
The same routes serve both deployments over their own availability-filtered tool set, so a
self-host card is truthful rather than a copy of the cloud one -- a cloud-only tool is
absent because it is absent from that deployment's list, not because a second
implementation remembered to exclude it. Locality is deliberately NOT filtered: the remote
serves local-git tools too, it just expects the caller to supply the branch metadata.
generated_at derives from the version rather than the clock. A wall-clock value would
change the body on every request, changing the ETag with it and leaving the 304 path as
dead code that never fires.
serverInfo.version stops being the hardcoded "0.1.0" it has reported since the server was
written, and reads the same @loopover/mcp version the compatibility metadata already did --
so serverInfo, the card, and server.json cannot disagree about what shipped.
The manifest check carries the anti-rot guard this issue asked for: it asserts every
watched path EXISTS before validating a field. metagraphed's version-sync workflow watched
a renamed path and kept passing for months while doing nothing, and a workflow that can
quietly watch nothing is worse than no workflow.
* feat(mcp): mount the remote tool set from the stdio server (#9526)
The stdio server now discovers the remote server's tools/list with the login session and
registers anything it does not already serve, so one config gets a contributor every tool
their session entitles them to instead of one stdio config for local-git tools and a
separate remote endpoint for the rest.
The mount is best-effort by design. No session, no network, or a hostile response all leave
a server that starts and lists its local tools plus one advisory resource explaining how to
get the rest; --no-remote opts out to byte-identical pre-gateway behavior. Proxied tools
re-use the contract registry's zod schemas rather than trusting a remote-supplied schema, so
the proxy advertises exactly what the remote enforces, and carry _meta.transport="proxied"
so telemetry can tell a proxied call from a local one.
Name collisions cannot happen by construction — one registry, one entry per name, so a name
is either local-git or remote — and validate:mcp now asserts that invariant. The mount still
skips an already-registered name so a future violation degrades to "local wins" rather than
crashing the server on a duplicate registration.
* feat(mcp): derive every client-config surface and tag proxied calls (#9526)
The three ways to connect -- the stdio gateway, the remote streamable-http endpoint, and the
miner's own stdio server -- were spelled out by hand in four places: clientSnippet() in the
stdio bin, the mcp-clients docs page, and both package READMEs. They had already drifted; the
docs page documented no remote auth at all, and nothing described the gateway because the
prose predates it. The grid now lives in @loopover/contract as data, `init-client --print`
renders it, and a generator writes the docs and README blocks from it under a --check drift
guard, so a snippet copied from the docs and one printed by the CLI are byte-identical.
`init-client` gains `--mode stdio|remote|miner`, defaulting to stdio so a pre-gateway
invocation prints exactly what it always did. A remote entry names the token's environment
variable in whichever dialect the host reads -- Codex's own key, the JSON hosts' header --
and never carries a value; a host whose remote dialect this repo cannot vouch for is refused
rather than given a plausible snippet that fails on paste.
Telemetry gains a `transport` dimension, so gateway adoption is measurable: `surface` says
which server was asked, and only `transport` says whether a stdio call ran against the local
checkout or was forwarded. Proxied tools now run through the same telemetry chokepoint local
ones do, and re-register with the contract's schema OBJECTS rather than their `.shape`, which
the SDK re-wraps in a way that drops the catchall and turns every extra field into a -32602.
The discovery routes are specced through the #9519 seam with real response schemas: the four
documents are now zod schemas with their TypeScript types inferred, so the published operation
describes the object actually served instead of a second declaration of it. They also report
the deployment that ANSWERED -- src/server.ts serves this same Hono app, so a self-host card
was advertising the cloud's tool set, which is a list of calls that 404 there.
Fix-what-you-find, all three the same rot: the vitest alias list for @loopover/contract, the
published-tarball allowlist, and the mount's startup cost. The first two are now derived from
the package's own exports map and its committed sources -- both had just failed by lagging a
new file, with an error naming the symptom and not the cause. The third: the mount moved after
connect() and is no longer awaited, so a network round-trip no longer sits between a client
launching the process and it answering, and registering a tool on a connected server emits
notifications/tools/list_changed -- the refresh path a stateless remote cannot push itself.
* fix(mcp): pin the publisher binary by checksum and gate the publish job (#9526)
Three defects in the registry-publish workflow, two of them raised by the security scan and all
three real.
The install step's comment said "SHA-pinned" while the code floated on a release tag, which a
maintainer can move to a different commit after review -- for a binary that authenticates as this
repository and writes to a public registry. It now verifies a sha256 of the download before
extracting it, so a moved tag fails the job rather than publishing under bytes nobody looked at.
The job holds `id-token: write` and had no deployment environment, so protection rules had nothing
to attach to and anyone able to dispatch a workflow could publish. It now declares one.
And the download would have 404'd regardless: the asset was requested as
`mcp-publisher_${VERSION}_linux_amd64.tar.gz`, but the release publishes it with no version in the
name. A publish workflow that cannot fetch its own tool is a workflow nobody has run, which is
exactly the class the anti-rot guard exists for -- so the guard now covers the workflow itself:
dispatch-only, main-only, environment present, permissions minimal, actions sha-pinned, the
checksum verified before extraction, and the manifest check ordered before login and publish.
Also fixes a genuine flake this PR's CI hit twice, unrelated to the change: selfhost-pg-queue's
recentDeadCount assertion compared the bound cutoff only against a timestamp captured BEFORE the
call, while the implementation reads the clock after it -- so one millisecond ticking between the
two failed the test (1785302335173 <= 1785302335172). Bracketed on both sides now.
* fix(mcp): forward the caller's arguments through a proxied tool (#9526)
Raising the gateway's patch coverage found a real defect in it. registerProxiedTool declared an
inputSchema only for a tool the contract registry knows, and the SDK invokes a handler as
`(args, extra)` ONLY when an inputSchema is declared -- as `(extra)` alone when it is not. So a
proxied tool absent from the registry, which is precisely the case the fallback exists for (a
remote running ahead of this package), forwarded the SDK's own `{ signal, requestId }` to the
remote AS THE ARGUMENTS and silently dropped everything the caller passed.
An unmodelled tool now gets a fully open input schema instead of none, which keeps the remote the
only validator -- where validation belongs for a tool this package does not model -- while making
the SDK route arguments the way it does for every other tool.
The test that caught it was previously asserting only that the call resolved. callTool resolves
for a FAILED call too, so it passed while nothing was being proxied at all: the fixture had no
/mcp route, the proxy 404'd, and the SDK turned the throw into an isError result nobody checked.
The fixture now answers tools/call with a real JSON-RPC envelope and the assertion is on the
payload -- the tool name and the arguments as the remote received them.
Also: the advisory resource is registered on every outcome rather than only on failure, so a
successful mount no longer leaves an earlier failure's advisory standing as the answer to "why
don't I have the remote tools". Plus coverage for the paths that had none -- calling a proxied
tool, reading the advisory, a descriptor carrying annotations, one carrying only a name, an
envelope with no `result`, the real fetch transport against the loopback fixture, and the
no-session path that makes no outbound request at all.
* test(mcp): cover init-client's host and mode guards (#9526)
The five branch partials codecov/patch was holding the PR on, all in init-client's argument
handling: no host at all, an unknown host, the --client spelling as well as --print, a
host/mode pair the grid refuses, and the per-host remote caveat's absent side.
Each asserts the message rather than merely the throw -- "unsupported client" without the list
leaves a reader guessing at a five-value set they cannot see from outside the process.
1 parent 81cecee commit 983ea31
40 files changed
Lines changed: 3887 additions & 379 deletions
File tree
- .github/workflows
- apps/loopover-ui
- content/docs
- public
- src
- packages
- loopover-contract
- src
- loopover-mcp
- bin
- lib
- loopover-miner
- scripts
- src
- api
- auth
- mcp
- openapi
- test
- contract
- integration
- unit
- support
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
| 8 | + | |
7 | 9 | | |
8 | 10 | | |
9 | | - | |
| 11 | + | |
10 | 12 | | |
11 | 13 | | |
12 | 14 | | |
13 | 15 | | |
14 | 16 | | |
15 | 17 | | |
16 | 18 | | |
17 | | - | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
18 | 29 | | |
19 | 30 | | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
24 | 34 | | |
25 | | - | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
26 | 40 | | |
27 | 41 | | |
28 | 42 | | |
| |||
32 | 46 | | |
33 | 47 | | |
34 | 48 | | |
35 | | - | |
| 49 | + | |
36 | 50 | | |
37 | 51 | | |
38 | 52 | | |
| |||
47 | 61 | | |
48 | 62 | | |
49 | 63 | | |
50 | | - | |
| 64 | + | |
51 | 65 | | |
52 | 66 | | |
53 | 67 | | |
| |||
62 | 76 | | |
63 | 77 | | |
64 | 78 | | |
65 | | - | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
66 | 93 | | |
67 | | - | |
68 | | - | |
| 94 | + | |
69 | 95 | | |
70 | 96 | | |
71 | 97 | | |
| |||
81 | 107 | | |
82 | 108 | | |
83 | 109 | | |
84 | | - | |
| 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 | + | |
85 | 260 | | |
86 | | - | |
87 | | - | |
| 261 | + | |
88 | 262 | | |
89 | | - | |
| 263 | + | |
90 | 264 | | |
91 | 265 | | |
92 | 266 | | |
93 | | - | |
| 267 | + | |
| 268 | + | |
94 | 269 | | |
0 commit comments