mcp-server
Health Gecti
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 48 GitHub stars
Code Gecti
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
MCP SERVER
MCP Client server
One service that lets AI agents report their work to a person's Apple devices.
AI agents (Claude Code, Cursor, Codex, and other MCP clients) connect over the Model Context Protocol with an API key. They create tasks and keep them current. The app on iPhone, iPad, and Apple Watch shows those tasks live, gets push notifications, and shows a Live Activity for work in progress. The app makes the API keys.
The app name is configuration (APP_NAME). This document says "the app".
What is in the service
One ASGI application gives all of these surfaces:
| Surface | Path | For |
|---|---|---|
| REST API | /v1/... |
The app, and scripts |
| MCP endpoint | /mcp |
AI agents. MCP 2026-07-28 Streamable HTTP, stateless. Earlier revisions also work. |
| Event stream | /v1/events |
The app. Server-sent events for live updates. |
| Pages | /, /privacy, /terms, /support |
People |
| Health | /health |
The platform |
The contract for all of them is docs/api.md.
Architecture
app / scripts ──REST──┐ ┌── APNs (HTTP/2, JWT)
├─► services ─► store ─► Key Value (Valkey)
AI agents ─────MCP────┘ │ │
└─► push dispatcher └─ pub/sub ─► event hub ─► /v1/events
- One service layer. The REST routes and the MCP tools call the same functions in
services/. The MCP tools never call the REST API. - One store.
store/is the only code that talks to the Key Value instance. Each write that touches more than one key is one Lua script, so the indexes cannot go out of step. No request usesKEYSorSCAN. The key schema is at the top ofstore/schema.py. - Stateless processes. A process keeps no session. Several workers and several instances can serve the same account. Events go through pub/sub, and each process has one subscriber connection that fans out to its own streams.
- Push off the request path. A request returns before the server talks to APNs.
src/mcp_client_server/
config.py settings from environment variables
models.py tasks, keys, accounts, devices, and the request bodies
credentials.py secrets, hashes, the authenticated principal
store/ key schema, Lua scripts, the repository
services/ authentication, accounts, devices, keys, tasks, task expiry
api/ REST routes, error bodies, client IP
mcp/ MCP server: tools, resources, prompts, API key check
events/ SSE frames and the pub/sub hub
push/ APNs client, payloads, and the rules for who gets which push
web/ the HTML pages
main.py application factory
cli.py `mcp-client-server` command
Quick start
You need uv and a Valkey or Redis server.
brew install valkey && valkey-server --save "" & # or: docker run -p 6379:6379 valkey/valkey
uv sync
cp .env.example .env
uv run mcp-client-server
The server listens on http://localhost:8000. Without the APNs variables, push is off and all other functions work.
Try the API:
# 1. The app makes an account. The token comes back one time.
curl -s -X POST localhost:8000/v1/accounts -H 'Content-Type: application/json' -d '{}'
# 2. The app makes an API key for an agent. The secret comes back one time.
curl -s -X POST localhost:8000/v1/keys -H "Authorization: Bearer $ACCOUNT_TOKEN" \
-H 'Content-Type: application/json' -d '{"label": "Claude Code"}'
# 3. The agent (or a script) uses the key.
curl -s -X POST localhost:8000/v1/tasks -H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' -d '{"title": "Write unit tests"}'
# 4. The app follows the changes.
curl -N localhost:8000/v1/events -H "Authorization: Bearer $ACCOUNT_TOKEN"
Connect an agent
Use a key from the app in place of mck_YOUR_KEY, and your host in place of HOST. The start page of a running server shows the same text with its own URL.
Claude Code
claude mcp add --transport http tasks https://HOST/mcp --header "Authorization: Bearer mck_YOUR_KEY"
Cursor (~/.cursor/mcp.json)
{
"mcpServers": {
"tasks": {
"url": "https://HOST/mcp",
"headers": { "Authorization": "Bearer mck_YOUR_KEY" }
}
}
}
Codex (~/.codex/config.toml, with TASKS_API_KEY set in the shell)
[mcp_servers.tasks]
url = "https://HOST/mcp"
bearer_token_env_var = "TASKS_API_KEY"
Other MCP clients
Use the Streamable HTTP transport with the URL https://HOST/mcp and the header Authorization: Bearer mck_YOUR_KEY. The header X-API-Key: mck_YOUR_KEY also works. For a client that cannot send headers, use https://HOST/mcp?key=mck_YOUR_KEY. A URL can appear in the logs of a proxy, so use a header if you can.
The agent gets six tools (create_task, list_tasks, get_task, update_task, delete_task, notify_user), two resources, two prompts, and server instructions that tell it when to use them.
Configuration
All configuration comes from environment variables. A .env file is read in local work. The repository has no secrets.
| Variable | Default | Meaning |
|---|---|---|
APP_NAME |
MCP Client |
Product name in the pages and in the MCP server info |
SUPPORT_EMAIL |
[email protected] |
Contact address in the pages |
PUBLIC_BASE_URL |
http://localhost:8000 |
Public URL of the service, in the setup text and for the Origin check on /mcp |
APP_STORE_URL |
none | If set, the start page links to it |
REDIS_URL |
redis://localhost:6379/0 |
Key Value connection string |
REDIS_KEY_PREFIX |
mc |
Prefix of all keys and channels |
REDIS_MAX_CONNECTIONS |
20 |
Connection pool size for each process |
APNS_KEY_P8 |
none | Contents of the APNs .p8 key (\n is accepted for a new line) |
APNS_KEY_PATH |
none | Path of the .p8 key, as an alternative to APNS_KEY_P8 |
APNS_KEY_ID |
none | Key ID of the APNs key |
APNS_TEAM_ID |
none | Apple developer team ID |
APNS_BUNDLE_ID |
none | Bundle ID of the app. Live Activity pushes use <bundle ID>.push-type.liveactivity. |
APNS_WATCH_BUNDLE_ID |
APNS_BUNDLE_ID |
Bundle ID of the watch app, if its topic is different |
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 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 |
HOST, PORT |
0.0.0.0, 8000 |
Listen address |
WEB_CONCURRENCY |
1 |
Number of worker processes |
LOG_LEVEL |
INFO |
Log level |
Push is on only if a key (APNS_KEY_P8 or APNS_KEY_PATH), APNS_KEY_ID, APNS_TEAM_ID, and APNS_BUNDLE_ID are all set. If not, the server writes one log line at startup and sends no pushes.
Deploy to Render
render.yaml is a Blueprint with one web service (native Python runtime with uv, health check on /health) and one Key Value instance (maxmemoryPolicy: noeviction, with persistence).
- Put the repository on GitHub or GitLab.
- In the Render Dashboard, select New > Blueprint and select the repository.
- Give the values that the Blueprint asks for:
PUBLIC_BASE_URL,SUPPORT_EMAIL, and theAPNS_*values.APP_STORE_URLis optional. - After the first deploy, set
PUBLIC_BASE_URLto the URL of the service, or to your own domain.
Notes for production:
- The Key Value instance is the only database. Keep
noevictionand a plan that has persistence. - The store must be one node. The Lua scripts build key names at run time, which a cluster does not permit.
- On Render, the server takes the client address for the per-IP limit from the
CF-Connecting-IPheader that the Render edge sets. It reads proxy headers only if theRENDERenvironment variable is set, which Render does. On a different platform, put the server behind a proxy that you control and adaptapi/clientip.py.
Security
- A credential (
mca_account token,mck_API key) has 256 random bits. The server stores only its SHA-256 hash and returns the secret one time. - The server does not write credentials to its logs. It removes
?key=from the access log. - An API key reaches only its own tasks. An account reaches only its own keys, devices, and tasks. A resource of a different owner gives
404. - Rate limits are counters in the Key Value store, so they hold across workers.
Development
uv run pytest # tests
uv run ruff check . # lint
uv run ruff format . # format
uv run pyright # types
The tests start a private valkey-server or redis-server if one is installed. If not, they use fakeredis. Set TEST_STORE=fakeredis to use fakeredis always, or TEST_REDIS_URL to use a server that runs.
The MCP tests connect the official SDK client to the application, one time with protocol 2026-07-28 and one time with the initialize handshake of the earlier revisions.
Load test
RATE_LIMIT_PER_MINUTE=0 RATE_LIMIT_ACCOUNTS_PER_HOUR=0 LOG_LEVEL=WARNING uv run mcp-client-server
uv run python scripts/load.py --url http://127.0.0.1:8000
The script measures create_task, list_tasks with 500 tasks, and an MCP tools/call, and prints requests for each second with the p50, p95, and p99 latency.
One measurement, as a reference: one worker process, 32 connections, Valkey 9.1 on the same machine (an Apple silicon laptop that also did other work), push off.
| Operation | Requests/s | p50 | p95 | p99 |
|---|---|---|---|---|
create_task (REST) |
4,410 | 7.1 ms | 8.3 ms | 9.1 ms |
list_tasks, 500 tasks in one page (REST) |
2,126 | 14.3 ms | 19.4 ms | 21.8 ms |
tools/call update_task (MCP 2026-07-28) |
2,753 | 11.5 ms | 14.3 ms | 17.4 ms |
With 32 connections on one worker, most of each latency is time in the queue.
License
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi