Skip to main content
Glama
MyOpenFund

MyOpenFund MCP Server

Official
by MyOpenFund

myopenfund-mcp

One MCP server over the MyOpenFund bricks: official documents and pipeline health today, RAG and company data next.

Any MCP client (a desktop assistant, a coding agent, your own agent, a local model) gets outcome-oriented tools over MyOpenFund's data: find official primary-source documents, read their facts with provenance (doc_id, dates, source URLs), count the corpus, check the ingestion pipeline's health. Toolsets are enabled per deployment: a toolset whose environment variables are not set is not configured, so it is not mounted, and server_info says so. A toolset whose variables are set but whose extra is not installed stops the server at start with a clear configuration error (it never runs silently without it).

MyOpenFund manages nobody's money and gives no investment advice. This server returns documents and facts from official sources, with their provenance. What you do with them is your decision.

  • Two transports, same code: stdio for local use, Streamable HTTP (bearer tokens) in a Docker image.

  • Host-agnostic: the same image runs on a laptop, on a host on a private network or on a cloud VM; everything deployment-specific comes from MOF_* environment variables.

  • Deterministic: no tool needs a hosted LLM (see Local models).

Contents

Related MCP server: learning-mcp-server

Quick start (stdio)

Prerequisites:

  • Python 3.12+ and git (the [vault] extra installs a pinned dependency from git);

  • read access to the vault database as the read-only role vault_readonly (never a writing role).

git clone https://github.com/MyOpenFund/myopenfund-mcp.git
cd myopenfund-mcp
python -m venv .venv
.venv/bin/pip install ".[vault]"

Register it with your MCP client. With Claude Code:

claude mcp add myopenfund \
  -e MOF_VAULT_DATABASE_URL='postgresql://vault_readonly:<password>@<host>:5432/documents?sslmode=verify-full' \
  -- /path/to/myopenfund-mcp/.venv/bin/myopenfund-mcp serve

Any other client: run the command myopenfund-mcp serve (stdio is the default transport) with the same environment. Over stdio there is no token: the local user owns the process and gets every tier (MOF_MCP_TOKENS, if set, is ignored with a warning on stderr). Then ask the client to call server_info.

The snippet above stores the database URL, password included, in the MCP client's configuration file. Use the read-only role vault_readonly only, and keep that file private.

HTTP deployment

MOF_MCP_TRANSPORT=http serves Streamable HTTP at MOF_MCP_PATH (default /mcp). It requires MOF_MCP_TOKENS: the HTTP server never runs unauthenticated, and refuses to start without tokens.

The server speaks plain HTTP. Network exposure and TLS are the deployment's job, never the public internet by default. The repository ships a generic Dockerfile, compose.example.yaml and .env.example; nothing host-specific lives in it.

cp compose.example.yaml compose.yaml
cp .env.example .env                                   # never committed
.venv/bin/myopenfund-mcp hash-token alice              # once per client, see Tiers and tokens
.venv/bin/myopenfund-mcp hash-token agent-1 --tier read
# edit .env: MOF_MCP_TOKENS, MOF_VAULT_DATABASE_URL, MOF_MCP_BIND_ADDR ...
docker compose up -d --build

No venv on the deployment host? Run hash-token in the container instead (after cp .env.example .env, which compose needs, and with the image built):

docker compose build
docker compose run --rm mcp myopenfund-mcp hash-token alice

hash-token prints the token on stderr (copy it to the client) and only the MOF_MCP_TOKENS entry on stdout, so redirecting stdout (>> entries.txt) never writes the clear token into a file.

The image runs as a non-root user (uid 10001), listens on 0.0.0.0:8000 inside the container, and the compose file publishes it on 127.0.0.1 of the host unless MOF_MCP_BIND_ADDR says otherwise. In .env, leave MOF_MCP_TRANSPORT and MOF_MCP_HOST unset: the image sets them. The compose file also runs it with a read-only root filesystem (writable /tmp only), no Linux capabilities and no-new-privileges.

A client connects with its token, e.g. with Claude Code:

claude mcp add --transport http myopenfund https://<your-host>/mcp \
  --header "Authorization: Bearer <token>"

Pattern 1: a host on a private network

A home server, a NAS or an office machine reachable only over a VPN or a private interface.

  • Set MOF_MCP_BIND_ADDR to the host's private address (the VPN or LAN interface), never 0.0.0.0 on a host that also has a public interface.

  • If the vault Postgres runs as a compose stack on the same Docker host, do not publish its port: join its Docker network instead (uncomment the networks blocks in compose.yaml, set MOF_VAULT_DOCKER_NETWORK) and use the database service name as <host> in MOF_VAULT_DATABASE_URL. On that private Docker network, sslmode=disable is acceptable.

  • TLS: the VPN already encrypts the traffic; for HTTPS on top, put a reverse proxy holding a certificate for the private name in front, or use your VPN's own HTTPS feature if it has one.

Pattern 2: a cloud VM (or container service)

  • Keep the default MOF_MCP_BIND_ADDR=127.0.0.1 and terminate TLS at a reverse proxy on the same VM (Caddy, nginx, Traefik) proxying to 127.0.0.1:8000, or run the container behind the provider's load balancer with a TLS listener and a security group that only admits the balancer.

  • Health check for the load balancer or orchestrator: GET /health answers 200 with the body ok, without a token (it reveals no version and no data); /mcp keeps requiring one. The image's Docker HEALTHCHECK uses the same endpoint.

  • Reach the vault database over the provider's private network with sslmode=verify-full and the provider's CA certificate (sslrootcert=...). sslmode=require is a fallback: it encrypts but does not authenticate the server.

  • Restrict who can reach the HTTPS endpoint (IP allow-list, private load balancer) wherever you can: the bearer token is the only authentication in v1.

Tools

Tool names carry their toolset prefix. All v1 tools are read-only (readOnlyHint). Only vault_search_documents is paged: it returns next_cursor to pass back unchanged with the same filters. vault_corpus_stats and vault_runs_status only bound their lists with limit (no cursor). Every limit defaults to MOF_MCP_DEFAULT_ROWS; a value outside [1, MOF_MCP_MAX_ROWS] is refused, not clamped. String arguments doc_id, text and cursor are at most 200 characters.

Tool

Purpose

Key arguments

server_info

What this deployment offers: enabled toolsets, your tier, version, data freshness (last vault ingest). Call it first. Open to every authenticated caller.

none

vault_search_documents

Find official documents (metadata, not text). Start here: filter, then open one with vault_get_document. Rows are ordered by sort_by in sort_dir; equal sort values are always ordered by doc_id ascending, whatever sort_dir. total counts the whole filtered set. Cursors are offsets: if the vault is ingesting between two pages, a row can shift (seen twice or skipped).

corpus (e.g. central-bank), source_code (publisher, e.g. us for the Fed), doc_type, language, year, mime_type, date_from / date_to (YYYY-MM-DD, inclusive), text (case-insensitive substring of the title; % and _ act as wildcards), sort_by (date default, year, corpus, source_code, doc_type, title, created_at), sort_dir (desc default), limit, cursor, include_deleted (also return withdrawn documents, with deleted_at set)

vault_get_document

Every stored fact of one document: dates, source and PDF URLs, publisher, type, extra.

doc_id

vault_corpus_stats

Live-document counts grouped by one field, largest first (NULL is a real group).

group_by (source_code default, corpus, doc_type, language, year, provenance, mime_type), corpus, limit

vault_runs_status

Is the pipeline healthy? Four lists, newest first: runs (one row per run, with outcome and totals; a run that crashed before reaching any source appears only here), source_runs (one row per run and source: counts, outcome, bounded error samples), open_discovery_errors (unresolved discovery failures; while seen_at_is_ingestion_time is true, last_seen_at is approximate), source_health (expected-vs-observed publication cadence per series).

corpus, source_code, since (ISO 8601, UTC if no offset, an explicit offset is kept; keeps runs finished at or after it, leaves out runs still in flight; applies to runs and source_runs only), limit (applies to each of runs, source_runs, open_discovery_errors)

Resource: vault://documents/{doc_id}, one document's metadata as JSON (same content as vault_get_document).

The vault_* tools and the resource exist only when MOF_VAULT_DATABASE_URL is set; the [vault] extra must then be installed, or the server refuses to start (MOF_VAULT_DATABASE_URL is set but the [vault] extra is not installed). Timestamps in results are UTC: the server's database session runs with timezone=UTC, whatever the database or role default.

If the vault database is down, server_info still answers (version, tier, toolsets) with freshness null and "freshness_error": "backend_unavailable"; the server log names the exception class only.

Errors

Tool errors are MCP tool errors whose message starts with a stable code, [code] message, so agents can branch on the code and show the message.

Code

Meaning

What to do

invalid_argument

A bad argument: limit outside [1, MOF_MCP_MAX_ROWS], a cursor this server did not issue, date_from after date_to, a value the vault database refuses.

Fix the argument; pass next_cursor unchanged.

not_found

No document with that doc_id.

Find ids with vault_search_documents.

timeout

The vault query exceeded MOF_MCP_QUERY_TIMEOUT_S (cancelled by Postgres).

Narrow the filters.

backend_unavailable

The vault database is unreachable or refused the query.

Retry later; check the deployment.

result_too_large

The result exceeds MOF_MCP_MAX_RESULT_CHARS. Results are refused, never silently cut.

Ask for a smaller limit.

Arguments that do not match a tool's input schema (wrong type, string too long) are refused by schema validation before the tool runs. An unexpected internal failure reaches the client as a generic error without details (they may hold connection strings); the server log has the rest. Over HTTP, a missing or unknown token is rejected with HTTP 401 before any tool runs.

Tiers and tokens

  • Two tiers: read (read-only tools) and act (every tool; implies read). An entry without a tier is act. v1 ships no act-only tool yet; the tiers are enforced all the same. A tool above the caller's tier is not listed to that caller and cannot be called.

  • One token per client, each with its tier, in MOF_MCP_TOKENS as client:sha256hex[:tier], comma-separated. Client ids are 1-32 characters of A-Z a-z 0-9 _ . -. The client id appears in the audit log line of every tool call (tool=... client=... outcome=... ms=..., on stderr; arguments are never logged).

  • Make a token with myopenfund-mcp hash-token <client> [--tier read|act] (in a container: docker compose run --rm mcp myopenfund-mcp hash-token <client>). It generates a random 256-bit token, prints it on stderr, and prints the MOF_MCP_TOKENS entry alone on stdout. Give the token to that client only; the server stores only its SHA-256 digest. It never accepts a token you supply.

  • Entropy: digests are unsalted SHA-256, which is safe only for high-entropy random tokens such as the generated ones. Never use a passphrase or a hand-picked token.

  • Revoke a client: remove its entry and restart. Configuration errors name the faulty entry by position, never by content, so a misplaced token is not echoed into logs.

Configuration and limits

Environment variables only (a .env file in a deployment); see .env.example. An unset or empty variable takes its default; a bad value stops the server at start, naming the variable.

Variable

Default

Meaning

MOF_MCP_TRANSPORT

stdio

stdio or http. The Docker image sets http.

MOF_MCP_HOST

127.0.0.1

HTTP listen address. The Docker image sets 0.0.0.0 (inside the container).

MOF_MCP_PORT

8000

HTTP port (1-65535).

MOF_MCP_PATH

/mcp

HTTP endpoint path; must start with /.

MOF_MCP_TOKENS

unset

client:sha256hex[:tier],...; required when the transport is http.

MOF_VAULT_DATABASE_URL

unset

Vault Postgres URL, read-only role; unset = no vault toolset. Set sslmode explicitly (also avoids a wasted SSL attempt per call): verify-full with the provider's CA preferred; require as a fallback (encrypts, does not authenticate the server); disable only on a trusted private network.

MOF_MCP_QUERY_TIMEOUT_S

20

Postgres statement_timeout per call, in seconds (1-2147483).

MOF_MCP_CONNECT_TIMEOUT_S

10

Postgres connection timeout, in seconds (2-300).

MOF_MCP_DEFAULT_ROWS

50

Rows per page when the caller gives no limit; at most MOF_MCP_MAX_ROWS.

MOF_MCP_MAX_ROWS

200

Largest limit a caller may ask for.

MOF_MCP_MAX_RESULT_CHARS

200000

Largest tool result in characters (at least 1000); larger results are refused. The cap measures the result's text block (compact JSON); the protocol also carries the same data as structured content, so a response on the wire is roughly twice that size.

MOF_MCP_MAX_ERROR_SAMPLES

5

Error samples kept per row of vault_runs_status.

MOF_MCP_MAX_TEXT_CHARS

2000

Each error text in vault_runs_status is cut to this length.

Read by compose.example.yaml only, not by the server: MOF_MCP_BIND_ADDR (host address the port is published on, default 127.0.0.1), MOF_MCP_BIND_PORT (default 8000), MOF_VAULT_DOCKER_NETWORK (the vault stack's Docker network to join).

Local models

The server works with any MCP client, including clients driving local models. No tool needs a hosted LLM: every v1 tool is deterministic and the client's model does the reasoning. Tools that will use an LLM later (such as an answer tool over RAG) are optional, always support a local backend, and are not mounted when no LLM is configured. Tool descriptions are directive and arguments are validated against the input schema, to help small models call them correctly.

Roadmap

  • v1 (this release): server_info and the vault_* toolset over the central-bank corpus, read-only; tiers, tokens, Docker image.

  • v1.1: rag_* (cited passage search over the embedded corpus, embedding as an act job) and jobs_* to follow long actions.

  • v2: companies_* (identity resolution, filings fetched on demand, reported fundamentals) and datasets_* over the MyOpenFund data library.

  • Later: graph_* and more sources.

Development

python -m venv .venv
.venv/bin/pip install -e ".[vault,dev]"
.venv/bin/ruff check .
.venv/bin/python -m pytest -q

The default test run needs no database, network or model. Opt-in suites are excluded by default and run with an explicit marker:

  • pytest -m integration: against a real vault database, with MOF_MCP_IT_DATABASE_URL set.

  • pytest -m llm: a scripted session with a local model, with MOF_MCP_IT_OLLAMA_URL set.

Build the image with docker build -t myopenfund-mcp .; docker run --rm myopenfund-mcp myopenfund-mcp --help lists the commands.

License

MIT, see LICENSE.

Available Tools

1 tool
server_infoServer InfoA
Read-only

What this deployment offers: enabled toolsets, your tier, version, data freshness.

If the vault cannot be read, freshness is null and freshness_error says why; the rest still answers.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and destructiveHint=false, so safety is covered. The description adds genuinely non-obvious behavior: on a vault read failure, `freshness` is null and `freshness_error` explains why while the rest of the response still succeeds. That partial-degradation contract is exactly the kind of thing an agent needs and can't get from the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, zero waste. The payoff (what you get) is front-loaded, and the failure-mode caveat follows. Nothing is repeated from the schema or annotations.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return fields need not be enumerated, and annotations cover the safety profile. The description supplies the one thing structured data can't: the degraded-response behavior on vault read failure. An agent has everything needed to call this and interpret partial results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is nothing for the description to disambiguate; the schema (additionalProperties: false, empty properties) already makes the no-argument contract unambiguous. Baseline 4 applies to a 0-param tool.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states concretely what the tool returns: enabled toolsets, tier, version, and data freshness for 'this deployment' — a specific introspection resource. It reads clearly as a capability/diagnostic tool, though it never uses a verb naming the action (e.g. 'report' or 'get'). With no siblings, differentiation isn't a concern.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied rather than stated: an agent can infer it should call this to learn what is available before using other tools, but the description never says when to call it or why. With no sibling tools there is nothing to route against, so the omission is less costly, but the guidance remains implicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool updatev0.1.0
    • First observedserver_info

TDQS

A3.8/5.0

Scored across 1 tool

Disambiguation5/5

There is only one tool, so there is no possibility of confusing it with another. Its purpose—returning deployment metadata—is clear.

Naming Consistency5/5

With a single tool, there is no pattern to violate. The name `server_info` is clear snake_case and readable.

Tool Count2/5

A one-tool surface is very thin for a server whose name suggests fund/vault operations. The sole tool only reports deployment metadata rather than exposing domain functionality.

Completeness1/5

The surface is severely incomplete for a fund-oriented MCP server: it offers no tools to read or manage vault/fund data. `server_info` is introspection only and leaves obvious domain operations missing.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    A thin stdio gateway to Nodus's Streamable HTTP MCP server, exposing a curated allowlist of tools for the Research Workbench. It enables secure, bearer-authenticated interactions with Nodus without graph logic or caching.
    1
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables MCP clients such as Cursor and Claude Desktop to query a GRC platform over stdio or HTTP, with read-only access by default and opt-in create, update, and delete tools subject to platform authorization.
    3
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Exposes two tools, ask and search, over both streamable HTTP and stdio transports, letting MCP clients pull grounded, inline-cited answers or ranked chunks from an indexed corpus of registered repositories and web pages. The HTTP endpoint is unauthenticated and pinned to the public visibility tier, while the stdio proxy carries the caller's API key and therefore their tier.
    MIT