research

package
v0.0.0-...-1a68bba Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 18, 2026 License: AGPL-3.0 Imports: 21 Imported by: 0

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

View Source
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

func CanonicalMenuURL(raw string) (string, error)

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

func (*FetchPage) Menu

func (s *FetchPage) Menu() *URLMenu

Menu exposes the menu this tool enforces, so a registrar can verify a select-class tool really is menu-locked rather than trusting the label.

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

type SearchUsage struct {
	PromptTokens     int64
	CompletionTokens int64
}

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 NewURLMenu

func NewURLMenu() *URLMenu

NewURLMenu builds an empty menu.

func (*URLMenu) Allow

func (m *URLMenu) Allow(runID string, rawURL string)

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.

func (*URLMenu) Allowed

func (m *URLMenu) Allowed(runID string, rawURL string) (string, bool)

Allowed reports whether a run may fetch rawURL, returning the canonical form to fetch when it may. The canonical return is what the caller must hand to the HTTP client: fetching anything else would reopen the parsing gap Canonical exists to close.

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

func (s *WebSearch) DrainUsage(chatID string) (promptTokens int64, completionTokens int64)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL