mcp

package
v0.0.0-...-294bb90 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 17, 2026 License: AGPL-3.0 Imports: 32 Imported by: 0

Documentation

Overview

coverage-ignore: MCP prompt handlers - tested via integration tests

coverage-ignore: MCP resource handlers - tested via integration tests

coverage-ignore: MCP server - tested via integration tests

coverage-ignore: MCP tool handlers - tested via integration tests

coverage-ignore: MCP tool handlers - tested via integration tests

coverage-ignore: MCP tool handlers - tested via integration tests

coverage-ignore: MCP tool handlers - tested via integration tests

coverage-ignore: MCP tool handlers - tested via integration tests

coverage-ignore: MCP tool handlers - tested via integration tests

coverage-ignore: MCP tool handlers - tested via integration tests

coverage-ignore: file watcher - now managed by workspace

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Deps

type Deps struct {
	// Store is the READ handle for every MCP read surface — tools,
	// resources, prompts, export and analyze alike. It is deliberately
	// the narrow [GraphReader], not `store.Store`: writes go through
	// EntityManager, so MCP never needs the wide composite, and typing
	// the field this way makes an ungated raw read *unavailable* rather
	// than merely discouraged (the TKT-80EWGM "make the mistake
	// impossible" pattern, applied to reads).
	//
	// The wiring site decides what this is. `rela mcp` (stdio) passes the
	// raw store — the filesystem is the trust boundary there, so a gate
	// would defend nothing. A networked wiring passes a
	// visibility-wrapped reader that resolves the ctx principal per call.
	// Either way the handlers are identical; gating is entirely a wiring
	// decision (DEC-ZBI39P).
	Store         GraphReader
	Meta          *metamodel.Metamodel
	Tracer        tracer.Tracer
	Searcher      search.Searcher
	Validator     validator.Validator
	EntityManager EntityWriter
	Config        config.Loader
	LuaWriteDeps  lua.WriteDeps
	LuaCache      *lua.Cache
	Watcher       Watcher
	ProjectRoot   string
}

Deps is the focused bundle of backend services the MCP server needs. Every field is a domain type — the server holds no reference to any composition-root aggregate, so `internal/mcp` does not import `internal/appbuild` (enforced by arch-lint). The wiring site (`internal/cli`) constructs a Deps from focused services and supplies it to NewServer; tests build a Deps literal directly.

ProjectRoot is the absolute project root, used by the lua tools to resolve relative script paths. It is the only piece of the project context MCP consumes — passing the string instead of a `*project.Context` keeps that type from leaking into MCP test stubs.

type EntityWriter

type EntityWriter interface {
	CreateEntity(ctx context.Context, e *entity.Entity, opts entity.CreateOptions) (*entity.CreateResult, error)
	PatchEntity(ctx context.Context, id string, p entity.Patch) (*entity.UpdateResult, error)
	DeleteEntity(ctx context.Context, id string, cascade bool) (*entity.DeleteResult, error)
	RenameEntity(
		ctx context.Context, oldID, newID string, opts entity.RenameOptions,
	) (*entity.RenameResult, error)
	CreateRelation(
		ctx context.Context, from, relType, to string, opts entity.RelationOptions,
	) (*entity.Relation, error)
	DeleteRelation(ctx context.Context, from, relType, to string) error
}

EntityWriter is the write capability MCP requires — the exact set its tool handlers call, declared here at the CALL SITE for the same reason GraphReader is: `entitymanager`'s own interface was a nine-method producer-side type that MCP has no business holding in full (TKT-IVSJV6). The wiring site supplies the project's *entitymanager.Manager, which satisfies this structurally.

Three of the nine are absent because no MCP tool invokes them: UpdateEntity (the entity tool patches — it names the properties it touched rather than holding the whole record, TKT-80EWGM), ValidateCreate (an advisory dry-run the data-entry form path uses), and UpdateRelation.

Every method here still routes through the manager, so ACL, audit and automations apply exactly as they do on any other write path — narrowing the interface removes methods, never gates.

type GraphCounter

type GraphCounter interface {
	CountEntities(ctx context.Context, q store.EntityQuery) (int, error)
	CountRelations(ctx context.Context, q store.RelationQuery) (int, error)
}

GraphCounter is the structural half of GraphReader: type-level tallies that name no individual row. Kept as its own interface so a wiring site can compose a gated row-reader with a raw counter without either pretending to be the other.

type GraphReader

type GraphReader interface {
	GraphCounter

	GetEntity(ctx context.Context, id string) (*entity.Entity, error)
	ListEntities(ctx context.Context, q store.EntityQuery) iter.Seq2[*entity.Entity, error]
	GetRelation(ctx context.Context, from, relType, to string) (*entity.Relation, error)
	ListRelations(ctx context.Context, q store.RelationQuery) iter.Seq2[*entity.Relation, error]
}

GraphReader is the read capability MCP requires of its store — the exact set the handlers call, declared here at the CALL SITE rather than reused from `store.Store`, which is a ten-interface composite (CRUD, attachments, watching, transactions) MCP has no business holding.

It is split deliberately. The three ENTITY/RELATION reads are the gated surface: they return rows, so a wiring may substitute a decorator that hides some. The two COUNTS are GraphCounter, kept separate because a count is structural — it discloses how many rows of a declared type exist, not which ones — and `internal/dataentry` already draws this exact line (`analyzeService.relCounts` is "raw (ungated) on purpose").

`store.Store` satisfies the whole thing structurally, so the stdio wiring passes one unchanged; a visibility decorator satisfies the gated half, which is the point.

type Option

type Option func(*Server)

Option configures a Server at construction.

func WithPrincipal

func WithPrincipal(p principal.Principal) Option

WithPrincipal stamps p onto every tool-handler ctx via a server middleware so downstream audit records are correctly attributed. Applies to every registered tool — including lua_eval / lua_run / any future write tool — because the middleware runs ahead of all handlers (registration-time wrapping, not per-handler opt-in).

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server wraps the MCP server with rela-specific state.

TODO(TKT-N0IKN9): Server started this arc over the 40-method load line; it is under it now (25 methods). The directive stays pinned to the actual count so extraction gains cannot silently erode; ratchet it further as the remaining handler clusters move out.

48 → 49: Server.HTTPHandler (TKT-BDG8U9). It belongs on Server — it exposes THIS server over a second transport, the peer of Server.Serve — and it is the only method the remote endpoint added: the stateless-transport choice lives inside it, and the wiring site holds an http.Handler rather than reaching for the SDK.

49 → 38 (TKT-YUETL7): the type-name helpers moved to typeResolver (they need only the metamodel) and the trace/export tool handlers moved to traceHandler / exportHandler (store + tracer, and store + resolver, respectively). Each is a field below, wired from Deps in NewServer; registerTools points the affected AddTool lines at the field's methods.

38 → 25 (TKT-MGNE5L): the lua tools moved to luaHandler (the sole user of LuaWriteDeps / LuaCache / ProjectRoot), the schema tools + resource reads to schemaResourceHandler (store + metamodel), and the prompt handlers to promptHandler (store + metamodel + tracer + resolver). register* stay on Server and point at the fields' methods. The remaining handlers genuinely span deps — the next TKT-N0IKN9 slice.

25 → 27 (TKT-NU247U): Server.ReloadDeps and the unexported deps accessor. Both are the reload seam itself — ReloadDeps is the capability, and deps() is what makes every handler read the CURRENT snapshot instead of one baked in at construction. The six handler-group accessors that would also have landed here are free functions ([group], [setDeps], [bind]) precisely to keep this number from moving further; ratchet it back down with the remaining handler clusters.

func NewServer

func NewServer(deps Deps, version string, opts ...Option) (*Server, error)

NewServer creates a new MCP server for a rela project. Returns an error if WithPrincipal was not supplied — silently degrading to `unknown/unknown` audit attribution would be an invisible production bug (CLAUDE.md "constructors reject nil required fields"). Tests must pass a non-zero Principal too — use any non-empty `principal.Principal{User: ..., Tool: ...}`.

func (*Server) HTTPHandler

func (s *Server) HTTPHandler() http.Handler

HTTPHandler returns an http.Handler serving this server over Streamable HTTP, for mounting inside an existing router (TKT-BDG8U9). The caller owns authentication, ACL and routing; this method owns only the MCP transport.

**Stateless is required, not a tuning choice.** Protocol revision 2026-07-28 is reachable ONLY on a stateless server in the go-sdk — a session-bearing one negotiates down to 2025-11-25, because the newer revision removes sessions entirely. Consequences the caller inherits:

  • GET and DELETE get 405; only POST carries messages.
  • Server→client requests are rejected (there is no channel to answer on).
  • Notifications reach the client only within an in-flight request.

That last point is why the file watcher is pointless on this transport and a caller should pass a no-op Watcher: `resources/list_changed` has no stateless equivalent. Remote clients re-read on demand and see fresh data, because every read goes to the store.

The returned handler serves THIS server for every request, so per-request state must travel on the ctx rather than be baked in here. That is exactly how identity works: the transport passes the *http.Request ctx through to handlers, and Server.principalMiddleware preserves a principal already stamped there in preference to the construction-time one.

func (*Server) ReloadDeps

func (s *Server) ReloadDeps(d Deps) error

ReloadDeps atomically republishes the server against a freshly built dependency bundle, so subsequent requests observe the new metamodel and the services derived from it.

This is how `rela mcp` picks up a `schema.yaml` edit without a restart (TKT-NU247U): the wiring site rebuilds the metamodel-derived service stack against the SAME store and searcher, then hands the new Deps here. The registered tool/resource/prompt SET is unchanged — only what the handlers read through — so no capability renegotiation is involved.

An invalid bundle is refused and the previous one stays published: a reload driven by a file watcher must never be able to leave a running server without a usable metamodel.

Safe to call while requests are in flight. A request that has already resolved the snapshot completes against it; the next one sees the new bundle.

func (*Server) Serve

func (s *Server) Serve(ctx context.Context) error

Serve starts the MCP server on stdio and blocks until the peer disconnects or ctx is cancelled.

type Watcher

type Watcher interface {
	Start(onChange func()) error
	Stop()
	Pause()
	Resume()
}

Watcher is the narrow file-watching capability MCP requires from its wiring site. Start arms the watcher with an opaque "something changed" callback; Pause / Resume temporarily suppress callbacks while in-process writes happen (e.g. entity rename). The wiring site supplies an adapter that translates these calls into the underlying filesystem watcher.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL