Documentation
¶
Overview ¶
Package mcp exposes kbot's TA format tooling over the Model Context Protocol so that AI assistants (Claude Desktop, Cursor, etc.) can decompile, lint, inspect and extract Total Annihilation assets.
Index ¶
- func NewServer(cfg Config) (*server.MCPServer, func() error, error)
- func ServeHTTP(_ context.Context, s *server.MCPServer, addr string) error
- func ServeStdio(s *server.MCPServer) error
- type Config
- type ContextSpec
- type GameData
- type GameDataOption
- type Hit
- type PathGuard
- type Registry
- func (r *Registry) Add(spec string) (*GameData, error)
- func (r *Registry) AddNamed(name, path string, opts ...GameDataOption) (*GameData, error)
- func (r *Registry) Close() error
- func (r *Registry) Default() *GameData
- func (r *Registry) Get(name string) (*GameData, error)
- func (r *Registry) Names() []string
- type ResolvedDir
- type ResolvedFile
- type Resolver
- func (r *Resolver) PathGuard() *PathGuard
- func (r *Resolver) Registry() *Registry
- func (r *Resolver) ResolveDir(path, gameData string, exts []string) (*ResolvedDir, error)
- func (r *Resolver) ResolveFile(path, gameData string) (*ResolvedFile, error)
- func (r *Resolver) ResolveOutput(path, gameData string) (string, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NewServer ¶
NewServer constructs a configured MCP server with every kbot tool registered. Callers serve it with ServeStdio or ServeHTTP.
The returned cleanup function must be called when the server stops to release any loaded game-data filesystems. It is safe to call multiple times.
func ServeHTTP ¶
ServeHTTP runs the server over the streamable HTTP transport on the given listen address (e.g. ":8765" or "127.0.0.1:8765"). The function blocks until the HTTP server stops.
The context is currently unused by the underlying transport but is reserved for future cancellation support.
func ServeStdio ¶
ServeStdio runs the server over the stdio transport. This is the canonical MCP transport for command-line tools launched by an AI assistant. The function blocks until the connection ends.
IMPORTANT: anything written to os.Stdout while this is running will corrupt the protocol. Callers must keep stdout silent.
Types ¶
type Config ¶
type Config struct {
// Version is reported to MCP clients during the handshake.
Version string
// MountRoots is an allow-list of filesystem roots that tools may
// read from or write to. When empty (and no GameData / Contexts
// entries either) the server runs in permissive mode (all absolute
// paths allowed).
MountRoots []string
// GameData lists game-data folders to expose as named virtual
// filesystems. Each entry is either "PATH" or "NAME=PATH"; the
// first entry becomes the default used when a tool call omits
// game_data. Game-data base paths are added implicitly to the
// guard's mount roots so on-disk paths within them resolve too.
GameData []string
// Contexts lists kbot ctx entries to expose as named virtual
// filesystems. Registered alongside GameData; the Current entry is
// inserted first so it becomes the registry default. Use this
// instead of GameData when you want the model to see the kbot ctx
// metadata (game flavour, version) via the ctx_* tools.
Contexts []ContextSpec
}
Config configures a kbot MCP server.
type ContextSpec ¶
ContextSpec describes a kbot ctx entry to expose through the MCP server. Contexts are richer than --game-data flags: they carry the game flavour and version label that kbot ctx tracks, and one entry may be marked Current so it becomes the registry default.
type GameData ¶
type GameData struct {
Name string
BasePath string
// Game and Version are optional metadata copied from the kbot ctx
// entry the folder was registered from. They are empty when the
// folder was registered via --game-data.
Game string
Version string
// Source records where this registration came from: "flag" for
// --game-data, "context" for a kbot ctx entry, "" for ad-hoc.
Source string
// contains filtered or unexported fields
}
GameData represents a single Total Annihilation (or TA: Kingdoms) install rooted at BasePath. The on-demand VirtualFileSystem layers any HPI/UFO/CCX/GP3 archives found in the tree on top of physical files, mirroring how the game itself sees its content.
A GameData also indexes top-level archive files by basename so that callers can refer to e.g. "totala1.hpi" without knowing the absolute path.
func NewGameData ¶
func NewGameData(name, basePath string, opts ...GameDataOption) (*GameData, error)
NewGameData constructs an unloaded GameData. The VFS is built on first use.
func (*GameData) FindArchives ¶
FindArchives returns absolute paths of archives whose basename matches name (case-insensitive). Returns nil if none.
func (*GameData) FindByBasename ¶
FindByBasename returns every file (and archive) whose final path segment matches name, case-insensitively. Used to answer "where is ARMCOM.bos".
func (*GameData) FindByPattern ¶
FindByPattern returns every file whose virtual path matches the provided shell glob (matched against the full virtual path with filepath.Match semantics, case-insensitive). Archive files matched by basename are also included.
func (*GameData) VFS ¶
func (g *GameData) VFS() (*filesystem.VirtualFileSystem, error)
VFS returns the lazily-loaded virtual filesystem. The first call walks the game-data tree, opens every archive, and starts background MD5 hashing — this can take a few seconds on a full TA install. Subsequent calls return the cached instance.
type GameDataOption ¶
type GameDataOption func(*GameData)
GameDataOption customises a GameData at construction time. Used to attach metadata (game flavour, version label) sourced from a kbot ctx entry.
func WithGame ¶
func WithGame(game string) GameDataOption
WithGame records the game flavour (totala, takingdoms, custom) for the folder. Surfaced through vfs_game_data and ctx_list.
func WithSource ¶
func WithSource(source string) GameDataOption
WithSource records the registration source ("flag" or "context").
func WithVersion ¶
func WithVersion(version string) GameDataOption
WithVersion records the version label for the folder.
type Hit ¶
type Hit struct {
VirtualPath string // virtual path inside the VFS; empty for archive hits
DiskPath string // absolute on-disk path; set for physical files and archives
Source string // archive name, "disk", or "archive"
}
FindByBasename searches the VFS for files whose final path segment matches basename (case-insensitive) and, for archive types, falls back to the archive index. The returned paths are virtual paths inside the VFS, except for archive hits which are absolute on-disk paths (since archives are not VFS entries).
type PathGuard ¶
type PathGuard struct {
// contains filtered or unexported fields
}
PathGuard is an allow-list of filesystem roots that MCP tools are permitted to read from or write to. When constructed with no roots it is permissive — any absolute path resolves through unchanged.
The intent is that `kbot mcp --mount /path/to/ta-content` constrains the model to a single sandbox; running `kbot mcp` with no mounts gives ad-hoc access to the whole filesystem, which is convenient during local development.
func NewPathGuard ¶
NewPathGuard builds a guard from the given roots. Each root is converted to an absolute, symlink-resolved path; relative roots are resolved against the current working directory.
func (*PathGuard) Permissive ¶
Permissive reports whether the guard has no roots configured.
func (*PathGuard) Resolve ¶
Resolve returns the cleaned, absolute form of p and validates that it lies under one of the configured roots. An empty input always errors.
Resolution rules:
- Relative paths are resolved against the first root, or the cwd if no roots are configured.
- Symlinks are followed when the target exists. When the target does not exist (e.g. an output path the caller intends to create) the lexical absolute path is used and the parent directory is used for containment checking.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is the set of named game-data folders the MCP server knows about. The first folder registered is treated as the default and is used when a tool call does not name one explicitly.
func (*Registry) Add ¶
Add registers a game-data folder. Spec may be "PATH" or "NAME=PATH". When the name is omitted it is derived from the basename of PATH.
func (*Registry) AddNamed ¶
func (r *Registry) AddNamed(name, path string, opts ...GameDataOption) (*GameData, error)
AddNamed registers a game-data folder with an explicit name and optional metadata. Used by the kbot ctx loader to attach game/version info.
type ResolvedDir ¶
type ResolvedDir struct {
LocalDir string // populated when the directory is a real on-disk dir
Files []*ResolvedFile
Source string
GameData string
}
ResolvedDir is the result of ResolveDir — a flat list of resolved files that match the requested extensions. Close() releases every backing temp file.
func (*ResolvedDir) Close ¶
func (rd *ResolvedDir) Close() error
Close releases every resolved file's temp backing.
type ResolvedFile ¶
type ResolvedFile struct {
// LocalPath is an absolute on-disk path the caller can hand to any
// existing file-based loader. For VFS hits inside an archive this is
// a temp file; for everything else it is the real file.
LocalPath string
// VirtualPath is the canonical VFS path the hit was found at (empty
// for direct disk hits).
VirtualPath string
// Source describes where the file came from: "disk", an archive name
// like "totala1.hpi", or "archive" for top-level archive files.
Source string
// GameData is the name of the game-data folder this hit resolved
// against, or "" if the path was resolved directly via the guard.
GameData string
// contains filtered or unexported fields
}
ResolvedFile is the result of a successful ResolveFile call. Callers must Close() it to release any temporary file backing a VFS-extracted hit.
func (*ResolvedFile) Close ¶
func (rf *ResolvedFile) Close() error
Close releases any temp file created for this hit. Safe to call multiple times and on the zero value.
type Resolver ¶
type Resolver struct {
// contains filtered or unexported fields
}
Resolver turns the (`path`, `game_data`) arguments that every kbot MCP tool accepts into a concrete on-disk path the underlying format loaders can use.
Resolution order for ResolveFile(path, gameData):
- If path exists on disk and passes the PathGuard, return it as-is.
- If a game-data folder is selected, try to interpret path as virtual: a. join(BasePath, path) — handles top-level files like an archive. b. exact virtual-path lookup in the VFS. c. bare-basename lookup across the VFS and the archive index.
Files sourced from inside an archive are extracted to a temp file; the returned ResolvedFile.Close() removes it.
func NewResolver ¶
NewResolver wires up the path guard and game-data registry.
func (*Resolver) ResolveDir ¶
func (r *Resolver) ResolveDir(path, gameData string, exts []string) (*ResolvedDir, error)
ResolveDir resolves a directory-like path. When path is a real on-disk directory, the result lists every file under it with a matching extension. When path is a VFS directory, each entry is extracted to a temp file. The exts argument is a list of lowercase extensions including the leading dot, e.g. []string{".cob"}.
func (*Resolver) ResolveFile ¶
func (r *Resolver) ResolveFile(path, gameData string) (*ResolvedFile, error)
ResolveFile turns (path, gameData) into a usable ResolvedFile.
func (*Resolver) ResolveOutput ¶
ResolveOutput resolves a path that the caller intends to write to. It always returns a real on-disk path that lies inside a mount root. When a game-data folder is selected, relative paths are anchored to its base.