docs+smoke(v7): visibility section in virtual-llms.md + register/list smoke
Some checks failed
CI/CD / typecheck (pull_request) Successful in 55s
CI/CD / test (pull_request) Successful in 1m11s
CI/CD / lint (pull_request) Successful in 2m49s
CI/CD / smoke (pull_request) Failing after 1m42s
CI/CD / build (pull_request) Successful in 5m37s
CI/CD / publish (pull_request) Has been skipped
Some checks failed
CI/CD / typecheck (pull_request) Successful in 55s
CI/CD / test (pull_request) Successful in 1m11s
CI/CD / lint (pull_request) Successful in 2m49s
CI/CD / smoke (pull_request) Failing after 1m42s
CI/CD / build (pull_request) Successful in 5m37s
CI/CD / publish (pull_request) Has been skipped
Wraps up v7 Stage 3: - docs/virtual-llms.md gains a "Visibility scope (v7)" section that explains public-vs-private semantics, who skips the filter (owner + `*` admin), how to grant single-row exceptions via name-scoped RBAC, per-row override syntax in mcplocal config, the `--visibility` flag on `mcpctl create llm`/`create agent`, and YAML round-trip behavior. - New smoke (virtual-llm-visibility.smoke.test.ts) publishes one public + one private virtual Llm via the registrar against the live mcpd and asserts the GET /llms response carries visibility + a non-empty ownerId for both, and that GET /llms/<name> returns the private row to its owner without 404. Cross-user filtering is covered by mcpd's visibility-filter unit tests; smoke proves the fields make the round-trip end-to-end. Will pass once mcpd is rebuilt + deployed via fulldeploy.sh on this branch (current main is v6, doesn't yet serialize visibility). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -431,10 +431,100 @@ mid-task reverts the row to pending instead of failing the caller.
|
||||
See [inference-tasks.md](./inference-tasks.md) for the full data
|
||||
model, async API, lifecycle, RBAC, and CLI surface.
|
||||
|
||||
## Visibility scope (v7)
|
||||
|
||||
Virtual Llms and Agents now carry an explicit **visibility** field that
|
||||
decides who can see the row in listings.
|
||||
|
||||
| Visibility | Meaning |
|
||||
|-------------|----------------------------------------------------------------------------------|
|
||||
| `public` | Visible to anyone with `view:llms` / `view:agents`. Default for hand-created Llms. |
|
||||
| `private` | Only the **owner** plus principals with a name-scoped grant can see it. Default for virtual Llms and Agents on first publish. |
|
||||
|
||||
The owner is whichever user authenticated the publishing
|
||||
`POST /api/v1/llms/_provider-register` (or `mcpctl create llm`). For
|
||||
mcplocal that's whichever `~/.mcpctl/credentials` token is on disk.
|
||||
Legacy rows from before v7 default to `visibility=public, ownerId=NULL`,
|
||||
so the upgrade is a no-op for everything that already exists.
|
||||
|
||||
### Who skips the filter?
|
||||
|
||||
Two principals see every row regardless of visibility:
|
||||
|
||||
1. The **row owner** (`ownerId === request.userId`).
|
||||
2. Anyone with a **cross-resource admin** grant — RBAC binding
|
||||
`{ resource: '*' }`. Operationally this is the SRE / cluster admin.
|
||||
|
||||
A plain `view:llms` resource grant is *not* the same as admin: it's a
|
||||
RBAC wildcard for name-scoping (you can name any Llm), but the
|
||||
visibility filter still applies on top. This is the v7 split that
|
||||
prevents a user with `view:llms` from enumerating every developer's
|
||||
private virtual Llm.
|
||||
|
||||
### Granting a single-row exception
|
||||
|
||||
When alice wants bob to see her private virtual Llm `alice-vllm-local`
|
||||
without making it public, she binds:
|
||||
|
||||
```sh
|
||||
mcpctl create rbac bob view:llms --name alice-vllm-local
|
||||
```
|
||||
|
||||
Same shape as any other name-scoped binding. Removing the binding
|
||||
flips bob back to "row not found".
|
||||
|
||||
### Publishing as private from mcplocal
|
||||
|
||||
mcplocal defaults to `private` for every published provider and agent.
|
||||
Override per-row in `~/.mcpctl/config.json`:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"llm": {
|
||||
"providers": [
|
||||
{ "name": "vllm-local", "type": "vllm", "model": "...", "publish": true,
|
||||
"visibility": "private" }, // default; explicit for clarity
|
||||
{ "name": "shared-qwen", "type": "vllm", "model": "...", "publish": true,
|
||||
"visibility": "public" } // every team member can chat with it
|
||||
]
|
||||
},
|
||||
"agents": [
|
||||
{ "name": "local-coder", "llm": "vllm-local",
|
||||
"visibility": "private" } // private agents pinned to private Llms
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
On a sticky reconnect (`providerSessionId` matches an existing row)
|
||||
the visibility is **only** updated when the publisher explicitly sends
|
||||
it — leaving the field off keeps whatever the row already has,
|
||||
including any field admin set out-of-band.
|
||||
|
||||
### Hand-created Llms
|
||||
|
||||
`mcpctl create llm` defaults to `public` (matches pre-v7 behavior).
|
||||
Pass `--visibility private` to opt in:
|
||||
|
||||
```sh
|
||||
mcpctl create llm my-key --type openai --model gpt-4o \
|
||||
--api-key-ref my-secret/key --visibility private
|
||||
```
|
||||
|
||||
The same `--visibility` flag is on `mcpctl create agent`.
|
||||
|
||||
### CLI surface
|
||||
|
||||
`mcpctl get llm` and `mcpctl get agent` show a `VISIBILITY` column.
|
||||
YAML round-trips cleanly: `mcpctl get llm X -o yaml | mcpctl apply -f -`
|
||||
preserves visibility, and `ownerId` is stripped from the apply doc
|
||||
because it's server-side state (the apply re-stamps the ownerId of the
|
||||
authenticated caller, not the original creator).
|
||||
|
||||
## Roadmap (later stages)
|
||||
|
||||
(LB pool by name landed in v4; durable task queue landed in v5.)
|
||||
- **v6** — multi-instance mcpd via pg `LISTEN/NOTIFY` (replaces the
|
||||
(LB pool by name landed in v4; durable task queue landed in v5;
|
||||
visibility scope landed in v7.)
|
||||
- **v8** — multi-instance mcpd via pg `LISTEN/NOTIFY` (replaces the
|
||||
per-instance EventEmitter wakeup), per-session worker capacity,
|
||||
remote cancel protocol over the SSE channel.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user