From 89e1b30a93272b960549d3fd7e9495a7fd48544f Mon Sep 17 00:00:00 2001 From: Danijel Date: Wed, 18 Mar 2026 00:39:09 +0100 Subject: [PATCH] feat: update to cloud-hosted architecture with port 3045 Switch default port from 3000 to 3045 in Dockerfile and docker-compose. Add MCP instructions to server for AI client guidance. Update README to reflect cloud-hosted model with per-user headers, 42 tools, and session management. Add .mcp.json to .gitignore to prevent leaking credentials. Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 1 + Dockerfile | 4 +- README.md | 196 ++++++++++++++++++++++++++++++++++----------- docker-compose.yml | 6 +- src/server.ts | 51 +++++++++++- 5 files changed, 201 insertions(+), 57 deletions(-) diff --git a/.gitignore b/.gitignore index a2ef9d5..9e06e9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ dist/ .env +.mcp.json *.tsbuildinfo test-all-tools.sh diff --git a/Dockerfile b/Dockerfile index ebf7c5e..105b309 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ RUN npm ci --omit=dev --ignore-scripts && npm cache clean --force COPY --from=builder /app/dist ./dist -ENV PORT=3000 -EXPOSE 3000 +ENV PORT=3045 +EXPOSE 3045 CMD ["node", "dist/http-server.js"] diff --git a/README.md b/README.md index 3fcc15d..edc5785 100644 --- a/README.md +++ b/README.md @@ -4,43 +4,32 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![npm](https://img.shields.io/npm/v/solidtime-mcp-server)](https://www.npmjs.com/package/solidtime-mcp-server) -MCP server for [SolidTime](https://www.solidtime.io/) — the open-source time tracking app. Start/stop timers, manage time entries, projects, clients, tags, and tasks directly from Claude, Cursor, or any MCP-compatible client. +Cloud-hosted MCP server for [SolidTime](https://www.solidtime.io/) — the open-source time tracking app. Start/stop timers, manage time entries, projects, clients, tags, tasks, members, invitations, and organizations directly from Claude, Cursor, or any MCP-compatible client. ## Features -- **22 tools** covering time entries, projects, clients, tags, tasks, and user info +- **42 tools** covering time entries, projects, clients, tags, tasks, members, organizations, project members, and invitations +- **Cloud-hosted** — no local installation required, connect via URL from any MCP client +- **Per-user credentials** — API token and settings passed through MCP client headers - **Start/stop timers** with automatic active-timer detection -- **Aggregated reports** grouped by day, week, project, client, and more +- **Batch operations** — update or delete multiple time entries at once +- **Aggregated reports** grouped by day, week, month, year, project, client, task, user, billable, description, or tag — with optional sub-grouping +- **Member & invitation management** — list, update, remove members; send and manage invitations +- **Organization management** — view and update organization settings - **Auto member_id resolution** — no manual configuration needed +- **Timezone support** — display and accept times in your local timezone +- **Built-in MCP instructions** — the server provides contextual guidance to AI clients for optimal tool usage +- **Session management** — per-user sessions with automatic 30-minute expiry +- **Docker-ready** — multi-stage Dockerfile and docker-compose included - **Actionable error messages** — every error tells you what to do next - **Zero external dependencies** beyond the MCP SDK (uses native `fetch`) - Works with self-hosted SolidTime instances and the hosted version ## Quick Start -### Using npx (no install) +The server is hosted in the cloud — just point your MCP client to the server URL and pass your SolidTime credentials as headers. -```bash -npx solidtime-mcp-server -``` - -### Install globally - -```bash -npm install -g solidtime-mcp-server -``` - -### Environment Variables - -| Variable | Required | Default | Description | -|----------|----------|---------|-------------| -| `SOLIDTIME_API_TOKEN` | Yes | — | Your SolidTime API token | -| `SOLIDTIME_ORGANIZATION_ID` | Yes | — | Your organization UUID | -| `SOLIDTIME_API_URL` | No | `https://app.solidtime.io` | Base URL for self-hosted instances | - -Get your API token from **SolidTime > Settings > API**. - -## Claude Desktop Configuration +### Claude Desktop Add to your `claude_desktop_config.json`: @@ -48,19 +37,19 @@ Add to your `claude_desktop_config.json`: { "mcpServers": { "solidtime": { - "command": "npx", - "args": ["-y", "solidtime-mcp-server"], - "env": { - "SOLIDTIME_API_TOKEN": "your-token-here", - "SOLIDTIME_ORGANIZATION_ID": "your-org-uuid-here", - "SOLIDTIME_API_URL": "https://your-instance.example.com" + "url": "https://your-server.example.com/mcp", + "headers": { + "x-solidtime-api-token": "your-token-here", + "x-solidtime-organization-id": "your-org-uuid-here", + "x-solidtime-api-url": "https://your-instance.example.com", + "x-solidtime-timezone": "Europe/Berlin" } } } } ``` -## Claude Code Configuration +### Claude Code Add to your `.mcp.json`: @@ -68,21 +57,78 @@ Add to your `.mcp.json`: { "mcpServers": { "solidtime": { - "command": "npx", - "args": ["-y", "solidtime-mcp-server"], - "env": { - "SOLIDTIME_API_TOKEN": "your-token-here", - "SOLIDTIME_ORGANIZATION_ID": "your-org-uuid-here", - "SOLIDTIME_API_URL": "https://your-instance.example.com" + "url": "https://your-server.example.com/mcp", + "headers": { + "x-solidtime-api-token": "your-token-here", + "x-solidtime-organization-id": "your-org-uuid-here", + "x-solidtime-api-url": "https://your-instance.example.com", + "x-solidtime-timezone": "Europe/Berlin" } } } } ``` +### Cursor / Windsurf / Other MCP Clients + +Use the streamable HTTP transport with these settings: + +| Setting | Value | +|---------|-------| +| **URL** | `https://your-server.example.com/mcp` | +| **Transport** | Streamable HTTP | + +Pass the headers below in your client's MCP configuration. + +### Headers + +| Header | Required | Description | +|--------|----------|-------------| +| `x-solidtime-api-token` | Yes | Your SolidTime API token | +| `x-solidtime-organization-id` | No | Your organization UUID (auto-selected if you belong to only one) | +| `x-solidtime-api-url` | No | Base URL for self-hosted SolidTime instances (default: `https://app.solidtime.io`) | +| `x-solidtime-timezone` | No | IANA timezone name (e.g. `Europe/Berlin`, `America/New_York`). Without this, all times are in UTC | + +Get your API token from **SolidTime > Settings > API**. + +## Server Deployment + +### Docker Compose (recommended) + +```bash +docker compose up -d +``` + +### Docker Manual + +```bash +docker build -t solidtime-mcp-server . +docker run -p 3000:3000 solidtime-mcp-server +``` + +### Server Environment Variables + +These are set on the server, not by MCP clients: + +| Variable | Required | Default | Description | +|----------|----------|---------|-------------| +| `PORT` | No | `3000` | HTTP server port | +| `SOLIDTIME_API_URL` | No | `https://app.solidtime.io` | Default SolidTime API URL (clients can override via header) | + +### Endpoints + +| Method | Path | Description | +|--------|------|-------------| +| `POST` | `/mcp` | Initialize session / send MCP messages | +| `GET` | `/mcp` | SSE stream listener (requires `mcp-session-id` header) | +| `DELETE` | `/mcp` | Close session | +| `GET` | `/health` | Health check (returns status and active session count) | + +Sessions expire automatically after 30 minutes of inactivity. + ## Tools -### Time Entries (8 tools) +### Time Entries (10 tools) | Tool | Description | |------|-------------| @@ -93,7 +139,9 @@ Add to your `.mcp.json`: | `solidtime_create_time_entry` | Create a completed entry with start and end times | | `solidtime_update_time_entry` | Update any field on an existing entry | | `solidtime_delete_time_entry` | Permanently delete an entry | -| `solidtime_get_time_entry_report` | Aggregated report by day/week/month/project/client/etc. | +| `solidtime_update_multiple_time_entries` | Update multiple entries at once | +| `solidtime_delete_multiple_time_entries` | Delete multiple entries at once | +| `solidtime_get_time_entry_report` | Aggregated report by day/week/month/year/project/client/task/user/billable/description/tag with optional sub-grouping | ### Projects (4 tools) @@ -104,29 +152,67 @@ Add to your `.mcp.json`: | `solidtime_update_project` | Update project fields | | `solidtime_delete_project` | Permanently delete a project | -### Clients (3 tools) +### Clients (4 tools) | Tool | Description | |------|-------------| | `solidtime_list_clients` | List all clients (filter by archived status) | | `solidtime_create_client` | Create a client | | `solidtime_update_client` | Update a client's name | +| `solidtime_delete_client` | Permanently delete a client | -### Tags (3 tools) +### Tags (4 tools) | Tool | Description | |------|-------------| | `solidtime_list_tags` | List all tags | | `solidtime_create_tag` | Create a tag | | `solidtime_update_tag` | Update a tag's name | +| `solidtime_delete_tag` | Permanently delete a tag | -### Tasks (3 tools) +### Tasks (4 tools) | Tool | Description | |------|-------------| | `solidtime_list_tasks` | List tasks (filter by project, done status) | | `solidtime_create_task` | Create a task within a project | | `solidtime_update_task` | Update task name, done status, or estimated time | +| `solidtime_delete_task` | Permanently delete a task | + +### Members (5 tools) + +| Tool | Description | +|------|-------------| +| `solidtime_list_members` | List all members with roles and billable rates | +| `solidtime_update_member` | Update a member's role or billable rate | +| `solidtime_remove_member` | Remove a member from the organization | +| `solidtime_invite_placeholder_member` | Invite a placeholder member to create an account | +| `solidtime_make_placeholder_member` | Convert a real member into a placeholder | + +### Organizations (2 tools) + +| Tool | Description | +|------|-------------| +| `solidtime_get_organization` | Get organization details (name, currency, billable rate) | +| `solidtime_update_organization` | Update organization name, currency, or billable rate | + +### Project Members (4 tools) + +| Tool | Description | +|------|-------------| +| `solidtime_list_project_members` | List all members assigned to a project | +| `solidtime_add_project_member` | Add a member to a project with optional billable rate | +| `solidtime_update_project_member` | Update a project member's billable rate | +| `solidtime_remove_project_member` | Remove a member from a project | + +### Invitations (4 tools) + +| Tool | Description | +|------|-------------| +| `solidtime_list_invitations` | List all pending invitations | +| `solidtime_create_invitation` | Invite a new user by email | +| `solidtime_resend_invitation` | Resend an invitation email | +| `solidtime_delete_invitation` | Delete a pending invitation | ### Users (1 tool) @@ -148,19 +234,28 @@ Add to your `.mcp.json`: **Check what's running:** > "Is there a timer running?" +**Manage team members:** +> "List all members in the organization" + +**Invite someone:** +> "Invite alice@example.com to the organization as an employee" + ## Troubleshooting ### "Authentication failed" -Your `SOLIDTIME_API_TOKEN` is invalid or expired. Generate a new one in SolidTime under Settings > API. +Your `x-solidtime-api-token` is invalid or expired. Generate a new one in SolidTime under Settings > API. ### "Permission denied" -Your token doesn't have access to the specified organization. Verify `SOLIDTIME_ORGANIZATION_ID`. +Your token doesn't have access to the specified organization. Verify `x-solidtime-organization-id`. ### "Cannot reach SolidTime" -Check that `SOLIDTIME_API_URL` is correct and the instance is accessible. For self-hosted: ensure the URL includes the protocol (e.g., `https://solidtime.example.com`). +Check that `x-solidtime-api-url` is correct and the instance is accessible. For self-hosted: ensure the URL includes the protocol (e.g., `https://solidtime.example.com`). ### "Could not find member for user" -The authenticated user is not a member of the specified organization. Check `SOLIDTIME_ORGANIZATION_ID`. +The authenticated user is not a member of the specified organization. Check `x-solidtime-organization-id`. + +### "Session not found" +Sessions expire after 30 minutes of inactivity. Reconnect to create a new session. ## Development @@ -168,9 +263,14 @@ The authenticated user is not a member of the specified organization. Check `SOL git clone https://github.com/SwamiRama/solidtime-mcp-server.git cd solidtime-mcp-server npm install -npm run dev # Run with tsx (dev mode) +npm run dev:http # Run HTTP mode with tsx (dev) +npm run dev # Run stdio mode with tsx (dev) npm run build # Compile TypeScript +npm run start:http # Run compiled HTTP mode +npm run start # Run compiled stdio mode npm run lint # ESLint +npm run format # Prettier (write) +npm run format:check # Prettier (check only) npm run typecheck # Type checking npm run inspector # Test with MCP Inspector ``` diff --git a/docker-compose.yml b/docker-compose.yml index 13e739f..28cfe66 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,15 +2,15 @@ services: solidtime-mcp: build: . ports: - - "${PORT:-3000}:3000" + - "${PORT:-3045}:3045" environment: - - PORT=3000 + - PORT=3045 # Optional: default SolidTime API URL for all sessions. # Clients can override this via the x-solidtime-api-url header. - SOLIDTIME_API_URL=${SOLIDTIME_API_URL:-https://app.solidtime.io} restart: unless-stopped healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"] + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3045/health"] interval: 30s timeout: 5s retries: 3 diff --git a/src/server.ts b/src/server.ts index 07c380d..c9838ca 100644 --- a/src/server.ts +++ b/src/server.ts @@ -87,10 +87,53 @@ export async function createServer(config: ServerConfig) { (timezone ? `, timezone ${timezone}` : "") ); - const server = new McpServer({ - name: "solidtime", - version: "1.0.0", - }); + const server = new McpServer( + { + name: "solidtime", + version: "1.0.0", + }, + { + instructions: [ + `You are connected to a SolidTime time tracking server for the organization "${membership.organization.name}".`, + `Authenticated as ${user.name} (${user.email}), member ID: ${memberId}.`, + timezone ? `Timezone is set to ${timezone}. Display and accept times in this timezone unless the user specifies otherwise.` : `No timezone is configured — all times are in UTC.`, + "", + "## Key Workflows", + "", + "### Timers", + "- Before starting a timer, check for an active one with solidtime_get_active_timer.", + "- If a timer is already running, ask the user whether to stop it before starting a new one.", + "- Use solidtime_stop_timer to stop the active timer.", + "", + "### Time Entries", + "- Use solidtime_list_time_entries to browse entries with date/project/client/tag filters.", + "- Use solidtime_create_time_entry for completed work (requires both start and end).", + "- Use solidtime_get_time_entry_report for aggregated summaries (group by day, week, month, project, client, task, user, billable, description, or tag).", + "- Batch operations: solidtime_update_multiple_time_entries and solidtime_delete_multiple_time_entries for bulk changes.", + "", + "### Lookups", + "- Most create/update tools require UUIDs (project_id, client_id, tag_ids, task_id).", + "- Always list the relevant resource first (solidtime_list_projects, solidtime_list_clients, solidtime_list_tags, solidtime_list_tasks) to resolve names to UUIDs before creating or updating entries.", + "- Cache IDs within the conversation to avoid redundant lookups.", + "", + "### Projects & Clients", + "- Projects can optionally belong to a client (client_id).", + "- Use solidtime_list_projects and solidtime_list_clients to discover existing resources before creating duplicates.", + "", + "### Members & Invitations", + "- solidtime_list_members shows all organization members with roles and billable rates.", + "- Use solidtime_create_invitation to invite new users by email.", + "- solidtime_list_project_members / solidtime_add_project_member to manage per-project member assignments.", + "", + "## Best Practices", + "- When the user mentions a project, client, or tag by name, look it up first to get the UUID.", + "- Present time durations in human-readable format (e.g. 2h 30m, not 9000 seconds).", + "- When creating time entries, confirm the details with the user if they seem ambiguous.", + "- For reports, suggest useful groupings based on what the user is asking about.", + "- Destructive operations (delete) are permanent — confirm with the user before proceeding.", + ].join("\n"), + } + ); registerUserTools(server, api, getMemberId); registerTimeEntryTools(server, api, orgId, getMemberId, timezone);