Package boundaries¶
The module is five packages with a strict dependency direction, and a test that keeps it so.
flowchart LR
cli --> cobra
cli --> server
cobra --> root
server --> root
grpc --> root
cli --> controls[go/controls, go/transport]
server --> sdk[modelcontextprotocol/go-sdk]
cobra --> spf13[spf13/cobra, pflag]
grpc --> google[google.golang.org/grpc, protobuf, go/authn, go/transit]
The root is framework-free¶
gitlab.com/phpboyscout/go/mcp owns the operation model: definitions, the
immutable registry, owned results and content, failures, form and URL
continuations, and resources. It imports the estate's errors module and a
JSON Schema compiler, and nothing else of consequence. No protocol SDK, no
Cobra, no HTTP, no gRPC, no lifecycle framework. depfootprint_test.go runs
go list -deps and fails if any of those enter the root graph.
The reason is the two consumers the module was designed for. A CLI built on Cobra and an HTTP service built on the estate's transport stack share the operation model and nothing else; the root is what they share, so it must be importable by either without dragging in the other's framework.
The adapters are opt-in¶
server is the protocol adapter over the official Go SDK. It translates the
registry into compact or direct tools, resources, interactions and an Apps
shell, over stdio or Streamable HTTP. It knows nothing about where
operations came from.
cobra is a producer of operations. It snapshots a Cobra tree into
definitions and handlers that run the commands as subprocesses. It knows
nothing about the protocol.
grpc is the other producer. It binds a gRPC service's unary methods, one
call each, into operations that run the generated server method in-process
through the service's own interceptors, with the host's verified identity
established as the transport's auth interceptor would establish it. It too
knows nothing about the protocol, and it owns no listener: the service keeps
its gRPC server exactly as it was (spec 0002).
cli is the convenience that composes the two for a command-line tool: the
mcp command tree with start, stream, tools and the editor helpers. It
is the only package that imports go/controls and go/transport, because
stream is the one place this module hosts a listener of its own, and a
binding should not carry a lifecycle framework it does not use. A tool that
wants to own the composition itself imports cobra and server directly and
never pays for cli.
What a framework adds on top¶
A framework built over this module has conventions of its own: which flags
are the process's rather than the command's, how commands are grouped, where
the publication mode is configured, whose logger to use. Those belong in the
framework, applied once. go-tool-base does this in its pkg/mcp, which passes
WithExposure, WithFlagFilter, WithGroup and the server mode into
cli.Command from the framework's own types (its spec
0201).
The module offers the seams; it does not know the framework exists, and a
gtb subpackage here was rejected for exactly that reason.
Internal packages¶
internal/serving (server lifetime epochs), internal/resourceexec (request
scopes), internal/inputprofile and internal/progressdiag are shared
between the root and server and are not API. internal/fixturetree is the
real Cobra tree the binding and command-tree tests execute as a built binary.
internal/grpcfixture is the notes service the gRPC binding's integration
tests run: a real gRPC server behind the transport's auth interceptor, and
the same implementation bound behind the HTTP chain, with the generated
contract under internal/grpcfixture/notesv1 (source in proto/).
internal/servicefixture is the HTTP service the service-integration tests
run: an application route beside the MCP handler, behind go/transport, a
go/transit chain and go/authn API keys, under a go/controls controller.
It is the second place those modules are imported, and being a fixture rather
than API it does not change the picture above: a consumer that mounts the
handler brings its own.