Self-hosting
The server is one Python service and one Redis-compatible Key Value store (Valkey). You can run it on your own machine or on a host such as Render.
Requirements#
| Part | Version |
|---|---|
| Python | 3.13 |
| uv | A current release |
| Valkey or Redis | One node, no cluster |
| An Apple Developer account | Only for push notifications |
Run it locally#
git clone https://github.com/Aayush9029/mcp-server.git
cd mcp-server
uv sync
cp .env.example .env
valkey-server --save "" &
uv run mcp-client-serverThe server listens on http://localhost:8000. Check it:
curl http://localhost:8000/health{ "status": "ok", "store": "ok" }Make an account with POST /v1/accounts and a key with POST /v1/keys, or point the app at your server. Then see Quickstart, with your own URL.
Settings#
All configuration is environment variables. .env.example lists them. An empty value is the same as no value.
Product#
| Variable | Default | Meaning |
|---|---|---|
APP_NAME |
MCP Client |
The name in the pages, the MCP server info, and the server instructions |
SUPPORT_EMAIL |
support@example.com |
The contact address in the pages |
PUBLIC_BASE_URL |
http://localhost:8000 |
The public URL of the server. Its origin can call /mcp from a browser. |
APP_STORE_URL |
https://apps.apple.com/app/id6821470281 |
The link to the app in the pages |
Store#
| Variable | Default | Meaning |
|---|---|---|
REDIS_URL |
redis://localhost:6379/0 |
The URL of the store |
REDIS_KEY_PREFIX |
mc |
The prefix of each key in the store |
REDIS_MAX_CONNECTIONS |
20 |
The size of the connection pool of each worker |
Use a store that keeps its data on disk and does not evict keys (maxmemory-policy noeviction).
Push notifications#
Push is off until the key, APNS_KEY_ID, APNS_TEAM_ID, and APNS_BUNDLE_ID are all there. The server then logs one line at the start that says that push is off, and each route still works.
| Variable | Meaning |
|---|---|
APNS_KEY_P8 |
The APNs authentication key (.p8) as text. \n is accepted for a new line. |
APNS_KEY_PATH |
The path of the .p8 file. Use this or APNS_KEY_P8. |
APNS_KEY_ID |
The key ID of that key |
APNS_TEAM_ID |
The team ID of the Apple Developer account |
APNS_BUNDLE_ID |
The bundle ID of the app |
APNS_WATCH_BUNDLE_ID |
The bundle ID of the watch app. The default is APNS_BUNDLE_ID. |
Each device says in its registration if its tokens are for the production or the sandbox environment of APNs.
App integrity#
See App integrity. Each check is off until its variable has a value.
| Variable | Default | Meaning |
|---|---|---|
APP_ATTEST_APP_IDS |
none | The app IDs (TEAMID.bundle.identifier) that can make accounts, with commas between them |
APP_ATTEST_ENVIRONMENT |
production |
production or development |
APP_TOTP_SECRET |
none | The base32 secret of the app code |
Limits and access#
| Variable | Default | Meaning |
|---|---|---|
RATE_LIMIT_PER_MINUTE |
600 |
Requests for each credential in one minute. 0 turns the limit off. |
RATE_LIMIT_ACCOUNTS_PER_HOUR |
20 |
New accounts, and challenges, for each IP address in one hour. 0 turns the limit off. |
MCP_ALLOWED_ORIGINS |
none | More browser origins that can call /mcp, with commas between them |
Process#
| Variable | Default | Meaning |
|---|---|---|
HOST |
0.0.0.0 |
The address that the server listens on |
PORT |
8000 |
The port |
WEB_CONCURRENCY |
1 |
The number of worker processes |
LOG_LEVEL |
INFO |
DEBUG, INFO, WARNING, ERROR, or CRITICAL |
Deploy on Render#
The repository has a render.yaml Blueprint. It makes one web service and one Key Value store.
- In Render, make a new Blueprint from your fork of the repository.
- Give the values that Render asks for:
PUBLIC_BASE_URL,SUPPORT_EMAIL, and theAPNS_values. - Wait for the health check on
/health.
A push to the main branch deploys a new version.
Operation#
- The server keeps no state in memory. Several workers, and several instances, can serve the same account.
- The server starts also when the store is not there yet. Until the store answers,
/healthgives503and each API request gives503with the codeunavailable. No restart is necessary after that. - The server deletes old data by itself. See Overview.
- If the server runs behind a proxy that is not Render, the limit for each IP address uses the address of the proxy. Put a rate limit in your proxy in that case.
Delete an account for a person#
A person who lost the device cannot delete the account in the app. Run this inside the service environment (on Render, as a one-off job of the web service), with the account ID or the ID of one of its keys from the support request:
uv run mcp-client-server admin delete-account --key-id key_01jb2k3v9dIt deletes the account with all its data, as DELETE /v1/account does, and prints only the account ID. The exit code is 1 if there is no such account, and 2 if the store does not answer. The data goes in small steps; if the command stops before the end, run it again, or wait for the maintenance loop of the server.
Develop#
uv run pytest
TEST_STORE=fakeredis uv run pytest
uv run ruff check . && uv run ruff format --check .
uv run pyrightuv run pytest starts its own Valkey if valkey-server is installed. TEST_STORE=fakeredis needs no store.