Skip to content

feat: declare read-only annotations on all tools (fixes #1) - #4

Open
narko4u wants to merge 2 commits into
GenAI-Security-Project:mainfrom
narko4u:fix/tool-annotations
Open

feat: declare read-only annotations on all tools (fixes #1)#4
narko4u wants to merge 2 commits into
GenAI-Security-Project:mainfrom
narko4u:fix/tool-annotations

Conversation

@narko4u

@narko4u narko4u commented Aug 18, 2026

Copy link
Copy Markdown

feat: declare read-only annotations on all tools (fixes #1)

What

All five tools now pass annotations to tools/list, so a client can
tell a first-party read-only server from a silent third-party one:

  • readOnlyHint: true -- every path is a GET against raw.githubusercontent.com
    or 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_REPO
    at 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_REF env vars), not
chosen per-call. Per the MCP spec, openWorldHint signals whether the
tool 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 -- clean
  • npm run dry-run (wrangler deploy --dry-run) -- clean, uploads 900 KiB

…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tools carry no annotations, so the read-only property lives only in prose

1 participant