Skip to main content
Glama
README.md
# goalslot-mcp

An [MCP](https://modelcontextprotocol.io) server for [GoalSlot](https://goalslot.io). It gives an AI assistant tools for goals, tasks, the weekly schedule template, time tracking, the shared timer, reports, notes and the journal, against your own GoalSlot account.

Runs over stdio, so any MCP host can spawn it. Ships four skills that teach a model how to use the tools well.

## Install

You need the [`goalslot` CLI](https://github.com/ZeeshanAdilButt/goalslot-cli) signed in first, because this server reads the credential the CLI writes. It never runs a login flow itself.

```bash
npm install -g goalslot-cli
goalslot login
```

Then add the MCP server to Claude Code:

```bash
claude mcp add goalslot -- npx -y goalslot-mcp
```

For Claude Desktop, or any host that takes a JSON config:

```json
{
  "mcpServers": {
    "goalslot": {
      "command": "npx",
      "args": ["-y", "goalslot-mcp"]
    }
  }
}
```

Check it is working:

```bash
npx -y goalslot-mcp --list-tools
```

> This package is not published to npm yet. Until it is, install it from source: clone the repo, `npm install && npm run build`, then point the host at `node /path/to/goalslot-mcp/dist/cli.js`.

## How auth works

There is no login flow in this server. stdio is the protocol channel, so a server cannot prompt for anything, and asking a model to handle a token would be worse. It reads a credential that already exists on the machine.

Resolution order:

1. `GOALSLOT_ACCESS_TOKEN` from the environment, if set and non-empty.
2. `$GOALSLOT_CONFIG_DIR/credentials.json`.
3. `%APPDATA%\goalslot\credentials.json` on Windows.
4. `$XDG_CONFIG_HOME/goalslot/credentials.json`, else `~/.config/goalslot/credentials.json`.

The file is written by `goalslot login` and shared with the CLI. Its shape:

```json
{
  "version": 1,
  "apiBaseUrl": "https://api.goalslot.io/api",
  "apiUrl": "https://api.goalslot.io/api",
  "accessToken": "<jwt>",
  "refreshToken": "gsl_rt_...",
  "accessTokenExpiresAt": "2026-08-25T12:00:00.000Z",
  "refreshTokenExpiresAt": "2026-11-23T00:00:00.000Z",
  "tokenId": "<uuid>",
  "scopes": ["full"],
  "user": { "id": "...", "email": "..." },
  "defaultTimezone": "Asia/Karachi",
  "weekStartsOn": 1
}
```

`apiBaseUrl` and `apiUrl` are both read and both written with the same value, so the CLI and this server cannot clobber each other. Unknown keys survive a rewrite.

CLI tokens are ordinary Bearer JWTs carrying `typ: "cli"` and a `cid` claim naming the revocable token row, and every normal API route accepts them. Access tokens live one hour. On a 401 this server rotates the refresh token against `POST /api/auth/cli/token/refresh`, writes the new pair to disk atomically **before** using it, and replays the original request once. Rotation is single-flight: refresh tokens are single use, and replaying a rotated one revokes the whole credential, so two concurrent refreshes would be a permanent logout.

No token, header or Authorization line is ever written to any log, at any level.

If the credential is missing or dead, every tool returns a structured `NOT_AUTHENTICATED` or `SESSION_EXPIRED` telling the model to have you run `goalslot login`. It will not ask you to paste a token.

### Headless and CI

`GOALSLOT_ACCESS_TOKEN` overrides the file entirely. Pair it with `GOALSLOT_REFRESH_TOKEN` if you want refresh to work; without one, refresh is disabled and the session dies when the one-hour access token expires. For anything long-lived, set `GOALSLOT_CONFIG_DIR` instead and let the server manage the file.

## Tools

Nine read tools and nine write tools. Read tools are safe to call freely and are annotated `readOnlyHint`. Every write tool's description starts with `WRITES.` and nothing exposed here deletes user data.

### Read

| Tool | What it does |
| --- | --- |
| `goalslot_get_context` | Orientation call, no arguments. User, plan limits and current usage, category value strings, labels, timezone, today's date, the current week, and any running timer. Every skill calls this first. |
| `goalslot_list_goals` | Goals with target hours, logged hours, progress, deadline and labels. Optional status, category and label filters. |
| `goalslot_get_goal` | One goal in full, optionally with the user's written reflections on it. |
| `goalslot_list_tasks` | Tasks filtered by status, goal, schedule block or day of week, capped so a big backlog cannot flood the context. |
| `goalslot_get_schedule` | The weekly schedule template, grouped by day and sorted by start time, with planned minutes per day. |
| `goalslot_list_time_entries` | Individual time entries over a window or a preset, with a total. Optional goal filter and text search. |
| `goalslot_get_report` | Nine report endpoints behind one `view` enum: `dashboard`, `weekly`, `monthly`, `detailed`, `summary`, `day_by_task`, `day_total`, `schedule`, `goals_progress`. |
| `goalslot_search_notes` | Finds notes by title or body substring, or fetches one by id. Returns the tree path and converts the HTML body to markdown. |
| `goalslot_get_journal` | Journal entries and daily check-ins merged by date: mood, energy, focus, what worked, what blocked. |

### Write

| Tool | What it does |
| --- | --- |
| `goalslot_create_goal` | Creates a goal. `targetHours` is the one field measured in hours. |
| `goalslot_update_goal` | Updates a goal including its status. Deliberately cannot set `loggedHours`. |
| `goalslot_create_task` | Creates a task, optionally linked to a goal and a schedule block. |
| `goalslot_update_task` | The whole task lifecycle behind an `action`: `update`, `complete` (which also logs time), `restore`. |
| `goalslot_log_time` | Records time already spent. Supports `dryRun`. Credits the linked goal. |
| `goalslot_start_timer` | Starts the shared server timer. Returns `TIMER_ALREADY_RUNNING` rather than silently taking over. |
| `goalslot_stop_timer` | Stops the timer into a time entry, or discards it. |
| `goalslot_manage_schedule_block` | Creates, updates or deletes one block in the weekly template. Supports `dryRun` and `updateScope`. |
| `goalslot_write_journal` | Upserts a journal entry and a daily check-in for one date. Markdown in, TipTap HTML out. |

### Deliberately not tools

Deleting goals, tasks and time entries; clearing the whole schedule; sharing anything publicly or with another person; the AI coach chat endpoints; billing and account settings; template imports. Destruction with cascading effects and anything that publishes personal data belongs where a human types it, not where an agent can call it in a loop. Use the GoalSlot app or the CLI.

### Conventions the tools enforce

- **Durations are whole minutes**, always named `durationMinutes`, never a bare `duration`. Reads also return `durationHours` as a float for display only. A goal's `targetHours` is the single exception.
- **Dates are `YYYY-MM-DD`** and are rejected at the boundary if they carry a time. "Today" is computed in your timezone, not from a UTC ISO slice.
- **The schedule is a repeating weekly template**, not dated events. Every relevant tool description says so.
- **`dayOfWeek` is Sunday-first** (0 = Sunday), which is not the Monday-first ordering weekly reports use. Both are labelled inline and every response carries a `dayName`.
- Weekly buckets are recomputed from entry dates rather than read from the API's stored `dayOfWeek` column, which is derived in the API server's timezone and can be off by one.

### Errors

Failures come back as `isError: true` with one JSON object:

```json
{
  "error": {
    "code": "PLAN_LIMIT",
    "message": "You've reached your FREE plan limit for goals.",
    "httpStatus": 403,
    "retryable": false,
    "remedy": "The account is at its goal limit. Ask the user to pause a goal with update_goal (status PAUSED), delete one from the CLI, or upgrade. Do not retry.",
    "details": { "plan": "FREE", "limitType": "goals" }
  }
}
```

`remedy` is written in the imperative, for the model. Codes: `NOT_AUTHENTICATED`, `SESSION_EXPIRED`, `PLAN_LIMIT`, `FORBIDDEN`, `SCHEDULE_CONFLICT`, `INVALID_INPUT`, `NOT_FOUND`, `RATE_LIMITED`, `UPSTREAM_ERROR`, `WRITE_BUDGET_EXCEEDED`, `READ_ONLY_MODE`, `TIMER_ALREADY_RUNNING`, `NO_TIMER_RUNNING`.

## Skills

Four skills ship in `skills/`. They carry the judgement the tool descriptions cannot: which order to call things in, what to confirm before writing, and what not to say.

| Skill | When |
| --- | --- |
| `goalslot-plan-my-week` | Planning a week, blocking time for a goal, rebalancing the schedule. |
| `goalslot-log-time` | Catching up on untracked time, correcting entries, driving the timer. |
| `goalslot-weekly-review` | End of week, "how did my week go", writing a reflection. |
| `goalslot-goal-checkup` | "Am I on track for X", deciding between cutting a target and moving a deadline. |

Install them into Claude Code by copying the directories into your skills folder:

```bash
# macOS and Linux
cp -r "$(npm root -g)/goalslot-mcp/skills/"* ~/.claude/skills/

# Windows PowerShell
Copy-Item "$(npm root -g)\goalslot-mcp\skills\*" "$env:USERPROFILE\.claude\skills\" -Recurse
```

From a clone, copy `skills/*` from the repo instead. Project-scoped works too: put them in `.claude/skills/` inside a repo.

## Configuration

| Variable | Effect |
| --- | --- |
| `GOALSLOT_CONFIG_DIR` | Directory holding `credentials.json`. Checked first. |
| `GOALSLOT_ACCESS_TOKEN` | Overrides the credential file entirely. CI escape hatch. |
| `GOALSLOT_REFRESH_TOKEN` | Refresh token to pair with the above. Rotations are held in memory only. |
| `GOALSLOT_API_URL` | API base URL. Defaults to `https://api.goalslot.io/api`. |
| `GOALSLOT_TZ` | IANA timezone overriding the one from login. |
| `GOALSLOT_MCP_READONLY=1` | Every write tool returns `READ_ONLY_MODE`. Set this when pointing an agent you do not fully trust at a live account. |
| `GOALSLOT_MCP_MAX_WRITES` | Write calls allowed per process. Defaults to 25. A runaway loop stops here rather than at the plan limit, which only backstops free accounts. |

## HTTP transport

Optional, behind a flag:

```bash
npx -y goalslot-mcp --http --port 7801
```

It serves the same tool registry at `http://127.0.0.1:7801/mcp` over the streamable HTTP transport, statelessly.

This is **single user and local only**. Every request is served with this machine's GoalSlot credentials. There is no per-caller authentication, no OAuth and no token custody, so anyone who can reach the port can read and write the account. It binds to loopback and should stay there. It is not the hosted multi-tenant MCP server tracked in [goal-slot-api#55](https://github.com/ZeeshanAdilButt/goal-slot-api/issues/55); that needs a real OAuth flow and per-user token storage, which is a different piece of work.

stdio is the supported path.

## Development

```bash
npm install
npm run typecheck
npm test
npm run build
node dist/cli.js --list-tools
```

TypeScript strict, ESM, Node 20 or newer. Tests are vitest with a stubbed fetch; nothing in the suite touches a live API. CI runs typecheck, tests and build on Node 20 and 22 across Ubuntu and Windows.

## License

MIT

TDQS

A4.2/5.0

Scored across 18 tools

Disambiguation5/5

Each tool targets a distinct resource and action (e.g., log_time vs. stop_timer, get_report vs. list_time_entries, get_schedule vs. manage_schedule_block). The overlapping cases are clearly differentiated by descriptions, such as manual logging vs. timer-driven logging, and raw entries vs. aggregated reports. No two tools appear to do the same thing.

Naming Consistency5/5

All tools follow the 'goalslot_' prefix plus a consistent snake_case verb_noun pattern (e.g., get_goal, create_task, stop_timer). The verb choice is uniform across read (get/list/search) and write (create/update/start/stop/manage/log/write) operations. There are no stylistic deviations or mixed conventions.

Tool Count4/5

18 tools cover a broad domain (goals, tasks, schedule, time tracking, reporting, journal, timer, notes). While this falls slightly above the typical 3–15 'well-scoped' range, the count is justified by the variety of features; each tool has a clear purpose and none are redundant. It feels comprehensive without being bloated.

Completeness3/5

The core workflows are covered: goal lifecycle (create/update, though delete is deliberately absent), task lifecycle (create/update/complete/restore, but no delete), schedule management (create/update/delete), time logging (manual and timer), reporting (multiple views), and journaling. However, there are no tools to delete or edit time entries, and goals/tasks cannot be fully removed, which are notable gaps for a productivity domain. The server explicitly documents these limitations, but they still create dead ends for agents.

Maintenance

ActivityMaintained
ResponsivenessNo issues