feat(mcpd+cli+mcplocal): wire visibility filter through routes, CLI, registrar (v7 Stage 2)

Stage 1 added the schema + service predicate. This stage threads the
filter through every surface that lists or fetches Llms/Agents:

- mcpd routes: viewerFromRequest helper builds a Viewer from the
  request's RBAC scope. List endpoints rely on the existing
  preSerialization hook (now two-phase: name-scope first, visibility
  second). get-by-id/get-by-name routes pass the viewer to the service
  which 404s on hidden rows.
- RBAC: AllowedScope gains `isAdmin` to distinguish a `*` cross-resource
  grant (admins skip visibility) from a plain `view:llms` grant
  (wildcard for RBAC, but visibility still applies). FastifyRequest
  augmentation updated.
- VirtualLlmService.register accepts ownerId and stamps it on freshly
  created virtual rows; defaults visibility to 'private' on first
  create, leaves existing rows untouched on sticky reconnect.
- AgentService.registerVirtualAgents mirrors the same defaults.
- mcplocal: LlmProviderFileEntry / AgentFileEntry / RegistrarPublishedX
  carry visibility through to the register payload (default 'private').
- CLI: VISIBILITY column on `mcpctl get llm` and `mcpctl get agent`,
  `--visibility` flag on `mcpctl create llm` / `create agent`. YAML
  round-trip works because visibility passes through stripInternalFields
  unchanged (ownerId is already stripped). Completions regenerated.

Tests: mcpd 908/908, mcplocal 731/731, cli 437/437.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michal
2026-04-29 01:03:58 +01:00
parent 21f8bede2e
commit 2c98a21323
16 changed files with 241 additions and 33 deletions

View File

@@ -105,6 +105,14 @@ export interface LlmProviderFileEntry {
* logical pool that auto-grows as more workers come online.
*/
poolName?: string;
/**
* v7: per-user RBAC scoping. mcplocal-published virtuals default to
* 'private' on register — the publishing user owns the row and other
* users don't see it without an explicit `view:llms:<name>` grant.
* Set to 'public' here to opt into org-wide sharing for this
* provider.
*/
visibility?: 'public' | 'private';
}
export type WakeRecipe =
@@ -136,6 +144,8 @@ export interface AgentFileEntry {
project?: string;
defaultParams?: Record<string, unknown>;
extras?: Record<string, unknown>;
/** v7: see LlmProviderFileEntry.visibility — same default ('private'). */
visibility?: 'public' | 'private';
}
/**

View File

@@ -233,6 +233,10 @@ async function maybeStartVirtualLlmRegistrar(
if (entry.wake !== undefined) item.wake = entry.wake;
if (entry.poolName !== undefined) item.poolName = entry.poolName;
if (wireName !== provider.name) item.publishName = wireName;
// v7: pass visibility through; registrar already defaults to
// 'private' when omitted, and the per-provider override flows
// straight through to the register payload.
if (entry.visibility !== undefined) item.visibility = entry.visibility;
published.push(item);
}
// v3: forward locally-declared agents alongside the providers. We
@@ -255,6 +259,7 @@ async function maybeStartVirtualLlmRegistrar(
if (a.project !== undefined) item.project = a.project;
if (a.defaultParams !== undefined) item.defaultParams = a.defaultParams;
if (a.extras !== undefined) item.extras = a.extras;
if (a.visibility !== undefined) item.visibility = a.visibility;
publishedAgents.push(item);
}

View File

@@ -72,6 +72,14 @@ export interface RegistrarPublishedProvider {
* `publishName ?? provider.name` everywhere.
*/
publishName?: string;
/**
* v7: per-user RBAC scoping. mcplocal-published virtuals default to
* 'private' (visible only to the publishing user) — workstations
* shouldn't broadcast their models org-wide unless explicitly
* shared. The publisher can override per provider with
* `"visibility": "public"` in their mcplocal config.
*/
visibility?: 'public' | 'private';
}
/**
@@ -88,6 +96,8 @@ export interface RegistrarPublishedAgent {
project?: string;
defaultParams?: Record<string, unknown>;
extras?: Record<string, unknown>;
/** v7: per-user RBAC scoping, defaults to 'private' on register. */
visibility?: 'public' | 'private';
}
export interface RegistrarOptions {
@@ -207,6 +217,10 @@ export class VirtualLlmRegistrar {
...(p.tier !== undefined ? { tier: p.tier } : {}),
...(p.description !== undefined ? { description: p.description } : {}),
...(p.poolName !== undefined ? { poolName: p.poolName } : {}),
// v7: virtuals default to private. Operators who want their
// workstation model org-visible set "visibility": "public" per
// provider in mcplocal config.
visibility: p.visibility ?? 'private',
initialStatus,
};
}));
@@ -224,6 +238,9 @@ export class VirtualLlmRegistrar {
...(a.project !== undefined ? { project: a.project } : {}),
...(a.defaultParams !== undefined ? { defaultParams: a.defaultParams } : {}),
...(a.extras !== undefined ? { extras: a.extras } : {}),
// v7: forward visibility to mcpd. Defaults to 'private' for
// virtual agents on the server side when omitted.
visibility: a.visibility ?? 'private',
}));
}