Documentation
¶
Overview ¶
Package mcp exposes osctrl's read surface to Model Context Protocol clients, so an LLM agent can inspect a fleet — nodes, environments, query results, and the osquery schema — without being handed the raw REST API.
The tool set here is deliberately read-only. Nothing in this package schedules a query, mutates a node, or touches user or service configuration; write tools are gated behind a separate opt-in and are not part of this package yet.
Authorization ¶
This package performs no authorization of its own. Every call goes through a Backend, and the only Backend today is *apiclient.OsctrlAPI talking to osctrl-api with a user's Bearer token — so osctrl-api's existing per-environment RBAC is what actually constrains the agent. A token that cannot see an environment gets the same empty results an operator with that token would get. Give the MCP server a service user scoped to exactly what the agent should read.
Untrusted content ¶
Hostnames, process names, file paths, and query result rows originate on monitored endpoints, which are precisely the machines an attacker might control. Everything this package returns is data, never instructions. Tool descriptions say so explicitly, because that text is what the model actually reads.
Index ¶
Constants ¶
const ServerName = "osctrl"
ServerName is what MCP clients see in the initialize handshake.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Backend ¶
type Backend interface {
GetEnvironments() ([]environments.TLSEnvironment, error)
GetStats() (apiclient.StatsResponse, error)
GetNodes(env, target string) ([]nodes.OsqueryNode, error)
GetNode(env, identifier string) (nodes.OsqueryNode, error)
GetNodePosture(env, uuid string) ([]posture.NodePosture, error)
GetNodePostureScore(env, uuid string) (posture.PostureScore, error)
GetOsqueryTables() ([]types.OsqueryTable, error)
GetQueries(target, env string) ([]queries.DistributedQuery, error)
GetQueryResults(env, name string, page, pageSize int) (types.QueryResultsResponse, error)
GetSavedQueries(env string) ([]types.SavedQueryView, error)
}
Backend is the slice of osctrl the MCP tools need.
Narrow on purpose. *apiclient.OsctrlAPI satisfies it as-is, and keeping the surface this small means the tools can be tested against a fake without an HTTP server — and lets a future in-process implementation (MCP mounted inside osctrl-api) skip the network hop without touching any tool code.
type EnvironmentStats ¶
type EnvironmentStats struct {
Name string `json:"name"`
TotalNodes int64 `json:"total_nodes"`
ActiveNodes int64 `json:"active_nodes"`
InactiveNodes int64 `json:"inactive_nodes"`
Linux int64 `json:"linux"`
Darwin int64 `json:"darwin"`
Windows int64 `json:"windows"`
Other int64 `json:"other"`
}
EnvironmentStats is the per-environment row of fleet_stats.
type EnvironmentSummary ¶
type EnvironmentSummary struct {
UUID string `json:"uuid"`
Name string `json:"name"`
Hostname string `json:"hostname"`
Type string `json:"type"`
}
EnvironmentSummary is the projection of environments.TLSEnvironment that tools return.
The projection is a security boundary, not a formatting nicety: TLSEnvironment carries Secret, EnrollSecretPath, RemoveSecretPath and Certificate. Returning the struct as-is would put live enrollment secrets into an LLM context window — and, from there, into whatever transcript store the client keeps. Add fields here deliberately.
type NodeDetail ¶
type NodeDetail struct {
NodeSummary
Localname string `json:"localname"`
Username string `json:"username"`
OsqueryUser string `json:"osquery_user"`
CPU string `json:"cpu"`
Memory string `json:"memory"`
HardwareSerial string `json:"hardware_serial"`
ConfigHash string `json:"config_hash"`
DaemonHash string `json:"daemon_hash"`
BytesReceived int `json:"bytes_received"`
FirstSeen time.Time `json:"first_seen"`
}
NodeDetail extends NodeSummary with the fields worth a second call.
type NodePosture ¶
type NodePosture struct {
TotalScore int `json:"total_score"`
RiskLevel string `json:"risk_level"`
PassCount int `json:"pass_count"`
WarnCount int `json:"warn_count"`
FailCount int `json:"fail_count"`
Categories []string `json:"categories"`
}
NodePosture is the posture projection returned when include_posture is set.
type NodeSummary ¶
type NodeSummary struct {
UUID string `json:"uuid"`
Hostname string `json:"hostname"`
Platform string `json:"platform"`
PlatformVersion string `json:"platform_version"`
OsqueryVersion string `json:"osquery_version"`
IPAddress string `json:"ip_address"`
Environment string `json:"environment"`
LastSeen time.Time `json:"last_seen"`
}
NodeSummary is the compact node projection used by search_nodes.
type Option ¶
type Option func(*options)
Option configures the server built by NewServer.
func WithWrites ¶
func WithWrites(w WriteBackend) Option
WithWrites registers the mutating tools, backed by w.
Opt-in by construction: NewServer without this returns a read-only server, so no configuration mistake or refactor can quietly hand an agent the ability to schedule queries. Callers gate it on an explicit operator setting.
type QuerySummary ¶
type QuerySummary struct {
Name string `json:"name"`
Query string `json:"query"`
Creator string `json:"creator"`
Executions int `json:"executions"`
Errors int `json:"errors"`
Expected int `json:"expected"`
Active bool `json:"active"`
Completed bool `json:"completed"`
Expired bool `json:"expired"`
CreatedAt time.Time `json:"created_at"`
}
QuerySummary is the projection of a distributed query.
type SavedQuerySummary ¶
SavedQuerySummary is the projection of a saved (reusable) query.
type TableColumn ¶
type TableColumn struct {
Name string `json:"name"`
Type string `json:"type"`
Description string `json:"description"`
// Required marks a column osquery will not scan without: a query that
// omits it in the WHERE clause returns zero rows rather than an error.
Required bool `json:"required"`
Index bool `json:"index"`
Hidden bool `json:"hidden"`
}
TableColumn describes one column of an osquery table.
type TableSummary ¶
type TableSummary struct {
Name string `json:"name"`
Description string `json:"description"`
Platforms []string `json:"platforms"`
Evented bool `json:"evented"`
}
TableSummary is one row of list_osquery_tables — enough to pick a table, not enough to write a query. get_table_schema supplies the columns.
type WriteBackend ¶
type WriteBackend interface {
RunQuery(env, query string, uuids, hosts, platforms, tags []string, hidden bool, exp int) (types.ApiQueriesResponse, error)
ExpireQuery(env, name string) (types.ApiGenericResponse, error)
CompleteQuery(env, name string) (types.ApiGenericResponse, error)
TagNode(env, identifier, tag string, tagType uint, custom string) error
}
WriteBackend is the mutating slice of osctrl, kept separate from Backend so the write tools cannot be registered by accident: NewServer takes a Backend and stays read-only unless a caller also passes WithWrites.
As with Backend, nothing here performs authorization. RunQuery needs QueryLevel on the environment (and CarveLevel too if the SQL touches carves); TagNode needs AdminLevel. Those checks belong to osctrl-api.