@uteamup/mcp-server 1.0.0-beta.1
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/CHANGELOG.md +39 -0
- package/LICENSE +21 -0
- package/README.md +443 -0
- package/index.js +333 -0
- package/package.json +57 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0-beta.1] - 2026-02-10
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial beta release
|
|
12
|
+
- OAuth 2.0 Authorization Code + PKCE authentication flow
|
|
13
|
+
- Automatic token caching with 7-day expiration
|
|
14
|
+
- Stdio transport for Claude Desktop/Code compatibility
|
|
15
|
+
- Support for self-signed certificates (development mode)
|
|
16
|
+
- Debug logging via `UTEAMUP_DEBUG` environment variable
|
|
17
|
+
- Graceful shutdown handling (SIGINT/SIGTERM)
|
|
18
|
+
- Comprehensive error messages for common authentication issues
|
|
19
|
+
- Configuration validation with helpful error messages
|
|
20
|
+
- JSDoc documentation for all public functions
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Token response parsing to use OAuth 2.0 standard `access_token` field (was incorrectly expecting `accessToken`)
|
|
24
|
+
- JSON-RPC 2.0 response format to always include request IDs
|
|
25
|
+
- Error handling for 401/403 HTTP status codes with user-friendly messages
|
|
26
|
+
|
|
27
|
+
### Security
|
|
28
|
+
- Single-use authorization codes enforced
|
|
29
|
+
- PKCE S256 challenge method (SHA-256)
|
|
30
|
+
- Token-only authentication (credentials never logged)
|
|
31
|
+
- Memory-only token storage (not persisted to disk)
|
|
32
|
+
|
|
33
|
+
## [Unreleased]
|
|
34
|
+
|
|
35
|
+
### Planned
|
|
36
|
+
- Automatic token refresh on expiration
|
|
37
|
+
- Configurable token cache location
|
|
38
|
+
- Additional error recovery strategies
|
|
39
|
+
- Rate limiting support
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 UteamUP
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
# @uteamup/mcp-server
|
|
2
|
+
|
|
3
|
+
> MCP server client for UteamUP - enables Claude Desktop/Code integration with automatic OAuth 2.0 authentication
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@uteamup/mcp-server)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://nodejs.org)
|
|
8
|
+
|
|
9
|
+
## What is this?
|
|
10
|
+
|
|
11
|
+
This package enables AI assistants like Claude Desktop and Claude Code to connect to your UteamUP CMMS system using the Model Context Protocol (MCP). It handles OAuth 2.0 authentication automatically, so you can manage work orders, assets, inventory, and more through natural conversation.
|
|
12
|
+
|
|
13
|
+
**Key Features:**
|
|
14
|
+
- ✅ Automatic OAuth 2.0 + PKCE authentication
|
|
15
|
+
- ✅ Token caching (7-day expiration, auto-refresh)
|
|
16
|
+
- ✅ Works with Claude Desktop, Claude Code, and any MCP-compatible client
|
|
17
|
+
- ✅ Zero configuration - just provide API credentials
|
|
18
|
+
- ✅ Comprehensive error messages
|
|
19
|
+
- ✅ Development mode support (self-signed certificates)
|
|
20
|
+
|
|
21
|
+
**Example Conversation:**
|
|
22
|
+
```
|
|
23
|
+
You: Show me all high-priority work orders in Building A
|
|
24
|
+
AI: I found 5 high-priority work orders in Building A...
|
|
25
|
+
|
|
26
|
+
You: Assign them all to John Doe
|
|
27
|
+
AI: Done! All 5 work orders are now assigned to John Doe.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
No installation needed! Use `npx` to run directly:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx -y @uteamup/mcp-server
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or install globally:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install -g @uteamup/mcp-server
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
### 1. Get Your API Credentials
|
|
51
|
+
|
|
52
|
+
1. Log in to [UteamUP](https://app.uteamup.com)
|
|
53
|
+
2. Go to **Tenant Settings** → **API Keys**
|
|
54
|
+
3. Click **"Set up AI Integration"** or **"Create API Key"**
|
|
55
|
+
4. Enable **"MCP Enabled"** toggle
|
|
56
|
+
5. Click **Create API Key**
|
|
57
|
+
6. **Copy and save** your API Key and Secret (you won't see them again!)
|
|
58
|
+
|
|
59
|
+
### 2. Configure Claude Desktop
|
|
60
|
+
|
|
61
|
+
**macOS/Linux:** Edit `~/.config/claude/mcp.json`
|
|
62
|
+
**Windows:** Edit `%APPDATA%\Claude\mcp.json`
|
|
63
|
+
|
|
64
|
+
Add this configuration:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"mcpServers": {
|
|
69
|
+
"uteamup": {
|
|
70
|
+
"command": "npx",
|
|
71
|
+
"args": ["-y", "@uteamup/mcp-server"],
|
|
72
|
+
"env": {
|
|
73
|
+
"UTEAMUP_API_KEY": "your-32-character-api-key",
|
|
74
|
+
"UTEAMUP_SECRET": "your-64-character-secret"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Optional:** For development/testing against a local backend:
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"mcpServers": {
|
|
85
|
+
"uteamup-dev": {
|
|
86
|
+
"command": "npx",
|
|
87
|
+
"args": ["-y", "@uteamup/mcp-server"],
|
|
88
|
+
"env": {
|
|
89
|
+
"UTEAMUP_API_KEY": "your-api-key",
|
|
90
|
+
"UTEAMUP_SECRET": "your-secret",
|
|
91
|
+
"UTEAMUP_API_BASE_URL": "https://devback.uteamup.com"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 3. Restart Claude Desktop
|
|
99
|
+
|
|
100
|
+
After saving the configuration, restart Claude Desktop. The UteamUP tools will be available automatically.
|
|
101
|
+
|
|
102
|
+
### 4. Test the Connection
|
|
103
|
+
|
|
104
|
+
In Claude, try:
|
|
105
|
+
```
|
|
106
|
+
What UteamUP tools do you have access to?
|
|
107
|
+
```
|
|
108
|
+
or
|
|
109
|
+
```
|
|
110
|
+
Show me all open work orders
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
That's it! 🎉
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Configuration
|
|
118
|
+
|
|
119
|
+
### Environment Variables
|
|
120
|
+
|
|
121
|
+
| Variable | Required | Description | Default |
|
|
122
|
+
|----------|----------|-------------|---------|
|
|
123
|
+
| `UTEAMUP_API_KEY` | **Yes** | Your MCP-enabled API key (32 chars) | - |
|
|
124
|
+
| `UTEAMUP_SECRET` | **Yes** | Your API key secret (64 chars) | - |
|
|
125
|
+
| `UTEAMUP_API_BASE_URL` | No | API endpoint URL | `https://api.uteamup.com` |
|
|
126
|
+
| `UTEAMUP_DEBUG` | No | Enable debug logging (`1` or `true`) | `false` |
|
|
127
|
+
|
|
128
|
+
### API Base URLs
|
|
129
|
+
|
|
130
|
+
| Environment | URL | When to Use |
|
|
131
|
+
|-------------|-----|-------------|
|
|
132
|
+
| **Production** | `https://api.uteamup.com` | Default - for normal use |
|
|
133
|
+
| **Development** | `https://devback.uteamup.com` | Testing against dev backend |
|
|
134
|
+
| **Local** | `https://localhost:5002` | Local development with self-signed cert |
|
|
135
|
+
|
|
136
|
+
### Permissions
|
|
137
|
+
|
|
138
|
+
Your AI assistant's permissions are controlled by the **role assigned to your API key**. To change what the AI can do:
|
|
139
|
+
|
|
140
|
+
1. Go to **Tenant Settings** → **API Keys** in UteamUP
|
|
141
|
+
2. Edit your API key
|
|
142
|
+
3. Change the **Role** (e.g., Admin, Supervisor, Read-Only)
|
|
143
|
+
4. Changes take effect immediately - no reconnection needed
|
|
144
|
+
|
|
145
|
+
**Tip:** Create separate API keys with different roles for different use cases (e.g., one for read-only queries, one for full access).
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Troubleshooting
|
|
150
|
+
|
|
151
|
+
### Common Issues
|
|
152
|
+
|
|
153
|
+
#### "Missing required environment variables"
|
|
154
|
+
|
|
155
|
+
**Cause:** `UTEAMUP_API_KEY` or `UTEAMUP_SECRET` not set in MCP configuration.
|
|
156
|
+
|
|
157
|
+
**Solution:**
|
|
158
|
+
1. Check your MCP configuration file (`~/.config/claude/mcp.json` or `%APPDATA%\Claude\mcp.json`)
|
|
159
|
+
2. Verify the `env` section includes both `UTEAMUP_API_KEY` and `UTEAMUP_SECRET`
|
|
160
|
+
3. Ensure there are no typos in the environment variable names
|
|
161
|
+
4. Restart Claude Desktop after making changes
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
#### "Authentication failed: Invalid API Key or Secret"
|
|
166
|
+
|
|
167
|
+
**Cause:** Wrong credentials or API key has been revoked.
|
|
168
|
+
|
|
169
|
+
**Solution:**
|
|
170
|
+
1. Verify your credentials in UteamUP:
|
|
171
|
+
- Go to **Tenant Settings** → **API Keys**
|
|
172
|
+
- Check that your API key is **Active** (not expired or revoked)
|
|
173
|
+
2. If credentials are lost, create a new API key:
|
|
174
|
+
- Click **"Create API Key"**
|
|
175
|
+
- Enable **"MCP Enabled"**
|
|
176
|
+
- Copy the new credentials
|
|
177
|
+
3. Update your MCP configuration with the new credentials
|
|
178
|
+
4. Restart Claude Desktop
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
#### "MCP access denied: Your API key does not have MCP enabled"
|
|
183
|
+
|
|
184
|
+
**Cause:** The API key was created without MCP support.
|
|
185
|
+
|
|
186
|
+
**Solution:**
|
|
187
|
+
1. Create a new API key with MCP enabled:
|
|
188
|
+
- Go to **Tenant Settings** → **API Keys**
|
|
189
|
+
- Click **"Set up AI Integration"** or **"Create API Key"**
|
|
190
|
+
- **Important:** Check the **"MCP Enabled"** toggle
|
|
191
|
+
- Click **Create API Key**
|
|
192
|
+
2. Update your MCP configuration with the new credentials
|
|
193
|
+
3. Restart Claude Desktop
|
|
194
|
+
|
|
195
|
+
**Note:** You cannot enable MCP on an existing API key. You must create a new one.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
#### "ECONNREFUSED" or "Connection refused"
|
|
200
|
+
|
|
201
|
+
**Cause:** Cannot reach the API server.
|
|
202
|
+
|
|
203
|
+
**Solutions:**
|
|
204
|
+
1. **Check your internet connection**
|
|
205
|
+
2. **Verify the base URL:**
|
|
206
|
+
- Production: `https://api.uteamup.com` (default, no env var needed)
|
|
207
|
+
- Development: Set `UTEAMUP_API_BASE_URL=https://devback.uteamup.com`
|
|
208
|
+
3. **Check if UteamUP is down:**
|
|
209
|
+
- Contact support@uteamup.com
|
|
210
|
+
4. **For local development:**
|
|
211
|
+
- Ensure backend is running: `cd UteamUP_Backend/UteamUP_API && make watch`
|
|
212
|
+
- Use `UTEAMUP_API_BASE_URL=https://localhost:5002`
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
#### "UNABLE_TO_VERIFY_LEAF_SIGNATURE" or certificate errors
|
|
217
|
+
|
|
218
|
+
**Cause:** Self-signed certificate (development mode).
|
|
219
|
+
|
|
220
|
+
**Solution:** This package automatically accepts self-signed certificates for development. If you're seeing this error:
|
|
221
|
+
1. Verify `UTEAMUP_API_BASE_URL` is set correctly
|
|
222
|
+
2. If using local backend, ensure it's running on HTTPS
|
|
223
|
+
3. For production, this should never happen - contact support
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
#### Tools list is empty or incomplete
|
|
228
|
+
|
|
229
|
+
**Cause:** API key role has limited permissions.
|
|
230
|
+
|
|
231
|
+
**Solution:**
|
|
232
|
+
1. Go to **Tenant Settings** → **API Keys** in UteamUP
|
|
233
|
+
2. Edit your API key
|
|
234
|
+
3. Change the role to one with more permissions (e.g., Admin or Supervisor)
|
|
235
|
+
4. Available tools depend on your role's permissions
|
|
236
|
+
5. Changes take effect immediately
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
### Debugging
|
|
241
|
+
|
|
242
|
+
Enable debug logging to see detailed information:
|
|
243
|
+
|
|
244
|
+
```json
|
|
245
|
+
{
|
|
246
|
+
"mcpServers": {
|
|
247
|
+
"uteamup": {
|
|
248
|
+
"command": "npx",
|
|
249
|
+
"args": ["-y", "@uteamup/mcp-server"],
|
|
250
|
+
"env": {
|
|
251
|
+
"UTEAMUP_API_KEY": "your-key",
|
|
252
|
+
"UTEAMUP_SECRET": "your-secret",
|
|
253
|
+
"UTEAMUP_DEBUG": "1"
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Debug logs include:
|
|
261
|
+
- OAuth flow details
|
|
262
|
+
- PKCE generation
|
|
263
|
+
- Token caching events
|
|
264
|
+
- Request/response details
|
|
265
|
+
|
|
266
|
+
**Note:** Debug logs are written to stderr, not stdout (which is reserved for JSON-RPC messages).
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
### Still Having Issues?
|
|
271
|
+
|
|
272
|
+
1. **Check the logs:**
|
|
273
|
+
- Claude Desktop: View → Developer → Developer Tools → Console
|
|
274
|
+
- Look for `[MCP Proxy]` messages
|
|
275
|
+
|
|
276
|
+
2. **Verify credentials format:**
|
|
277
|
+
- API Key: exactly 32 characters
|
|
278
|
+
- Secret: at least 64 characters
|
|
279
|
+
- No extra spaces or quotes
|
|
280
|
+
|
|
281
|
+
3. **Test manually:**
|
|
282
|
+
```bash
|
|
283
|
+
export UTEAMUP_API_KEY="your-key"
|
|
284
|
+
export UTEAMUP_SECRET="your-secret"
|
|
285
|
+
export UTEAMUP_DEBUG="1"
|
|
286
|
+
echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | npx @uteamup/mcp-server
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
4. **Get help:**
|
|
290
|
+
- Email: support@uteamup.com
|
|
291
|
+
- Documentation: https://docs.uteamup.com/mcp
|
|
292
|
+
- GitHub Issues: https://github.com/uteamup/mcp-server/issues
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Development
|
|
297
|
+
|
|
298
|
+
### Local Development Setup
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
# Clone the repository
|
|
302
|
+
git clone https://github.com/UteamUP/uteamup-mcp-proxy.git
|
|
303
|
+
cd uteamup-mcp-proxy
|
|
304
|
+
|
|
305
|
+
# Install dev dependencies
|
|
306
|
+
npm install
|
|
307
|
+
|
|
308
|
+
# Run tests
|
|
309
|
+
npm test
|
|
310
|
+
|
|
311
|
+
# Run with debug logging
|
|
312
|
+
export UTEAMUP_DEBUG=1
|
|
313
|
+
export UTEAMUP_API_KEY="your-dev-key"
|
|
314
|
+
export UTEAMUP_SECRET="your-dev-secret"
|
|
315
|
+
export UTEAMUP_API_BASE_URL="https://localhost:5002"
|
|
316
|
+
node index.js
|
|
317
|
+
|
|
318
|
+
# Test with a sample request
|
|
319
|
+
echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | node index.js
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Testing Locally with Claude Desktop
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
# Create a local link
|
|
326
|
+
npm link
|
|
327
|
+
|
|
328
|
+
# Update Claude Desktop config to use local version
|
|
329
|
+
{
|
|
330
|
+
"mcpServers": {
|
|
331
|
+
"uteamup-local": {
|
|
332
|
+
"command": "node",
|
|
333
|
+
"args": ["/absolute/path/to/uteamup-mcp-proxy/index.js"],
|
|
334
|
+
"env": {
|
|
335
|
+
"UTEAMUP_API_KEY": "your-key",
|
|
336
|
+
"UTEAMUP_SECRET": "your-secret",
|
|
337
|
+
"UTEAMUP_DEBUG": "1"
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Project Structure
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
uteamup-mcp-proxy/
|
|
348
|
+
├── index.js # Main entry point (OAuth + MCP client)
|
|
349
|
+
├── package.json # Package metadata
|
|
350
|
+
├── README.md # This file
|
|
351
|
+
├── LICENSE # MIT license
|
|
352
|
+
├── CHANGELOG.md # Version history
|
|
353
|
+
├── .gitignore # Git ignore rules
|
|
354
|
+
├── .npmignore # Files to exclude from npm package
|
|
355
|
+
└── test/ # Test files (not published)
|
|
356
|
+
├── oauth.test.js
|
|
357
|
+
└── integration.test.js
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Running Tests
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
# Run all tests
|
|
364
|
+
npm test
|
|
365
|
+
|
|
366
|
+
# Run tests in watch mode
|
|
367
|
+
npm run test:watch
|
|
368
|
+
|
|
369
|
+
# Run only integration tests (requires credentials)
|
|
370
|
+
npm run test:integration
|
|
371
|
+
|
|
372
|
+
# Run with coverage
|
|
373
|
+
npm test -- --coverage
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Code Style
|
|
377
|
+
|
|
378
|
+
This project uses:
|
|
379
|
+
- **ESLint** for linting
|
|
380
|
+
- **Prettier** for formatting
|
|
381
|
+
- **CommonJS** modules (not ESM) for maximum compatibility
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
# Lint code
|
|
385
|
+
npm run lint
|
|
386
|
+
|
|
387
|
+
# Format code
|
|
388
|
+
npm run format
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Contributing
|
|
394
|
+
|
|
395
|
+
Contributions are welcome! Please:
|
|
396
|
+
|
|
397
|
+
1. Fork the repository
|
|
398
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
399
|
+
3. Make your changes
|
|
400
|
+
4. Add tests for new functionality
|
|
401
|
+
5. Ensure all tests pass (`npm test`)
|
|
402
|
+
6. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
403
|
+
7. Push to the branch (`git push origin feature/amazing-feature`)
|
|
404
|
+
8. Open a Pull Request
|
|
405
|
+
|
|
406
|
+
### Commit Message Format
|
|
407
|
+
|
|
408
|
+
```
|
|
409
|
+
type(scope): subject
|
|
410
|
+
|
|
411
|
+
Types: feat, fix, docs, refactor, test, chore
|
|
412
|
+
Scope: oauth, client, docs, etc.
|
|
413
|
+
|
|
414
|
+
Example:
|
|
415
|
+
feat(oauth): add automatic token refresh on expiry
|
|
416
|
+
fix(client): handle network timeouts gracefully
|
|
417
|
+
docs(readme): update troubleshooting section
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## License
|
|
423
|
+
|
|
424
|
+
MIT © UteamUP
|
|
425
|
+
|
|
426
|
+
See [LICENSE](LICENSE) for details.
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## Support
|
|
431
|
+
|
|
432
|
+
- **Documentation:** https://docs.uteamup.com/mcp
|
|
433
|
+
- **Email:** support@uteamup.com
|
|
434
|
+
- **Issues:** https://github.com/uteamup/mcp-server/issues
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## Related Links
|
|
439
|
+
|
|
440
|
+
- [UteamUP Website](https://uteamup.com)
|
|
441
|
+
- [Model Context Protocol Specification](https://modelcontextprotocol.io)
|
|
442
|
+
- [Claude Desktop](https://claude.ai/download)
|
|
443
|
+
- [OAuth 2.0 PKCE](https://oauth.net/2/pkce/)
|
package/index.js
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* UteamUP MCP Proxy - Simple OAuth handler
|
|
5
|
+
* Usage: Set environment variables and run via npx or node
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const https = require('https');
|
|
9
|
+
const crypto = require('crypto');
|
|
10
|
+
|
|
11
|
+
// Configuration
|
|
12
|
+
const API_KEY = process.env.UTEAMUP_API_KEY;
|
|
13
|
+
const SECRET = process.env.UTEAMUP_SECRET;
|
|
14
|
+
const BASE_URL = process.env.UTEAMUP_API_BASE_URL || 'https://localhost:5002';
|
|
15
|
+
const DEBUG = process.env.UTEAMUP_DEBUG === '1' || process.env.UTEAMUP_DEBUG === 'true';
|
|
16
|
+
|
|
17
|
+
// Token cache
|
|
18
|
+
let accessToken = null;
|
|
19
|
+
let tokenExpiry = null;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Debug logging helper (only outputs when UTEAMUP_DEBUG is enabled)
|
|
23
|
+
* @param {...any} args - Arguments to log
|
|
24
|
+
*/
|
|
25
|
+
function debug(...args) {
|
|
26
|
+
if (DEBUG) console.error('[DEBUG]', ...args);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generates a PKCE (Proof Key for Code Exchange) code verifier and challenge pair.
|
|
31
|
+
* @returns {{verifier: string, challenge: string}} PKCE pair with S256 challenge
|
|
32
|
+
*/
|
|
33
|
+
function generatePKCE() {
|
|
34
|
+
const verifier = crypto.randomBytes(32).toString('base64url');
|
|
35
|
+
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
|
|
36
|
+
debug('Generated PKCE pair:', { verifierLength: verifier.length, challengeLength: challenge.length });
|
|
37
|
+
return { verifier, challenge };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Gets an OAuth access token, using cached token if still valid.
|
|
42
|
+
* Implements full OAuth 2.0 Authorization Code + PKCE flow.
|
|
43
|
+
* @returns {Promise<string>} JWT access token valid for 7 days
|
|
44
|
+
* @throws {Error} If authentication fails or credentials are invalid
|
|
45
|
+
*/
|
|
46
|
+
async function getAccessToken() {
|
|
47
|
+
// Return cached token if still valid
|
|
48
|
+
if (accessToken && tokenExpiry && Date.now() < tokenExpiry - 60000) {
|
|
49
|
+
debug('Using cached access token');
|
|
50
|
+
return accessToken;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.error('[MCP Proxy] Getting new access token...');
|
|
54
|
+
|
|
55
|
+
const { verifier, challenge } = generatePKCE();
|
|
56
|
+
|
|
57
|
+
// Step 1: Get authorization code
|
|
58
|
+
const authCode = await new Promise((resolve, reject) => {
|
|
59
|
+
const authParams = new URLSearchParams({
|
|
60
|
+
response_type: 'code',
|
|
61
|
+
client_id: API_KEY,
|
|
62
|
+
redirect_uri: 'http://localhost:3000/callback',
|
|
63
|
+
code_challenge: challenge,
|
|
64
|
+
code_challenge_method: 'S256',
|
|
65
|
+
scope: 'mcp:full'
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
debug('Authorization request params:', authParams.toString());
|
|
69
|
+
|
|
70
|
+
https.get(`${BASE_URL}/oauth/authorize?${authParams}`, {
|
|
71
|
+
rejectUnauthorized: false // Allow self-signed certs for dev
|
|
72
|
+
}, (res) => {
|
|
73
|
+
const location = res.headers.location || '';
|
|
74
|
+
debug('Authorization redirect location:', location);
|
|
75
|
+
|
|
76
|
+
// Check for OAuth error in redirect
|
|
77
|
+
if (location.includes('error=')) {
|
|
78
|
+
const url = new URL(location, 'http://localhost');
|
|
79
|
+
const error = url.searchParams.get('error');
|
|
80
|
+
const description = url.searchParams.get('error_description');
|
|
81
|
+
reject(new Error(`OAuth authorization failed: ${error} - ${description || 'Unknown error'}`));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const match = location.match(/code=([^&]+)/);
|
|
86
|
+
if (match) {
|
|
87
|
+
debug('Authorization code received');
|
|
88
|
+
resolve(match[1]);
|
|
89
|
+
} else {
|
|
90
|
+
reject(new Error('No authorization code in redirect. Location: ' + location));
|
|
91
|
+
}
|
|
92
|
+
}).on('error', reject);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
console.error(`[MCP Proxy] Got auth code: ${authCode.substring(0, 20)}...`);
|
|
96
|
+
|
|
97
|
+
// Step 2: Exchange for access token
|
|
98
|
+
const tokenData = new URLSearchParams({
|
|
99
|
+
grant_type: 'authorization_code',
|
|
100
|
+
code: authCode,
|
|
101
|
+
redirect_uri: 'http://localhost:3000/callback',
|
|
102
|
+
client_id: API_KEY,
|
|
103
|
+
client_secret: SECRET,
|
|
104
|
+
code_verifier: verifier
|
|
105
|
+
}).toString();
|
|
106
|
+
|
|
107
|
+
const token = await new Promise((resolve, reject) => {
|
|
108
|
+
const req = https.request(`${BASE_URL}/oauth/token`, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: {
|
|
111
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
112
|
+
'Content-Length': Buffer.byteLength(tokenData)
|
|
113
|
+
},
|
|
114
|
+
rejectUnauthorized: false
|
|
115
|
+
}, (res) => {
|
|
116
|
+
let data = '';
|
|
117
|
+
res.on('data', chunk => data += chunk);
|
|
118
|
+
res.on('end', () => {
|
|
119
|
+
debug('Token exchange response status:', res.statusCode);
|
|
120
|
+
debug('Token exchange response data:', data);
|
|
121
|
+
|
|
122
|
+
// Check for error status codes
|
|
123
|
+
if (res.statusCode === 401) {
|
|
124
|
+
reject(new Error('Authentication failed: Invalid API Key or Secret. Please verify your credentials in UteamUP Tenant Settings → API Keys.'));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (res.statusCode === 403) {
|
|
128
|
+
if (data.includes('MCP') || data.includes('mcp')) {
|
|
129
|
+
reject(new Error('MCP access denied: Your API key does not have MCP enabled. Create a new MCP-enabled key in Tenant Settings → API Keys.'));
|
|
130
|
+
} else {
|
|
131
|
+
reject(new Error('Access forbidden: ' + data));
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (res.statusCode !== 200) {
|
|
136
|
+
reject(new Error(`Token exchange failed (HTTP ${res.statusCode}): ${data}`));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const json = JSON.parse(data);
|
|
142
|
+
// CRITICAL FIX: Backend returns access_token (OAuth 2.0 standard), not accessToken
|
|
143
|
+
if (json.access_token) {
|
|
144
|
+
debug('Access token received successfully');
|
|
145
|
+
resolve(json.access_token);
|
|
146
|
+
} else if (json.error) {
|
|
147
|
+
reject(new Error(`OAuth error: ${json.error} - ${json.error_description || ''}`));
|
|
148
|
+
} else {
|
|
149
|
+
reject(new Error(`Token exchange failed: ${data}`));
|
|
150
|
+
}
|
|
151
|
+
} catch (e) {
|
|
152
|
+
reject(new Error(`Invalid JSON response: ${data}`));
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
req.on('error', reject);
|
|
158
|
+
req.write(tokenData);
|
|
159
|
+
req.end();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
accessToken = token;
|
|
163
|
+
tokenExpiry = Date.now() + (7 * 24 * 60 * 60 * 1000); // 7 days
|
|
164
|
+
console.error('[MCP Proxy] ✅ Got access token');
|
|
165
|
+
debug('Token cached, expires at:', new Date(tokenExpiry).toISOString());
|
|
166
|
+
|
|
167
|
+
return accessToken;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Forwards a JSON-RPC 2.0 MCP request to the backend.
|
|
172
|
+
* @param {object} request - JSON-RPC 2.0 request object
|
|
173
|
+
* @returns {Promise<object>} JSON-RPC 2.0 response object
|
|
174
|
+
*/
|
|
175
|
+
async function forwardRequest(request) {
|
|
176
|
+
const token = await getAccessToken();
|
|
177
|
+
const data = JSON.stringify(request);
|
|
178
|
+
|
|
179
|
+
debug('Forwarding request:', { method: request.method, id: request.id });
|
|
180
|
+
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
const req = https.request(`${BASE_URL}/mcp`, {
|
|
183
|
+
method: 'POST',
|
|
184
|
+
headers: {
|
|
185
|
+
'Authorization': `Bearer ${token}`,
|
|
186
|
+
'Content-Type': 'application/json',
|
|
187
|
+
'Content-Length': Buffer.byteLength(data)
|
|
188
|
+
},
|
|
189
|
+
rejectUnauthorized: false
|
|
190
|
+
}, (res) => {
|
|
191
|
+
let responseData = '';
|
|
192
|
+
res.on('data', chunk => responseData += chunk);
|
|
193
|
+
res.on('end', () => {
|
|
194
|
+
debug('MCP response received:', { statusCode: res.statusCode, dataLength: responseData.length });
|
|
195
|
+
try {
|
|
196
|
+
resolve(JSON.parse(responseData));
|
|
197
|
+
} catch (e) {
|
|
198
|
+
resolve({ error: { code: -32700, message: 'Parse error', data: responseData } });
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
req.on('error', (error) => {
|
|
204
|
+
console.error('[MCP Proxy] Network error:', error.message);
|
|
205
|
+
resolve({ error: { code: -32603, message: error.message } });
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
req.write(data);
|
|
209
|
+
req.end();
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Main stdio loop
|
|
214
|
+
async function main() {
|
|
215
|
+
// Validate required environment variables
|
|
216
|
+
if (!API_KEY || !SECRET) {
|
|
217
|
+
console.error('ERROR: Missing required environment variables');
|
|
218
|
+
console.error('');
|
|
219
|
+
console.error('Required:');
|
|
220
|
+
console.error(' UTEAMUP_API_KEY - Your MCP-enabled API key (32 characters)');
|
|
221
|
+
console.error(' UTEAMUP_SECRET - Your API key secret (64+ characters)');
|
|
222
|
+
console.error('');
|
|
223
|
+
console.error('Optional:');
|
|
224
|
+
console.error(' UTEAMUP_API_BASE_URL - API endpoint (default: https://api.uteamup.com)');
|
|
225
|
+
console.error(' UTEAMUP_DEBUG - Enable debug logging (1 or true)');
|
|
226
|
+
console.error('');
|
|
227
|
+
console.error('Example:');
|
|
228
|
+
console.error(' export UTEAMUP_API_KEY="your-api-key"');
|
|
229
|
+
console.error(' export UTEAMUP_SECRET="your-secret"');
|
|
230
|
+
console.error(' npx @uteamup/mcp-server');
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Validate API key format (should be 32 chars)
|
|
235
|
+
if (API_KEY.length !== 32) {
|
|
236
|
+
console.error('WARNING: API Key should be exactly 32 characters');
|
|
237
|
+
console.error(` Current length: ${API_KEY.length}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Validate secret format (should be 64+ chars)
|
|
241
|
+
if (SECRET.length < 64) {
|
|
242
|
+
console.error('WARNING: Secret should be at least 64 characters');
|
|
243
|
+
console.error(` Current length: ${SECRET.length}`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
console.error('[MCP Proxy] Starting UteamUP MCP Server');
|
|
247
|
+
console.error(`[MCP Proxy] Version: 1.0.0-beta.1`);
|
|
248
|
+
console.error(`[MCP Proxy] Base URL: ${BASE_URL}`);
|
|
249
|
+
console.error(`[MCP Proxy] API Key: ${API_KEY.substring(0, 8)}...`);
|
|
250
|
+
console.error(`[MCP Proxy] Debug: ${DEBUG ? 'enabled' : 'disabled'}`);
|
|
251
|
+
console.error('[MCP Proxy] Ready to receive JSON-RPC requests');
|
|
252
|
+
|
|
253
|
+
let buffer = '';
|
|
254
|
+
|
|
255
|
+
process.stdin.on('data', async (chunk) => {
|
|
256
|
+
buffer += chunk.toString();
|
|
257
|
+
|
|
258
|
+
let newlineIndex;
|
|
259
|
+
while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
|
|
260
|
+
const line = buffer.slice(0, newlineIndex).trim();
|
|
261
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
262
|
+
|
|
263
|
+
if (!line) continue;
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
const request = JSON.parse(line);
|
|
267
|
+
debug('Processing request:', request.method, 'id:', request.id);
|
|
268
|
+
console.error(`[MCP Proxy] Request: ${request.method}`);
|
|
269
|
+
|
|
270
|
+
const response = await forwardRequest(request);
|
|
271
|
+
|
|
272
|
+
// Ensure response includes request ID for proper JSON-RPC 2.0 format
|
|
273
|
+
if (request.id !== undefined && response.id === undefined) {
|
|
274
|
+
response.id = request.id;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
278
|
+
debug('Response sent for request id:', request.id);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.error('[MCP Proxy] Error:', error.message);
|
|
281
|
+
|
|
282
|
+
// Try to extract request ID if possible
|
|
283
|
+
let requestId = null;
|
|
284
|
+
try {
|
|
285
|
+
const req = JSON.parse(line);
|
|
286
|
+
requestId = req.id;
|
|
287
|
+
} catch (e) {
|
|
288
|
+
// Line wasn't valid JSON, use null
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const errorResponse = {
|
|
292
|
+
jsonrpc: '2.0',
|
|
293
|
+
error: {
|
|
294
|
+
code: -32603,
|
|
295
|
+
message: error.message,
|
|
296
|
+
data: DEBUG ? error.stack : undefined
|
|
297
|
+
},
|
|
298
|
+
id: requestId
|
|
299
|
+
};
|
|
300
|
+
process.stdout.write(JSON.stringify(errorResponse) + '\n');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
process.stdin.on('end', () => {
|
|
306
|
+
console.error('[MCP Proxy] stdin closed, exiting');
|
|
307
|
+
process.exit(0);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
process.stdin.resume();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Graceful shutdown handlers
|
|
314
|
+
process.on('SIGINT', () => {
|
|
315
|
+
console.error('[MCP Proxy] Received SIGINT, shutting down gracefully...');
|
|
316
|
+
process.exit(0);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
process.on('SIGTERM', () => {
|
|
320
|
+
console.error('[MCP Proxy] Received SIGTERM, shutting down gracefully...');
|
|
321
|
+
process.exit(0);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
process.on('uncaughtException', (error) => {
|
|
325
|
+
console.error('[MCP Proxy] Uncaught exception:', error.message);
|
|
326
|
+
if (DEBUG) console.error(error.stack);
|
|
327
|
+
process.exit(1);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
main().catch((error) => {
|
|
331
|
+
console.error('[MCP Proxy] Fatal error:', error);
|
|
332
|
+
process.exit(1);
|
|
333
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uteamup/mcp-server",
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"description": "MCP server client for UteamUP - enables Claude Desktop/Code integration with automatic OAuth 2.0 authentication",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"uteamup-mcp-server": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "commonjs",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18.0.0"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"model-context-protocol",
|
|
16
|
+
"uteamup",
|
|
17
|
+
"claude",
|
|
18
|
+
"claude-desktop",
|
|
19
|
+
"claude-code",
|
|
20
|
+
"oauth",
|
|
21
|
+
"oauth2",
|
|
22
|
+
"pkce",
|
|
23
|
+
"cmms",
|
|
24
|
+
"maintenance",
|
|
25
|
+
"work-orders",
|
|
26
|
+
"asset-management"
|
|
27
|
+
],
|
|
28
|
+
"author": "UteamUP <support@uteamup.com>",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/uteamup/mcp-server.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/uteamup/mcp-server/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/uteamup/mcp-server#readme",
|
|
38
|
+
"scripts": {
|
|
39
|
+
"test": "jest",
|
|
40
|
+
"test:watch": "jest --watch",
|
|
41
|
+
"test:integration": "jest test/integration.test.js",
|
|
42
|
+
"lint": "eslint index.js test/",
|
|
43
|
+
"format": "prettier --write \"**/*.{js,json,md}\"",
|
|
44
|
+
"prepublishOnly": "npm test"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"jest": "^29.7.0",
|
|
48
|
+
"eslint": "^8.56.0",
|
|
49
|
+
"prettier": "^3.2.5"
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"index.js",
|
|
53
|
+
"README.md",
|
|
54
|
+
"LICENSE",
|
|
55
|
+
"CHANGELOG.md"
|
|
56
|
+
]
|
|
57
|
+
}
|