feat: v6 polish — per-publisher namespacing + auto-create project #71

Merged
michal merged 2 commits from feat/v6-polish into main 2026-04-28 23:33:40 +00:00
Owner

Summary

Two small org-grade quality-of-life fixes for first-five-minutes
friction. Both close items the original v1-v3 docs deferred.

Stage 1 (c346b93) — per-publisher namespacing

mcplocal can now append a suffix to its published Llm/Agent names so
two users running the same config template don't collide on the
cluster-wide unique-name constraint. Pair with an explicit poolName
(v4) and the rows still load-balance as one pool.

{
  "publisher": { "suffix": "auto" }   // or "alice" or via env
}

Resolves to:

  • vllm-local-qwen3 (alice's mcplocal) → wire name vllm-local-qwen3-alice
  • vllm-local-qwen3 (bob's mcplocal) → wire name vllm-local-qwen3-bob

Both rows are addressable; agents pinned to either dispatch through
the v4 pool. Inbound infer/wake task lookups match by wire name on
the registrar side, so the local registry stays keyed by its
original (non-suffixed) name.

Stage 2 (ee18c51) — auto-create project for virtual agents

registerVirtualAgents now creates the named project if missing
(with the publishing user as owner) instead of 404'ing. Idempotent:
second publisher landing on the same project name reuses the
existing row. Closes the v3-out-of-scope item.

Test plan

  • mcpd unit suite: 870/870 (was 868; +2 v6 Stage 2 tests)
  • mcplocal unit suite: 731/731 (was 723; +8 v6 Stage 1 tests)
  • Pre-v6 configs (no publisher field, no auto-create) keep
    working byte-identically — empty suffix is the no-op code path,
    and ensureByName only kicks in when the project would have
    404'd anyway.

Note on stacking with v5

This branch is from main (= v4 final). v5 (PR #70) is still open;
when it merges I'll rebase v6 onto it before deploy. Both v6 stages
touch paths v5 didn't change, so the rebase should be mechanical.

## Summary Two small org-grade quality-of-life fixes for first-five-minutes friction. Both close items the original v1-v3 docs deferred. ### Stage 1 (`c346b93`) — per-publisher namespacing mcplocal can now append a suffix to its published Llm/Agent names so two users running the same config template don't collide on the cluster-wide unique-name constraint. Pair with an explicit `poolName` (v4) and the rows still load-balance as one pool. ```json { "publisher": { "suffix": "auto" } // or "alice" or via env } ``` Resolves to: - `vllm-local-qwen3` (alice's mcplocal) → wire name `vllm-local-qwen3-alice` - `vllm-local-qwen3` (bob's mcplocal) → wire name `vllm-local-qwen3-bob` Both rows are addressable; agents pinned to either dispatch through the v4 pool. Inbound infer/wake task lookups match by wire name on the registrar side, so the local registry stays keyed by its original (non-suffixed) name. ### Stage 2 (`ee18c51`) — auto-create project for virtual agents `registerVirtualAgents` now creates the named project if missing (with the publishing user as owner) instead of 404'ing. Idempotent: second publisher landing on the same project name reuses the existing row. Closes the v3-out-of-scope item. ### Test plan - [x] mcpd unit suite: 870/870 (was 868; +2 v6 Stage 2 tests) - [x] mcplocal unit suite: 731/731 (was 723; +8 v6 Stage 1 tests) - [x] Pre-v6 configs (no `publisher` field, no auto-create) keep working byte-identically — empty suffix is the no-op code path, and ensureByName only kicks in when the project would have 404'd anyway. ### Note on stacking with v5 This branch is from main (= v4 final). v5 (PR #70) is still open; when it merges I'll rebase v6 onto it before deploy. Both v6 stages touch paths v5 didn't change, so the rebase should be mechanical.
michal added 2 commits 2026-04-28 14:54:49 +00:00
Two mcplocals sharing the same config template (`vllm-local-qwen3`)
no longer collide on mcpd's cluster-wide unique-name constraint.
Each publisher can append a suffix derived from hostname (or any
other stable per-host identifier) so the wire-side names become
distinct (`vllm-local-qwen3-alice`, `vllm-local-qwen3-bob`).
Pair with an explicit `poolName` (v4) and the rows still appear as
one logical pool — agents pinned to any member load-balance across
both.

Config (`~/.mcpctl/config.json`):

  {
    "publisher": { "suffix": "auto" }   // → os.hostname() sanitized
                  // or { "suffix": "alice" } for explicit override
  }

Or via env: `MCPCTL_PUBLISHER_SUFFIX=alice` (operations override).

Resolution order: env var → config.publisher.suffix → empty
(legacy behavior, no mangling). Sanitization lowercases, replaces
non-`[a-z0-9-]` runs with `-`, strips leading/trailing dashes —
the result must satisfy mcpd's name validation, otherwise the
register POST would 422.

Wire shape: RegistrarPublishedProvider gets an optional
`publishName` field. When set, the wire payload's `name` is
`publishName` (suffixed); when not, today's `provider.name`.
Inbound infer/wake task lookups match `publishName ?? provider.name`
so the local registry stays addressable by its original name —
SSE frames carrying the suffixed wire name still find their
provider.

Agents are forwarded with their own suffixed name AND a
`llmName` rewritten through the same per-local→wire map so the
agent rows pin to the suffixed Llm wire name (otherwise
registerVirtualAgents would 404).

Tests: 8 new tests covering applyPublisherSuffix (empty, normal,
length limit, exact-100) and loadPublisherSuffix (env override,
absent, sanitization, dash stripping). Existing registrar tests
untouched — no suffix means no behavior change.
feat(mcpd): auto-create project on virtual-agent register (v6 Stage 2)
Some checks failed
CI/CD / typecheck (pull_request) Successful in 55s
CI/CD / test (pull_request) Successful in 1m12s
CI/CD / lint (pull_request) Successful in 3m0s
CI/CD / smoke (pull_request) Failing after 1m44s
CI/CD / build (pull_request) Successful in 6m41s
CI/CD / publish (pull_request) Has been skipped
ee18c5107e
Closes the v3-deferred "project must already exist" gap. When a
virtual agent declares `project: "my-team"` and no such project
exists, mcpd creates it idempotently with the publishing user as
owner (instead of throwing 404 from registerVirtualAgents).

ProjectService gains `ensureByName(name, ownerId, opts)` — find
the project or create it with sensible defaults (description carries
an audit note pointing at the registrar; proxyModel/gated take
their schema defaults). First publisher to land on a name owns the
row; subsequent publishers reuse the existing one.

AgentService.registerVirtualAgents calls ensureByName instead of
resolveAndGet, so the same agent register payload works regardless
of whether the project pre-existed or not.

Tests: 2 new tests (auto-creates a missing project on first publish;
reuses an existing project without re-creating). Mock projects
factory rebuilt to track _created names + maintain id→name reverse
lookup so the agent's toView returns the correct project name
(prior mock hardcoded 'mcpctl-dev').

Existing 13 virtual-agent tests + 870 mcpd suite green.
michal merged commit 3071bcee8e into main 2026-04-28 23:33:40 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: michal/mcpctl#71