You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Land the structural payoff of the vMCP domain/transport split: split the in-memory server.Config into a core Config + a transport ServerConfig (via deriveCoreConfig/deriveServerConfig), then reduce server.New's body to the
thin wrapper Serve(ctx, New(deriveCoreConfig(cfg)), deriveServerConfig(cfg)) —
removing both //nolint:gocyclo as the god-object dismantles. This story maps to RFC Phase 3 and is the only phase that touches server.New's body; the
7-param signature and observable behavior stay byte-for-byte stable while the
implementation behind them collapses onto the New/Serve seam built in Phases 1–2.
By the time this story runs, Phase 1 has produced the VMCP interface + New(cfg) -> VMCP core (with the admission and elicitation seams) and Phase 2 has
produced Serve(ctx, VMCP, *ServerConfig) -> *Server with all transport concerns
(mcp-go server, SDK hooks, two-phase session creation, the full middleware chain,
AS runner, status reporter, optimizer, health monitor) already re-homed under it —
but Serve is not yet called by server.New. This story closes that loop. Per architecture.md ("PR-Sized Decomposition Guidance → Phase 3" and "Key Files to
Modify"), the work is the config decomposition plus the wrapper reduction at pkg/vmcp/server/server.go:301.
The config split is not a clean partition (R3): per architecture.md
"Constraints", the cross-cutting fields TelemetryProvider, AuditConfig, and the
health view are consumed on both sides (the core decorates the backend client
with telemetry and runs the workflow auditor; the transport adds telemetry/audit
middleware and lifecycles the health monitor). The health monitor is built at the
composition root (A2): because New runs before Serve, the wrapper builds it once,
injects its StatusProvider into New, and hands the built *health.Monitor to Serve. ServerConfig carries noAuthzMiddleware (authz lives in the core admission seam);
the AuthzMiddlewarefield on server.Config is kept vestigial — cli/serve.go
sets it and stays unchanged — and only the dead HTTP authz/annotation blocks (plus the
discovery middleware/seam) are deleted in #5445 once the legacy path is gone. This is an
in-memory-only change: vmcpconfig.Config, the CRD/YAML model, and the wire/storage format are unchanged
inputs to New.
This phase is small but high-stakes: it is gated by the full behavioral-parity
suite (driving the stable server.New wrapper) plus the unchanged thv vmcp serve E2E suite — the only safety net proving the reimplemented body is
observably identical.
Split the in-memory-only server.Config (server.go:92-185) into the core Config (collaborators + workflowDefs + Authz for the admission seam +
cross-cutting TelemetryProvider/AuditConfig) and a transport-only ServerConfig, exposed via deriveCoreConfig(cfg, …) and deriveServerConfig(cfg)
(P3.1 deriveCoreConfig/deriveServerConfig config split #5444).
Pass the cross-cutting fields (TelemetryProvider, AuditConfig) to bothNew
and Serve (R3 — not a clean partition); build the health monitor at the composition
root and thread its StatusProvider into New + the built *Monitor into Serve
(A2). ServerConfig omits AuthzMiddleware; the field on server.Config is kept
vestigial (cli/serve.go unchanged) (P3.1 deriveCoreConfig/deriveServerConfig config split #5444).
Reduce server.New's body to Serve(ctx, New(deriveCoreConfig(cfg, …)), deriveServerConfig(cfg, healthMon)), keeping
the 7-param signature byte-for-byte unchanged, and remove both //nolint:gocyclo
(New@300, Start@682). With the legacy path now gone, complete the A1-deferred
cleanup: delete the now-dead authz/annotation HTTP blocks and retire the discovery
middleware/seam + its s.core == nil guard (keeping convertAnnotations and the
vestigial AuthzMiddleware field). Highest-integration-risk PR; likely splits (P3.2 Reduce server.New body to the wrapper #5445).
server.New's 7-param signature and observable behavior are unchanged
(byte-for-byte signature; clients/embedders see no behavioral difference)
deriveCoreConfig/deriveServerConfig derive the core Config and the
transport ServerConfig from the existing in-memory server.Config, with the
cross-cutting TelemetryProvider/AuditConfig passed to both sides, the health
monitor built at the composition root and threaded both ways (A2), and ServerConfig
omitting AuthzMiddleware (the server.Config field is kept vestigial; cli/serve.go
unchanged) (R3)
server.New's body is reduced to Serve(ctx, New(deriveCoreConfig(cfg, …)), deriveServerConfig(cfg, healthMon)), both //nolint:gocyclo (New@300, Start@682) are removed with lint clean, and the
A1-deferred cleanup is completed (dead authz/annotation blocks + discovery middleware/seam
deleted; convertAnnotations and the vestigial AuthzMiddleware field retained)
pkg/vmcp/cli/serve.go is unchanged; no in-repo caller (nor the external brood-box embedder) requires any change
No serialized / wire / CRD / YAML / storage format change — the split is
in-memory only and vmcpconfig.Config is an unchanged input to New
Acceptance gate: the full behavioral-parity suite (tools/list,
tools/call, resources, prompts, composite workflows, session lifecycle, cross-pod
Redis paths) passes equivalently before/after, and the existing thv vmcp serve
E2E suite passes unchanged
Description
Land the structural payoff of the vMCP domain/transport split: split the in-memory
server.Configinto a coreConfig+ a transportServerConfig(viaderiveCoreConfig/deriveServerConfig), then reduceserver.New's body to thethin wrapper
Serve(ctx, New(deriveCoreConfig(cfg)), deriveServerConfig(cfg))—removing both
//nolint:gocycloas the god-object dismantles. This story maps toRFC Phase 3 and is the only phase that touches
server.New's body; the7-param signature and observable behavior stay byte-for-byte stable while the
implementation behind them collapses onto the
New/Serveseam built in Phases 1–2.Context
See RFC THV-0076: vMCP Core Interface
for full design details. Part of the vMCP interface refactor (epic #5419).
By the time this story runs, Phase 1 has produced the
VMCPinterface +New(cfg) -> VMCPcore (with the admission and elicitation seams) and Phase 2 hasproduced
Serve(ctx, VMCP, *ServerConfig) -> *Serverwith all transport concerns(mcp-go server, SDK hooks, two-phase session creation, the full middleware chain,
AS runner, status reporter, optimizer, health monitor) already re-homed under it —
but
Serveis not yet called byserver.New. This story closes that loop. Perarchitecture.md("PR-Sized Decomposition Guidance → Phase 3" and "Key Files toModify"), the work is the config decomposition plus the wrapper reduction at
pkg/vmcp/server/server.go:301.The config split is not a clean partition (R3): per
architecture.md"Constraints", the cross-cutting fields
TelemetryProvider,AuditConfig, and thehealth view are consumed on both sides (the core decorates the backend client
with telemetry and runs the workflow auditor; the transport adds telemetry/audit
middleware and lifecycles the health monitor). The health monitor is built at the
composition root (A2): because
Newruns beforeServe, the wrapper builds it once,injects its
StatusProviderintoNew, and hands the built*health.MonitortoServe.ServerConfigcarries noAuthzMiddleware(authz lives in the core admission seam);the
AuthzMiddlewarefield onserver.Configis kept vestigial —cli/serve.gosets it and stays unchanged — and only the dead HTTP authz/annotation blocks (plus the
discovery middleware/seam) are deleted in #5445 once the legacy path is gone. This is an
in-memory-only change:
vmcpconfig.Config, the CRD/YAML model, and the wire/storage format are unchangedinputs to
New.This phase is small but high-stakes: it is gated by the full behavioral-parity
suite (driving the stable
server.Newwrapper) plus the unchangedthv vmcp serveE2E suite — the only safety net proving the reimplemented body isobservably identical.
RFC Phase(s): Phase 3
Dependencies: #5431
Scope
In scope
server.Config(server.go:92-185) into the coreConfig(collaborators +workflowDefs+Authzfor the admission seam +cross-cutting
TelemetryProvider/AuditConfig) and a transport-onlyServerConfig, exposed viaderiveCoreConfig(cfg, …)andderiveServerConfig(cfg)(P3.1 deriveCoreConfig/deriveServerConfig config split #5444).
TelemetryProvider,AuditConfig) to bothNewand
Serve(R3 — not a clean partition); build the health monitor at the compositionroot and thread its
StatusProviderintoNew+ the built*MonitorintoServe(A2).
ServerConfigomitsAuthzMiddleware; the field onserver.Configis keptvestigial (
cli/serve.gounchanged) (P3.1 deriveCoreConfig/deriveServerConfig config split #5444).server.New's body toServe(ctx, New(deriveCoreConfig(cfg, …)), deriveServerConfig(cfg, healthMon)), keepingthe 7-param signature byte-for-byte unchanged, and remove both
//nolint:gocyclo(
New@300,Start@682). With the legacy path now gone, complete the A1-deferredcleanup: delete the now-dead authz/annotation HTTP blocks and retire the discovery
middleware/seam + its
s.core == nilguard (keepingconvertAnnotationsand thevestigial
AuthzMiddlewarefield). Highest-integration-risk PR; likely splits (P3.2 Reduce server.New body to the wrapper #5445).Out of scope
Serve, or theVMCP/Newcoreitself — all of that lands in Phase 1: VMCP interface, core constructor, admission + elicitation seams #5430 (Phase 1) and Phase 2: Serve transport helper, re-home transport, replace discovery #5431 (Phase 2);
this story only derives configs and wires the existing
New/Servetogether.server.New's signature — it is stable throughout the epic; onlythe body is reimplemented.
pkg/vmcp/cli/serve.go(the composition root) — it must requireno changes and keep calling
vmcpserver.New(...)as today.vmcpconfig.Configloaders / wire /storage format — the split is in-memory only.
docs/arch/vmcp-library.md,pkg/vmcp/doc.go) and the runnable decoratorexample — Phase 4: Documentation + runnable decorator example #5433 (Phase 4).
Child Tasks
PR-sized tasks under this story (each ≤ 400 LOC, ≤ 10 files changed excluding
tests/docs/generated, one logical change):
deriveCoreConfig/deriveServerConfigconfig splitserver.Newbody to the wrapperAcceptance Criteria
server.New's 7-param signature and observable behavior are unchanged(byte-for-byte signature; clients/embedders see no behavioral difference)
deriveCoreConfig/deriveServerConfigderive the coreConfigand thetransport
ServerConfigfrom the existing in-memoryserver.Config, with thecross-cutting
TelemetryProvider/AuditConfigpassed to both sides, the healthmonitor built at the composition root and threaded both ways (A2), and
ServerConfigomitting
AuthzMiddleware(theserver.Configfield is kept vestigial;cli/serve.gounchanged) (R3)
server.New's body is reduced toServe(ctx, New(deriveCoreConfig(cfg, …)), deriveServerConfig(cfg, healthMon)), both//nolint:gocyclo(New@300,Start@682) are removed with lint clean, and theA1-deferred cleanup is completed (dead authz/annotation blocks + discovery middleware/seam
deleted;
convertAnnotationsand the vestigialAuthzMiddlewarefield retained)pkg/vmcp/cli/serve.gois unchanged; no in-repo caller (nor the externalbrood-boxembedder) requires any changein-memory only and
vmcpconfig.Configis an unchanged input toNewtools/call, resources, prompts, composite workflows, session lifecycle, cross-pod
Redis paths) passes equivalently before/after, and the existing
thv vmcp serveE2E suite passes unchanged
References