feat: declare read-only annotations on all tools (fixes #1) - #4
Open
narko4u wants to merge 2 commits into
Open
Conversation
…y-Project#1) All five tools (list_resources, list_initiatives, get_resource, get_file, search_corpus) now pass annotations so clients can distinguish this server's read-only behavior from a silent third-party one. - readOnlyHint: true (every path is a GET against raw.githubusercontent.com or the GitHub trees API) - idempotentHint: true (same reason; repeated calls are safe) - openWorldHint: false (the only host reached is the pinned SOURCE_REPO at SOURCE_REF; no user-selected external destinations) Additive only: no type or signature changes. Signed-off-by: Empire Labs <contact@empirelabs.com.au>
MCP spec (schema 2025-06-18 / 2026-07-28): idempotentHint is meaningful
only when readOnlyHint == false. With readOnlyHint: true the flag is
inert, so it was a claim the spec says carries no meaning in this
position. Keep the block to { readOnlyHint: true, openWorldHint: false }.
Signed-off-by: Empire Labs <contact@empirelabs.com.au>
narko4u
added a commit
to narko4u/GenAI-Security-Advisor-MCP
that referenced
this pull request
Aug 19, 2026
…ity-Project#2) Incorporate PR GenAI-Security-Project#4's read-only annotations so all six tools declare readOnlyHint/openWorldHint -- six tools, six declarations, regardless of merge order. The 6th tool (get_corpus_revision) now carries the same annotation block. Hardening from self-review, in response to review feedback on fail-closed vs fail-open semantics: - Document the deliberate FAIL-CLOSED policy in getSourceRevision, the server instructions text, and the README: if the revision cannot be resolved the server errors instead of serving unpinned content. - list_initiatives now returns source_revision too, so the documented contract ('every tool result carries source_revision') holds. - get_resource only emits files that exist in the pinned tree, so a drifted MANIFEST path can't produce raw_urls that 404. - corpus.ts path safety: reject backslashes, percent-encoded traversal (%2e%2e), and control characters in isSafeCorpusPath. - Encode each URL path segment in rawUrl/fetchRawFile so spaces or special characters in corpus filenames survive. - getTree fails closed if GitHub's recursive API truncates the tree. - getManifest validates every resource entry has id/title. - Add unit tests (vitest) covering path safety and helpers. Signed-off-by: narko4u <narko4u@users.noreply.github.com>
narko4u
added a commit
to narko4u/GenAI-Security-Advisor-MCP
that referenced
this pull request
Aug 19, 2026
…ity-Project#2) Revision-pinning hardening from self-review, in response to review feedback on fail-closed vs fail-open semantics: - Document the deliberate FAIL-CLOSED policy in getSourceRevision, the server instructions text, and the README: if the revision cannot be resolved the server errors instead of serving unpinned content. - list_initiatives now returns source_revision too, so the documented contract ('every tool result carries source_revision') holds. - get_resource only emits files that exist in the pinned tree, so a drifted MANIFEST path can't produce raw_urls that 404. - corpus.ts path safety: reject backslashes, percent-encoded traversal (%2e%2e), and control characters in isSafeCorpusPath. - Encode each URL path segment in rawUrl/fetchRawFile so spaces or special characters in corpus filenames survive. - getTree fails closed if GitHub's recursive API truncates the tree. - getManifest validates every resource entry has id/title. - The new get_corpus_revision tool ships with its own read-only annotations (readOnlyHint/openWorldHint); annotations for the five pre-existing tools are covered by PR GenAI-Security-Project#4 (fixes GenAI-Security-Project#1). - Add unit tests (vitest) covering path safety and helpers. Signed-off-by: narko4u <narko4u@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat: declare read-only annotations on all tools (fixes #1)
What
All five tools now pass annotations to
tools/list, so a client cantell a first-party read-only server from a silent third-party one:
readOnlyHint: true-- every path is a GET against raw.githubusercontent.comor the GitHub trees API. Nothing on this server mutates state.
idempotentHint: true-- same reason; repeated identical calls are safe.openWorldHint: false-- the only host reached is the pinned SOURCE_REPOat SOURCE_REF (env-configured). There are no user-selected external
destinations, so the world this server touches is closed.
Additive only: five object literals, no type or signature changes.
Why openWorldHint: false
The issue left this to us. The tools do reach an external host, but the
host is fixed at deploy time (
SOURCE_REPO/SOURCE_REFenv vars), notchosen per-call. Per the MCP spec,
openWorldHintsignals whether thetool may interact with an open world of external entities; a single
pinned repository does not meet that bar. Happy to flip it if the
project prefers to treat "external host at all" as open.
Verification
npm run typecheck-- cleannpm run dry-run(wrangler deploy --dry-run) -- clean, uploads 900 KiB