syncromsp-mcp 1.3.2 → 1.4.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/README.md +204 -124
- package/dist/auth.d.ts +13 -7
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +117 -23
- package/dist/auth.js.map +1 -1
- package/dist/index.js +45 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,22 +1,45 @@
|
|
|
1
1
|
# SyncroMSP MCP Server
|
|
2
2
|
|
|
3
|
-
A fully-featured [Model Context Protocol](https://modelcontextprotocol.io) server for the [SyncroMSP](https://syncromsp.com) IT/MSP platform.
|
|
3
|
+
A fully-featured [Model Context Protocol](https://modelcontextprotocol.io) server for the [SyncroMSP](https://syncromsp.com) IT/MSP platform. Gives AI assistants full access to tickets, customers, assets, invoices, and 30+ resource types.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **170 API endpoints** across 15
|
|
8
|
-
- **Domain navigation** — only 3-8 tools visible at a time, not 170
|
|
7
|
+
- **170 API endpoints** across 15 domains
|
|
9
8
|
- **Full CRUD** for tickets, customers, invoices, estimates, appointments, contracts, products, and more
|
|
10
9
|
- **Ticket comments** — email replies, public notes, and private/internal notes
|
|
11
10
|
- **Line items** — add products from catalog or manual entries to tickets, invoices, estimates, schedules
|
|
12
11
|
- **RMM alerts** — create, read, mute, resolve alerts on assets
|
|
13
12
|
- **Rate limiting** — built-in 180 req/min token bucket (Syncro API limit)
|
|
14
13
|
- **Confirmation required** for all destructive operations (DELETE, etc.)
|
|
15
|
-
- **
|
|
14
|
+
- **Auto-update check** — warns on startup if a newer version is available
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
### Deployment Options
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
| Method | Best For | Auto-Updates |
|
|
19
|
+
|--------|----------|-------------|
|
|
20
|
+
| [**Claude Code**](#claude-code) | Developers using Claude Code CLI | Yes (npx) |
|
|
21
|
+
| [**Claude Desktop**](#claude-desktop) | Local desktop app users | Yes (npx) |
|
|
22
|
+
| [**Docker + Claude.ai**](#docker-deployment-remote-mcp) | Teams, remote access, Claude.ai web | Yes (Watchtower) |
|
|
23
|
+
| [**From Source**](#from-source) | Development and customization | Manual |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Prerequisites
|
|
28
|
+
|
|
29
|
+
### Getting Your Syncro API Key
|
|
30
|
+
|
|
31
|
+
1. Log in to your Syncro account
|
|
32
|
+
2. Go to **Admin** > **API Tokens**
|
|
33
|
+
3. Click **+ New Token**
|
|
34
|
+
4. Select the **Custom Permissions** tab
|
|
35
|
+
5. Name your token and set permissions for the resources you need
|
|
36
|
+
6. Click **Create** and copy the token (it cannot be retrieved later)
|
|
37
|
+
|
|
38
|
+
Your **subdomain** is the part before `.syncromsp.com` in your Syncro URL (e.g., `mycompany` from `mycompany.syncromsp.com`).
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Claude Code
|
|
20
43
|
|
|
21
44
|
```bash
|
|
22
45
|
claude mcp add syncromsp \
|
|
@@ -25,13 +48,17 @@ claude mcp add syncromsp \
|
|
|
25
48
|
-- npx syncromsp-mcp
|
|
26
49
|
```
|
|
27
50
|
|
|
28
|
-
|
|
51
|
+
That's it. Claude Code will download and run the latest version automatically.
|
|
29
52
|
|
|
30
|
-
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Claude Desktop
|
|
56
|
+
|
|
57
|
+
### Option 1: MCPB Extension
|
|
31
58
|
|
|
32
59
|
Download the latest `.mcpb` file from [Releases](https://github.com/advenimus/syncromsp-mcp/releases) and double-click to install. Claude Desktop will prompt you for your API key and subdomain.
|
|
33
60
|
|
|
34
|
-
|
|
61
|
+
### Option 2: Manual Configuration
|
|
35
62
|
|
|
36
63
|
Add to your `claude_desktop_config.json`:
|
|
37
64
|
|
|
@@ -40,7 +67,7 @@ Add to your `claude_desktop_config.json`:
|
|
|
40
67
|
"mcpServers": {
|
|
41
68
|
"syncromsp": {
|
|
42
69
|
"command": "npx",
|
|
43
|
-
"args": ["syncromsp-mcp"],
|
|
70
|
+
"args": ["-y", "syncromsp-mcp"],
|
|
44
71
|
"env": {
|
|
45
72
|
"SYNCRO_API_KEY": "your-api-key",
|
|
46
73
|
"SYNCRO_SUBDOMAIN": "your-subdomain"
|
|
@@ -54,162 +81,198 @@ Config file location:
|
|
|
54
81
|
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
55
82
|
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
56
83
|
|
|
57
|
-
|
|
84
|
+
Restart Claude Desktop after saving. The server updates automatically via npx on each restart.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Docker Deployment (Remote MCP)
|
|
89
|
+
|
|
90
|
+
Deploy as a Docker container for remote access from Claude.ai, shared team usage, or running on a server. Includes built-in OAuth 2.1 authentication so only authorized users can connect.
|
|
91
|
+
|
|
92
|
+
### Step 1: Clone and Configure
|
|
58
93
|
|
|
59
94
|
```bash
|
|
60
95
|
git clone https://github.com/advenimus/syncromsp-mcp.git
|
|
61
96
|
cd syncromsp-mcp
|
|
62
|
-
|
|
63
|
-
npm run build
|
|
64
|
-
|
|
65
|
-
# Set environment variables
|
|
66
|
-
export SYNCRO_API_KEY=your-api-key
|
|
67
|
-
export SYNCRO_SUBDOMAIN=your-subdomain
|
|
68
|
-
|
|
69
|
-
# Run
|
|
70
|
-
npm start
|
|
97
|
+
cp .env.example .env
|
|
71
98
|
```
|
|
72
99
|
|
|
73
|
-
|
|
100
|
+
Edit `.env` with your settings:
|
|
74
101
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
5. Name your token and set permissions for the resources you need
|
|
80
|
-
6. Click **Create** and copy the token (it cannot be retrieved later)
|
|
81
|
-
|
|
82
|
-
Your subdomain is the part before `.syncromsp.com` in your Syncro URL (e.g., `mycompany` from `mycompany.syncromsp.com`).
|
|
102
|
+
```bash
|
|
103
|
+
# Required: Syncro credentials
|
|
104
|
+
SYNCRO_API_KEY=your-api-key
|
|
105
|
+
SYNCRO_SUBDOMAIN=your-subdomain
|
|
83
106
|
|
|
84
|
-
|
|
107
|
+
# Required: The public URL where this server will be reachable
|
|
108
|
+
# Must be HTTPS for production (put behind Traefik, Caddy, nginx, etc.)
|
|
109
|
+
MCP_BASE_URL=https://mcp.yourcompany.com
|
|
85
110
|
|
|
86
|
-
|
|
111
|
+
# Required: Access key that users must enter to authorize connections
|
|
112
|
+
# Generate one with: openssl rand -hex 32
|
|
113
|
+
MCP_AUTH_SECRET=your-strong-secret-here
|
|
114
|
+
```
|
|
87
115
|
|
|
88
|
-
|
|
116
|
+
### Step 2: Deploy
|
|
89
117
|
|
|
118
|
+
```bash
|
|
119
|
+
docker compose up -d
|
|
90
120
|
```
|
|
91
|
-
Startup → 3 tools visible:
|
|
92
|
-
syncro_navigate("tickets") → loads ticket tools
|
|
93
|
-
syncro_status() → shows current domain
|
|
94
|
-
syncro_back() → returns to root
|
|
95
|
-
|
|
96
|
-
After navigating to "tickets" → domain tools visible:
|
|
97
|
-
tickets_list, tickets_get, tickets_create, tickets_update,
|
|
98
|
-
tickets_delete, tickets_comment, tickets_add_line_item, ...
|
|
99
|
-
syncro_back()
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Available Domains
|
|
103
121
|
|
|
104
|
-
|
|
105
|
-
|--------|-------------|---------------|
|
|
106
|
-
| **tickets** | Service tickets | CRUD, comments (email/public/private), line items, timers, attachments |
|
|
107
|
-
| **customers** | Customer records | CRUD, phone numbers, autocomplete |
|
|
108
|
-
| **assets** | Customer assets | CRUD, patches, properties (OS, RAM, HDD, etc.) |
|
|
109
|
-
| **contacts** | Customer contacts | CRUD |
|
|
110
|
-
| **invoices** | Invoices | CRUD, line items (manual + product catalog), print, email |
|
|
111
|
-
| **estimates** | Estimates/quotes | CRUD, line items, print, email, convert to invoice |
|
|
112
|
-
| **appointments** | Calendar appointments | CRUD, appointment types, ticket linking |
|
|
113
|
-
| **products** | Inventory/products | CRUD, serials, SKUs, categories, images |
|
|
114
|
-
| **payments** | Payment records | Create, read, multi-invoice distribution |
|
|
115
|
-
| **leads** | Leads/opportunities | Create, read, update |
|
|
116
|
-
| **contracts** | Service contracts | CRUD |
|
|
117
|
-
| **rmm** | RMM alerts | Create, read, mute, resolve |
|
|
118
|
-
| **scheduling** | Recurring invoices | CRUD, schedule line items |
|
|
119
|
-
| **time** | Timers and time logs | List, update |
|
|
120
|
-
| **admin** | Search, users, vendors, wiki, portal, settings, purchase orders, and more | Various |
|
|
122
|
+
The container runs on port 8080 by default. You need a reverse proxy (Traefik, Caddy, nginx) in front to provide HTTPS.
|
|
121
123
|
|
|
122
|
-
|
|
124
|
+
### Step 3: Connect from Claude.ai
|
|
123
125
|
|
|
124
|
-
|
|
126
|
+
1. In Claude.ai, go to **Settings** > **MCP Servers** > **Add Remote Server**
|
|
127
|
+
2. Enter your MCP URL: `https://mcp.yourcompany.com/mcp`
|
|
128
|
+
3. Claude.ai will auto-discover the OAuth endpoints
|
|
129
|
+
4. A login page appears — enter the `MCP_AUTH_SECRET` you configured in Step 1
|
|
130
|
+
5. Once authenticated, Claude.ai connects and all 170 tools become available
|
|
125
131
|
|
|
126
|
-
|
|
127
|
-
cp .env.example .env
|
|
128
|
-
# Edit .env with your Syncro credentials and base URL
|
|
129
|
-
```
|
|
132
|
+
### How Authentication Works
|
|
130
133
|
|
|
131
|
-
|
|
134
|
+
The server implements the [MCP OAuth 2.1 + PKCE](https://spec.modelcontextprotocol.io) spec with an access key gate:
|
|
132
135
|
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
```
|
|
137
|
+
Client connects → 401 Unauthorized
|
|
138
|
+
→ Client discovers /.well-known/oauth-authorization-server
|
|
139
|
+
→ Client dynamically registers (RFC 7591)
|
|
140
|
+
→ Client redirects user to /authorize
|
|
141
|
+
→ User sees login page, enters MCP_AUTH_SECRET
|
|
142
|
+
→ Correct key: auth code issued → token granted → MCP access
|
|
143
|
+
→ Wrong key: 403 Access Denied, connection rejected
|
|
137
144
|
```
|
|
138
145
|
|
|
139
|
-
|
|
146
|
+
- Tokens are validated on every MCP request via bearer auth
|
|
147
|
+
- Access tokens expire after 24 hours (refresh tokens last 30 days)
|
|
148
|
+
- Timing-safe secret comparison prevents side-channel attacks
|
|
149
|
+
- Server **refuses to start** if `MCP_AUTH_SECRET` is not set
|
|
140
150
|
|
|
141
|
-
|
|
142
|
-
|
|
151
|
+
### Example: Docker with Traefik
|
|
152
|
+
|
|
153
|
+
```yaml
|
|
154
|
+
services:
|
|
155
|
+
syncro-mcp:
|
|
156
|
+
image: ghcr.io/advenimus/syncromsp-mcp:latest
|
|
157
|
+
container_name: syncromsp-mcp
|
|
158
|
+
restart: unless-stopped
|
|
159
|
+
environment:
|
|
160
|
+
- SYNCRO_API_KEY=${SYNCRO_API_KEY}
|
|
161
|
+
- SYNCRO_SUBDOMAIN=${SYNCRO_SUBDOMAIN}
|
|
162
|
+
- MCP_TRANSPORT=http
|
|
163
|
+
- MCP_PORT=8080
|
|
164
|
+
- MCP_BASE_URL=https://mcp.yourcompany.com
|
|
165
|
+
- MCP_AUTH_SECRET=${MCP_AUTH_SECRET}
|
|
166
|
+
labels:
|
|
167
|
+
- "traefik.enable=true"
|
|
168
|
+
- "traefik.http.routers.mcp.rule=Host(`mcp.yourcompany.com`)"
|
|
169
|
+
- "traefik.http.routers.mcp.entrypoints=websecure"
|
|
170
|
+
- "traefik.http.routers.mcp.tls.certresolver=letsencrypt"
|
|
171
|
+
- "traefik.http.services.mcp.loadbalancer.server.port=8080"
|
|
143
172
|
```
|
|
144
173
|
|
|
145
|
-
###
|
|
174
|
+
### Example: Docker with Caddy
|
|
175
|
+
|
|
176
|
+
```yaml
|
|
177
|
+
services:
|
|
178
|
+
syncro-mcp:
|
|
179
|
+
image: ghcr.io/advenimus/syncromsp-mcp:latest
|
|
180
|
+
container_name: syncromsp-mcp
|
|
181
|
+
restart: unless-stopped
|
|
182
|
+
environment:
|
|
183
|
+
- SYNCRO_API_KEY=${SYNCRO_API_KEY}
|
|
184
|
+
- SYNCRO_SUBDOMAIN=${SYNCRO_SUBDOMAIN}
|
|
185
|
+
- MCP_TRANSPORT=http
|
|
186
|
+
- MCP_BASE_URL=https://mcp.yourcompany.com
|
|
187
|
+
- MCP_AUTH_SECRET=${MCP_AUTH_SECRET}
|
|
188
|
+
expose:
|
|
189
|
+
- "8080"
|
|
190
|
+
|
|
191
|
+
caddy:
|
|
192
|
+
image: caddy:2
|
|
193
|
+
restart: unless-stopped
|
|
194
|
+
ports:
|
|
195
|
+
- "80:80"
|
|
196
|
+
- "443:443"
|
|
197
|
+
volumes:
|
|
198
|
+
- ./Caddyfile:/etc/caddy/Caddyfile
|
|
199
|
+
- caddy_data:/data
|
|
146
200
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
4. Claude.ai will auto-discover the OAuth endpoints and authenticate
|
|
201
|
+
volumes:
|
|
202
|
+
caddy_data:
|
|
203
|
+
```
|
|
151
204
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
-
|
|
156
|
-
|
|
157
|
-
|
|
205
|
+
`Caddyfile`:
|
|
206
|
+
```
|
|
207
|
+
mcp.yourcompany.com {
|
|
208
|
+
reverse_proxy syncro-mcp:8080
|
|
209
|
+
}
|
|
210
|
+
```
|
|
158
211
|
|
|
159
|
-
### Disabling Auth
|
|
212
|
+
### Disabling Auth (Not Recommended)
|
|
160
213
|
|
|
161
|
-
For testing
|
|
214
|
+
For testing on private networks only:
|
|
162
215
|
|
|
163
216
|
```bash
|
|
164
217
|
MCP_AUTH=false docker compose up -d
|
|
165
218
|
```
|
|
166
219
|
|
|
220
|
+
**Warning:** Without auth, anyone who can reach the URL gets full access to your Syncro account.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
167
224
|
## Environment Variables
|
|
168
225
|
|
|
169
|
-
| Variable | Required | Description |
|
|
170
|
-
|
|
171
|
-
| `SYNCRO_API_KEY` | Yes | Your Syncro API token |
|
|
172
|
-
| `SYNCRO_SUBDOMAIN` | Yes | Your Syncro subdomain |
|
|
173
|
-
| `MCP_TRANSPORT` | No | `stdio` (
|
|
174
|
-
| `MCP_PORT` | No |
|
|
175
|
-
| `MCP_BASE_URL` | For
|
|
176
|
-
| `MCP_AUTH` | No | `true`
|
|
177
|
-
| `
|
|
226
|
+
| Variable | Required | Default | Description |
|
|
227
|
+
|----------|----------|---------|-------------|
|
|
228
|
+
| `SYNCRO_API_KEY` | Yes | — | Your Syncro API token |
|
|
229
|
+
| `SYNCRO_SUBDOMAIN` | Yes | — | Your Syncro subdomain |
|
|
230
|
+
| `MCP_TRANSPORT` | No | `stdio` | `stdio` (local) or `http` (Docker/remote) |
|
|
231
|
+
| `MCP_PORT` | No | `8080` | HTTP listen port |
|
|
232
|
+
| `MCP_BASE_URL` | For Docker | — | Public HTTPS URL (e.g., `https://mcp.yourcompany.com`) |
|
|
233
|
+
| `MCP_AUTH` | No | `true` | `true` or `false` to disable OAuth |
|
|
234
|
+
| `MCP_AUTH_SECRET` | For Docker | — | Access key users enter to authorize (min 8 chars) |
|
|
235
|
+
| `MCP_TOOL_MODE` | No | `flat` | `flat` (all tools) or `navigation` (lazy domains) |
|
|
178
236
|
|
|
179
|
-
|
|
237
|
+
---
|
|
180
238
|
|
|
181
|
-
|
|
239
|
+
## Available Domains
|
|
182
240
|
|
|
183
|
-
|
|
241
|
+
| Domain | Description | Key Operations |
|
|
242
|
+
|--------|-------------|---------------|
|
|
243
|
+
| **tickets** | Service tickets | CRUD, comments (email/public/private), line items, timers, attachments |
|
|
244
|
+
| **customers** | Customer records | CRUD, phone numbers, autocomplete |
|
|
245
|
+
| **assets** | Customer assets | CRUD, patches, properties (OS, RAM, HDD, etc.) |
|
|
246
|
+
| **contacts** | Customer contacts | CRUD |
|
|
247
|
+
| **invoices** | Invoices | CRUD, line items (manual + product catalog), print, email |
|
|
248
|
+
| **estimates** | Estimates/quotes | CRUD, line items, print, email, convert to invoice |
|
|
249
|
+
| **appointments** | Calendar appointments | CRUD, appointment types, ticket linking |
|
|
250
|
+
| **products** | Inventory/products | CRUD, serials, SKUs, categories, images |
|
|
251
|
+
| **payments** | Payment records | Create, read, multi-invoice distribution |
|
|
252
|
+
| **leads** | Leads/opportunities | Create, read, update |
|
|
253
|
+
| **contracts** | Service contracts | CRUD |
|
|
254
|
+
| **rmm** | RMM alerts | Create, read, mute, resolve |
|
|
255
|
+
| **scheduling** | Recurring invoices | CRUD, schedule line items |
|
|
256
|
+
| **time** | Timers and time logs | List, update |
|
|
257
|
+
| **admin** | Search, users, vendors, wiki, portal, settings, purchase orders, and more | Various |
|
|
184
258
|
|
|
185
|
-
|
|
186
|
-
- **Line items** cannot be added inline during resource creation — always add them via separate API calls after creating the parent resource
|
|
187
|
-
- **Ticket comments** have 3 modes: email reply, public note, and private/internal note
|
|
188
|
-
- Some resources have no DELETE endpoint (vendors, leads, products, assets) — use `disabled: true` via update instead
|
|
259
|
+
---
|
|
189
260
|
|
|
190
261
|
## Staying Up to Date
|
|
191
262
|
|
|
192
263
|
The server checks for updates on startup and logs a warning if a newer version is available.
|
|
193
264
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
### Claude Desktop (MCPB)
|
|
202
|
-
|
|
203
|
-
Download the latest `.mcpb` from [Releases](https://github.com/advenimus/syncromsp-mcp/releases) and reinstall.
|
|
265
|
+
| Method | How to Update |
|
|
266
|
+
|--------|--------------|
|
|
267
|
+
| **npx / Claude Desktop** | Automatic — npx pulls latest on each run |
|
|
268
|
+
| **Docker** | `docker compose pull && docker compose up -d` |
|
|
269
|
+
| **Docker (auto)** | Add [Watchtower](https://containrrr.dev/watchtower/) for automatic daily updates |
|
|
270
|
+
| **MCPB** | Download latest `.mcpb` from [Releases](https://github.com/advenimus/syncromsp-mcp/releases) |
|
|
271
|
+
| **From Source** | `git pull && npm install && npm run build` |
|
|
204
272
|
|
|
205
|
-
###
|
|
206
|
-
|
|
207
|
-
```bash
|
|
208
|
-
docker compose pull
|
|
209
|
-
docker compose up -d
|
|
210
|
-
```
|
|
273
|
+
### Auto-Update with Watchtower
|
|
211
274
|
|
|
212
|
-
|
|
275
|
+
Add to your `docker-compose.yml`:
|
|
213
276
|
|
|
214
277
|
```yaml
|
|
215
278
|
services:
|
|
@@ -218,16 +281,33 @@ services:
|
|
|
218
281
|
volumes:
|
|
219
282
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
220
283
|
environment:
|
|
221
|
-
- WATCHTOWER_POLL_INTERVAL=86400 # Check
|
|
284
|
+
- WATCHTOWER_POLL_INTERVAL=86400 # Check every 24 hours
|
|
222
285
|
- WATCHTOWER_CLEANUP=true
|
|
223
286
|
```
|
|
224
287
|
|
|
225
|
-
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Important Notes
|
|
291
|
+
|
|
292
|
+
- **Destructive operations** (DELETE, remove line item, etc.) require explicit confirmation
|
|
293
|
+
- **Line items** cannot be added inline during resource creation — always add them via separate API calls after creating the parent resource
|
|
294
|
+
- **Ticket comments** have 3 modes: email reply (`do_not_email: false`), public note (`do_not_email: true, hidden: false`), and private note (`hidden: true`)
|
|
295
|
+
- Some resources have no DELETE endpoint (vendors, leads, products, assets) — use `disabled: true` via update instead
|
|
296
|
+
- **Rate limit**: 180 requests per minute per IP (enforced by Syncro, managed by built-in rate limiter)
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## From Source
|
|
226
301
|
|
|
227
302
|
```bash
|
|
228
|
-
git
|
|
303
|
+
git clone https://github.com/advenimus/syncromsp-mcp.git
|
|
304
|
+
cd syncromsp-mcp
|
|
229
305
|
npm install
|
|
230
306
|
npm run build
|
|
307
|
+
|
|
308
|
+
export SYNCRO_API_KEY=your-api-key
|
|
309
|
+
export SYNCRO_SUBDOMAIN=your-subdomain
|
|
310
|
+
npm start
|
|
231
311
|
```
|
|
232
312
|
|
|
233
313
|
## Development
|
package/dist/auth.d.ts
CHANGED
|
@@ -9,22 +9,28 @@ declare class InMemoryClientsStore implements OAuthRegisteredClientsStore {
|
|
|
9
9
|
registerClient(clientMetadata: OAuthClientInformationFull): Promise<OAuthClientInformationFull>;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* OAuth provider for MCP server authentication.
|
|
12
|
+
* Secure OAuth provider for MCP server authentication.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* When MCP_AUTH_SECRET is set, the authorize flow presents a login page
|
|
15
|
+
* requiring the secret before granting access. This prevents unauthorized
|
|
16
|
+
* clients from completing the OAuth flow even if they discover the server URL.
|
|
16
17
|
*
|
|
17
|
-
*
|
|
18
|
-
* deployed this themselves with their Syncro API key, any client that
|
|
19
|
-
* can reach the server and complete the OAuth flow is authorized.
|
|
18
|
+
* Without MCP_AUTH_SECRET, the server refuses to start in auth mode.
|
|
20
19
|
*/
|
|
21
20
|
export declare class McpOAuthProvider implements OAuthServerProvider {
|
|
22
21
|
readonly clientsStore: InMemoryClientsStore;
|
|
23
22
|
private readonly codes;
|
|
24
23
|
private readonly tokens;
|
|
24
|
+
private readonly pendingAuths;
|
|
25
25
|
private readonly tokenLifetimeMs;
|
|
26
|
-
|
|
26
|
+
private readonly authSecret;
|
|
27
|
+
constructor(authSecret: string, tokenLifetimeHours?: number);
|
|
27
28
|
authorize(client: OAuthClientInformationFull, params: AuthorizationParams, res: Response): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Called from the /authorize/callback POST handler.
|
|
31
|
+
* Validates the secret and either redirects with an auth code or shows an error.
|
|
32
|
+
*/
|
|
33
|
+
handleAuthCallback(pendingId: string, secret: string, res: Response): Promise<void>;
|
|
28
34
|
challengeForAuthorizationCode(_client: OAuthClientInformationFull, authorizationCode: string): Promise<string>;
|
|
29
35
|
exchangeAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string, _codeVerifier?: string): Promise<OAuthTokens>;
|
|
30
36
|
exchangeRefreshToken(client: OAuthClientInformationFull, refreshToken: string, _scopes?: string[], _resource?: URL): Promise<OAuthTokens>;
|
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,mDAAmD,CAAC;AAC3D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,kDAAkD,CAAC;AACpG,OAAO,KAAK,EACV,0BAA0B,EAC1B,WAAW,EACZ,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gDAAgD,CAAC;AAC/E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,mDAAmD,CAAC;AAC3D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,kDAAkD,CAAC;AACpG,OAAO,KAAK,EACV,0BAA0B,EAC1B,WAAW,EACZ,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gDAAgD,CAAC;AAC/E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AA4BxC,cAAM,oBAAqB,YAAW,2BAA2B;IAC/D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiD;IAEnE,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,GAAG,SAAS,CAAC;IAI5E,cAAc,CAClB,cAAc,EAAE,0BAA0B,GACzC,OAAO,CAAC,0BAA0B,CAAC;CAIvC;AAED;;;;;;;;GAQG;AACH,qBAAa,gBAAiB,YAAW,mBAAmB;IAC1D,QAAQ,CAAC,YAAY,uBAA8B;IACnD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;IACvD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;IAC/D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,UAAU,EAAE,MAAM,EAAE,kBAAkB,GAAE,MAAW;IAUzD,SAAS,CACb,MAAM,EAAE,0BAA0B,EAClC,MAAM,EAAE,mBAAmB,EAC3B,GAAG,EAAE,QAAQ,GACZ,OAAO,CAAC,IAAI,CAAC;IAqDhB;;;OAGG;IACG,kBAAkB,CACtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,QAAQ,GACZ,OAAO,CAAC,IAAI,CAAC;IAmDV,6BAA6B,CACjC,OAAO,EAAE,0BAA0B,EACnC,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,MAAM,CAAC;IAQZ,yBAAyB,CAC7B,MAAM,EAAE,0BAA0B,EAClC,iBAAiB,EAAE,MAAM,EACzB,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,WAAW,CAAC;IA6CjB,oBAAoB,CACxB,MAAM,EAAE,0BAA0B,EAClC,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM,EAAE,EAClB,SAAS,CAAC,EAAE,GAAG,GACd,OAAO,CAAC,WAAW,CAAC;IA2BjB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAcnD,WAAW,CACf,OAAO,EAAE,0BAA0B,EACnC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GACzB,OAAO,CAAC,IAAI,CAAC;CAGjB"}
|
package/dist/auth.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
1
|
+
import { randomUUID, createHash, timingSafeEqual } from "node:crypto";
|
|
2
|
+
function safeCompare(a, b) {
|
|
3
|
+
const hashA = createHash("sha256").update(a).digest();
|
|
4
|
+
const hashB = createHash("sha256").update(b).digest();
|
|
5
|
+
return timingSafeEqual(hashA, hashB);
|
|
6
|
+
}
|
|
2
7
|
class InMemoryClientsStore {
|
|
3
8
|
clients = new Map();
|
|
4
9
|
async getClient(clientId) {
|
|
@@ -10,40 +15,126 @@ class InMemoryClientsStore {
|
|
|
10
15
|
}
|
|
11
16
|
}
|
|
12
17
|
/**
|
|
13
|
-
* OAuth provider for MCP server authentication.
|
|
18
|
+
* Secure OAuth provider for MCP server authentication.
|
|
14
19
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
20
|
+
* When MCP_AUTH_SECRET is set, the authorize flow presents a login page
|
|
21
|
+
* requiring the secret before granting access. This prevents unauthorized
|
|
22
|
+
* clients from completing the OAuth flow even if they discover the server URL.
|
|
17
23
|
*
|
|
18
|
-
*
|
|
19
|
-
* deployed this themselves with their Syncro API key, any client that
|
|
20
|
-
* can reach the server and complete the OAuth flow is authorized.
|
|
24
|
+
* Without MCP_AUTH_SECRET, the server refuses to start in auth mode.
|
|
21
25
|
*/
|
|
22
26
|
export class McpOAuthProvider {
|
|
23
27
|
clientsStore = new InMemoryClientsStore();
|
|
24
28
|
codes = new Map();
|
|
25
29
|
tokens = new Map();
|
|
30
|
+
pendingAuths = new Map();
|
|
26
31
|
tokenLifetimeMs;
|
|
27
|
-
|
|
32
|
+
authSecret;
|
|
33
|
+
constructor(authSecret, tokenLifetimeHours = 24) {
|
|
34
|
+
if (!authSecret || authSecret.length < 8) {
|
|
35
|
+
throw new Error("MCP_AUTH_SECRET must be set and at least 8 characters for secure operation");
|
|
36
|
+
}
|
|
37
|
+
this.authSecret = authSecret;
|
|
28
38
|
this.tokenLifetimeMs = tokenLifetimeHours * 60 * 60 * 1000;
|
|
29
39
|
}
|
|
30
40
|
async authorize(client, params, res) {
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
// Store the pending auth and show login page
|
|
42
|
+
const pendingId = randomUUID();
|
|
43
|
+
this.pendingAuths.set(pendingId, {
|
|
44
|
+
client,
|
|
45
|
+
params,
|
|
46
|
+
createdAt: Date.now(),
|
|
47
|
+
});
|
|
48
|
+
// Clean up expired pending auths (older than 10 minutes)
|
|
36
49
|
const cutoff = Date.now() - 10 * 60 * 1000;
|
|
37
|
-
for (const [key, value] of this.
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
for (const [key, value] of this.pendingAuths) {
|
|
51
|
+
if (value.createdAt < cutoff)
|
|
52
|
+
this.pendingAuths.delete(key);
|
|
53
|
+
}
|
|
54
|
+
// Serve a login page that requires the auth secret
|
|
55
|
+
res.setHeader("Content-Type", "text/html");
|
|
56
|
+
res.send(`<!DOCTYPE html>
|
|
57
|
+
<html>
|
|
58
|
+
<head>
|
|
59
|
+
<title>SyncroMSP MCP - Authorize</title>
|
|
60
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
61
|
+
<style>
|
|
62
|
+
body { font-family: -apple-system, system-ui, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: #0f172a; color: #e2e8f0; }
|
|
63
|
+
.card { background: #1e293b; padding: 2rem; border-radius: 12px; max-width: 400px; width: 90%; box-shadow: 0 4px 24px rgba(0,0,0,0.3); }
|
|
64
|
+
h1 { font-size: 1.25rem; margin: 0 0 0.5rem; color: #f8fafc; }
|
|
65
|
+
p { font-size: 0.875rem; color: #94a3b8; margin: 0 0 1.5rem; }
|
|
66
|
+
label { display: block; font-size: 0.875rem; margin-bottom: 0.5rem; color: #cbd5e1; }
|
|
67
|
+
input[type="password"] { width: 100%; padding: 0.75rem; border: 1px solid #334155; border-radius: 8px; background: #0f172a; color: #f8fafc; font-size: 1rem; box-sizing: border-box; }
|
|
68
|
+
input[type="password"]:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59,130,246,0.2); }
|
|
69
|
+
button { width: 100%; padding: 0.75rem; background: #3b82f6; color: white; border: none; border-radius: 8px; font-size: 1rem; cursor: pointer; margin-top: 1rem; }
|
|
70
|
+
button:hover { background: #2563eb; }
|
|
71
|
+
.error { color: #f87171; font-size: 0.875rem; margin-top: 0.5rem; display: none; }
|
|
72
|
+
.client-info { font-size: 0.75rem; color: #64748b; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #334155; }
|
|
73
|
+
</style>
|
|
74
|
+
</head>
|
|
75
|
+
<body>
|
|
76
|
+
<div class="card">
|
|
77
|
+
<h1>SyncroMSP MCP Server</h1>
|
|
78
|
+
<p>Enter the server access key to authorize this connection.</p>
|
|
79
|
+
<form method="POST" action="/authorize/callback">
|
|
80
|
+
<input type="hidden" name="pending_id" value="${pendingId}">
|
|
81
|
+
<label for="secret">Access Key</label>
|
|
82
|
+
<input type="password" id="secret" name="secret" placeholder="Enter MCP_AUTH_SECRET" required autofocus>
|
|
83
|
+
<div class="error" id="error">Invalid access key. Please try again.</div>
|
|
84
|
+
<button type="submit">Authorize</button>
|
|
85
|
+
</form>
|
|
86
|
+
<div class="client-info">Client: ${client.client_name || client.client_id}</div>
|
|
87
|
+
</div>
|
|
88
|
+
</body>
|
|
89
|
+
</html>`);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Called from the /authorize/callback POST handler.
|
|
93
|
+
* Validates the secret and either redirects with an auth code or shows an error.
|
|
94
|
+
*/
|
|
95
|
+
async handleAuthCallback(pendingId, secret, res) {
|
|
96
|
+
const pending = this.pendingAuths.get(pendingId);
|
|
97
|
+
if (!pending) {
|
|
98
|
+
res.status(400).send("Authorization request expired. Please try again.");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Timing-safe comparison of the secret
|
|
102
|
+
if (!safeCompare(secret, this.authSecret)) {
|
|
103
|
+
// Re-show the form with error
|
|
104
|
+
this.pendingAuths.delete(pendingId);
|
|
105
|
+
res.status(403).send(`<!DOCTYPE html>
|
|
106
|
+
<html>
|
|
107
|
+
<head>
|
|
108
|
+
<title>Access Denied</title>
|
|
109
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
110
|
+
<style>
|
|
111
|
+
body { font-family: -apple-system, system-ui, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: #0f172a; color: #e2e8f0; }
|
|
112
|
+
.card { background: #1e293b; padding: 2rem; border-radius: 12px; max-width: 400px; width: 90%; text-align: center; }
|
|
113
|
+
h1 { color: #f87171; font-size: 1.25rem; }
|
|
114
|
+
p { color: #94a3b8; }
|
|
115
|
+
</style>
|
|
116
|
+
</head>
|
|
117
|
+
<body>
|
|
118
|
+
<div class="card">
|
|
119
|
+
<h1>Access Denied</h1>
|
|
120
|
+
<p>Invalid access key. Connection rejected.</p>
|
|
121
|
+
</div>
|
|
122
|
+
</body>
|
|
123
|
+
</html>`);
|
|
124
|
+
return;
|
|
42
125
|
}
|
|
43
|
-
|
|
126
|
+
// Secret valid — issue auth code
|
|
127
|
+
this.pendingAuths.delete(pendingId);
|
|
128
|
+
const code = randomUUID();
|
|
129
|
+
this.codes.set(code, {
|
|
130
|
+
client: pending.client,
|
|
131
|
+
params: pending.params,
|
|
132
|
+
createdAt: Date.now(),
|
|
133
|
+
});
|
|
134
|
+
const redirectUrl = new URL(pending.params.redirectUri);
|
|
44
135
|
redirectUrl.searchParams.set("code", code);
|
|
45
|
-
if (params.state !== undefined) {
|
|
46
|
-
redirectUrl.searchParams.set("state", params.state);
|
|
136
|
+
if (pending.params.state !== undefined) {
|
|
137
|
+
redirectUrl.searchParams.set("state", pending.params.state);
|
|
47
138
|
}
|
|
48
139
|
res.redirect(redirectUrl.toString());
|
|
49
140
|
}
|
|
@@ -62,6 +153,11 @@ export class McpOAuthProvider {
|
|
|
62
153
|
if (codeData.client.client_id !== client.client_id) {
|
|
63
154
|
throw new Error("Authorization code was not issued to this client");
|
|
64
155
|
}
|
|
156
|
+
// Auth codes expire after 10 minutes
|
|
157
|
+
if (Date.now() - codeData.createdAt > 10 * 60 * 1000) {
|
|
158
|
+
this.codes.delete(authorizationCode);
|
|
159
|
+
throw new Error("Authorization code expired");
|
|
160
|
+
}
|
|
65
161
|
this.codes.delete(authorizationCode);
|
|
66
162
|
const accessToken = randomUUID();
|
|
67
163
|
const refreshToken = randomUUID();
|
|
@@ -72,7 +168,6 @@ export class McpOAuthProvider {
|
|
|
72
168
|
expiresAt: Date.now() + this.tokenLifetimeMs,
|
|
73
169
|
resource: codeData.params.resource,
|
|
74
170
|
});
|
|
75
|
-
// Store refresh token with longer lifetime (30 days)
|
|
76
171
|
this.tokens.set(refreshToken, {
|
|
77
172
|
token: refreshToken,
|
|
78
173
|
clientId: client.client_id,
|
|
@@ -96,7 +191,6 @@ export class McpOAuthProvider {
|
|
|
96
191
|
if (tokenData.clientId !== client.client_id) {
|
|
97
192
|
throw new Error("Refresh token was not issued to this client");
|
|
98
193
|
}
|
|
99
|
-
// Issue new access token
|
|
100
194
|
const newAccessToken = randomUUID();
|
|
101
195
|
this.tokens.set(newAccessToken, {
|
|
102
196
|
token: newAccessToken,
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -28,65 +28,83 @@ if (transport === "http") {
|
|
|
28
28
|
const { isInitializeRequest } = await import("@modelcontextprotocol/sdk/types.js");
|
|
29
29
|
const port = parseInt(process.env.MCP_PORT || "8080", 10);
|
|
30
30
|
const useAuth = process.env.MCP_AUTH !== "false";
|
|
31
|
+
const authSecret = process.env.MCP_AUTH_SECRET;
|
|
31
32
|
const baseUrl = process.env.MCP_BASE_URL || `http://localhost:${port}`;
|
|
32
33
|
const mcpServerUrl = new URL(baseUrl);
|
|
34
|
+
if (useAuth && !authSecret) {
|
|
35
|
+
console.error("ERROR: MCP_AUTH_SECRET is required when auth is enabled.\n" +
|
|
36
|
+
" Set MCP_AUTH_SECRET to a strong secret (min 8 chars) in your environment.\n" +
|
|
37
|
+
" Or set MCP_AUTH=false to disable auth (not recommended for public deployments).");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
33
40
|
const app = express();
|
|
34
41
|
app.use(express.json());
|
|
42
|
+
app.use(express.urlencoded({ extended: false }));
|
|
35
43
|
// Health check (always unauthenticated)
|
|
36
44
|
app.get("/health", (_req, res) => {
|
|
37
45
|
res.json({ status: "ok", transport: "http", auth: useAuth });
|
|
38
46
|
});
|
|
39
47
|
let authMiddleware;
|
|
40
|
-
if (useAuth) {
|
|
48
|
+
if (useAuth && authSecret) {
|
|
41
49
|
const { McpOAuthProvider } = await import("./auth.js");
|
|
42
50
|
const { mcpAuthRouter } = await import("@modelcontextprotocol/sdk/server/auth/router.js");
|
|
43
51
|
const { requireBearerAuth } = await import("@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js");
|
|
44
52
|
const { getOAuthProtectedResourceMetadataUrl } = await import("@modelcontextprotocol/sdk/server/auth/router.js");
|
|
45
|
-
const oauthProvider = new McpOAuthProvider();
|
|
53
|
+
const oauthProvider = new McpOAuthProvider(authSecret);
|
|
46
54
|
// Install OAuth routes (/.well-known/*, /authorize, /token, /register, /revoke)
|
|
47
55
|
app.use(mcpAuthRouter({
|
|
48
56
|
provider: oauthProvider,
|
|
49
57
|
issuerUrl: mcpServerUrl,
|
|
50
58
|
scopesSupported: ["mcp:tools"],
|
|
51
59
|
}));
|
|
60
|
+
// Handle the auth secret validation callback
|
|
61
|
+
app.post("/authorize/callback", async (req, res) => {
|
|
62
|
+
const { pending_id, secret } = req.body;
|
|
63
|
+
if (!pending_id || !secret) {
|
|
64
|
+
res.status(400).send("Missing required fields");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
await oauthProvider.handleAuthCallback(pending_id, secret, res);
|
|
68
|
+
});
|
|
52
69
|
authMiddleware = requireBearerAuth({
|
|
53
70
|
verifier: oauthProvider,
|
|
54
71
|
requiredScopes: [],
|
|
55
72
|
resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(mcpServerUrl),
|
|
56
73
|
});
|
|
57
|
-
console.error(`OAuth enabled. Issuer: ${mcpServerUrl}`);
|
|
74
|
+
console.error(`OAuth enabled with access key. Issuer: ${mcpServerUrl}`);
|
|
58
75
|
}
|
|
59
76
|
else {
|
|
60
|
-
console.error("Auth disabled (MCP_AUTH=false)");
|
|
77
|
+
console.error("Auth disabled (MCP_AUTH=false). WARNING: Server is unprotected.");
|
|
61
78
|
}
|
|
62
|
-
// Session management
|
|
63
|
-
|
|
79
|
+
// Session management — each session gets its own Server + Transport pair
|
|
80
|
+
// because the MCP SDK Server can only bind to one transport at a time
|
|
81
|
+
const sessions = {};
|
|
64
82
|
const mcpHandler = async (req, res) => {
|
|
65
83
|
const sessionId = req.headers["mcp-session-id"];
|
|
66
84
|
try {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
if (sessionId && sessions[sessionId]) {
|
|
86
|
+
await sessions[sessionId].transport.handleRequest(req, res, req.body);
|
|
87
|
+
return;
|
|
70
88
|
}
|
|
71
|
-
|
|
72
|
-
|
|
89
|
+
if (!sessionId && isInitializeRequest(req.body)) {
|
|
90
|
+
// Create a fresh Server + Transport for this session
|
|
91
|
+
const sessionServer = createServer(client, toolMode);
|
|
92
|
+
const sessionTransport = new StreamableHTTPServerTransport({
|
|
73
93
|
sessionIdGenerator: () => randomUUID(),
|
|
74
94
|
onsessioninitialized: (sid) => {
|
|
75
|
-
|
|
95
|
+
sessions[sid] = { transport: sessionTransport, server: sessionServer };
|
|
76
96
|
},
|
|
77
97
|
});
|
|
78
98
|
sessionTransport.onclose = () => {
|
|
79
99
|
const sid = sessionTransport.sessionId;
|
|
80
100
|
if (sid)
|
|
81
|
-
delete
|
|
101
|
+
delete sessions[sid];
|
|
82
102
|
};
|
|
83
|
-
await
|
|
84
|
-
|
|
85
|
-
else {
|
|
86
|
-
res.status(400).json({ error: "Bad request: missing session ID or not an init request" });
|
|
103
|
+
await sessionServer.connect(sessionTransport);
|
|
104
|
+
await sessionTransport.handleRequest(req, res, req.body);
|
|
87
105
|
return;
|
|
88
106
|
}
|
|
89
|
-
|
|
107
|
+
res.status(400).json({ error: "Bad request: missing session ID or not an init request" });
|
|
90
108
|
}
|
|
91
109
|
catch (error) {
|
|
92
110
|
console.error("MCP handler error:", error);
|
|
@@ -106,6 +124,13 @@ if (transport === "http") {
|
|
|
106
124
|
app.get("/mcp", mcpHandler);
|
|
107
125
|
app.delete("/mcp", mcpHandler);
|
|
108
126
|
}
|
|
127
|
+
// Log active session count periodically
|
|
128
|
+
setInterval(() => {
|
|
129
|
+
const count = Object.keys(sessions).length;
|
|
130
|
+
if (count > 0) {
|
|
131
|
+
console.error(`Active MCP sessions: ${count}`);
|
|
132
|
+
}
|
|
133
|
+
}, 60000);
|
|
109
134
|
app.listen(port, () => {
|
|
110
135
|
console.error(`SyncroMSP MCP server listening on http://0.0.0.0:${port}`);
|
|
111
136
|
console.error(`Health: http://0.0.0.0:${port}/health`);
|
|
@@ -115,8 +140,8 @@ if (transport === "http") {
|
|
|
115
140
|
}
|
|
116
141
|
});
|
|
117
142
|
const shutdown = () => {
|
|
118
|
-
for (const
|
|
119
|
-
|
|
143
|
+
for (const s of Object.values(sessions)) {
|
|
144
|
+
s.transport.close();
|
|
120
145
|
}
|
|
121
146
|
process.exit(0);
|
|
122
147
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAiB,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE3D,uCAAuC;AACvC,eAAe,EAAE,CAAC;AAElB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;AAC1C,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;AAE/C,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;IAC1B,OAAO,CAAC,KAAK,CACX,2CAA2C;QACzC,kEAAkE;QAClE,8FAA8F,CACjG,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,2EAA2E;AAC3E,4EAA4E;AAC5E,iDAAiD;AACjD,6CAA6C;AAC7C,MAAM,QAAQ,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,MAAM,CAAa,CAAC;AAEnE,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;AAC1D,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAEjD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC;AAEvD,IAAI,SAAS,KAAK,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;IACrD,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IACnD,MAAM,EAAE,6BAA6B,EAAE,GAAG,MAAM,MAAM,CACpD,oDAAoD,CACrD,CAAC;IACF,MAAM,EAAE,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAC1C,oCAAoC,CACrC,CAAC;IAEF,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC;IAC1D,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC;IACjD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,oBAAoB,IAAI,EAAE,CAAC;IACvE,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAEtC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED