Security
-
Upgraded
modelcontextprotocol/go-sdkfrom v1.4.0 to v1.7.0, which clears
two HIGH advisories affecting v1.4.0:GHSA-89xv-2j6f-qhc8(CVE-2026-33252,
cross-site tool execution against Streamable HTTP servers without inbound
auth) andGHSA-q382-vc8q-7jhj(null-Unicode JSON parsing). -
HTTP transport now verifies request origin explicitly. The SDK applied
cross-origin protection by default in v1.4.1–v1.5.0 but made it opt-in again
in v1.6.0, so the handler now passes its ownhttp.CrossOriginProtection.
Browser cross-originPOSTrequests to/mcpare rejected with403.
Non-browser clients (noOrigin/Sec-Fetch-Siteheader) are unaffected. -
Cursors are always signed in HTTP mode. When
OPENGROK_MCP_CURSOR_SECRET
is unset and the transport is HTTP, the server generates a random 32-byte
process-local HMAC key at startup instead of leaving cursors unsigned. Over
HTTP a cursor is an attacker-reachable blob, so unsigned was the wrong
default. Cursors signed with a generated key are invalidated on restart and
are not valid across replicas — setOPENGROK_MCP_CURSOR_SECRETexplicitly
for either. Stdio behavior is unchanged (unsigned, with the existing startup
warning). Implements ROADMAP item 001, Option A.
Fixed
- Search paging no longer drops results. OpenGrok pages by file while
page_sizecaps returned lines, so line matches beyond the cap inside an
already-fetched file were discarded and unreachable by any cursor — a
page_sizeof 2 over a query with 85 line matches in the first two files
surfaced 2 and lost 83. Cursors now carry a line offset that resumes inside
the file window, so a full walk yields every matching line exactly once
(verified live: 910 results, 910 unique, 0 duplicates, matching the corpus).
Previously minted cursors decode with a zero line offset and stay valid.
total_pagescounts file pages and is therefore a lower bound on a full
walk — stop on a nullnext_cursor, not ontotal_pages. - Identical searches now return identical results. The OpenGrok response was
decoded into a map and ranged over directly, so Go's randomized map iteration
reordered results on every call; combined with page truncation, the same query
returned different hits run to run. The decoder now records and follows
OpenGrok's emission order, preserving relevance ranking. PAGE_SIZE_TRUNCATEDno longer reports data loss. Same code, same
trigger, but it now states the surplus lines are reachable vianext_cursor
instead of implying they were dropped.expand_contextis no longer silently ignored. Expansion was skipped
wheneverresponse_moderesolved tocompact, which is the default under the
economy profile — so an explicitexpand_context: truehad no effect on a
stock server. An explicitly passedexpand_contextnow outranks the compact
default. Omitting it still keeps economy responses lean.- Search modes the instance cannot serve report a capability gap, not a parse
error. OpenGrok answers400both for an unparseable query and for a mode it
does not serve. When startup probing already found the mode unavailable, the
newSEARCH_MODE_UNSUPPORTEDerror says so instead of offering query-syntax
advice that cannot help. Genuine parse failures still return
QUERY_PARSER_FAILED. file_typedocuments that it takes an analyzer name, and warns when it
matches nothing.file_typemaps to OpenGrok'stypeparameter, which wants
the analyzer name (golang), not the extension (go). A wrong value is not an
upstream error — it silently returns zero hits. The schema now says so, and a
newFILE_TYPE_NO_MATCHwarning fires when afile_typefilter yields no hits.- Snippets no longer leak OpenGrok's
<html>marker. For struct-field
definition hits OpenGrok returns a synthesized symbol+scope string prefixed
with<html>rather than the source line. The marker is stripped;<b>match
highlighting is preserved. - Tool errors now carry their remediation in the text content. The
suggestionfield (which valid fields to use, what to change) was only
present instructuredContent. Many MCP clients never forward that to the
model, so an agent hitting e.g.UNKNOWN_FIELDsaw onlyUnknown field "max_results" for operation "code".with no indication thatpage_sizewas
the field it wanted.suggestionis now appended to the text content.
structuredContentis unchanged.
Added
results_on_pageon every paginated response.total_hitsis OpenGrok's
count of matching files, but code-search results are one per matching
line, solen(results)could exceedtotal_hitswith no way to tell from
the response.results_on_pagealways equalslen(results), making the two
units reconcilable. OpenGrok reports no global line count (resultCountis
constant regardless of how much is fetched), so there is deliberately no
global counterpart.total_hitssemantics are unchanged — this is additive.- Compact schemas mark fields that only some operations accept. The
top-level property bag is the union of every operation's fields (so strict
clients stop stripping them), while eachoneOfbranch rejects fields it does
not own. Six such fields —max_results,lines_before,lines_after,
expand_context,sort,max_hits_per_file— now carry an
(operation=... only)note, so agents stop discovering ownership by rejection.
Costs ~1,800 bytes ontools/list.
Changed
page_sizedocuments that paging is file-based while it caps lines.
OpenGrok pages by file;page_sizecaps returned line results. Line matches
beyondpage_sizeinside an already-fetched file are dropped and cannot be
reached by paging — raisepage_sizeinstead. Behavior is unchanged; this
documents a pre-existing defect now tracked indocs/limitations.md.tools/listnow emits"idempotentHint": falseon every tool. The SDK
droppedomitemptyfromToolAnnotations.IdempotentHintin v1.7.0. Read-only
annotations are otherwise unchanged.- List results carry SDK-emitted
ttlMsandcacheScopefields (SEP-2549
cache hints,ttlMs: 0/cacheScope: "public"). These are protocol-level
fields written by the SDK, not part of this server's tool contract.