Documentation
¶
Overview ¶
Package research provides the MCP research agent's web tools: search and page fetch. They exist for the questions no deterministic source answers — is this vendor real, do they publish a security policy, has anything been written about this server — and they are the only research tools the agent gets; everything deterministic reaches it through the stored evidence document instead.
The defining constraint, stated here because every tool in this package inherits it: everything these tools return is untrusted. Pages are authored by the party under review, and search results are cheap to seed. Results are data for the caller to weigh and cite, never instructions, and nothing fetched may drive further tool calls on its own authority.
Index ¶
Constants ¶
const FetchTimeout = 15 * time.Second
FetchTimeout bounds one page fetch. Exported for the wiring layer, which applies it to the guardian client this package receives.
Variables ¶
This section is empty.
Functions ¶
func CanonicalMenuURL ¶
CanonicalMenuURL is the single canonicalization point for menu entries and fetch targets. The string it returns is both what the menu stores and what the HTTP client is handed, byte for byte — the menu check and the request can never diverge on parsing, which is the classic bypass against allow-list designs. Fragments are stripped because they never reach the wire: two URLs differing only in fragment are the same request.
func ConfigureFetchClient ¶
func ConfigureFetchClient(client *guardian.HTTPClient) *guardian.HTTPClient
ConfigureFetchClient applies the fetch tool's transport bounds — timeout and redirect depth — to the guardian client the wiring layer built for it. Kept here so the bounds live next to the tool they protect.
Types ¶
type CompletionProvider ¶
type CompletionProvider interface {
GetCompletion(ctx context.Context, req openrouter.CompletionRequest) (*openrouter.CompletionResponse, error)
}
CompletionProvider is the slice of the OpenRouter client search needs. *openrouter.ChatClient satisfies it.
type FetchPage ¶
type FetchPage struct {
// contains filtered or unexported fields
}
FetchPage fetches one public web page and returns its readable text. The guardian-backed client is the SSRF control: the agent follows links derived from search results about an untrusted target, so every request — including each redirect hop — dials under egress policy. The menu is the exfiltration control: only URLs trusted code observed are fetchable, so the model selects destinations and never composes them.
func NewFetchPageTool ¶
func NewFetchPageTool(client *guardian.HTTPClient, menu *URLMenu) *FetchPage
NewFetchPageTool builds the page-fetch tool. Pass the client through ConfigureFetchClient at wiring time so the transport bounds apply. The menu must be the same instance the search tool feeds.
func (*FetchPage) Call ¶
func (s *FetchPage) Call(ctx context.Context, env toolconfig.ToolCallEnv, payload io.Reader, wr io.Writer) error
func (*FetchPage) Descriptor ¶
func (s *FetchPage) Descriptor() core.ToolDescriptor
type SearchClient ¶
type SearchClient struct {
// contains filtered or unexported fields
}
SearchClient runs web searches through OpenRouter's web-search plugin, so search shares the org's existing OpenRouter billing instead of introducing a search vendor and secret.
func NewSearchClient ¶
func NewSearchClient(completions CompletionProvider) *SearchClient
NewSearchClient builds a search client over the supplied completion provider.
func (*SearchClient) Search ¶
func (c *SearchClient) Search(ctx context.Context, orgID, projectID, query string, maxResults int) ([]SearchResult, SearchUsage, error)
Search runs one web search and returns the plugin's cited results. The model's own prose is discarded: the citations are the deliverable, and keeping them free of model narration keeps this tool a search, not a summarizer. No results with a nil error is a real answer.
type SearchResult ¶
type SearchResult struct {
// Title is the page title as the search engine reports it.
Title string `json:"title,omitempty"`
// URL is the result's address, which is also its citation.
URL string `json:"url"`
// Snippet is the search engine's excerpt for the page — third-party
// text, untrusted like the page itself.
Snippet string `json:"snippet,omitempty"`
}
SearchResult is one cited web result.
type SearchUsage ¶
SearchUsage is what one search cost, for the caller's own run accounting.
type URLMenu ¶
type URLMenu struct {
// contains filtered or unexported fields
}
URLMenu is the set of URLs a run may fetch: the model selects from it and can never add to it. Only trusted code writes entries — search results, links harvested from fetched pages, and the briefing's own URLs — so a fetched URL is always one the ecosystem presented, never one the model composed. That is the property that keeps fetch from being an exfiltration channel: an injected model cannot address a byte of its context to an attacker endpoint, only choose among endpoints trusted code already saw.
Entries are keyed per run and expire with the same window as the call budgets, so the map stays bounded by the runs active within it.
func (*URLMenu) Allow ¶
Allow adds one URL to a run's menu. URLs that do not canonicalize are dropped silently: the sources feeding the menu (search results, harvested hrefs, briefing text) routinely contain relative links, mailto:, http-only pages and plain junk, and none of that is an error — it is simply not fetchable.
type WebSearch ¶
type WebSearch struct {
// contains filtered or unexported fields
}
WebSearch runs one web search and returns cited results for the research agent to read and follow up on.
func NewWebSearchTool ¶
func NewWebSearchTool(search *SearchClient, menu *URLMenu) *WebSearch
NewWebSearchTool builds the search tool over the supplied search client. The menu must be the same instance the fetch tool checks, or nothing a search returns becomes fetchable.
func (*WebSearch) Call ¶
func (s *WebSearch) Call(ctx context.Context, env toolconfig.ToolCallEnv, payload io.Reader, wr io.Writer) error
func (*WebSearch) Descriptor ¶
func (s *WebSearch) Descriptor() core.ToolDescriptor
func (*WebSearch) DrainUsage ¶
DrainUsage returns what this caller's searches have cost since the last drain, and forgets it. Draining rather than reading keeps the map bounded: the tool outlives every run that uses it.