databasedmcp gives your AI assistant databases it keeps between conversations, so everything in this article works from the day you connect it. Free to start, no card.
Get started freeWhy build it yourself
If you freelance, you track time for one reason: to bill it. Toggl and Harvest do that with timers, apps and reports. Strip it down and it's clients with a rate, their projects, and entries that start and stop.
When you code with Codex, you're in the terminal, telling it what you're about to work on. Ask it to start the clock as you start the task and stop it when you're done. At the end of the month, one query gives the hours per client and what you're owed.
Connect Codex to databasedmcp
databasedmcp is a remote MCP server that gives AI assistants private SQLite databases, with nothing to install.
Create an API key on the API keys page. Keep it in an environment variable, with export DATABASED_API_KEY=… in your shell profile. The config below names the variable, never the key.
[mcp_servers.databased] url = "https://databasedmcp.com/mcp" bearer_token_env_var = "DATABASED_API_KEY"
Restart Codex and it lists the databasedmcp tools.
Any other MCP client works the same way; the docs have the details.
Copy this prompt to build it
Paste this into Codex to set up the database, its tables and its guide in one go. The rest of this article walks through what it makes and how you use it.
Set up a time tracker for me in databasedmcp, in one go. 1. Create a database called timesheet, described as "Time tracker: clients, projects and time entries started and stopped from the terminal, with invoiced hours marked". 2. Create three tables: - clients: id, name (required), hourly_rate (required, a number in my currency). - projects: id, client_id (references clients), name (required), archived_at (NULL while the project is active). - entries: id, project_id (references projects), started_at (defaults to now, in UTC), ended_at (NULL while the clock runs), note, invoiced_at (NULL until I bill it). 3. Write the database a guide that says what each table is for, that only one entry is open at a time so any open one is closed before another starts, that times are stored in UTC and shown in my local time, and the queries for "hours this week by project" (a running entry counts up to now) and "what can I invoice" (closed entries not yet invoiced: hours times rate per client). From then on: when I say start, close any open entry and open a new one on the project with a note of what I'm working on. When I say stop, set ended_at. When I invoice a client, set invoiced_at on their entries. Show me the tables when you're finished.
Ask for the database
You don't write the schema. Describe what you want:
Set up a time tracker in databasedmcp: clients with an hourly rate, their projects, and time entries with a start, an end and a note. When I say start, open an entry; when I say stop, close it. Track which entries I've invoiced.
Codex creates a database called timesheet and its tables:
CREATE TABLE clients ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, hourly_rate REAL NOT NULL ); CREATE TABLE projects ( id INTEGER PRIMARY KEY, client_id INTEGER NOT NULL REFERENCES clients(id), name TEXT NOT NULL, archived_at TEXT ); CREATE TABLE entries ( id INTEGER PRIMARY KEY, project_id INTEGER NOT NULL REFERENCES projects(id), started_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, ended_at TEXT, note TEXT, invoiced_at TEXT );
It also writes the database a guide on how the tables are meant to be used. The guide comes back with the schema whenever an assistant looks the database up. Codex files things the same way next week as today, and so does any other assistant you connect.
# timesheet: clients, projects, entries
- **clients**: who I bill, with `hourly_rate` in my currency.
- **projects**: one per piece of work for a client.
- **entries**: one per stretch of work. `ended_at` is NULL while the clock runs; only one entry should be open at a time, so close any open one before starting another. `invoiced_at` is set when I bill it.
Times are UTC (CURRENT_TIMESTAMP). Convert to my local time when you show them.
Hours this week: `SELECT c.name AS client, p.name AS project, round(sum(julianday(coalesce(e.ended_at, datetime('now'))) - julianday(e.started_at)) * 24, 2) AS hours FROM entries e JOIN projects p ON p.id = e.project_id JOIN clients c ON c.id = p.client_id WHERE e.started_at >= date('now', 'weekday 0', '-6 days') GROUP BY p.id ORDER BY hours DESC`
To invoice: closed entries with `invoiced_at IS NULL`, hours times rate per client.
Use it
From here on you talk, and Codex writes the rows. Each request below is one statement:
New client: Brightmoor Studio, 95 an hour. The project is their booking site rebuild.
INSERT INTO clients (name, hourly_rate) VALUES (?, ?) RETURNING id -- params: ["Brightmoor Studio",95]
Add the project.
INSERT INTO projects (client_id, name) VALUES (?, ?) RETURNING id -- params: [1,"Booking site rebuild"]
Start the clock on the booking site. I'm on the calendar widget.
INSERT INTO entries (project_id, note) VALUES (?, ?) -- params: [1,"Calendar widget"]
Stop the clock.
UPDATE entries SET ended_at = datetime('now') WHERE ended_at IS NULL
I forgot one: yesterday from 1 to 3 pm, a call with Brightmoor about payments.
INSERT INTO entries (project_id, started_at, ended_at, note) VALUES (?, datetime('now', '-1 day', 'start of day', '+13 hours'), datetime('now', '-1 day', 'start of day', '+15 hours'), ?)
-- params: [1,"Call about payments"]
Written as UTC here. Say your time zone once and have Codex add it to the guide, so every session converts the same way.
Ask it questions
Once there's data, each report you'd pay for is a single query:
How many hours this week, by project?
SELECT c.name AS client, p.name AS project, round(sum(julianday(coalesce(e.ended_at, datetime('now'))) - julianday(e.started_at)) * 24, 2) AS hours FROM entries e JOIN projects p ON p.id = e.project_id JOIN clients c ON c.id = p.client_id WHERE e.started_at >= date('now', 'weekday 0', '-6 days') GROUP BY p.id ORDER BY hours DESC
julianday counts in days, so the difference times 24 is hours. An entry that's still running counts up to now.
What can I invoice?
SELECT c.name AS client, round(sum(julianday(e.ended_at) - julianday(e.started_at)) * 24, 2) AS hours, round(sum(julianday(e.ended_at) - julianday(e.started_at)) * 24 * c.hourly_rate, 2) AS amount FROM entries e JOIN projects p ON p.id = e.project_id JOIN clients c ON c.id = p.client_id WHERE e.ended_at IS NOT NULL AND e.invoiced_at IS NULL GROUP BY c.id ORDER BY amount DESC
What did I work on each day for the last two weeks?
SELECT date(e.started_at) AS day, p.name AS project, round(sum(julianday(coalesce(e.ended_at, datetime('now'))) - julianday(e.started_at)) * 24, 2) AS hours, group_concat(e.note, '; ') AS notes FROM entries e JOIN projects p ON p.id = e.project_id WHERE e.started_at >= date('now', '-14 days') GROUP BY day, p.id ORDER BY day DESC
You can also open the database on the website, page through its rows and run read-only SQL yourself.
What you give up
There's no timer you can see. Toggl and Harvest put a running clock in your menu bar, browser and phone, and some notice when you've gone idle. Here, the clock runs because you asked Codex to start it. It keeps running through lunch if you forget to say stop, so ask "is a timer running?" before you walk away.
It doesn't send invoices, take payments or keep the team's timesheets with approvals. It gives you the hours and the amount, and the invoice goes out from wherever you send invoices now. Anyone on your account can see the entries, but if a team tracks time and a manager approves it, pay for the real thing.
Try it
databasedmcp is free to start, with no card. Free has 100 tool calls a week, enough to set this up and try it for a week. Starter has 1,000 for everyday use.