Privacy

Privacy Policy

Last updated 2026-08-08

This policy covers the OpenNIH website at opennih.org and the public MCP endpoint at https://mcp.opennih.org/mcp. The service is anonymous: there are no accounts, no registration, no authentication, and no login. To keep an unauthenticated public service healthy and observable, the server records usage telemetry, and this page states exactly what that telemetry contains.

Anonymous is not the same as PII-free

OpenNIH does not ask who you are and cannot look you up. But the telemetry above is not guaranteed to be free of personal information, because two of the recorded values are text you control: the arguments you pass to a tool, and the values of the allowlisted headers your client sends. If you type a person's name, an email address, or anything else personal into a query, it is recorded. Treat tool arguments the way you would a search-engine query log: do not put personal or sensitive data in a query.

What is collected, and why

Telemetry is recorded per HTTP request and per tool call. Recording is best-effort and never blocks a live call: a record is placed on a bounded in-memory queue and written by a background thread. If the queue is full the line is dropped and counted; if the disk write then fails, that line is lost silently. So “recorded” below means a record is enqueued for the call, subject to these rare losses.

Tool arguments

The arguments you pass to a tool (for example a search_grants query, a text_search, or a pi_name) are recorded after MCP protocol validation: each validated non-null value is converted to its Python text form (str()) and truncated to its first 200 characters, while arguments you left unset or passed as null are omitted entirely. This is a faithful record of what you searched for, but it is not your raw JSON and not lexically exact — a JSON true is stored as True, a list as ['a', 'b'], and anything past 200 characters is cut.

Why: Know which tools are used and with what shape of input, so broken or missing capabilities are visible.

Salted hash of your IP address

Client IP addresses are never stored raw. Each address is salted-SHA256-hashed to a 16-character digest before it reaches disk. The raw IP exists only inside the in-memory rate limiter and is never persisted.

Why: Distinguish one caller from another for abuse control and usage counts, without holding an identifier that points back to you.

Country

Whatever the CF-IPCountry request header carries, bounded to 200 characters. In production Cloudflare stamps this header at the edge with a two-letter country code; the server stores the header value as received and does not itself geolocate a stored IP address.

Why: Understand where the service is used so capacity and coverage decisions are informed.

User agent

The User-Agent header you send, bounded to its first 400 characters.

Why: Identify which clients and versions call the service, and spot automated abuse.

MCP session id and origin

The per-connection MCP session id and the Origin header, each bounded to 200 characters. The server has no accounts and assigns you no identifier of its own, but it does persist the session id your client sends.

Why: Group the calls of one connection together when diagnosing a failed session.

Allowlisted request headers

Only request headers whose lowercase name appears in a fixed allowlist are recorded — accept, accept-encoding, accept-language, cache-control, cf-ipcountry, connection, content-length, content-type, host, mcp-protocol-version, mcp-session-id, origin, te, transfer-encoding, user-agent, via, x-forwarded-proto — each value bounded to 200 characters. Every other header is dropped whole: neither its name nor its value is written. A header dropped for not being on the allowlist increments a single other_header_names_count tally; a header dropped earlier by the credential/client-IP denylist is discarded without being counted at all.

Why: Diagnose protocol and transport problems without letting caller-controlled text into the log through a header name.

Request and result metadata

The HTTP method; the request path and query string, each bounded to 200 characters; scheme; HTTP version; status code; response byte count; latency in milliseconds; a success/failure flag; the error class name and a bounded error message; the tool name; result-shape counts; a bounded result-metadata summary; a cache-hit flag; and your MCP client's reported name, version, protocol version, and capabilities.

Why: Measure health, latency, error rates, and rate-limit rejections on an endpoint with no other observability.

Server-generated record stamps

Every stored line also carries a UTC timestamp, the OpenNIH version that served the request, a per-process boot id that groups lines by container generation, and a flag marking the line as a request record or a tool-call record. These are generated by the server, not taken from you.

Why: Order events, attribute a problem to a specific deploy, and make server restarts visible.

What is never collected

Retention

Indefinite. Usage history is kept as a permanent product asset; telemetry files are never deleted on a schedule and the service implements no automatic expiry. Records are written to daily files on the server that runs the endpoint.

Sharing and sale

Telemetry is not sold, rented, or shared with third parties. It is used only to operate and improve OpenNIH. Traffic reaches the service through Cloudflare, which processes requests as part of delivering and protecting the endpoint.

When telemetry runs

Telemetry is off by default and is collected only when the deployment sets the OPENNIH_MCP_TELEMETRY_DIR environment variable. The production deployment sets it, so assume telemetry is on for the public endpoint.

Data source

All grant data served by OpenNIH is public U.S. Government data from NIH ExPORTER and the NIH RePORTER API. It contains no private data of yours and is not derived from your use of the service.

Children

OpenNIH is a research-data tool intended for a general professional audience. It is not directed at children, it has no registration, and it asks no user of any age for their name or age. It does record the pseudonymous and client-supplied values listed above — a salted IP hash, user agent, origin, MCP session id, and whatever text is passed in a query — for every caller alike, and the correction form has an optional contact field that stores whatever address is typed into it.

Changes to this policy

If what the service collects changes, this page changes with it and the “last updated” date above moves. There is no mailing list to notify, because there are no accounts; check this page for the current statement.

Contact

Questions about this policy, or about a specific figure OpenNIH displays, go through the public channels listed on the support page. Data corrections are reported through the correction intake form, which needs no account.