@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 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
+ [![npm version](https://img.shields.io/npm/v/@uteamup/mcp-server.svg)](https://www.npmjs.com/package/@uteamup/mcp-server)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![Node.js Version](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen)](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
+ }