MyOpenFund MCP Server
OfficialClick on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@MyOpenFund MCP Serverfind official primary-source documents about inflation with provenance"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 serveAny 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_readonlyonly, 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 --buildNo 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 alicehash-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_ADDRto the host's private address (the VPN or LAN interface), never0.0.0.0on 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
networksblocks incompose.yaml, setMOF_VAULT_DOCKER_NETWORK) and use the database service name as<host>inMOF_VAULT_DATABASE_URL. On that private Docker network,sslmode=disableis 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.1and terminate TLS at a reverse proxy on the same VM (Caddy, nginx, Traefik) proxying to127.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 /healthanswers200with the bodyok, without a token (it reveals no version and no data);/mcpkeeps requiring one. The image's DockerHEALTHCHECKuses the same endpoint.Reach the vault database over the provider's private network with
sslmode=verify-fulland the provider's CA certificate (sslrootcert=...).sslmode=requireis 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 |
| What this deployment offers: enabled toolsets, your tier, version, data freshness (last vault ingest). Call it first. Open to every authenticated caller. | none |
| Find official documents (metadata, not text). Start here: filter, then open one with |
|
| Every stored fact of one document: dates, source and PDF URLs, publisher, type, |
|
| Live-document counts grouped by one field, largest first (NULL is a real group). |
|
| Is the pipeline healthy? Four lists, newest first: |
|
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 |
| A bad argument: | Fix the argument; pass |
| No document with that | Find ids with |
| The vault query exceeded | Narrow the filters. |
| The vault database is unreachable or refused the query. | Retry later; check the deployment. |
| The result exceeds | Ask for a smaller |
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) andact(every tool; impliesread). An entry without a tier isact. 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_TOKENSasclient:sha256hex[:tier], comma-separated. Client ids are 1-32 characters ofA-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 theMOF_MCP_TOKENSentry 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 |
|
|
|
|
| HTTP listen address. The Docker image sets |
|
| HTTP port (1-65535). |
|
| HTTP endpoint path; must start with |
| unset |
|
| unset | Vault Postgres URL, read-only role; unset = no vault toolset. Set |
|
| Postgres |
|
| Postgres connection timeout, in seconds (2-300). |
|
| Rows per page when the caller gives no |
|
| Largest |
|
| 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. |
|
| Error samples kept per row of |
|
| Each error text in |
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_infoand thevault_*toolset over the central-bank corpus, read-only; tiers, tokens, Docker image.v1.1:
rag_*(cited passage search over the embedded corpus, embedding as anactjob) andjobs_*to follow long actions.v2:
companies_*(identity resolution, filings fetched on demand, reported fundamentals) anddatasets_*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 -qThe 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, withMOF_MCP_IT_DATABASE_URLset.pytest -m llm: a scripted session with a local model, withMOF_MCP_IT_OLLAMA_URLset.
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 toolserver_infoServer InfoARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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 tool update
v0.1.0- First observed
server_info
TDQS
Scored across 1 tool
There is only one tool, so there is no possibility of confusing it with another. Its purpose—returning deployment metadata—is clear.
With a single tool, there is no pattern to violate. The name `server_info` is clear snake_case and readable.
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.
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
Related MCP Connectors
Query SEC EDGAR filings, XBRL financials, and company data via MCP. STDIO or Streamable HTTP.
Guarded MCP server for agent-readable business truth, provenance, readiness, and discovery.
AI Reasoning Cache & Consensus Layer with 11 MCP tools via Streamable HTTP.
Streamable HTTP MCP gateway for NV oOS fleets — one API key, namespaced tools, quotas and policy.
Related MCP Servers
- AlicenseAqualityCmaintenanceA 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.1MIT
- FlicenseNot gradedqualityCmaintenanceEnables MCP clients to call simple tools (add, echo), access a memo resource, and run a summarization prompt over stdio or streamable HTTP.-
- AlicenseNot gradedqualityBmaintenanceEnables 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.3Apache 2.0
- AlicenseNot gradedqualityBmaintenanceExposes 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