@spotfitr/mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +177 -0
- package/dist/index.js +33 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# @spotfitr/mcp
|
|
2
|
+
|
|
3
|
+
MCP server that exposes the Spotfitr API as tools for Claude. Allows trainers and clients to manage sessions, bookings, and clients directly from Claude Desktop or any MCP-compatible AI client.
|
|
4
|
+
|
|
5
|
+
## Architecture decisions
|
|
6
|
+
|
|
7
|
+
**Transport: stdio (local)**
|
|
8
|
+
|
|
9
|
+
The server runs as a local subprocess of the AI client (Claude Desktop). Each user runs their own isolated process — there is no shared state between users, no open network port, and credentials never leave the user's machine except when calling the Spotfitr API.
|
|
10
|
+
|
|
11
|
+
This was chosen over an HTTP remote server because:
|
|
12
|
+
- No infrastructure required (no always-on server, no TLS setup)
|
|
13
|
+
- Security model is trivial: one process = one user, fully isolated auth state
|
|
14
|
+
- The target users (fitness professionals) use Claude Desktop, so npm install is feasible
|
|
15
|
+
- A remote HTTP server would require rewriting the auth state model to be per-session, adding OAuth, rate limiting, and ongoing hosting costs — complexity that adds no real value for this use case
|
|
16
|
+
|
|
17
|
+
**Authentication: environment variables only**
|
|
18
|
+
|
|
19
|
+
Credentials are passed exclusively via `SPOTFITR_EMAIL` and `SPOTFITR_PASSWORD` environment variables, set in the Claude Desktop config file. A `login` tool that accepted credentials as parameters was considered and rejected: tool parameters appear in Claude's conversation context and tool call logs, which would expose the password to the model and any logging infrastructure.
|
|
20
|
+
|
|
21
|
+
**Authorization: delegated to the API**
|
|
22
|
+
|
|
23
|
+
The MCP server does not enforce any role-based access control itself. It passes the JWT from the Spotfitr API to every request and lets the API enforce permissions (OWNER vs CLIENT). Tool descriptions document which role each tool requires, but enforcement happens server-side.
|
|
24
|
+
|
|
25
|
+
**Error handling: sanitized**
|
|
26
|
+
|
|
27
|
+
API errors are filtered before being returned to the LLM. Only the API's own `error` field is forwarded (e.g. "Session is full"), never raw stack traces or internal error details. This prevents leaking implementation details through the model context.
|
|
28
|
+
|
|
29
|
+
**URL security: HTTPS enforced at startup**
|
|
30
|
+
|
|
31
|
+
If `SPOTFITR_API_URL` is set to a non-HTTPS URL (and is not localhost), the server refuses to start. This prevents the JWT from being transmitted in plaintext by misconfiguration.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
Requires Node.js 18 or later.
|
|
36
|
+
|
|
37
|
+
The package is published to npm as `@spotfitr/mcp`. Users do not need to install it manually — `npx` handles it automatically.
|
|
38
|
+
|
|
39
|
+
### Claude Desktop
|
|
40
|
+
|
|
41
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"spotfitr": {
|
|
47
|
+
"command": "npx",
|
|
48
|
+
"args": ["-y", "@spotfitr/mcp"],
|
|
49
|
+
"env": {
|
|
50
|
+
"SPOTFITR_EMAIL": "your-email@example.com",
|
|
51
|
+
"SPOTFITR_PASSWORD": "your-password",
|
|
52
|
+
"SPOTFITR_API_URL": "https://api.spotfitr.com/api"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Restart Claude Desktop after saving. Claude will have access to all Spotfitr tools automatically.
|
|
60
|
+
|
|
61
|
+
### Claude Code (CLI)
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
claude mcp add spotfitr \
|
|
65
|
+
-e SPOTFITR_EMAIL=your-email@example.com \
|
|
66
|
+
-e SPOTFITR_PASSWORD=your-password \
|
|
67
|
+
-e SPOTFITR_API_URL=https://api.spotfitr.com/api \
|
|
68
|
+
-- npx -y @spotfitr/mcp
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Environment variables
|
|
72
|
+
|
|
73
|
+
| Variable | Required | Default | Description |
|
|
74
|
+
|----------|----------|---------|-------------|
|
|
75
|
+
| `SPOTFITR_EMAIL` | Yes | — | Spotfitr account email |
|
|
76
|
+
| `SPOTFITR_PASSWORD` | Yes | — | Spotfitr account password |
|
|
77
|
+
| `SPOTFITR_API_URL` | No | `http://localhost:3001/api` | Spotfitr API base URL. Must be HTTPS for non-localhost URLs. |
|
|
78
|
+
|
|
79
|
+
If `SPOTFITR_EMAIL` or `SPOTFITR_PASSWORD` are missing, the server starts but all tool calls will fail until credentials are provided. Auto-login errors are non-fatal and logged to stderr.
|
|
80
|
+
|
|
81
|
+
## Available tools
|
|
82
|
+
|
|
83
|
+
### Auth
|
|
84
|
+
| Tool | Role | Description |
|
|
85
|
+
|------|------|-------------|
|
|
86
|
+
| `get_current_user` | Any | Get the profile of the authenticated user |
|
|
87
|
+
| `get_auth_status` | Any | Check if the server is authenticated and see basic user info |
|
|
88
|
+
|
|
89
|
+
### Sessions
|
|
90
|
+
| Tool | Role | Description |
|
|
91
|
+
|------|------|-------------|
|
|
92
|
+
| `list_sessions` | Any | List sessions within an optional date range |
|
|
93
|
+
| `create_session` | OWNER | Create a new individual training session |
|
|
94
|
+
| `update_session` | OWNER | Update date, times, capacity, or cancel a session |
|
|
95
|
+
| `delete_session` | OWNER | Permanently delete a session |
|
|
96
|
+
| `get_session_bookings` | OWNER | List all confirmed bookings for a session (with client details) |
|
|
97
|
+
| `get_session_attendees` | CLIENT | List names and profile pictures of confirmed attendees |
|
|
98
|
+
|
|
99
|
+
### Session types
|
|
100
|
+
| Tool | Role | Description |
|
|
101
|
+
|------|------|-------------|
|
|
102
|
+
| `list_session_types` | Any | List all workout types (e.g. CrossFit, Yoga, HIIT) |
|
|
103
|
+
| `create_session_type` | OWNER | Create a new workout type with name, description, and color |
|
|
104
|
+
| `update_session_type` | OWNER | Update name, description, or color of a workout type |
|
|
105
|
+
| `delete_session_type` | OWNER | Delete a workout type (fails if sessions are using it) |
|
|
106
|
+
|
|
107
|
+
### Recurrence rules
|
|
108
|
+
| Tool | Role | Description |
|
|
109
|
+
|------|------|-------------|
|
|
110
|
+
| `list_recurrence_rules` | OWNER | List all recurring session rules |
|
|
111
|
+
| `create_recurrence_rule` | OWNER | Create a recurring rule that auto-generates sessions |
|
|
112
|
+
| `delete_recurrence_rule` | OWNER | Delete a rule and its future unbooked sessions |
|
|
113
|
+
|
|
114
|
+
### Clients
|
|
115
|
+
| Tool | Role | Description |
|
|
116
|
+
|------|------|-------------|
|
|
117
|
+
| `list_clients` | OWNER | List all active clients with their monthly booking count |
|
|
118
|
+
| `list_archived_clients` | OWNER | List archived (inactive) clients |
|
|
119
|
+
| `create_client` | OWNER | Add a new client and send them an invitation email |
|
|
120
|
+
| `update_client` | OWNER | Update name, email, or monthly session limit |
|
|
121
|
+
| `delete_client` | OWNER | Delete a client (fails if they have any bookings) |
|
|
122
|
+
| `archive_client` | OWNER | Deactivate a client without deleting them |
|
|
123
|
+
| `unarchive_client` | OWNER | Reactivate an archived client |
|
|
124
|
+
|
|
125
|
+
### Bookings
|
|
126
|
+
| Tool | Role | Description |
|
|
127
|
+
|------|------|-------------|
|
|
128
|
+
| `list_my_bookings` | CLIENT | List upcoming confirmed bookings |
|
|
129
|
+
| `get_my_usage` | CLIENT | Get booking usage for the current month vs the session limit |
|
|
130
|
+
| `book_session` | CLIENT | Book a spot in a session |
|
|
131
|
+
| `cancel_booking` | CLIENT | Cancel a booking (subject to cancellation window) |
|
|
132
|
+
|
|
133
|
+
### Waitlist
|
|
134
|
+
| Tool | Role | Description |
|
|
135
|
+
|------|------|-------------|
|
|
136
|
+
| `list_my_waitlist` | CLIENT | List sessions you are on the waitlist for |
|
|
137
|
+
| `join_waitlist` | CLIENT | Join the waitlist for a full session |
|
|
138
|
+
| `leave_waitlist` | CLIENT | Remove yourself from the waitlist |
|
|
139
|
+
|
|
140
|
+
### Subscription
|
|
141
|
+
| Tool | Role | Description |
|
|
142
|
+
|------|------|-------------|
|
|
143
|
+
| `get_subscription_info` | OWNER | Get plan (FREE/PRO), status, and client usage vs limit |
|
|
144
|
+
|
|
145
|
+
## Local development
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
cd mcp
|
|
149
|
+
npm install
|
|
150
|
+
cp .env.example .env # fill in your credentials
|
|
151
|
+
npm run dev
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The dev script uses `tsx --watch` for hot reload.
|
|
155
|
+
|
|
156
|
+
To test with Claude Code locally before publishing:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npm run build
|
|
160
|
+
claude mcp add spotfitr-local \
|
|
161
|
+
-e SPOTFITR_EMAIL=your-email@example.com \
|
|
162
|
+
-e SPOTFITR_PASSWORD=your-password \
|
|
163
|
+
-e SPOTFITR_API_URL=http://localhost:3001/api \
|
|
164
|
+
-- node /absolute/path/to/spotfitr/mcp/dist/index.js
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Publishing
|
|
168
|
+
|
|
169
|
+
The package is published to npm under the `@spotfitr` org scope.
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
cd mcp
|
|
173
|
+
npm version patch # or minor / major
|
|
174
|
+
npm publish # runs build automatically via prepublishOnly
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Requires npm login with an account that has publish access to the `@spotfitr` org.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { autoLogin } from "./auth.js";
|
|
5
|
+
import { registerAllTools } from "./tools/index.js";
|
|
6
|
+
async function main() {
|
|
7
|
+
const server = new McpServer({
|
|
8
|
+
name: "spotfitr",
|
|
9
|
+
version: "0.1.0"
|
|
10
|
+
});
|
|
11
|
+
registerAllTools(server);
|
|
12
|
+
const email = process.env.SPOTFITR_EMAIL;
|
|
13
|
+
const password = process.env.SPOTFITR_PASSWORD;
|
|
14
|
+
const baseUrl = process.env.SPOTFITR_API_URL ?? "http://localhost:3001/api";
|
|
15
|
+
if (email && password) {
|
|
16
|
+
try {
|
|
17
|
+
await autoLogin(baseUrl, email, password);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
process.stderr.write(`[spotfitr-mcp] Auto-login failed: ${err.message}
|
|
20
|
+
`);
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
process.stderr.write("[spotfitr-mcp] No credentials provided. Call the login tool to authenticate.\n");
|
|
24
|
+
}
|
|
25
|
+
const transport = new StdioServerTransport();
|
|
26
|
+
await server.connect(transport);
|
|
27
|
+
process.stderr.write("[spotfitr-mcp] Server running on stdio\n");
|
|
28
|
+
}
|
|
29
|
+
main().catch((err) => {
|
|
30
|
+
process.stderr.write(`[spotfitr-mcp] Fatal error: ${err.message}
|
|
31
|
+
`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@spotfitr/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server exposing the Spotfitr fitness API as tools for Claude",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"spotfitr-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"dev": "tsx --watch src/index.ts",
|
|
19
|
+
"start": "node dist/index.js",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
24
|
+
"zod": "^3.24.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^22.0.0",
|
|
28
|
+
"tsup": "^8.5.1",
|
|
29
|
+
"tsx": "^4.19.0",
|
|
30
|
+
"typescript": "^5.7.0"
|
|
31
|
+
}
|
|
32
|
+
}
|