Server data from the Official MCP Registry
Query PostgreSQL, MySQL, MariaDB, SQLite and MongoDB with two tools and few tokens.
About
Query PostgreSQL, MySQL, MariaDB, SQLite and MongoDB with two tools and few tokens.
Security Report
Valid MCP server (2 strong, 0 medium validity signals). No known CVEs in dependencies. Imported from the Official MCP Registry.
6 files analyzed · No issues found
Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.
Permissions Required
This plugin requests these system permissions. Most are normal for its category.
What You'll Need
Set these up before or after installing:
Environment variable: QH_DSN
Environment variable: QH_ENGINE
Environment variable: QH_NAME
Environment variable: QH_DESCRIPTION
Environment variable: QH_MODE
Documentation
View on GitHubFrom the project's GitHub README.
qh
A database server for AI agents, built to use as few tokens as possible. It exposes two MCP tools, whatever the number of connections, and names the connections in its MCP instructions:
| Tool | What it does |
|---|---|
schema | Overview of tables with their columns, keys and foreign keys, or full definitions with types, foreign keys in both directions, enum values and indexes. With no arguments in a workspace with several connections, lists the connections |
query | Runs one statement and returns TSV, 100 rows by default, with a footer that says when rows were cut |
Supported now: PostgreSQL 13+, MySQL 8.0.16+, MariaDB 10.6+, SQLite and MongoDB (7 and 8).
qh serve adds an HTTP server mode with a REST API.
bench/ compares qh with other database MCP servers on the same questions, model and prompts (see bench/README.md). Accuracy was equal in every comparison:
| Rival | Datasets | Fewer tokens per task with qh |
|---|---|---|
| Google's MCP Toolbox for Databases 1.14.0 | Postgres, MySQL, SQLite | 83.6% |
| dbhub 1.5.0 | Postgres, MySQL, SQLite | 40% |
| mongodb-mcp-server 3.0.5 | MongoDB | 95.2% |
Install
With Homebrew, on macOS or Linux:
brew install anderson-0/tap/qh
Or download a binary for Linux, macOS or Windows (amd64 or arm64) from the
releases page, unpack it and put qh on your PATH.
Each release lists SHA-256 checksums in checksums.txt. The binaries are not signed: on macOS, a binary
downloaded with a browser needs xattr -d com.apple.quarantine qh before its first run.
Or run the server image, which starts server mode on port 8765 and keeps its store in the /data volume.
Create the master key once and keep it: it decrypts the stored connection strings, so pass the same key every
time you recreate the container, for example to upgrade:
openssl rand -base64 32 > qh-master.key && chmod 600 qh-master.key
docker run -d --name qh -p 127.0.0.1:8765:8765 -v qh-data:/data \
-e QH_MASTER_KEY="$(cat qh-master.key)" ghcr.io/anderson-0/qh:1
docker logs qh # shows the admin API key once, on first start
- Exposure. API keys travel in plain HTTP, so the example listens on localhost only. Put a TLS proxy in front of qh before exposing it to other machines.
- Arguments. Arguments after the image name replace the default
serve --http :8765. - Reaching host databases. Inside the container,
localhostis the container itself. Reach a database on the Docker host ashost.docker.internal; on Linux, add--add-host=host.docker.internal:host-gateway. - Health checks.
GET /healthzanswersokwithout a key, for liveness and readiness probes. - Storage. The image runs as uid 65532:
- a named volume works as shown;
- a bind-mounted directory must be writable by that uid (
chown 65532 DIR); - on Kubernetes, set
securityContext.fsGroup: 65532.
Or build it with Go:
go install github.com/anderson-0/qh/cmd/qh@latest
Add a connection
Connections are read-only unless you pass --mode rw. Pass --dsn - to read the connection string from stdin
so the password stays out of your shell history:
qh conn add shop --engine postgres --dsn - --description "Orders and customers"
qh conn add local --engine sqlite --dsn ./app.db
qh conn add app --engine mysql --dsn - --description "Product catalogue"
Connection strings are encrypted (AES-256-GCM) in ~/.config/qh/qh.db; the key is ~/.config/qh/master.key
(or $QH_MASTER_KEY). $XDG_CONFIG_HOME/qh is used instead of ~/.config/qh when $XDG_CONFIG_HOME is set;
set $QH_HOME to use another directory.
Use it from your editor
qh speaks MCP over stdio with qh mcp. Connections you add or edit with qh conn while it runs are used on
the agent's next call (not when QH_DSN is set; see below). Some apps don't inherit your shell's PATH; if the server fails to start, use the full
path that which qh prints instead of qh.
Claude Code
claude mcp add qh -- qh mcp
Codex CLI
codex mcp add qh -- qh mcp
Gemini CLI
gemini mcp add -s user qh qh mcp
VS Code (GitHub Copilot agent mode)
code --add-mcp '{"name":"qh","command":"qh","args":["mcp"]}'
Claude Desktop, Cursor, Windsurf (Devin Desktop): add this to the app's MCP config file:
{
"mcpServers": {
"qh": { "command": "qh", "args": ["mcp"] }
}
}
| App | Config file |
|---|---|
| Claude Desktop | macOS ~/Library/Application Support/Claude/claude_desktop_config.json, Windows %APPDATA%\Claude\claude_desktop_config.json (Settings → Developer → Edit Config); restart the app afterwards |
| Cursor | ~/.cursor/mcp.json, or .cursor/mcp.json in a project |
| Windsurf (Devin Desktop) | ~/.config/devin/mcp_config.json, or %APPDATA%\devin\mcp_config.json on Windows; older Windsurf installs use ~/.codeium/windsurf/mcp_config.json |
Zed: add it under context_servers in ~/.config/zed/settings.json (Windows: %APPDATA%\Zed\settings.json):
{
"context_servers": {
"qh": { "command": "qh", "args": ["mcp"] }
}
}
One connection from the environment
Docker's MCP catalog and the official MCP registry configure a server through environment variables only.
When QH_DSN is set, qh mcp serves exactly that one connection and ignores the connections added with
qh conn add:
export QH_DSN='postgres://reader:…@db.example.com/shop'
docker run -i --rm -e QH_DSN ghcr.io/anderson-0/qh:1 mcp
Passing -e QH_DSN without a value copies it from your shell, so the password stays out of ps and your shell
history. Inside a container localhost is the container itself; reach a database on the Docker host as
host.docker.internal (see Install).
QH_DSN: the connection string. The engine comes from its scheme (postgres://,postgresql://,mysql://,mariadb://,mongodb://,mongodb+srv://,sqlite://,file:) or from a path ending in.db,.sqliteor.sqlite3. For any other string, such asuser:pw@tcp(host)/db, setQH_ENGINE.QH_ENGINE:postgres,mysql,mariadb,mongodborsqlite. It always wins over the inferred engine.QH_NAME: the connection name agents pass assource(defaultdb).QH_DESCRIPTION: optional text that tells agents what the database holds.QH_MODE:ro(default) orrw.
This mode keeps nothing on disk that outlives the process: the store and the query log are a temporary file
removed on exit, the random master key exists only in memory, and the data directory and master.key are never
created. Connections you add with qh conn are not used while QH_DSN is set. The string is checked at startup
but the database is not contacted, so an unreachable database fails per query rather than stopping the server.
QH_DSN cannot be combined with --store, QH_STORE_DSN, --server or a non-default --workspace.
With docker run --read-only, mount a writable /tmp for the temporary store: --tmpfs /tmp.
Server mode
Run qh for a team or as the backend of a product:
export QH_MASTER_KEY=$(openssl rand -base64 32) # keep it: it decrypts stored connection strings
qh serve --http :8080 --data-dir /var/lib/qh
On first start qh prints one admin API key (admin API key (shown once): qh_…), or uses QH_BOOTSTRAP_ADMIN_KEY
if you set it; that must be a qh_ key whose secret has at least 32 characters (for example one printed by
qh key create --scopes admin). Every request needs Authorization: Bearer <key>:
- Workspaces isolate connections. Admin keys manage workspaces and keys and pick a workspace with the
X-QH-Workspaceheader. - Scopes:
query(MCP tools, schema, query),manage(create, change and delete connections; create query/manage keys for the same workspace, which expire no later than the key that creates them),admin(everything). - MCP is served at
/mcp(Streamable HTTP). The REST API is under/api/v1; see/api/v1/openapi.json. qh keyandqh workspaceact on the local store at--home, or on a server with--server/--key(see Remote CLI).
curl -s -X POST localhost:8080/api/v1/workspaces -H "Authorization: Bearer $ADMIN" -d '{"name":"team"}'
curl -s -X POST localhost:8080/api/v1/keys -H "Authorization: Bearer $ADMIN" \
-d '{"workspace":"team","scopes":["query","manage"],"name":"team"}'
curl -s -X POST localhost:8080/api/v1/connections -H "Authorization: Bearer $TEAM" \
-d '{"name":"shop","engine":"postgres","dsn":"postgres://reader:…@db.internal/shop"}'
claude mcp add --transport http qh http://localhost:8080/mcp --header "Authorization: Bearer $TEAM"
Server mode refuses database addresses on loopback and link-local ranges (including cloud metadata at
169.254.169.254), and Unix sockets. Use --allow-cidr 127.0.0.0/8 to allow a local database (this does not help
when qh runs in a container, because the host's databases are not on the container's loopback; see the
host.docker.internal note under Install), and --deny-cidr to block more (for example your private ranges). SQLite is off unless you pass --sqlite-root DIR; files must
then live under DIR/<workspace>/, so each workspace sees only its own directory. Each API key may run 4
queries at once; an extra request waits up to 10 s for a free slot, then gets HTTP 429.
Server mode ignores PG* environment variables (it unsets them at startup and names them on stderr), and a
Postgres connection string may only use these settings: host, port, dbname, user, password,
sslmode, sslsni, sslnegotiation, connect_timeout, target_session_attrs, application_name,
channel_binding, require_auth, min_protocol_version and max_protocol_version. Settings that read
files on the server (passfile, service, sslcert, sslrootcert, …) are refused, and ~/.pgpass and
~/.postgresql/ are not used.
Without --http, qh serve runs in local mode on 127.0.0.1:8765 with no keys, for tools on your own machine.
Local mode is unauthenticated: every process on the machine can use it, with all your connections. It only
accepts requests addressed to localhost and refuses cross-origin browser requests.
Running several instances
By default qh serve keeps its store in a SQLite file in --data-dir. To run several instances behind a load balancer, point them all at one Postgres database:
export QH_STORE_DSN='postgres://qh@db.internal:5432/qh?sslmode=verify-full'
export QH_STORE_PASSWORD_FILE=/run/secrets/qh-store-password # or put the password in the URL
export QH_MASTER_KEY=… # the same key on every instance
qh serve --http :8765 --allow-cidr 10.0.0.0/8
- Store contents. qh creates its tables in a
qhschema on first start. Instances starting together apply each migration once and create one admin key; only the instance that created it prints it (setQH_BOOTSTRAP_ADMIN_KEYthe same on every instance to choose the key yourself). - Changes are shared. A connection, key or workspace changed through any instance applies to all of them on the next call. A key revoked on one is refused by all at once. A schema change made through one instance (a write or DDL statement sent to
query, a connection change,refresh, or deleting a workspace) drops the cached schema everywhere. DDL run directly against the database still waits for the cache to expire. - No stickiness needed. MCP over HTTP is stateless, so the load balancer needs no stickiness.
- Limits are per instance. That covers the per-key concurrency limit, each connection's
pool_size(so a database sees up to instances ×pool_sizeconnections), and the total connection cap. Size them for the instance count. - Query-log times come from each instance's clock. Keep clocks in sync.
- The store's URL must contain everything except the password. Server mode clears
PG*variables at startup, so host, port, database, user and TLS settings all go in the URL; the password may come fromQH_STORE_PASSWORD_FILEinstead. - The master key comes from
QH_MASTER_KEYonly. With a shared store, qh never reads or createsmaster.keyin the data directory: a key file on one host could seal data under a key the other instances lack. Every command that opens the store (serve,mcp,conn,query,masterkey rotate, and the rest) fails withoutQH_MASTER_KEYwhen--storeorQH_STORE_DSNis set. - Connections per process. Every qh process (each
qh serve, and eachqh mcptoo) holds up to 10 pooled store connections plus 1 for listening to changes. Size the database'smax_connectionsfor the number of processes × 11, and mind this when manyqh mcpprocesses share the store.
Moving an existing install: stop it, then copy its store into an empty database. Keep the same master key (the copy moves sealed data as it is, so it does not need the key itself). The target's password goes in the --to URL or in QH_STORE_PASSWORD_FILE. The copy reads the store selected by --store or QH_STORE_DSN, so leave QH_STORE_DSN unset for this command and give the new database with --to:
qh store copy --home /var/lib/qh --to 'postgres://qh@db.internal:5432/qh?sslmode=verify-full' --with-query-log
Rotating the master key without downtime:
- Restart every instance with
QH_MASTER_KEY=<new>andQH_MASTER_KEY_OLD=<old>. Each can open connections sealed with either key. Wait until every instance has restarted before changing any connection (conn add,conn edit, and the like), because a connection sealed under the new key cannot be opened by an instance that still runs with the old key only. - From any host, with
QH_MASTER_KEY=<new>and the store password (in the URL orQH_STORE_PASSWORD_FILE), runqh masterkey rotate --store "$QH_STORE_DSN" --old-key-file old.key, whereold.keyholds the old key. (Instances running with both keys may stay up; the single-instance advice to stopqh servefirst does not apply.) - Restart every instance without
QH_MASTER_KEY_OLD.
Commands run from an admin host between these steps read QH_MASTER_KEY_OLD too, so they can open connections sealed with either key.
Connection details and TLS
Instead of a connection string you can give the parts, which qh assembles and escapes:
printf '%s\n' "$PASSWORD" | qh conn add shop --engine postgres --host db.internal --database shop \
--user reader --password-stdin --param application_name=qh
Over REST, send host, port, database, username, password and params instead of dsn.
TLS settings replace whatever the connection string says about TLS:
qh conn add shop --engine postgres --dsn - --tls-mode verify-full --tls-ca ca.pem --tls-cert client.pem --tls-key client.key
- Modes:
disable;require, which encrypts without checking the certificate;verify-ca, which checks the certificate chain;verify-full, which checks the chain and the host name, or--tls-server-name.
- The private key is sealed with the connection string, and it is never shown again.
- In server mode this is the only way to use client certificates, because connection-string settings that read server files are refused.
qh conn edit NAME --no-tlsremoves the settings.
SSH tunnels and secret references
Reach a database through a bastion host:
qh conn add prod --engine postgres --dsn 'postgres://app@db.internal:5432/app' --password-ref env:PROD_DB_PASSWORD \
--ssh-host jump@bastion.example.com --ssh-key ~/.ssh/id_ed25519
The first run fails and shows the bastion's host-key fingerprint. Verify it (ssh-keyscan -t ed25519 bastion.example.com | ssh-keygen -lf -) and add --ssh-host-key SHA256:…. qh prefers the bastion's Ed25519 host key, as OpenSSH does, and refuses any other key. The key file is read by the CLI and stored sealed, like --tls-key. Give the SSH user with --ssh-user or as user@ in --ssh-host. For password authentication use --ssh-password-stdin; for an encrypted key use --ssh-passphrase-stdin.
Instead of storing a password or connection string, a connection can reference one:
| Reference | Value |
|---|---|
env:NAME | an environment variable of the qh process |
file:/path | a file's contents (at most 64 KiB, trailing newline removed), e.g. a Docker or Kubernetes secret |
aws-sm://<name or ARN>[#key] | an AWS Secrets Manager secret, or one field of a JSON secret, read with the qh process's AWS credentials |
Use them with --dsn-ref, --password-ref, --ssh-key-ref, --ssh-passphrase-ref or --ssh-password-ref. References are resolved when qh connects and never stored. After a rotation, an open connection keeps working until it fails to authenticate. That call returns connection_failed, and the next call reconnects with the new value.
Server mode. A tenant must not be able to read the server's environment or cloud secrets, or tunnel into its network:
- References are refused unless the operator allows them:
qh serve --http :8765 --allow-secret-ref 'env:TENANT_DB_*' --allow-secret-ref 'aws-sm://arn:aws:secretsmanager:us-east-1:123456789012:secret:qh/*'. - An allowed reference can be used by every tenant with a manage key on that server, and a tenant can read its value (for example by pointing a connection at a server it controls, which then receives the password). Allow only secrets you would hand to all tenants. Tenants who must not share secrets need separate servers.
- For
file:patterns, a mounted secret that is a symlink (as in Kubernetes secret volumes) must be allowed with a directory pattern such asfile:/var/run/secrets/db/*, because the real path behind the symlink must also match. - Both the bastion and the database behind it are checked against the server's network policy (
--allow-cidr/--deny-cidr). By default only loopback, unspecified and link-local addresses (including the cloud metadata addresses) are refused, so private ranges such as10.0.0.0/8stay reachable; add--deny-cidr 10.0.0.0/8 …to keep tenants out of them. A database on the bastion's own loopback is allowed. - A tenant's encrypted SSH private key may use at most 128 KDF rounds (
ssh-keygen -a; the default is 16). Re-encrypt one with more usingssh-keygen -p -a 100 -f KEY. RSA keys may be at most 8192 bits, and DSA keys are refused. The key is decrypted when qh connects, so a wrong passphrase is reported by the connection test, not when the connection is saved without one. --deny-sshturns tunnels off.
Query log
qh logs every query call: connection, API key, statement (up to 8 KiB), rows, duration and error category.
- Show it with
qh log [--connection NAME] [--since 24h] [--limit N], orGET /api/v1/query-log(scopemanage). qh serveandqh mcpkeep 30 days (--query-log-retention 7d), or log nothing with--no-query-log.- The log stores statements as sent. If statements carry sensitive literals, turn it off or shorten the retention.
Rotating the master key
With the default SQLite store, stop qh serve first. With a shared Postgres store, do not stop the instances: follow the steps under Running several instances instead.
printf '%s\n' "$OLD_QH_MASTER_KEY" > old.key # the previous key
export QH_MASTER_KEY=$(openssl rand -base64 32) # the new key
qh masterkey rotate --old-key-file old.key --home /var/lib/qh
In local mode, move master.key aside (e.g. to old.key) and run qh masterkey rotate --old-key-file old.key --generate;
--generate creates the new master.key. Without it, rotate refuses to run when no current key exists, so a missing
QH_MASTER_KEY can never silently re-seal everything with a key you did not choose.
Every stored connection is re-sealed with the new key in one transaction. If one cannot be opened with the old key, nothing changes. Restart the server with the new key. The old key is no longer needed.
Remote CLI
Every conn, query, schema, log, workspace and key command can run against a server:
export QH_SERVER=https://qh.example.com QH_KEY=qh_…
qh conn ls
qh query shop "SELECT count(*) FROM orders"
qh --key "$ADMIN" --workspace team key create --scopes query,manage # admin keys name a workspace
--workspace(orQH_WORKSPACE) also picks the workspace for local commands.- The CLI refuses to send a key over plain
http://to another machine. - Prefer
QH_KEYto--key: command-line arguments are visible to other users of the machine and saved in shell history. mcp,serveandmasterkeyalways run locally.
MongoDB
qh conn add events --engine mongodb --dsn - # paste mongodb://user:pw@host:27017/events
qh query events 'db.orders.find({status: "paid"}).sort({total: -1}).limit(5)'
Agents query MongoDB through the same schema and query tools, in mongosh syntax. qh parses a fixed subset and never runs JavaScript:
- Reads:
find,findOne,aggregate,countDocuments,estimatedDocumentCount,distinct,getIndexesanddb.getCollectionNames().findtakes.sort,.limit,.skip,.project,.hint,.collation,.count()and.explain(). - Writes and admin calls: these need a read-write connection. A write that is only partly applied reports how much was written.
- Document writes:
insertOne/Many,updateOne/Many,replaceOne,deleteOne/ManyandfindOneAnd*. - Aggregates with
$out/$merge. - Admin calls:
createIndex(es),dropIndex(es),drop,renameCollectionanddb.createCollection.
- Document writes:
- Literals: relaxed JSON plus
ObjectId(),ISODate(),new Date("…"),NumberLong(),NumberInt(),NumberDecimal(),UUID(),Timestamp(),MinKey(),MaxKey()and/regex/flags. - Results: TSV, with
_idfirst. Nested documents and arrays are compact JSON, and a missing field isNULL. - Schema: sampled from up to 100 documents per collection.
- Fields show as dotted paths with their types, and the overview folds nested fields as
address{city,country,…}. - String fields list the values seen, ending in
…, only when at least 50 sampled documents have the field, with at most 8 distinct values, none longer than 40 characters. - Fields named
<x>_id,<x>Id,<x>_keyor<x>_ref, such ascustomer_id, show as references (→customers). The target is the collection named<x>or<x>s, or else the one collection whose name ends in_<x>or_<x>s. The field's type must match the target's_idtype, and at least 4 of 5 sampled values must exist in the target (all of them, if fewer than 5 were sampled).
- Fields show as dotted paths with their types, and the overview folds nested fields as
- Databases: the database in the connection string is the default, and the only one qh introspects. If it names none, every non-system database is listed and
db.<collection>usestest, as mongosh does. Without--namespaces(allowed_namespacesin the REST API),db.getSiblingDB("x")still reaches other databases, and the server's own authorisation decides what is allowed. With it, a query is refused withforbiddenif it names another database, either throughgetSiblingDBor in a$out,$merge,$lookupor$unionWithstage. - Server mode: connection strings may only use an allowlist of options (names are matched the way the driver matches them): authentication and replica-set settings,
tls/ssl, timeouts, pool sizes, read and write concerns, compression and similar. Options that read files on the server, such astlsCAFile,tlsCertificateKeyFile,tlsCertificateKeyFilePasswordand theirssl*aliases, are refused, and so aretlsInsecure,tlsAllowInvalidCertificatesandtlsAllowInvalidHostnames. Use qh's TLS settings instead.authMechanismmay beSCRAM-SHA-1,SCRAM-SHA-256,PLAINorMONGODB-X509; the others (MONGODB-AWS,GSSAPI,MONGODB-OIDC) take the qh host's own credentials.authMechanismPropertiesis refused. The driver's OCSP checks go through the same address guard as database connections. - Server-side JavaScript: qh does not block
$whereand$function; they run as part of reads. On MongoDB 7 and 8 they have no database handle, so they cannot reach other databases. A MongoDB role cannot forbid them for a user who mayfindoraggregate; only disabling server-side scripting on the server (security.javascriptEnabled: false) does.
Use a MongoDB user with the read role for read-only connections; qh checks every call, and the role is a second line of defence.
Read-only safety
On read-only connections every query runs in a read-only transaction (Postgres, MySQL/MariaDB) or on a file opened
read-only (SQLite), and qh also rejects statements that would write before they reach the database. Read-only
transactions cannot stop everything. In Postgres, an extension such as dblink or postgres_fdw can open a
second connection that is not read-only. In MySQL/MariaDB, a read-only transaction does not limit server-side
file access, so don't grant FILE (LOAD_FILE, SELECT ... INTO OUTFILE). Give qh a database user that can
only read:
CREATE ROLE qh_reader LOGIN PASSWORD 'change-me';
GRANT CONNECT ON DATABASE shop TO qh_reader;
GRANT USAGE ON SCHEMA public TO qh_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO qh_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO qh_reader;
For MySQL/MariaDB, grant only SELECT (and SHOW VIEW for views):
CREATE USER 'qh_reader'@'%' IDENTIFIED BY 'change-me';
GRANT SELECT, SHOW VIEW ON shop.* TO 'qh_reader'@'%';
Don't grant the FILE privilege: with it, SELECT LOAD_FILE(...) reads files on the database server.
Development
make test # needs Docker for the Postgres, MySQL/MariaDB and MongoDB tests
make test-short # skips Docker tests
make build # static binary in bin/qh
License
MIT. See LICENSE.
Reviews
No reviews yet
Be the first to review this server!
More Developer Tools MCP Servers
Fetch
Freeby Modelcontextprotocol · Developer Tools
Web content fetching and conversion for efficient LLM usage
Git
Freeby Modelcontextprotocol · Developer Tools
Read, search, and manipulate Git repositories programmatically
Toleno
Freeby Toleno · Developer Tools
Toleno Network MCP Server — Manage your Toleno mining account with Claude AI using natural language.
MCP Marketplace
Freeby mcp-marketplace · Developer Tools
Search and install MCP servers from inside your AI client.
mcp-creator-python
Freeby mcp-marketplace · Developer Tools
Create, build, and publish Python MCP servers to PyPI — conversationally.
FinAgent
Freeby mcp-marketplace · Finance
Free stock data and market news for any MCP-compatible AI assistant.
