Documentation
¶
Overview ¶
Package mcpserver exposes Hermod workflows to Model Context Protocol clients: an MCP client (Claude, ChatGPT, an IDE agent) can list the workflows a user may see, read their status, and run the ones their owner has opted in.
The protocol binding lives in transport/http; this package holds the rules: which workflows are exposed, who may run them, and how a run is delivered and answered.
Index ¶
- Constants
- Variables
- func IsExposed(wf storage.Workflow) bool
- type Caller
- type ModelSource
- type ModelTool
- type PredictResult
- type RecommendArgs
- type RecommendResult
- type RunResult
- type ScenarioAnswer
- type ScenarioFor
- type ScenarioPeriod
- type Service
- func (s *Service) List(ctx context.Context, c Caller) ([]WorkflowSummary, error)
- func (s *Service) ModelTools(ctx context.Context, c Caller, vhosts []string) ([]ModelTool, error)
- func (s *Service) PredictTool(ctx context.Context, c Caller, t ModelTool, args json.RawMessage) (PredictResult, error)
- func (s *Service) RecommendTool(ctx context.Context, c Caller, t ModelTool, args json.RawMessage) (RecommendResult, error)
- func (s *Service) Run(ctx context.Context, c Caller, id string, input map[string]any) (RunResult, error)
- func (s *Service) Status(ctx context.Context, c Caller, id string) (WorkflowStatus, error)
- type Store
- type WorkflowStatus
- type WorkflowSummary
Constants ¶
const ExposeTag = "mcp"
ExposeTag is the workflow tag that opts a workflow into MCP. A workflow without it is invisible to MCP clients, whoever asks: exposing an automation to a model is a decision its owner makes, not a default.
It is a tag rather than a new column so that it needs no migration and is edited where tags already are, in the workflow settings.
Variables ¶
var ( // ErrNotFound is returned for a workflow that does not exist, is not // exposed, or is in a vhost the caller cannot see. The three are // deliberately indistinguishable, so the tool is not an oracle for which // workflow ids exist. ErrNotFound = errors.New("no MCP-exposed workflow with that id") // ErrNotAllowed is returned when the caller's role may not run workflows. ErrNotAllowed = errors.New("your role may read workflows but not run them; an Editor or Administrator can") // ErrNotRunnable is returned for an exposed workflow that has no webhook // source, which is the only entry point a run can be delivered through. ErrNotRunnable = errors.New("this workflow has no webhook source, so it cannot be run from MCP") // ErrNotListening is returned when the workflow's webhook source is not // running and could not be woken. ErrNotListening = errors.New("the workflow is not running, so nothing received the input; start it and try again") )
var ErrModelNotFound = errors.New("no MCP-exposed model by that name")
ErrModelNotFound is returned for a model tool the caller may not use.
Functions ¶
Types ¶
type Caller ¶
type Caller struct {
// Name labels the caller in the run's request log.
Name string
// CanRun is whether the caller's role may start a workflow run (Editor or
// Administrator). Reading is open to every role.
CanRun bool
// MayAccess reports whether the caller may see workflows in vhost.
MayAccess func(vhost string) bool
}
Caller is who is asking, as the transport authenticated them.
type ModelSource ¶
type ModelSource interface {
ListModels(ctx context.Context, vhost string) ([]storage.MLModel, error)
Predict(ctx context.Context, vhost, name string, rows []map[string]any) ([]map[string]any, error)
// Recommend calls a recommendation model, on the same path as REST, and
// returns the answer's recommendation_id with it.
Recommend(ctx context.Context, vhost, name string, req RecommendArgs) ([]recommend.Item, string, error)
// RecommendScenario is a scenario call ("for", "period", "rules"), on
// the path of POST .../recommend.
RecommendScenario(ctx context.Context, vhost, name string, req RecommendArgs) (ScenarioAnswer, error)
}
ModelSource is the part of the model registry the model tools use. Predict is the registry's one inference path, so a model called over MCP is held to the same quotas and metrics as every other caller's.
type ModelTool ¶
type ModelTool struct {
// Name is predict_<model>, or predict_<model>__<vhost> when the caller
// can reach two vhosts exposing a model of that name.
Name string
VHost string
Model string
Description string
// InputSchema has one property per feature, typed where the type is
// known, all required; a model that declares no features takes any
// object as its one row.
InputSchema map[string]any
// Recommend marks a recommendation model's tool, recommend_<model>,
// called with RecommendTool rather than PredictTool.
Recommend bool
// contains filtered or unexported fields
}
ModelTool is one model a vhost exposed, as an MCP tool. It is built per request for the caller, like the server it is added to.
type PredictResult ¶
type PredictResult struct {
Model string `json:"model"`
VHost string `json:"vhost"`
Prediction map[string]any `json:"prediction"`
}
PredictResult is what a model tool answers.
type RecommendArgs ¶
type RecommendArgs struct {
User any `json:"user,omitempty"`
Item any `json:"item,omitempty"`
For *ScenarioFor `json:"for,omitempty"`
Territory any `json:"territory,omitempty"`
Period *ScenarioPeriod `json:"period,omitempty"`
WithinDays int `json:"within_days,omitempty"`
K int `json:"k,omitempty"`
Rules []recommend.Rule `json:"rules,omitempty"`
ExcludeSeen *bool `json:"exclude_seen,omitempty"`
Stock map[string]float64 `json:"stock,omitempty"`
}
RecommendArgs are a recommend tool's arguments: "user" for a person's items or "item" for items like it, "k" and, for a user, "exclude_seen"; or a scenario call's "for", "territory", "period", "within_days", "k", "rules", "exclude_seen" and "stock", as POST .../recommend takes them.
type RecommendResult ¶
type RecommendResult struct {
Model string `json:"model"`
VHost string `json:"vhost"`
RecommendationID string `json:"recommendation_id"`
Items []recommend.Item `json:"items,omitempty"`
Results []recommend.Result `json:"results,omitempty"`
}
RecommendResult is what a recommend tool answers: items best first for a user or an item, or a scenario call's results; and the recommendation_id feedback on it is sent with.
type RunResult ¶
type RunResult struct {
// ID is the id of the message the input became.
ID string `json:"id"`
// Status is "dispatched" for a workflow that does not reply, "pending"
// when the wait ran out before the workflow finished, and otherwise what
// the workflow did: delivered, completed, filtered, dead_lettered or
// failed.
Status string `json:"status"`
Error string `json:"error,omitempty"`
// Record is the message as the workflow left it, when it replied.
Record any `json:"record,omitempty"`
}
RunResult is the answer to run_workflow.
type ScenarioAnswer ¶
ScenarioAnswer is a scenario call's answer.
type ScenarioFor ¶
type ScenarioFor struct {
Customer any `json:"customer,omitempty"`
Customers []any `json:"customers,omitempty"`
Product any `json:"product,omitempty"`
Salesman any `json:"salesman,omitempty"`
Territory any `json:"territory,omitempty"`
AllCustomers bool `json:"all_customers,omitempty"`
Segment any `json:"segment,omitempty"`
Size any `json:"size,omitempty"`
Brand any `json:"brand,omitempty"`
Subcategory any `json:"subcategory,omitempty"`
}
ScenarioFor is who or what a scenario call is for: one of customer, customers, product, salesman or all_customers, the territory a rule binds to, the customer category (segment) and size a list of customers keeps, and the brand and subcategory a list of products keeps.
type ScenarioPeriod ¶
ScenarioPeriod is a visit plan's days, YYYY-MM-DD, both included.
type Service ¶
type Service struct {
Store Store
// Wake starts a parked workflow whose source has no listener, and reports
// whether it started one. Optional.
Wake func(ctx context.Context, resourceType, path string) bool
// Models is the model registry the predict_<model> tools call. Optional:
// without it there are no model tools.
Models ModelSource
}
Service answers the MCP tools.
func (*Service) ModelTools ¶
ModelTools returns a tool for every model exposed to MCP in the vhosts given that the caller may use. A vhost the caller may not use is skipped whatever the list says.
func (*Service) PredictTool ¶
func (s *Service) PredictTool(ctx context.Context, c Caller, t ModelTool, args json.RawMessage) (PredictResult, error)
PredictTool runs one call of a model tool: the arguments, bound to the model's features, are its one row.
func (*Service) RecommendTool ¶
func (s *Service) RecommendTool(ctx context.Context, c Caller, t ModelTool, args json.RawMessage) (RecommendResult, error)
RecommendTool runs one call of a recommend tool.
func (*Service) Run ¶
func (s *Service) Run(ctx context.Context, c Caller, id string, input map[string]any) (RunResult, error)
Run delivers input to an exposed workflow's webhook source, as a POST to that webhook would, and answers the way the source is configured to: with the workflow's result when it replies synchronously (pkg/comm/reply), with "dispatched" otherwise.
The webhook's own API key and signature are not asked for. They authenticate anonymous HTTP callers; an MCP caller has already authenticated as a Hermod user whose role and vhost were checked, against a workflow its owner opted in.
type Store ¶
type Store interface {
ListWorkflows(ctx context.Context, filter storage.CommonFilter) ([]storage.Workflow, int, error)
GetWorkflow(ctx context.Context, id string) (storage.Workflow, error)
GetSource(ctx context.Context, id string) (storage.Source, error)
CreateWebhookRequest(ctx context.Context, req storage.WebhookRequest) error
}
Store is the part of storage the service reads and writes.
type WorkflowStatus ¶
type WorkflowStatus struct {
ID string `json:"id"`
Name string `json:"name"`
VHost string `json:"vhost,omitempty"`
Active bool `json:"active"`
Status string `json:"status,omitempty"`
Processed uint64 `json:"processed"`
Errors uint64 `json:"errors"`
Lag uint64 `json:"lag"`
}
WorkflowStatus is the answer to get_workflow_status.
type WorkflowSummary ¶
type WorkflowSummary struct {
ID string `json:"id"`
Name string `json:"name"`
VHost string `json:"vhost,omitempty"`
Active bool `json:"active"`
Status string `json:"status,omitempty"`
// Runnable is whether run_workflow can deliver input to it.
Runnable bool `json:"runnable"`
// RepliesWithResult is whether run_workflow waits for and returns what
// the workflow did, rather than only confirming the input was queued.
RepliesWithResult bool `json:"replies_with_result"`
}
WorkflowSummary is one entry of list_workflows.