Self-hosted REST and MCP server for T4L Trainer agent workflows.
Project description
T4L Server
Self-hosted REST and MCP server for exchanging T4L Trainer JSON artifacts between the iPhone app and a coaching agent.
The phone remains the source of truth for accepted training state. The server stores synced context and pending agent results in SQLite so local agents, home-server agents, and private VPS agents can work through the same protocol.
Install
pipx install t4l-server
t4l-server serve --data-dir ~/T4LServerData
The command prints the server URL, MCP URL, and API key. Enter the server URL
and API key in the T4L Trainer Settings screen. Pass --log-level DEBUG for
verbose request and error logging (logs go to stderr; the API key is only ever
printed to stdout on startup).
Agent setup
Agents should read the public instruction repo first:
https://github.com/BigSlikTobi/t4l-agent-instructions
Those instructions explain how to start or verify this server, connect through MCP, wait for fresh phone context, and write app-consumed results safely.
Safety model
- REST and MCP endpoints require the API key, compared in constant time.
- Request bodies over 25 MiB are rejected (413) to bound memory use.
- JSON artifacts are stored in
t4l.sqlite(WAL mode; writes use an immediate transaction so concurrent upserts cannot interleave). - Image blobs are stored under
blobs/. - Path traversal and arbitrary file access are rejected.
- Stored payloads are structurally validated before persisting.
Architecture
Zero runtime dependencies by design — the server is built on the Python
standard library (http.server) so it installs and runs anywhere pipx does.
Keep it dependency-free.
constants.py: single source of truth for artifact kinds, route maps, limits.store.py: SQLite artifact store (WAL, immediate-transaction upserts).server.py: REST handler, auth, body limits, logging.mcp.py: JSON-RPC MCP tools (read context / write results).validation.py: structural payload validation shared by REST + MCP writes.cli.py:serveentrypoint, logging, and thechat-user/chat-agenttest clients (see below).
Chat broker
The server also relays a live chat between the athlete (in the app) and the
coaching agent, replacing any third-party chat app. The conversation is an
append-only message log (its own chat_messages table, keyed by a monotonic
seq cursor — distinct from the latest-only artifact store). v1 is
message-level over polling; token/word streaming and SSE (GET /v1/chat/stream)
are reserved as future enhancements.
App side (REST, requires x-t4l-token):
| Method | Path | Body / query | Response |
|---|---|---|---|
| POST | /v1/chat/messages |
{"content": "..."} (optional conversationId) |
the stored message |
| GET | /v1/chat/messages?since=<seq> |
— | {"messages": [...], "latestSeq": <int>} |
Agent side (MCP tools over the authenticated POST /mcp):
| Tool | Arguments | structuredContent |
|---|---|---|
get_pending_chat_messages |
{} |
{"messages": [...unanswered user turns...]} |
write_chat_reply |
{"content": "...", "inReplyToSeq": <seq>} |
{"posted": {...assistant turn...}} |
Posting a reply atomically marks the answered user turn(s) answered, so
get_pending_chat_messages only ever returns work the agent still owes.
Test the round-trip without an app or an LLM
Two CLI simulators exercise the broker end to end in separate terminals:
# Terminal 1 — broker
t4l-server serve --data-dir ~/T4LChatTest --host 127.0.0.1 --port 8787 --api-key testkey
# Terminal 2 — agent simulator (auto-answers pending messages)
t4l-server chat-agent --server-url http://127.0.0.1:8787 --api-key testkey
# Terminal 3 — athlete/app simulator (interactive)
t4l-server chat-user --server-url http://127.0.0.1:8787 --api-key testkey
> How was my long run?
[user #1] How was my long run?
[assistant #2] You said: How was my long run?
chat-agent --reply "<text>" sends a fixed reply instead of echoing;
chat-agent --once answers the current backlog and exits; chat-user --message "<text>" sends one message, waits for the reply, and exits.
Browser test page
The server also serves a self-contained chat page at GET /chat for testing in
a browser instead of the chat-user CLI. With the broker and chat-agent
running (above), open:
http://127.0.0.1:8787/chat?key=testkey
Type a message and the agent's reply appears within ~1–2s. The page is
same-origin with the API (no CORS) and sends your API key as x-t4l-token; the
?key= query just pre-fills the key field. It is a debug aid — the real client
is the app.
Development
python -m venv .venv
. .venv/bin/activate
python -m pip install -e ".[dev]"
ruff format --check .
ruff check .
mypy
pytest
python -m build
twine check dist/*
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file t4l_server-0.3.0.tar.gz.
File metadata
- Download URL: t4l_server-0.3.0.tar.gz
- Upload date:
- Size: 24.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dc8822481a5c1e2010c8c821da0635cf102602f9d5d66784f159b54b922123b8
|
|
| MD5 |
07f79becedf7b15019b6bfdb654a4749
|
|
| BLAKE2b-256 |
d3fa5be3fda2ad2b43cb751ef3ea19dcc4770307830abb73aa4718c923c698e5
|
Provenance
The following attestation bundles were made for t4l_server-0.3.0.tar.gz:
Publisher:
publish.yml on BigSlikTobi/t4l-server
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
t4l_server-0.3.0.tar.gz -
Subject digest:
dc8822481a5c1e2010c8c821da0635cf102602f9d5d66784f159b54b922123b8 - Sigstore transparency entry: 1675082170
- Sigstore integration time:
-
Permalink:
BigSlikTobi/t4l-server@497184167441c21e2d831e4026b2e633dea29861 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/BigSlikTobi
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@497184167441c21e2d831e4026b2e633dea29861 -
Trigger Event:
push
-
Statement type:
File details
Details for the file t4l_server-0.3.0-py3-none-any.whl.
File metadata
- Download URL: t4l_server-0.3.0-py3-none-any.whl
- Upload date:
- Size: 22.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb35622ae361a5a15e6f35a045f49551d61eb55722008a43f1ab16ecd1677f20
|
|
| MD5 |
604098d53101641d6bbd5eddd7c078b5
|
|
| BLAKE2b-256 |
3886cfb5d701717d42ff4a6e5261fe3924bded3a72830b3cf081ffe0f09aa07e
|
Provenance
The following attestation bundles were made for t4l_server-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on BigSlikTobi/t4l-server
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
t4l_server-0.3.0-py3-none-any.whl -
Subject digest:
bb35622ae361a5a15e6f35a045f49551d61eb55722008a43f1ab16ecd1677f20 - Sigstore transparency entry: 1675082174
- Sigstore integration time:
-
Permalink:
BigSlikTobi/t4l-server@497184167441c21e2d831e4026b2e633dea29861 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/BigSlikTobi
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@497184167441c21e2d831e4026b2e633dea29861 -
Trigger Event:
push
-
Statement type: