API: tools/research
better-web-search-mcp / tools/research
tools/research#
Interfaces#
Citation#
Defined in: src/tools/research.ts:54
A span of a source page that supports the answer.
Properties#
| Property | Type | Description | Defined in |
|---|---|---|---|
citation | number | 1-based index into sources. | src/tools/research.ts:56 |
end | number | Character offset of the quote end. | src/tools/research.ts:66 |
quote | string | The supporting text, verbatim from the extracted content. | src/tools/research.ts:62 |
relevance | number | Relevance of the span to the question. Higher is stronger. | src/tools/research.ts:68 |
start | number | Character offset of the quote within that page’s extracted content. | src/tools/research.ts:64 |
title | string | The source title. | src/tools/research.ts:60 |
url | string | The source URL the span was taken from. | src/tools/research.ts:58 |
ResearchResponse#
Defined in: src/tools/research.ts:71
Properties#
| Property | Type | Description | Defined in |
|---|---|---|---|
answer | string | - | src/tools/research.ts:72 |
citations | Citation[] | The exact spans the answer was assembled from, so an agent can attribute a statement to a span rather than to a whole page. | src/tools/research.ts:79 |
evidence | { cited_spans: number; derivative_sources: number; independent_sources: number; low_density_sources: number; query_term_coverage: number; sources_opened: number; } | Countable facts about the evidence behind the answer. Every field is something the server measured, not a judgement: there is no language model here to score truth or agreement, and an invented confidence number would be worse than none. An agent can use these to decide whether to research further. | src/tools/research.ts:95 |
evidence.cited_spans | number | Number of cited spans in citations. | src/tools/research.ts:113 |
evidence.derivative_sources | number | Pages that duplicate another page’s content or host. | src/tools/research.ts:105 |
evidence.independent_sources | number | Distinct content clusters among those pages. Syndicated reprints and multiple pages from one host collapse into a single cluster, so this is the number of genuinely separate accounts. | src/tools/research.ts:103 |
evidence.low_density_sources | number | Opened pages whose information density fell below LOW_DENSITY_THRESHOLD — listicles, link farms, and templated filler. Their passages are down-weighted rather than dropped, since a thin page can still hold the one sentence that answers the question. | src/tools/research.ts:120 |
evidence.query_term_coverage | number | Fraction of the question’s content words that appear in the cited passages, 0..1. Low values mean the citations may not address the question, not that the answer is wrong. | src/tools/research.ts:111 |
evidence.sources_opened | number | Pages actually opened and extracted. | src/tools/research.ts:97 |
extraction_stats | { avgConfidence: number; method_counts: Record<string, number>; } | - | src/tools/research.ts:122 |
extraction_stats.avgConfidence | number | - | src/tools/research.ts:124 |
extraction_stats.method_counts | Record<string, number> | - | src/tools/research.ts:123 |
queries_used | string[] | - | src/tools/research.ts:74 |
sources | SearchSource[] | - | src/tools/research.ts:73 |
warnings? | string[] | Providers that refused or failed during the search phase. Without this a throttled research run is indistinguishable from a question the web has no answer to. | src/tools/research.ts:86 |
SourceIndependence#
Defined in: src/tools/research.ts:44
Whether a source is an independent account or a copy of another.
Properties#
| Property | Type | Description | Defined in |
|---|---|---|---|
cluster | number | Index of the content cluster this source belongs to. | src/tools/research.ts:46 |
duplicates | number | How many other opened sources share this cluster. | src/tools/research.ts:50 |
primary | boolean | Whether this source is the representative of its cluster. | src/tools/research.ts:48 |
Variables#
LOW_DENSITY_THRESHOLD#
constLOW_DENSITY_THRESHOLD:0.5=0.5
Defined in: src/tools/research.ts:158
Information density below which a page counts as thin.
researchInputSchema#
constresearchInputSchema: {count_per_query:ZodDefault<ZodNumber>;depth:ZodDefault<ZodEnum<["quick","deep"]>>;question:ZodString;recency_days:ZodOptional<ZodNumber>; }
Defined in: src/tools/research.ts:35
Input schema for web_research.
Type Declaration#
| Name | Type | Defined in |
|---|---|---|
count_per_query | ZodDefault<ZodNumber> | src/tools/research.ts:39 |
depth | ZodDefault<ZodEnum<["quick", "deep"]>> | src/tools/research.ts:37 |
question | ZodString | src/tools/research.ts:36 |
recency_days | ZodOptional<ZodNumber> | src/tools/research.ts:38 |
RESPONSE_SCHEMA#
constRESPONSE_SCHEMA:2=2
Defined in: src/tools/research.ts:155
Version of the ResearchResponse shape, embedded in the cache key.
Bump this whenever a field is added, removed, or given a new meaning, so an entry written by an older build can never be served as the current type.
Functions#
collectCitations()#
collectCitations(
pages,sources,question,limit?,independence?,densities?):Citation[]
Defined in: src/tools/research.ts:263
Pick the spans of the opened pages that actually address the question.
The previous implementation took the first 400 characters of each page and never looked at the question, so the “answer” was usually page intros and boilerplate. Passages are now scored against the question with BM25 and the best ones returned with their offsets, which raises the signal and cuts the token count, since only matching paragraphs are included.
Parameters#
| Parameter | Type | Default value |
|---|---|---|
pages | readonly RoutedPage[] | undefined |
sources | readonly SearchSource[] | undefined |
question | string | undefined |
limit | number | MAX_EXCERPTS |
independence | readonly IndependenceResult[] | [] |
densities | readonly number[] | [] |
Returns#
Citation[]
densityWeight()#
densityWeight(
density):number
Defined in: src/tools/research.ts:167
Weight a passage score by its page’s information density.
Kept as a multiplier in [0.5, 1] rather than a filter: a listicle can still carry the one sentence that answers the question, so a thin page is made to compete at a disadvantage instead of being discarded.
Parameters#
| Parameter | Type |
|---|---|
density | number |
Returns#
number
queryTermCoverage()#
queryTermCoverage(
question,citations):number
Defined in: src/tools/research.ts:376
Fraction of the question’s content words that appear in the cited passages.
A blunt but honest measure of whether the citations engage with the question at all. It says nothing about correctness, which is not something this server can determine.
Parameters#
| Parameter | Type |
|---|---|
question | string |
citations | readonly Citation[] |
Returns#
number
registerResearch()#
registerResearch(
server,cache?):void
Defined in: src/tools/research.ts:532
Register the web_research tool (alias deep_search) on the given MCP server.
Parameters#
| Parameter | Type |
|---|---|
server | McpServer |
cache? | Cache |
Returns#
void
runResearch()#
runResearch(
args):Promise<ResearchResponse>
Defined in: src/tools/research.ts:418
Run a deep research query and return the spec-shaped response.
Exported separately so tests and the smoke harness can call the handler logic directly.
Parameters#
| Parameter | Type |
|---|---|
args | { cache?: Cache; count_per_query?: number; depth?: "deep" | "quick"; question: string; recency_days?: number; } |
args.cache? | Cache |
args.count_per_query? | number |
args.depth? | "deep" | "quick" |
args.question | string |
args.recency_days? | number |
Returns#
Promise<ResearchResponse>