Documentation
¶
Overview ¶
Package lmchatkit is a self-contained chat UI + backend protocol that mounts into any HTTP server. The host implements Host to provide an LLM completion stream and (optionally) MCP tools, prompts and resources; lmchatkit owns the frontend bundle, the chat session protocol, persona loading and slash-command loading.
Routes are mounted under a configurable prefix (typically /chat) via Server.Mount. Auth is the host's responsibility — pass an [AuthMiddleware] in Config and it wraps every lmchatkit handler.
Index ¶
- Constants
- Variables
- func OpenAIChatRequest(req CompleteRequest) map[string]interface{}
- func TranslateOpenAIStream(ctx context.Context, body io.Reader, events chan<- Event) error
- type AllToolsHost
- type CommandSource
- type CompleteRequest
- type Config
- type ConversationSummary
- type Event
- type EventBroadcaster
- type EventType
- type FinishReason
- type HistoryStore
- type Host
- type Message
- type Model
- type Persona
- type PersonaSource
- type Prompt
- type PromptArgument
- type PromptMessage
- type PromptResult
- type Resource
- type ResourceResult
- type Role
- type Server
- type ServerEvent
- type SlashCommand
- type SourceScopedHost
- type SourcedToolProvider
- type StandardHost
- func (h *StandardHost) AugmentSystemPrompt(ctx context.Context, current string) string
- func (h *StandardHost) CallTool(ctx context.Context, name string, arguments json.RawMessage) (ToolResult, error)
- func (h *StandardHost) Complete(ctx context.Context, req CompleteRequest, events chan<- Event) error
- func (h *StandardHost) GetPrompt(ctx context.Context, name string, args map[string]string) (PromptResult, error)
- func (h *StandardHost) ListAllTools(ctx context.Context) ([]Tool, error)
- func (h *StandardHost) ListPrompts(ctx context.Context) ([]Prompt, error)
- func (h *StandardHost) ListResources(ctx context.Context) ([]Resource, error)
- func (h *StandardHost) ListTools(ctx context.Context) ([]Tool, error)
- func (h *StandardHost) Models(ctx context.Context) ([]Model, error)
- func (h *StandardHost) ReadResource(ctx context.Context, uri string) (ResourceResult, error)
- func (h *StandardHost) ReadResourceFromSource(ctx context.Context, source, uri string) (ResourceResult, error)
- func (h *StandardHost) ToolSource(ctx context.Context, name string) (string, bool)
- type StaticCommands
- type StaticPersonas
- type StoredConversation
- type SystemPromptAugmenter
- type Tool
- type ToolCall
- type ToolResult
- type Usage
Constants ¶
const SkillToolName = "lmchatkit__get_skill"
SkillToolName is the name of the virtual skill-retrieval tool. The lmchatkit__ prefix prevents collisions with local scriptling tools (no prefix) and remote MCP tools (namespace__ prefix).
Variables ¶
var AssetFS = assetsFS
AssetFS exposes the bundled chat.js so hosts can serve it from their own asset pipeline if they prefer. Server.Mount already wires it up at {prefix}/assets/chat.js.
var ErrMissingHost = errString("lmchatkit: Config.Host is required")
ErrMissingHost is returned by New when Config.Host is nil.
var SkillTool = Tool{ Name: SkillToolName, Description: "Retrieve a skill's detailed instructions by URI. Pass the skill URI (e.g. 'skill://golang/SKILL.md'). Returns the skill content as text.", InputSchema: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "uri": map[string]interface{}{ "type": "string", "description": "The skill URI to retrieve (e.g. skill://golang/SKILL.md)", }, }, "required": []string{"uri"}, }, }
SkillTool is the virtual tool definition appended to the tool list when the host has skill:// resources. It lets the LLM pull in skill instructions on demand — the model sees skill descriptions in the system prompt, then calls this tool to retrieve the full content.
Functions ¶
func OpenAIChatRequest ¶
func OpenAIChatRequest(req CompleteRequest) map[string]interface{}
use this instead of hand-rolling the conversion. The returned map is ready to json.Marshal and POST.
func TranslateOpenAIStream ¶
TranslateOpenAIStream reads an OpenAI-compatible SSE stream (the response body from POST /v1/chat/completions with stream:true) and emits lmchatkit events onto the events channel. Handles:
- delta.content → EventDelta
- delta.reasoning_content / delta.reasoning → EventReasoning
- delta.tool_calls (fragmented by index) → accumulated and flushed as EventToolCall before the terminal event
- finish_reason → mapped to FinishStop / FinishToolCalls / FinishLength
- data: [DONE] → stream end
Hosts that loopback to their own OpenAI endpoint can call this directly instead of reimplementing the SSE parsing. The events channel must be buffered (lmchatkit's chat handler uses a 32-slot buffer).
Types ¶
type AllToolsHost ¶ added in v0.10.2
AllToolsHost is an optional interface a Host can implement to list tools that ListTools omits because they're discoverable-only (e.g. an MCP server's search-only tools, reached by the model through a meta-tool like execute_tool rather than listed directly, to save context). An app view's own tool calls (POST /api/tools/call, Source: "app") name such a tool directly — chat.js resolves it to its real, namespaced name itself — so the visibility check in toolVisibilityAllows needs to find it, even though it would never appear in the model-facing list. A Host whose tools are never discoverable-only need not implement this; lmchatkit falls back to ListTools, same as before this existed.
type CommandSource ¶
type CommandSource interface {
Commands(ctx context.Context) ([]SlashCommand, error)
}
CommandSource is the backend behind /api/commands. Same contract as PersonaSource: a file-watching default exists, hosts with a database (e.g. per-user commands in knot) implement their own.
type CompleteRequest ¶
type CompleteRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
Tools []Tool `json:"tools,omitempty"`
Params map[string]interface{} `json:"params,omitempty"`
}
CompleteRequest is the host-facing request to stream a chat completion. Messages is the full conversation including any prior tool results. Tools is the subset of tools the user has enabled for this chat (may be empty). Params is model parameters merged from persona + per-request overrides (temperature, max_tokens, etc.); the host passes it through to the underlying LLM API as it sees fit.
type Config ¶
type Config struct {
// Prefix is the URL prefix lmchatkit mounts under, e.g. "/chat". Must be
// non-empty. Routes are registered as Prefix+"/api/..." and
// Prefix+"/assets/...". The host owns the Prefix page route itself —
// see lmchatkit/examples/chat.html.
Prefix string
// PersonasDir is the directory scanned for persona TOML files. Empty
// means no persona files — only the built-in Default persona is offered.
//
// Ignored when PersonaSource is set; use whichever fits the host. File
// watching only applies to this dir, not to a custom source.
PersonasDir string
// CommandsDir is the directory scanned for slash-command markdown files.
// Empty disables slash commands entirely. Ignored when CommandSource is
// set.
CommandsDir string
// PersonaSource overrides PersonasDir. Use this when personas come from
// somewhere other than the filesystem — typically a database, or a single
// system-defined persona via [StaticPersonas].
PersonaSource PersonaSource
// CommandSource overrides CommandsDir. Same contract as PersonaSource.
CommandSource CommandSource
// Host is the contract between lmchatkit and the embedding application.
// Must be non-nil.
Host Host
// AuthMiddleware wraps every lmchatkit HTTP handler. It is the host's
// responsibility to enforce authentication, sessions, rate limiting, etc.
// nil means no auth (rare; only appropriate for fully internal hosts).
AuthMiddleware func(http.Handler) http.Handler
// History persists chat conversations server-side. nil = browser
// sessionStorage (no persistence across browser restarts, no
// cross-tab sync). When non-nil, conversation CRUD endpoints are
// mounted and the browser switches to server-side mode automatically.
History HistoryStore
// Events broadcasts changes to connected SSE clients for cross-tab
// sync and push notifications (tools/prompts/resources changed).
// nil = no SSE (browser falls back to polling on chat completion).
Events *EventBroadcaster
// FileUpload enables the file-upload button in the chat composer.
// When true, users can attach images (and other files) which are
// sent to the LLM as base64-encoded content blocks. Default false —
// hosts opt in explicitly.
FileUpload bool
// MaxUploadBytes caps the size of a single uploaded file. Zero uses
// the default (10 MB).
MaxUploadBytes int64
}
Config configures a Server at mount time.
type ConversationSummary ¶
type ConversationSummary struct {
ID string `json:"id"`
Title string `json:"title"`
PersonaID string `json:"persona_id"`
Model string `json:"model"`
Params map[string]interface{} `json:"params,omitempty"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
ConversationSummary is the lightweight sidebar entry — no messages.
type Event ¶
type Event struct {
Type EventType `json:"type"`
Delta string `json:"delta,omitempty"`
Reasoning string `json:"reasoning,omitempty"` // carries EventReasoning fragments
ToolCall *ToolCall `json:"tool_call,omitempty"`
FinishReason FinishReason `json:"finish_reason,omitempty"`
Usage *Usage `json:"usage,omitempty"`
Error string `json:"error,omitempty"`
}
Event is one streamed server-sent event in the chat protocol. Type determines which fields are meaningful.
type EventBroadcaster ¶
type EventBroadcaster struct {
// contains filtered or unexported fields
}
EventBroadcaster fans out events to all connected SSE subscribers. Used for cross-tab sync (conversation saved/deleted from another tab) and push notifications (tools/prompts/resources changed by the scriptling watcher).
func NewEventBroadcaster ¶
func NewEventBroadcaster() *EventBroadcaster
NewEventBroadcaster creates a ready-to-use broadcaster.
func (*EventBroadcaster) Broadcast ¶
func (b *EventBroadcaster) Broadcast(event ServerEvent)
Broadcast sends an event to all subscribers. Non-blocking — if a subscriber's buffer is full, the event is dropped.
func (*EventBroadcaster) Subscribe ¶
func (b *EventBroadcaster) Subscribe() chan ServerEvent
Subscribe returns a channel that receives events. The caller must Unsubscribe when done (e.g. when the SSE connection closes).
func (*EventBroadcaster) Unsubscribe ¶
func (b *EventBroadcaster) Unsubscribe(ch chan ServerEvent)
Unsubscribe removes a subscriber and closes its channel.
type EventType ¶
type EventType string
EventType identifies one SSE event in the chat stream protocol.
const ( EventDelta EventType = "delta" // partial assistant text EventReasoning EventType = "reasoning" // partial reasoning/thinking text (separate from visible content) EventToolCall EventType = "tool_call" // model requested a tool call; frontend must confirm + execute then resubmit EventDone EventType = "done" // stream complete; carry usage/finish_reason EventError EventType = "error" // stream failed )
type FinishReason ¶
type FinishReason string
FinishReason explains why the stream ended.
const ( FinishStop FinishReason = "stop" FinishToolCalls FinishReason = "tool_calls" FinishLength FinishReason = "length" )
type HistoryStore ¶
type HistoryStore interface {
List(ctx context.Context) ([]ConversationSummary, error)
Get(ctx context.Context, id string) (*StoredConversation, error)
Save(ctx context.Context, conv *StoredConversation) error
Delete(ctx context.Context, id string) error
}
HistoryStore persists chat conversations server-side. If nil on Config, the browser uses sessionStorage (no server-side persistence, no cross-tab sync). Hosts implement this with KV stores, databases, files — whatever fits. For per-user stores (knot), the implementation extracts the user from the request context; lmchatkit passes ctx through unchanged, so the host owns both the identity context key (set in its auth middleware) and its interpretation here.
type Host ¶
type Host interface {
// Models returns the models the chat user may select from. The list may
// be empty if the host has no concept of model picker (rare).
Models(ctx context.Context) ([]Model, error)
// Complete streams a chat completion for the given request. Implementations
// push events onto events (never block on a full channel — lmchatkit's
// channel is buffered) and return when the stream is finished. If the
// model emitted tool calls, emit one [EventToolCall] per call and return
// with FinishReason == FinishToolCalls — the frontend will execute the
// tools via CallTool and resubmit the conversation.
Complete(ctx context.Context, req CompleteRequest, events chan<- Event) error
// ListTools returns the MCP tools available to chat. May return nil/empty
// if no tools are configured.
ListTools(ctx context.Context) ([]Tool, error)
// CallTool invokes a tool by name with raw-JSON arguments. The arguments
// are exactly what the model produced (after the user confirmed), so the
// host is responsible for any validation.
CallTool(ctx context.Context, name string, arguments json.RawMessage) (ToolResult, error)
// ListPrompts / GetPrompt expose MCP prompts. GetPrompt renders the prompt
// with the given arguments.
ListPrompts(ctx context.Context) ([]Prompt, error)
GetPrompt(ctx context.Context, name string, args map[string]string) (PromptResult, error)
// ListResources / ReadResource expose MCP resources. ReadResource takes a
// concrete URI (the caller is responsible for expanding templates).
ListResources(ctx context.Context) ([]Resource, error)
ReadResource(ctx context.Context, uri string) (ResourceResult, error)
}
Host is the contract between lmchatkit and its embedding application. Every method takes a context so hosts can enforce timeouts / cancellation. Any method may return an error; lmchatkit surfaces it to the user.
All methods must be safe for concurrent use: lmchatkit is stateless and a single Server may serve many simultaneous chat sessions across many users.
type Message ¶
type Message struct {
ID string `json:"id,omitempty"`
Role Role `json:"role"`
Content any `json:"content"`
Thinking string `json:"thinking,omitempty"`
Info map[string]interface{} `json:"info,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
ToolName string `json:"tool_name,omitempty"`
IsError bool `json:"is_error,omitempty"`
StructuredContent any `json:"structured_content,omitempty"`
UI *mcplib.UIToolMeta `json:"ui,omitempty"`
}
Message is one turn in a conversation. Content is normally a string, but when a message carries resource attachments the frontend may send it as an OpenAI-compatible content array. The Go type uses interface{} to accept both shapes and pass them through to the host's Complete implementation verbatim.
ID, Thinking, and Info are UI-only fields that the frontend sets on messages for display purposes (Alpine x-for keys, reasoning disclosure, info cards). They are persisted in conversation history so the UI can reconstruct the exact display on reload, but the chat handler strips them before passing messages to Host.Complete — the LLM never sees them.
Content does NOT use omitempty — empty string content must be preserved when saving/loading conversations from the history store. With omitempty, an empty assistant message would lose its "content" key entirely, and on reload the browser would see `undefined` instead of `""`.
IsError, StructuredContent, and UI are meaningful only on a RoleTool message (the message recording one tool call's result). They mirror the same-named fields on ToolResult, persisted here so the frontend can reconstruct a tool call's full display — including re-mounting an MCP Apps view — from a reloaded conversation, without re-invoking the tool itself (which may not be idempotent; re-running it could show a different result than what actually happened, or double a side effect).
type Model ¶
type Model struct {
ID string `json:"id"`
Label string `json:"label,omitempty"` // human-friendly label; falls back to ID
Provider string `json:"provider,omitempty"` // optional source tag for the UI
}
Model describes one model the chat user can pick from.
type Persona ¶
type Persona struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
SystemPrompt string `json:"system_prompt,omitempty"`
DefaultModel string `json:"default_model,omitempty"`
Params map[string]interface{} `json:"params,omitempty"`
// SourceFile is absolute path the persona was loaded from. Empty for the
// built-in "Default" persona.
SourceFile string `json:"-"`
}
Persona is one chat persona loaded from a TOML file in PersonasDir. SystemPrompt, DefaultModel and Params are all optional; an empty persona is valid and reduces to a no-op preset.
type PersonaSource ¶
PersonaSource is the backend behind /api/personas. The default implementation reads TOML files from a watched directory; hosts with a database (or a single system-defined persona) supply their own.
Personas is called on every /api/personas request so a DB-backed source always reflects current state without needing a watcher.
type Prompt ¶
type Prompt struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Arguments []PromptArgument `json:"arguments,omitempty"`
}
Prompt is one MCP prompt exposed to the chat.
type PromptArgument ¶
type PromptArgument struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Required bool `json:"required,omitempty"`
}
PromptArgument is one named argument a prompt accepts.
type PromptMessage ¶
PromptMessage is one message produced by rendering a prompt.
type PromptResult ¶
type PromptResult struct {
Description string `json:"description,omitempty"`
Messages []PromptMessage `json:"messages"`
}
PromptResult is the rendered output of GetPrompt.
type Resource ¶
type Resource struct {
URI string `json:"uri"`
Template bool `json:"template,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
MimeType string `json:"mime_type,omitempty"`
}
Resource is one MCP resource (static or templated) exposed to the chat. When Template is true, URI contains {var} placeholders the user must fill.
type ResourceResult ¶
type ResourceResult struct {
URI string `json:"uri"`
Text string `json:"text,omitempty"`
Blob string `json:"blob,omitempty"`
MimeType string `json:"mime_type,omitempty"`
UI *mcplib.UIResourceMeta `json:"ui,omitempty"`
}
ResourceResult is the content of a read resource. Text is used for textual content; Blob carries base64-encoded binary content.
UI carries the resource's own _meta.ui (per the MCP Apps extension, SEP-1865) — CSP/permissions/domain/prefersBorder hints a compliant host applies before rendering the content in a sandboxed iframe. Note its JSON field names (e.g. "resourceUri", "connectDomains") are camelCase, unlike the rest of this package's snake_case convention — this reuses the mcp library's own wire type directly rather than duplicating it.
type Role ¶
type Role string
Role identifies the speaker of a chat message. Mirrors OpenAI's role names so hosts that proxy to OpenAI-compatible APIs can pass messages through verbatim.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is a self-contained chat UI + backend. Build one with New and mount it into any *http.ServeMux via Server.Mount.
func New ¶
New builds a Server, eagerly loading personas and slash commands from the configured sources so the first request is fast.
func (*Server) Close ¶
func (s *Server) Close()
Close releases watchers and goroutines owned by this Server. Sources the host supplied via Config.PersonaSource / Config.CommandSource are NOT closed — the host owns their lifecycle. Safe to call multiple times.
func (*Server) Mount ¶
Mount registers lmchatkit's API and asset routes on mux under the configured prefix. It deliberately does NOT register a page route — the host owns the chat page template (so it lives in the host's source tree where Tailwind can scan it). Hosts render /chat themselves using the example template shipped at lmchatkit/examples/chat.html as a starting point.
Routes registered:
- {prefix}/api/... — chat/tools/prompts/resources endpoints
- {prefix}/assets/... — embedded chat.js, chat.css, markdown.js
The host's AuthMiddleware (if set) wraps every handler.
type ServerEvent ¶
type ServerEvent struct {
Type string `json:"type"` // conversation_saved, conversation_deleted, conversation_renamed, prompts_changed, resources_changed
ID string `json:"id,omitempty"`
}
ServerEvent is one event pushed to SSE subscribers.
type SlashCommand ¶
type SlashCommand struct {
ID string `json:"id"` // stable hash of name
Name string `json:"name"` // canonical name (filename stem), no leading slash
Description string `json:"description,omitempty"` // from frontmatter, shown in the selection menu
ArgumentHint string `json:"argument_hint,omitempty"` // from frontmatter, e.g. "<github-handle>"
AllowedTools string `json:"allowed_tools,omitempty"` // from frontmatter, comma-separated tool names to auto-allow
Body string `json:"body"` // raw markdown (frontmatter stripped); $ARGUMENTS replaced when rendered
Source string `json:"source"` // short source hint for debugging
}
SlashCommand is one user-invokable slash command loaded from a markdown file in CommandsDir. The command name is the filename stem (e.g. help.md -> /help). The Body is the raw markdown with optional $ARGUMENTS placeholder substituted at render time. Description and ArgumentHint come from YAML frontmatter at the top of the file (Claude-style):
--- description: Open a PR and tag a reviewer argument-hint: <github-handle> ---
Review the changes by $ARGUMENTS and suggest...
type SourceScopedHost ¶ added in v0.10.0
type SourceScopedHost interface {
// ToolSource returns an opaque, stable identifier for the MCP server
// the named tool belongs to ("" for a tool the host serves natively,
// as opposed to a federated remote), and whether name is a recognized
// tool at all. Implementations must fail closed: an error resolving
// the tool must return ok == false, never a guessed source.
ToolSource(ctx context.Context, name string) (source string, ok bool)
// ReadResourceFromSource reads uri restricted to the server identified
// by source (as returned by ToolSource): "" reads only natively-served
// resources, a non-empty source reads only from that one remote server
// — never falling through to any other server the way an unscoped
// ReadResource might.
ReadResourceFromSource(ctx context.Context, source, uri string) (ResourceResult, error)
}
SourceScopedHost is an optional interface a Host can implement to expose which underlying MCP server each tool/resource belongs to. lmchatkit's /api/tools/call and /api/resources/read handlers use it, when present, to enforce that a mounted MCP Apps view may only reach a tool or resource belonging to the same server as the tool that mounted it — visibility (_meta.ui.visibility) alone can't provide this: two different federated servers connected without namespace prefixes can expose same-shaped "app"-visible tools/resources under names that collide or simply aren't distinguishable by name alone.
A Host that aggregates only a single, non-federated MCP server (no cross-server ambiguity is possible) need not implement this — lmchatkit falls back to allowing the call/read unchecked (beyond the existing visibility check) when the Host doesn't implement it.
type SourcedToolProvider ¶ added in v0.10.2
type SourcedToolProvider interface {
mcplib.ToolProvider
// ToolSource returns an opaque, stable identifier for the server that
// owns name (never "" — that's reserved for natively-served tools per
// SourceScopedHost's contract), or ok=false if this provider doesn't
// recognize name. Must fail closed: an error resolving the tool returns
// ok == false, never a guessed source.
ToolSource(ctx context.Context, name string) (source string, ok bool)
// ReadResourceFromSource reads uri from the specific server identified
// by source (as returned by this same provider's ToolSource), or returns
// an error if source isn't one this provider owns.
ReadResourceFromSource(ctx context.Context, source, uri string) (*mcplib.ResourceResponse, error)
}
SourcedToolProvider is an optional interface a request-scoped mcplib.ToolProvider (attached via mcplib.WithToolProviders) can implement to identify which underlying MCP server one of its tools came from, and to read a resource restricted to that one server. Without this, StandardHost's SourceScopedHost support (ToolSource/ReadResourceFromSource) only sees servers registered directly on the *mcp.Server via RegisterRemoteServer* — mcp.Server's own ToolSource/ReadResourceFrom have no visibility into context-attached providers at all. A host with per-user/per-request federated servers (e.g. each user's own configured remote MCP servers) needs this to support MCP Apps' cross-server ownership enforcement for those providers; see SourceScopedHost for why that enforcement exists.
type StandardHost ¶
type StandardHost struct {
// ModelsFunc returns the models available for selection. Called on each
// /api/models request so the list is always current. Required.
ModelsFunc func(ctx context.Context) ([]Model, error)
// OpenAIBaseURL is where /v1/chat/completions lives. For a self-loopback
// (llmrouter), this is "http://127.0.0.1:<port>". For an external LLM
// proxy, it's that proxy's URL. Required unless ChatCompletionsURL is set.
OpenAIBaseURL string
// ChatCompletionsURL, when set, is the full chat completions URL to post
// to instead of OpenAIBaseURL + "/v1/chat/completions" — for
// OpenAI-compatible endpoints at another path (e.g. Gemini's
// .../v1beta/openai/chat/completions).
ChatCompletionsURL string
// OpenAIToken is the bearer token sent with the completion request.
// For a self-loopback this is the server's API token; for a user-scoped
// proxy it's the user's token. May be empty if the endpoint doesn't
// require auth.
OpenAIToken string
// MCPServer returns the *mcp.Server to use for tool/prompt/resource
// calls. For single-user hosts (llmrouter), return the same server
// every time. For per-user hosts (knot), extract the user from the
// context and return their server. Return nil to disable MCP for that
// request.
MCPServer func(ctx context.Context) *mcplib.Server
// SystemPromptAugmenter is called on every /api/chat request with the
// current system prompt (from the persona). The returned string
// replaces the system prompt sent to the LLM — the stored conversation
// is not modified. Use this to inject dynamic context like available
// skills. Optional: nil = pass through unchanged.
SystemPromptAugmenter func(ctx context.Context, current string) string
// HTTPClient overrides the default client. nil = use http.DefaultClient
// with no timeout (streaming needs no overall timeout).
HTTPClient *http.Client
}
StandardHost is a ready-to-use Host implementation for apps that:
- Talk to an OpenAI-compatible /v1/chat/completions endpoint
- Expose MCP tools/prompts/resources via an *mcp.Server
Hosts with different needs (non-OpenAI LLM, custom tool backends, per-user MCP servers) construct a StandardHost with the appropriate functions. Hosts with fundamentally different architectures implement Host themselves.
Auth is NOT handled here — the host wraps the lmchatkit routes with its own middleware via Config.AuthMiddleware, exactly like it protects any other route. StandardHost never sees tokens, sessions, or passwords.
func (*StandardHost) AugmentSystemPrompt ¶
func (h *StandardHost) AugmentSystemPrompt(ctx context.Context, current string) string
AugmentSystemPrompt satisfies SystemPromptAugmenter. If the function field is nil, the prompt passes through unchanged.
func (*StandardHost) CallTool ¶
func (h *StandardHost) CallTool(ctx context.Context, name string, arguments json.RawMessage) (ToolResult, error)
func (*StandardHost) Complete ¶
func (h *StandardHost) Complete(ctx context.Context, req CompleteRequest, events chan<- Event) error
func (*StandardHost) GetPrompt ¶
func (h *StandardHost) GetPrompt(ctx context.Context, name string, args map[string]string) (PromptResult, error)
func (*StandardHost) ListAllTools ¶ added in v0.10.2
func (h *StandardHost) ListAllTools(ctx context.Context) ([]Tool, error)
ListAllTools satisfies AllToolsHost. It additionally includes discoverable (search-only) tools that ListTools omits from the model-facing list — see mcplib.WithShowAllTools — so toolVisibilityAllows can find a tool an app view calls directly by its real name.
func (*StandardHost) ListPrompts ¶
func (h *StandardHost) ListPrompts(ctx context.Context) ([]Prompt, error)
func (*StandardHost) ListResources ¶
func (h *StandardHost) ListResources(ctx context.Context) ([]Resource, error)
func (*StandardHost) ListTools ¶
func (h *StandardHost) ListTools(ctx context.Context) ([]Tool, error)
func (*StandardHost) ReadResource ¶
func (h *StandardHost) ReadResource(ctx context.Context, uri string) (ResourceResult, error)
func (*StandardHost) ReadResourceFromSource ¶ added in v0.10.0
func (h *StandardHost) ReadResourceFromSource(ctx context.Context, source, uri string) (ResourceResult, error)
ReadResourceFromSource satisfies SourceScopedHost. It tries any context-attached [SourcedToolProvider]s first (they're the only ones who can resolve a source identifier they themselves handed out via ToolSource), falling back to the underlying *mcp.Server's own ReadResourceFrom for native/statically-registered sources.
func (*StandardHost) ToolSource ¶ added in v0.10.0
ToolSource satisfies SourceScopedHost. It checks the underlying *mcp.Server's own ToolSource first (native tools and statically-registered remote servers), then any context-attached [SourcedToolProvider]s.
type StaticCommands ¶
type StaticCommands []SlashCommand
StaticCommands is a CommandSource backed by a fixed slice.
func (StaticCommands) Commands ¶
func (s StaticCommands) Commands(ctx context.Context) ([]SlashCommand, error)
type StaticPersonas ¶
type StaticPersonas []Persona
StaticPersonas is a PersonaSource backed by a fixed slice. Useful for single-tenant hosts that have one system-defined persona (e.g. knot).
type StoredConversation ¶
type StoredConversation struct {
ConversationSummary
Messages []Message `json:"messages"`
EnabledTools []string `json:"enabled_tools,omitempty"`
}
StoredConversation is a full conversation including messages.
type SystemPromptAugmenter ¶
type SystemPromptAugmenter interface {
AugmentSystemPrompt(ctx context.Context, current string) string
}
SystemPromptAugmenter is an optional interface a Host can implement to dynamically augment the system prompt on every chat request. The chat handler calls AugmentSystemPrompt with the current system prompt content (from the persona) and uses the returned string when forwarding to the LLM. The stored conversation is not modified — the augmentation is transient, recomputed on each request so it always reflects current state (e.g. skills added/removed at runtime).
type Tool ¶
type Tool struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
InputSchema map[string]interface{} `json:"input_schema,omitempty"`
Visibility []string `json:"-"`
}
Tool describes one MCP tool exposed to the chat. InputSchema is the JSON schema for arguments (as exposed by MCP tools/list); the frontend uses it to render argument hints when confirming a tool call.
Visibility is the tool's _meta.ui.visibility (per the MCP Apps extension, SEP-1865): who may call it — "model" (the agent), "app" (a mounted view), or both. Nil/empty means both, per the spec's default. ListTools returns every tool regardless of visibility (callers needing the model-facing subset use FilterToolsForModel); it's carried here so the /api/tools/call handler can enforce it without a second round trip to the MCP server.
func FilterToolsForModel ¶ added in v0.10.0
FilterToolsForModel returns the subset of tools visible to the model, per the MCP Apps extension's tools/list MUST: a tool whose _meta.ui.visibility doesn't include "model" (e.g. an app-only action tool like a form submission) must never reach the agent's own tool list, even though it's still a real, callable tool for a mounted view. Hosts building the LLM-facing tool array from Host.ListTools's result MUST filter through this first — StandardHost's ListTools deliberately returns every tool unfiltered, since the same list also backs the /api/tools/call visibility check for app-initiated calls, which needs the app-only tools.
type ToolCall ¶
type ToolCall struct {
ID string `json:"id"`
Name string `json:"name"`
Arguments json.RawMessage `json:"arguments"`
}
ToolCall is a single tool invocation requested by the model. Arguments is the raw JSON arguments string (the host parses it according to the tool's input schema).
type ToolResult ¶
type ToolResult struct {
Content string `json:"content"`
IsError bool `json:"is_error,omitempty"`
StructuredContent any `json:"structured_content,omitempty"`
UI *mcplib.UIToolMeta `json:"ui,omitempty"`
}
ToolResult is the outcome of a tool call. Content is the model-facing text (typically the MCP tool response). isError flags the result as an error so the model knows not to treat Content as a successful payload.
StructuredContent and UI are frontend-only extras, not sent to the model: StructuredContent is the tool response's raw structuredContent (if any), which Content's text-only flattening otherwise discards entirely. UI is the called tool's own _meta.ui (per the MCP Apps extension, SEP-1865), when the underlying MCP server declared one — the frontend uses it to decide whether to render an interactive view for this result.