@metalabdesign/mcp-client 1.1.0 → 1.1.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 +471 -176
- package/dist/proxy.d.ts +23 -0
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +128 -24
- package/dist/proxy.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# @metalabdesign/mcp-client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Connect your AI assistant to Metalab's MCP services** - Figma, Notion, Jira, and more.
|
|
4
|
+
|
|
5
|
+
This package is a local OAuth 2.1 proxy that enables MCP clients (Claude Desktop, Claude Code, Windsurf, Cursor, etc.) to securely connect to Metalab's MCP Gateway. It handles authentication via Okta, manages tokens, and forwards your requests to backend services.
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
flowchart LR
|
|
9
|
+
A["Your AI Tool<br/>(Claude, etc.)"] -->|stdio| B["mcp-client<br/>(this package)"]
|
|
10
|
+
B -->|"HTTPS + OAuth"| C["MCP Gateway<br/>(Figma, Notion, Jira)"]
|
|
11
|
+
```
|
|
4
12
|
|
|
5
13
|
## Quick Start
|
|
6
14
|
|
|
@@ -8,10 +16,14 @@ MCP client for connecting to Metalab's MCP Gateway with OAuth 2.1 authentication
|
|
|
8
16
|
# Run directly - zero config needed!
|
|
9
17
|
npx @metalabdesign/mcp-client
|
|
10
18
|
|
|
11
|
-
# Generate config for your MCP client
|
|
19
|
+
# Generate config for your MCP client
|
|
12
20
|
npx @metalabdesign/mcp-client config --client claude
|
|
13
21
|
```
|
|
14
22
|
|
|
23
|
+
On first run, your browser will open for Okta authentication. Once authenticated, you can start using MCP tools in your AI assistant.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
15
27
|
## Installation
|
|
16
28
|
|
|
17
29
|
The package is published on **npmjs.org** (public, no authentication needed).
|
|
@@ -27,125 +39,23 @@ metalab-mcp
|
|
|
27
39
|
|
|
28
40
|
### Registry Configuration
|
|
29
41
|
|
|
30
|
-
If your `~/.npmrc` maps `@metalabdesign` to GitHub Packages for other internal packages, you'll need to explicitly use the npmjs.org registry
|
|
31
|
-
|
|
32
|
-
**Option 1: Use `--registry` flag**
|
|
42
|
+
If your `~/.npmrc` maps `@metalabdesign` to GitHub Packages for other internal packages, you'll need to explicitly use the npmjs.org registry:
|
|
33
43
|
|
|
34
44
|
```bash
|
|
45
|
+
# Option 1: Use --registry flag
|
|
35
46
|
npx --registry=https://registry.npmjs.org @metalabdesign/mcp-client
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**Option 2: Configure registry per-package in `~/.npmrc`**
|
|
39
47
|
|
|
40
|
-
|
|
41
|
-
# Other @metalabdesign packages from GitHub Packages
|
|
42
|
-
@metalabdesign:registry=https://npm.pkg.github.com
|
|
43
|
-
|
|
44
|
-
# Override for mcp-client to use npmjs.org
|
|
48
|
+
# Option 2: Add to ~/.npmrc
|
|
45
49
|
@metalabdesign/mcp-client:registry=https://registry.npmjs.org
|
|
46
50
|
```
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
If you don't need other `@metalabdesign` packages locally, remove the registry override from your `~/.npmrc`:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
# Remove this line if present:
|
|
54
|
-
# @metalabdesign:registry=https://npm.pkg.github.com
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Commands
|
|
58
|
-
|
|
59
|
-
### metalab-mcp (default)
|
|
60
|
-
|
|
61
|
-
Zero-config connection to Metalab MCP Gateway.
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
# Basic usage (auto-detects gateway URL, defaults to production)
|
|
65
|
-
metalab-mcp
|
|
66
|
-
|
|
67
|
-
# Explicitly use production environment
|
|
68
|
-
metalab-mcp --prod
|
|
69
|
-
|
|
70
|
-
# Use development environment
|
|
71
|
-
metalab-mcp --dev
|
|
72
|
-
|
|
73
|
-
# Use a custom gateway URL (highest priority)
|
|
74
|
-
metalab-mcp --gateway-url https://custom-gateway.example.com
|
|
75
|
-
|
|
76
|
-
# With Figma token
|
|
77
|
-
metalab-mcp --figma-token figd_YOUR_TOKEN
|
|
78
|
-
|
|
79
|
-
# With MCP Inspector for debugging
|
|
80
|
-
metalab-mcp --inspect
|
|
81
|
-
|
|
82
|
-
# Use specific AWS profile for SSM lookup
|
|
83
|
-
metalab-mcp --profile metalab-dev
|
|
84
|
-
|
|
85
|
-
# Using environment variables
|
|
86
|
-
FIGMA_ACCESS_TOKEN=figd_YOUR_TOKEN metalab-mcp
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
**Options**:
|
|
52
|
+
---
|
|
90
53
|
|
|
91
|
-
|
|
92
|
-
| ------------------------ | --------------------------------------- | ----------------------------- |
|
|
93
|
-
| `--gateway-url <url>` | Explicit gateway URL (highest priority) | Auto-detected |
|
|
94
|
-
| `--prod` | Use production environment | Default |
|
|
95
|
-
| `--dev` | Use development environment | - |
|
|
96
|
-
| `--profile <name>` | AWS profile for SSM lookup | Default profile |
|
|
97
|
-
| `--figma-token <token>` | Figma access token | `FIGMA_ACCESS_TOKEN` env var |
|
|
98
|
-
| `--notion-token <token>` | Notion access token | `NOTION_ACCESS_TOKEN` env var |
|
|
99
|
-
| `--jira-token <token>` | Jira API token | `JIRA_API_TOKEN` env var |
|
|
100
|
-
| `--jira-email <email>` | Jira account email | `JIRA_EMAIL` env var |
|
|
101
|
-
| `--jira-instance <url>` | Jira instance URL | `JIRA_INSTANCE_URL` env var |
|
|
102
|
-
| `--inspect` | Run with MCP Inspector | false |
|
|
103
|
-
| `-h, --help` | Show help | - |
|
|
54
|
+
## MCP Client Configuration
|
|
104
55
|
|
|
105
|
-
|
|
56
|
+
### Claude Desktop (Recommended)
|
|
106
57
|
|
|
107
|
-
1
|
|
108
|
-
2. AWS SSM Parameter Store (environment-specific path)
|
|
109
|
-
3. `MCP_SERVER_URL` environment variable
|
|
110
|
-
4. Hardcoded URL for the environment (fallback)
|
|
111
|
-
|
|
112
|
-
### metalab-mcp config
|
|
113
|
-
|
|
114
|
-
Generate configuration for various MCP clients.
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
# Interactive mode
|
|
118
|
-
metalab-mcp config
|
|
119
|
-
|
|
120
|
-
# Generate config for specific client
|
|
121
|
-
metalab-mcp config --client claude
|
|
122
|
-
metalab-mcp config --client claude-code
|
|
123
|
-
metalab-mcp config --client windsurf
|
|
124
|
-
metalab-mcp config --client cursor
|
|
125
|
-
metalab-mcp config --client cline
|
|
126
|
-
|
|
127
|
-
# Include tokens in config
|
|
128
|
-
metalab-mcp config --client claude --figma-token figd_YOUR_TOKEN
|
|
129
|
-
|
|
130
|
-
# Write directly to client's config file
|
|
131
|
-
metalab-mcp config --client claude --output
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
**Supported Clients**:
|
|
135
|
-
|
|
136
|
-
| Client | Config File Location |
|
|
137
|
-
| ------------------------ | ----------------------------------------------------------------- |
|
|
138
|
-
| Claude Desktop (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
|
|
139
|
-
| Claude Desktop (Windows) | `%APPDATA%\Claude\claude_desktop_config.json` |
|
|
140
|
-
| Claude Desktop (Linux) | `~/.config/Claude/claude_desktop_config.json` |
|
|
141
|
-
| Claude Code | `~/.claude.json` |
|
|
142
|
-
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
|
|
143
|
-
| Cursor | `~/.cursor/mcp.json` |
|
|
144
|
-
| Cline (VS Code) | `.vscode/cline_mcp_settings.json` |
|
|
145
|
-
|
|
146
|
-
## Claude Desktop Configuration
|
|
147
|
-
|
|
148
|
-
**Option 1: Use the config generator**
|
|
58
|
+
**Option 1: Auto-generate config**
|
|
149
59
|
|
|
150
60
|
```bash
|
|
151
61
|
npx @metalabdesign/mcp-client config --client claude --output
|
|
@@ -155,6 +65,12 @@ npx @metalabdesign/mcp-client config --client claude --output
|
|
|
155
65
|
|
|
156
66
|
Add to your Claude Desktop config file:
|
|
157
67
|
|
|
68
|
+
| Platform | Config File Location |
|
|
69
|
+
|----------|---------------------|
|
|
70
|
+
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
|
|
71
|
+
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
|
|
72
|
+
| Linux | `~/.config/Claude/claude_desktop_config.json` |
|
|
73
|
+
|
|
158
74
|
```json
|
|
159
75
|
{
|
|
160
76
|
"mcpServers": {
|
|
@@ -166,7 +82,7 @@ Add to your Claude Desktop config file:
|
|
|
166
82
|
}
|
|
167
83
|
```
|
|
168
84
|
|
|
169
|
-
**With service tokens
|
|
85
|
+
**With service tokens** (for Figma, Notion, Jira, GitHub access):
|
|
170
86
|
|
|
171
87
|
```json
|
|
172
88
|
{
|
|
@@ -179,55 +95,90 @@ Add to your Claude Desktop config file:
|
|
|
179
95
|
"NOTION_ACCESS_TOKEN": "secret_YOUR_TOKEN_HERE",
|
|
180
96
|
"JIRA_API_TOKEN": "YOUR_JIRA_API_TOKEN",
|
|
181
97
|
"JIRA_EMAIL": "your-email@company.com",
|
|
182
|
-
"JIRA_INSTANCE_URL": "https://company.atlassian.net"
|
|
98
|
+
"JIRA_INSTANCE_URL": "https://company.atlassian.net",
|
|
99
|
+
"GITHUB_TOKEN": "ghp_YOUR_TOKEN_HERE"
|
|
183
100
|
}
|
|
184
101
|
}
|
|
185
102
|
}
|
|
186
103
|
}
|
|
187
104
|
```
|
|
188
105
|
|
|
189
|
-
|
|
106
|
+
### Claude Code
|
|
107
|
+
|
|
108
|
+
**Option 1: Use `claude mcp add` command (recommended)**
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Basic installation
|
|
112
|
+
claude mcp add metalab --scope user -- npx @metalabdesign/mcp-client
|
|
113
|
+
|
|
114
|
+
# With service tokens
|
|
115
|
+
claude mcp add metalab --scope user \
|
|
116
|
+
-e "FIGMA_ACCESS_TOKEN=figd_YOUR_TOKEN" \
|
|
117
|
+
-e "NOTION_ACCESS_TOKEN=secret_YOUR_TOKEN" \
|
|
118
|
+
-e "JIRA_API_TOKEN=YOUR_JIRA_TOKEN" \
|
|
119
|
+
-e "JIRA_EMAIL=your-email@company.com" \
|
|
120
|
+
-e "JIRA_INSTANCE_URL=https://company.atlassian.net" \
|
|
121
|
+
-e "GITHUB_TOKEN=ghp_YOUR_TOKEN" \
|
|
122
|
+
-- npx @metalabdesign/mcp-client
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Option 2: Manual configuration**
|
|
126
|
+
|
|
127
|
+
Add to `~/.claude.json`:
|
|
190
128
|
|
|
191
129
|
```json
|
|
192
130
|
{
|
|
193
131
|
"mcpServers": {
|
|
194
|
-
"metalab
|
|
132
|
+
"metalab": {
|
|
195
133
|
"command": "npx",
|
|
196
|
-
"args": ["@metalabdesign/mcp-client",
|
|
134
|
+
"args": ["@metalabdesign/mcp-client"],
|
|
135
|
+
"env": {
|
|
136
|
+
"FIGMA_ACCESS_TOKEN": "figd_YOUR_TOKEN_HERE"
|
|
137
|
+
}
|
|
197
138
|
}
|
|
198
139
|
}
|
|
199
140
|
}
|
|
200
141
|
```
|
|
201
142
|
|
|
202
|
-
|
|
143
|
+
### Windsurf
|
|
203
144
|
|
|
204
|
-
|
|
145
|
+
Add to `~/.codeium/windsurf/mcp_config.json`:
|
|
205
146
|
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"mcpServers": {
|
|
150
|
+
"metalab": {
|
|
151
|
+
"command": "npx",
|
|
152
|
+
"args": ["@metalabdesign/mcp-client"],
|
|
153
|
+
"env": {
|
|
154
|
+
"FIGMA_ACCESS_TOKEN": "figd_YOUR_TOKEN_HERE"
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
209
160
|
|
|
210
|
-
|
|
211
|
-
claude mcp add metalab --scope user \
|
|
212
|
-
-e "FIGMA_ACCESS_TOKEN=figd_YOUR_TOKEN" \
|
|
213
|
-
-- npx @metalabdesign/mcp-client
|
|
161
|
+
### Cursor
|
|
214
162
|
|
|
215
|
-
|
|
216
|
-
claude mcp add metalab --scope user \
|
|
217
|
-
-e "FIGMA_ACCESS_TOKEN=figd_YOUR_TOKEN" \
|
|
218
|
-
-e "NOTION_ACCESS_TOKEN=secret_YOUR_TOKEN" \
|
|
219
|
-
-e "JIRA_API_TOKEN=YOUR_JIRA_TOKEN" \
|
|
220
|
-
-e "JIRA_EMAIL=your-email@company.com" \
|
|
221
|
-
-e "JIRA_INSTANCE_URL=https://company.atlassian.net" \
|
|
222
|
-
-- npx @metalabdesign/mcp-client
|
|
163
|
+
Add to `~/.cursor/mcp.json`:
|
|
223
164
|
|
|
224
|
-
|
|
225
|
-
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"mcpServers": {
|
|
168
|
+
"metalab": {
|
|
169
|
+
"command": "npx",
|
|
170
|
+
"args": ["@metalabdesign/mcp-client"],
|
|
171
|
+
"env": {
|
|
172
|
+
"FIGMA_ACCESS_TOKEN": "figd_YOUR_TOKEN_HERE"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
226
177
|
```
|
|
227
178
|
|
|
228
|
-
|
|
179
|
+
### Cline (VS Code)
|
|
229
180
|
|
|
230
|
-
Add to
|
|
181
|
+
Add to `.vscode/cline_mcp_settings.json` in your project:
|
|
231
182
|
|
|
232
183
|
```json
|
|
233
184
|
{
|
|
@@ -236,39 +187,332 @@ Add to your `~/.claude.json` file:
|
|
|
236
187
|
"command": "npx",
|
|
237
188
|
"args": ["@metalabdesign/mcp-client"],
|
|
238
189
|
"env": {
|
|
239
|
-
"FIGMA_ACCESS_TOKEN": "figd_YOUR_TOKEN_HERE"
|
|
240
|
-
"NOTION_ACCESS_TOKEN": "secret_YOUR_TOKEN_HERE",
|
|
241
|
-
"JIRA_API_TOKEN": "YOUR_JIRA_API_TOKEN",
|
|
242
|
-
"JIRA_EMAIL": "your-email@company.com",
|
|
243
|
-
"JIRA_INSTANCE_URL": "https://company.atlassian.net"
|
|
190
|
+
"FIGMA_ACCESS_TOKEN": "figd_YOUR_TOKEN_HERE"
|
|
244
191
|
}
|
|
245
192
|
}
|
|
246
193
|
}
|
|
247
194
|
}
|
|
248
195
|
```
|
|
249
196
|
|
|
197
|
+
### Using Development Environment
|
|
198
|
+
|
|
199
|
+
For testing against the dev gateway, add the `--dev` flag:
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"mcpServers": {
|
|
204
|
+
"metalab-dev": {
|
|
205
|
+
"command": "npx",
|
|
206
|
+
"args": ["@metalabdesign/mcp-client", "--dev"]
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
250
214
|
## Service Tokens
|
|
251
215
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
|
255
|
-
|
|
256
|
-
|
|
|
216
|
+
Each MCP service requires a personal access token for authentication. The tokens you provide are forwarded to the respective service APIs.
|
|
217
|
+
|
|
218
|
+
| Service | Required Credentials | How to Provide |
|
|
219
|
+
|---------|---------------------|----------------|
|
|
220
|
+
| **Figma** | Access Token | `FIGMA_ACCESS_TOKEN` env var or `--figma-token` flag |
|
|
221
|
+
| **Notion** | Integration Secret | `NOTION_ACCESS_TOKEN` env var or `--notion-token` flag |
|
|
222
|
+
| **Jira** | API Token + Email + Instance URL | See Jira section below |
|
|
223
|
+
| **GitHub** | Personal Access Token | `GITHUB_TOKEN` env var or `--github-token` flag |
|
|
257
224
|
|
|
258
225
|
### Getting Your Figma Token
|
|
259
226
|
|
|
260
|
-
1. Log in to your Figma account
|
|
227
|
+
1. Log in to your [Figma](https://figma.com) account
|
|
261
228
|
2. Go to **Settings** > **Account**
|
|
262
229
|
3. Scroll to **Personal access tokens**
|
|
263
230
|
4. Click **Generate new token**
|
|
264
|
-
5.
|
|
231
|
+
5. Give it a descriptive name (e.g., "MCP Integration")
|
|
232
|
+
6. Copy the token (starts with `figd_`)
|
|
233
|
+
|
|
234
|
+
**Token format**: `figd_XXXXXXXXXXXXXXXXXXXXX`
|
|
235
|
+
|
|
236
|
+
### Getting Your Notion Token
|
|
237
|
+
|
|
238
|
+
1. Log in to your [Notion](https://notion.so) workspace
|
|
239
|
+
2. Go to **Settings & members** (gear icon in sidebar)
|
|
240
|
+
3. Click **My connections** in the left sidebar
|
|
241
|
+
4. Click **Develop or manage integrations** at the bottom
|
|
242
|
+
5. Click **+ New integration**
|
|
243
|
+
6. Configure the integration:
|
|
244
|
+
- **Name**: Give it a descriptive name (e.g., "MCP Integration")
|
|
245
|
+
- **Associated workspace**: Select your workspace
|
|
246
|
+
- **Capabilities**: Enable read content, read comments, etc.
|
|
247
|
+
7. Click **Submit** to create the integration
|
|
248
|
+
8. Copy the **Internal Integration Secret** (starts with `secret_`)
|
|
249
|
+
|
|
250
|
+
**Token format**: `secret_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`
|
|
251
|
+
|
|
252
|
+
**Important**: After creating the integration, you must share pages/databases with it:
|
|
253
|
+
1. Open the page or database you want to access
|
|
254
|
+
2. Click **•••** (more menu) in the top-right corner
|
|
255
|
+
3. Click **Add connections**
|
|
256
|
+
4. Search for and select your integration
|
|
257
|
+
|
|
258
|
+
### Getting Your Jira Token
|
|
259
|
+
|
|
260
|
+
Jira requires three pieces of information:
|
|
261
|
+
|
|
262
|
+
1. **API Token**: Generated from Atlassian
|
|
263
|
+
2. **Email**: Your Atlassian account email (optional - auto-derived from Okta if not provided)
|
|
264
|
+
3. **Instance URL**: Your Jira Cloud URL
|
|
265
265
|
|
|
266
|
-
|
|
266
|
+
**Steps to get your API token:**
|
|
267
267
|
|
|
268
268
|
1. Go to https://id.atlassian.com/manage-profile/security/api-tokens
|
|
269
269
|
2. Click **Create API token**
|
|
270
270
|
3. Give it a label (e.g., "Metalab MCP Access")
|
|
271
|
-
4. Copy the token immediately
|
|
271
|
+
4. Copy the token immediately (you won't see it again)
|
|
272
|
+
|
|
273
|
+
**Configuration example:**
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"env": {
|
|
278
|
+
"JIRA_API_TOKEN": "ATATT3xFfGF0...",
|
|
279
|
+
"JIRA_EMAIL": "your-email@company.com",
|
|
280
|
+
"JIRA_INSTANCE_URL": "https://company.atlassian.net"
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Note**: The `JIRA_EMAIL` is optional. If not provided, the gateway uses your authenticated Okta email.
|
|
286
|
+
|
|
287
|
+
### Getting Your GitHub Token
|
|
288
|
+
|
|
289
|
+
GitHub tokens enable access to private repositories (guides/skills) and authenticated GitHub API calls for dev-context tools like `get_recent_changes`, `get_pr_context`, `compare_branches`, etc.
|
|
290
|
+
|
|
291
|
+
#### Required Repository Access
|
|
292
|
+
|
|
293
|
+
Your GitHub token must have read access to **all repositories** you want to work with:
|
|
294
|
+
|
|
295
|
+
| Use Case | Required Repo Access |
|
|
296
|
+
|----------|---------------------|
|
|
297
|
+
| Guides & Skills | `metalabdesign/librarian-dev-ai-assistants` |
|
|
298
|
+
| Dev Context Tools | Any repository you query (e.g., `metalabdesign/web-app`, `yourorg/backend-api`) |
|
|
299
|
+
|
|
300
|
+
> **Important**: Dev context tools make API calls to whatever `owner/repo` you specify in the tool parameters. Your token must have access to those repositories.
|
|
301
|
+
|
|
302
|
+
#### Creating a Token
|
|
303
|
+
|
|
304
|
+
**For Personal Access Tokens (Classic)**:
|
|
305
|
+
|
|
306
|
+
1. Go to GitHub > **Settings** > **Developer settings** > **Personal access tokens** > **Tokens (classic)**
|
|
307
|
+
2. Click **Generate new token**
|
|
308
|
+
3. Give it a descriptive name (e.g., "Metalab MCP Access")
|
|
309
|
+
4. Select scopes:
|
|
310
|
+
- `repo` - Full control of private repositories (read access to code, commits, PRs)
|
|
311
|
+
- Or `public_repo` - If only working with public repositories
|
|
312
|
+
5. Click **Generate token** and copy it immediately
|
|
313
|
+
|
|
314
|
+
**For Fine-Grained Personal Access Tokens** (recommended):
|
|
315
|
+
|
|
316
|
+
1. Go to GitHub > **Settings** > **Developer settings** > **Personal access tokens** > **Fine-grained tokens**
|
|
317
|
+
2. Click **Generate new token**
|
|
318
|
+
3. Give it a descriptive name (e.g., "Metalab MCP Access")
|
|
319
|
+
4. Select repository access:
|
|
320
|
+
- **All repositories** (simplest, works with any repo)
|
|
321
|
+
- Or **Select repositories** (more secure, but must include all repos you'll query)
|
|
322
|
+
5. Set permissions:
|
|
323
|
+
- **Contents**: Read (for commits, branches, file content)
|
|
324
|
+
- **Pull requests**: Read (for PR context, reviews, comments)
|
|
325
|
+
- **Metadata**: Read (required by default)
|
|
326
|
+
6. Click **Generate token** and copy it immediately
|
|
327
|
+
|
|
328
|
+
**Token format**: `ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` (classic) or `github_pat_XXXXXX...` (fine-grained)
|
|
329
|
+
|
|
330
|
+
**Configuration example:**
|
|
331
|
+
|
|
332
|
+
```json
|
|
333
|
+
{
|
|
334
|
+
"env": {
|
|
335
|
+
"GITHUB_TOKEN": "ghp_YOUR_TOKEN_HERE"
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Or via CLI:
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
metalab-mcp --github-token ghp_YOUR_TOKEN_HERE
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## CLI Reference
|
|
349
|
+
|
|
350
|
+
### Commands
|
|
351
|
+
|
|
352
|
+
#### `metalab-mcp` (default)
|
|
353
|
+
|
|
354
|
+
Connect to the MCP Gateway.
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
metalab-mcp [options]
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
| Option | Description | Default |
|
|
361
|
+
|--------|-------------|---------|
|
|
362
|
+
| `--gateway-url <url>` | Explicit gateway URL (highest priority) | Auto-detected |
|
|
363
|
+
| `--prod` | Use production environment | Default |
|
|
364
|
+
| `--dev` | Use development environment | - |
|
|
365
|
+
| `--profile <name>` | AWS profile for SSM lookup | Default profile |
|
|
366
|
+
| `--figma-token <token>` | Figma access token | `FIGMA_ACCESS_TOKEN` env |
|
|
367
|
+
| `--notion-token <token>` | Notion access token | `NOTION_ACCESS_TOKEN` env |
|
|
368
|
+
| `--jira-token <token>` | Jira API token | `JIRA_API_TOKEN` env |
|
|
369
|
+
| `--jira-email <email>` | Jira account email | `JIRA_EMAIL` env |
|
|
370
|
+
| `--jira-instance <url>` | Jira instance URL | `JIRA_INSTANCE_URL` env |
|
|
371
|
+
| `--github-token <token>` | GitHub personal access token | `GITHUB_TOKEN` env |
|
|
372
|
+
| `--inspect` | Run with MCP Inspector | false |
|
|
373
|
+
| `-h, --help` | Show help | - |
|
|
374
|
+
|
|
375
|
+
**Examples:**
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
# Basic usage (production, auto-detects gateway URL)
|
|
379
|
+
metalab-mcp
|
|
380
|
+
|
|
381
|
+
# Use development environment
|
|
382
|
+
metalab-mcp --dev
|
|
383
|
+
|
|
384
|
+
# With Figma token
|
|
385
|
+
metalab-mcp --figma-token figd_YOUR_TOKEN
|
|
386
|
+
|
|
387
|
+
# With MCP Inspector for debugging
|
|
388
|
+
metalab-mcp --inspect
|
|
389
|
+
|
|
390
|
+
# Using environment variables
|
|
391
|
+
FIGMA_ACCESS_TOKEN=figd_YOUR_TOKEN metalab-mcp
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
#### `metalab-mcp config`
|
|
395
|
+
|
|
396
|
+
Generate configuration for MCP clients.
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
metalab-mcp config [options]
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
| Option | Description |
|
|
403
|
+
|--------|-------------|
|
|
404
|
+
| `--client <name>` | Target client: `claude`, `claude-code`, `windsurf`, `cursor`, `cline` |
|
|
405
|
+
| `--output` | Write directly to client's config file |
|
|
406
|
+
| `--figma-token <token>` | Include Figma token in config |
|
|
407
|
+
| `--notion-token <token>` | Include Notion token in config |
|
|
408
|
+
| `--jira-token <token>` | Include Jira token in config |
|
|
409
|
+
|
|
410
|
+
**Examples:**
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
# Interactive mode
|
|
414
|
+
metalab-mcp config
|
|
415
|
+
|
|
416
|
+
# Generate config for Claude Desktop
|
|
417
|
+
metalab-mcp config --client claude
|
|
418
|
+
|
|
419
|
+
# Write directly to Claude Desktop config
|
|
420
|
+
metalab-mcp config --client claude --output
|
|
421
|
+
|
|
422
|
+
# Include tokens in generated config
|
|
423
|
+
metalab-mcp config --client claude --figma-token figd_YOUR_TOKEN
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Gateway URL Resolution
|
|
427
|
+
|
|
428
|
+
The gateway URL is resolved in this priority order:
|
|
429
|
+
|
|
430
|
+
1. `--gateway-url` option (highest priority)
|
|
431
|
+
2. AWS SSM Parameter Store (environment-specific path)
|
|
432
|
+
3. `MCP_SERVER_URL` environment variable
|
|
433
|
+
4. Hardcoded URL for the environment (fallback)
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## How It Works
|
|
438
|
+
|
|
439
|
+
### Architecture
|
|
440
|
+
|
|
441
|
+
```mermaid
|
|
442
|
+
sequenceDiagram
|
|
443
|
+
participant Client as MCP Client<br/>(Claude, etc.)
|
|
444
|
+
participant Proxy as mcp-client<br/>(this package)
|
|
445
|
+
participant Gateway as MCP Gateway
|
|
446
|
+
participant Services as MCP Services<br/>(Figma, Notion, Jira)
|
|
447
|
+
|
|
448
|
+
Client->>Proxy: stdio
|
|
449
|
+
Note over Proxy: First request triggers<br/>OAuth authentication
|
|
450
|
+
Proxy->>Gateway: HTTPS + Bearer Token
|
|
451
|
+
Note over Gateway: Validates token,<br/>routes by tool prefix
|
|
452
|
+
Gateway->>Services: Forward request
|
|
453
|
+
Services-->>Gateway: Response
|
|
454
|
+
Gateway-->>Proxy: MCP response
|
|
455
|
+
Proxy-->>Client: stdio
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Key Features
|
|
459
|
+
|
|
460
|
+
1. **OAuth 2.1 with PKCE**: Secure authentication via Okta with browser-based login
|
|
461
|
+
2. **Token Management**: Automatically persists and refreshes access tokens
|
|
462
|
+
3. **Lazy Connection**: Only connects to gateway when first MCP request is made
|
|
463
|
+
4. **Service Token Forwarding**: Your personal tokens (Figma, Notion, Jira) are forwarded as HTTP headers
|
|
464
|
+
5. **Multiple Environments**: Supports production and development gateways
|
|
465
|
+
6. **Automatic Session Recovery**: Detects session expiration and reconnects automatically (see below)
|
|
466
|
+
|
|
467
|
+
### Automatic Session Recovery
|
|
468
|
+
|
|
469
|
+
Gateway sessions expire after a period of inactivity (default: 1 hour). When this happens, the proxy automatically:
|
|
470
|
+
|
|
471
|
+
1. Detects the session error (e.g., "session not found", "internal server error")
|
|
472
|
+
2. Resets the connection and establishes a new session
|
|
473
|
+
3. Retries the failed request once
|
|
474
|
+
|
|
475
|
+
This means you **don't need to restart Claude Code** when sessions expire - the proxy handles reconnection transparently.
|
|
476
|
+
|
|
477
|
+
**Rate limiting**: To prevent masking real server errors, only one reconnection attempt is allowed every 10 minutes. If errors persist after reconnection, subsequent requests will fail until the cooldown period passes.
|
|
478
|
+
|
|
479
|
+
### Token Flow
|
|
480
|
+
|
|
481
|
+
```mermaid
|
|
482
|
+
flowchart LR
|
|
483
|
+
subgraph Client["mcp-client (with tokens)"]
|
|
484
|
+
Tokens["X-Figma-Token<br/>X-Notion-Token<br/>X-Jira-Token"]
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
subgraph Gateway["MCP Gateway (forwards)"]
|
|
488
|
+
Forward["Routes tokens<br/>to services"]
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
subgraph Services["MCP Services"]
|
|
492
|
+
Figma["Figma MCP"]
|
|
493
|
+
Notion["Notion MCP"]
|
|
494
|
+
Jira["Jira MCP"]
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
Client -->|"Headers"| Gateway
|
|
498
|
+
Gateway --> Figma
|
|
499
|
+
Gateway --> Notion
|
|
500
|
+
Gateway --> Jira
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Your service tokens are included in every request to the gateway, which forwards them to the appropriate downstream service.
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## Security
|
|
508
|
+
|
|
509
|
+
- **PKCE**: Uses SHA-256 code challenge for OAuth 2.1
|
|
510
|
+
- **Token Storage**: Tokens stored with 0600 permissions (owner read/write only) in `~/.metalab/mcp-local-proxy/tokens/`
|
|
511
|
+
- **Localhost Binding**: OAuth callback server binds to 127.0.0.1 only
|
|
512
|
+
- **Gateway Protected**: MCP Gateway requires Okta authentication
|
|
513
|
+
- **Public Package**: Safe because gateway still requires authentication
|
|
514
|
+
|
|
515
|
+
---
|
|
272
516
|
|
|
273
517
|
## AWS SSO Integration
|
|
274
518
|
|
|
@@ -281,47 +525,98 @@ aws configure sso --profile metalab-dev
|
|
|
281
525
|
# Log in
|
|
282
526
|
aws sso login --profile metalab-dev
|
|
283
527
|
|
|
284
|
-
# Now
|
|
528
|
+
# Now mcp-client will auto-detect the gateway URL
|
|
285
529
|
metalab-mcp --profile metalab-dev
|
|
286
530
|
```
|
|
287
531
|
|
|
288
|
-
**SSM Parameter Paths
|
|
289
|
-
|
|
532
|
+
**SSM Parameter Paths:**
|
|
290
533
|
- Production: `/mcp/gateway-url` (us-east-1)
|
|
291
534
|
- Development: `/mcp/gateway-url` (us-east-2)
|
|
292
535
|
|
|
293
|
-
|
|
536
|
+
---
|
|
294
537
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
538
|
+
## Troubleshooting
|
|
539
|
+
|
|
540
|
+
### "No Figma/Notion/Jira access token available"
|
|
541
|
+
|
|
542
|
+
**Cause**: No token provided
|
|
543
|
+
|
|
544
|
+
**Solution**: Add the token to your MCP client config's `env` section or pass via CLI flag.
|
|
545
|
+
|
|
546
|
+
### OAuth callback timeout (5 minutes)
|
|
547
|
+
|
|
548
|
+
**Cause**: Browser didn't complete authentication in time
|
|
549
|
+
|
|
550
|
+
**Solutions**:
|
|
551
|
+
- Ensure your browser opened the Okta login page
|
|
552
|
+
- Complete authentication within 5 minutes
|
|
553
|
+
- Check if port 9876 is available (or the port in your callback URL)
|
|
554
|
+
|
|
555
|
+
### Token refresh fails
|
|
556
|
+
|
|
557
|
+
**Cause**: Refresh token expired or invalid
|
|
558
|
+
|
|
559
|
+
**Solution**: Clear stored tokens and re-authenticate:
|
|
560
|
+
```bash
|
|
561
|
+
rm -rf ~/.metalab/mcp-local-proxy/tokens/*
|
|
304
562
|
```
|
|
305
563
|
|
|
306
|
-
|
|
307
|
-
2. **OAuth 2.1**: Handles authentication with Okta via browser
|
|
308
|
-
3. **Token Management**: Persists and refreshes tokens automatically
|
|
309
|
-
4. **MCP Proxy**: Bridges stdio (local) to HTTPS (remote)
|
|
310
|
-
5. **Service Tokens**: Forwards per-user tokens to downstream services
|
|
564
|
+
### Port already in use
|
|
311
565
|
|
|
312
|
-
|
|
566
|
+
**Cause**: Another process is using the OAuth callback port
|
|
313
567
|
|
|
314
|
-
|
|
315
|
-
-
|
|
316
|
-
-
|
|
317
|
-
|
|
318
|
-
|
|
568
|
+
**Solutions**:
|
|
569
|
+
- Kill the process using the port: `lsof -ti:9876 | xargs kill -9`
|
|
570
|
+
- Use a different callback URL: `--callback-url http://localhost:9877/callback`
|
|
571
|
+
|
|
572
|
+
### Tools not showing up
|
|
573
|
+
|
|
574
|
+
**Cause**: No token configured for that service
|
|
575
|
+
|
|
576
|
+
**Expected behavior**: Tools only appear when you have a valid token for the service
|
|
319
577
|
|
|
320
|
-
|
|
578
|
+
**Solution**: Configure your personal token for the service you want to use
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
## Development
|
|
583
|
+
|
|
584
|
+
### Requirements
|
|
321
585
|
|
|
322
586
|
- Node.js 20 or later
|
|
323
587
|
- (Optional) AWS CLI for SSM-based gateway URL discovery
|
|
324
588
|
|
|
589
|
+
### Building
|
|
590
|
+
|
|
591
|
+
```bash
|
|
592
|
+
# Install dependencies
|
|
593
|
+
pnpm install
|
|
594
|
+
|
|
595
|
+
# Build TypeScript
|
|
596
|
+
pnpm run build
|
|
597
|
+
|
|
598
|
+
# Watch mode
|
|
599
|
+
pnpm run dev
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
### Testing Locally
|
|
603
|
+
|
|
604
|
+
```bash
|
|
605
|
+
# Build and run CLI
|
|
606
|
+
pnpm run build
|
|
607
|
+
node dist/bin/cli.js --dev --inspect
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### Publishing
|
|
611
|
+
|
|
612
|
+
```bash
|
|
613
|
+
# Build and publish
|
|
614
|
+
pnpm run build
|
|
615
|
+
npm publish --access public
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
325
620
|
## License
|
|
326
621
|
|
|
327
622
|
MIT
|
package/dist/proxy.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export declare class McpProxy {
|
|
|
15
15
|
private capabilities;
|
|
16
16
|
private isConnected;
|
|
17
17
|
private connectionPromise;
|
|
18
|
+
private isReconnecting;
|
|
19
|
+
private lastReconnectTime;
|
|
18
20
|
constructor(config: ProxyConfig, oauthManager: OAuthManager, logger: Logger);
|
|
19
21
|
/**
|
|
20
22
|
* Start the proxy using stdio transport
|
|
@@ -34,6 +36,27 @@ export declare class McpProxy {
|
|
|
34
36
|
*/
|
|
35
37
|
private buildServiceTokenHeaders;
|
|
36
38
|
private discoverCapabilities;
|
|
39
|
+
/**
|
|
40
|
+
* Check if an error indicates a recoverable session/connection issue
|
|
41
|
+
*/
|
|
42
|
+
private isRecoverableError;
|
|
43
|
+
/**
|
|
44
|
+
* Reset connection state to allow reconnection on next request
|
|
45
|
+
*/
|
|
46
|
+
private resetConnection;
|
|
47
|
+
/**
|
|
48
|
+
* Check if enough time has passed since the last reconnection attempt.
|
|
49
|
+
*/
|
|
50
|
+
private canAttemptReconnect;
|
|
51
|
+
/**
|
|
52
|
+
* Execute an operation with automatic retry on session expiration.
|
|
53
|
+
* If the operation fails due to a recoverable error (session expired, connection lost),
|
|
54
|
+
* it will reset the connection and retry once with a new session.
|
|
55
|
+
*
|
|
56
|
+
* Rate limited: Only one reconnection attempt allowed per 10 minutes to avoid
|
|
57
|
+
* masking real server errors with repeated reconnection attempts.
|
|
58
|
+
*/
|
|
59
|
+
private withAutoReconnect;
|
|
37
60
|
private setupProxyHandlers;
|
|
38
61
|
}
|
|
39
62
|
//# sourceMappingURL=proxy.d.ts.map
|
package/dist/proxy.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAwBH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAwBH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAkC/C,qBAAa,QAAQ;IAejB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAhBzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,YAAY,CAIlB;IACF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,iBAAiB,CAA8B;IACvD,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,iBAAiB,CAAK;gBAGX,MAAM,EAAE,WAAW,EACnB,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,MAAM;IAmBjC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAS5B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAUjC;;OAEG;YACW,eAAe;YAiBf,OAAO;IAqCrB;;OAEG;IACH,OAAO,CAAC,wBAAwB;YAkClB,oBAAoB;IAiBlC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;YACW,eAAe;IAkB7B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAK3B;;;;;;;OAOG;YACW,iBAAiB;IAqC/B,OAAO,CAAC,kBAAkB;CAyF3B"}
|
package/dist/proxy.js
CHANGED
|
@@ -7,6 +7,30 @@ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/
|
|
|
7
7
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
8
8
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
9
|
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Patterns that indicate session expiration or connection issues that
|
|
12
|
+
* can be recovered by reconnecting with a new session.
|
|
13
|
+
*/
|
|
14
|
+
const RECOVERABLE_ERROR_PATTERNS = [
|
|
15
|
+
'session not found',
|
|
16
|
+
'session expired',
|
|
17
|
+
'internal server error',
|
|
18
|
+
'session invalid',
|
|
19
|
+
'unauthorized',
|
|
20
|
+
'401',
|
|
21
|
+
'404',
|
|
22
|
+
'500',
|
|
23
|
+
'econnreset',
|
|
24
|
+
'econnrefused',
|
|
25
|
+
'socket hang up',
|
|
26
|
+
'network error',
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Minimum time between reconnection attempts (in milliseconds).
|
|
30
|
+
* Prevents rapid reconnection loops if the server is having issues.
|
|
31
|
+
* Set to 10 minutes to avoid masking real server errors.
|
|
32
|
+
*/
|
|
33
|
+
const RECONNECT_COOLDOWN_MS = 10 * 60 * 1000; // 10 minutes
|
|
10
34
|
export class McpProxy {
|
|
11
35
|
config;
|
|
12
36
|
oauthManager;
|
|
@@ -21,6 +45,8 @@ export class McpProxy {
|
|
|
21
45
|
};
|
|
22
46
|
isConnected = false;
|
|
23
47
|
connectionPromise = null;
|
|
48
|
+
isReconnecting = false;
|
|
49
|
+
lastReconnectTime = 0;
|
|
24
50
|
constructor(config, oauthManager, logger) {
|
|
25
51
|
this.config = config;
|
|
26
52
|
this.oauthManager = oauthManager;
|
|
@@ -154,23 +180,96 @@ export class McpProxy {
|
|
|
154
180
|
this.capabilities = { tools: true, resources: true, prompts: true };
|
|
155
181
|
}
|
|
156
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Check if an error indicates a recoverable session/connection issue
|
|
185
|
+
*/
|
|
186
|
+
isRecoverableError(error) {
|
|
187
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
188
|
+
const lowerMessage = message.toLowerCase();
|
|
189
|
+
return RECOVERABLE_ERROR_PATTERNS.some(pattern => lowerMessage.includes(pattern));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Reset connection state to allow reconnection on next request
|
|
193
|
+
*/
|
|
194
|
+
async resetConnection() {
|
|
195
|
+
this.logger.info('Resetting connection state for reconnection...');
|
|
196
|
+
// Close existing transport if any
|
|
197
|
+
if (this.transport) {
|
|
198
|
+
try {
|
|
199
|
+
await this.transport.close();
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// Ignore close errors during reset
|
|
203
|
+
}
|
|
204
|
+
this.transport = null;
|
|
205
|
+
}
|
|
206
|
+
this.client = null;
|
|
207
|
+
this.isConnected = false;
|
|
208
|
+
this.connectionPromise = null;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Check if enough time has passed since the last reconnection attempt.
|
|
212
|
+
*/
|
|
213
|
+
canAttemptReconnect() {
|
|
214
|
+
const timeSinceLastReconnect = Date.now() - this.lastReconnectTime;
|
|
215
|
+
return timeSinceLastReconnect >= RECONNECT_COOLDOWN_MS;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Execute an operation with automatic retry on session expiration.
|
|
219
|
+
* If the operation fails due to a recoverable error (session expired, connection lost),
|
|
220
|
+
* it will reset the connection and retry once with a new session.
|
|
221
|
+
*
|
|
222
|
+
* Rate limited: Only one reconnection attempt allowed per 10 minutes to avoid
|
|
223
|
+
* masking real server errors with repeated reconnection attempts.
|
|
224
|
+
*/
|
|
225
|
+
async withAutoReconnect(operation, operationName) {
|
|
226
|
+
try {
|
|
227
|
+
await this.ensureConnected();
|
|
228
|
+
return await operation();
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
// Check if this is a recoverable error, we're not already reconnecting,
|
|
232
|
+
// and we haven't reconnected recently (cooldown period)
|
|
233
|
+
if (this.isRecoverableError(error) && !this.isReconnecting && this.canAttemptReconnect()) {
|
|
234
|
+
this.logger.warn(`${operationName} failed with recoverable error, attempting reconnection...`, error instanceof Error ? error.message : String(error));
|
|
235
|
+
this.isReconnecting = true;
|
|
236
|
+
try {
|
|
237
|
+
// Reset connection and retry once
|
|
238
|
+
await this.resetConnection();
|
|
239
|
+
await this.ensureConnected();
|
|
240
|
+
// Record successful reconnection time
|
|
241
|
+
this.lastReconnectTime = Date.now();
|
|
242
|
+
this.logger.info(`Reconnected successfully, retrying ${operationName}...`);
|
|
243
|
+
return await operation();
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
this.isReconnecting = false;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Non-recoverable error, retry failed, or in cooldown period - rethrow
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
157
253
|
setupProxyHandlers() {
|
|
158
254
|
// Handle listTools
|
|
159
255
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
256
|
+
return this.withAutoReconnect(async () => {
|
|
257
|
+
if (!this.capabilities.tools)
|
|
258
|
+
return { tools: [] };
|
|
259
|
+
return await this.client.listTools();
|
|
260
|
+
}, 'listTools');
|
|
164
261
|
});
|
|
165
|
-
// Handle callTool
|
|
262
|
+
// Handle callTool - with error wrapping for better UX
|
|
166
263
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
167
|
-
await this.ensureConnected();
|
|
168
264
|
const { name, arguments: args } = request.params;
|
|
169
265
|
try {
|
|
170
|
-
|
|
171
|
-
|
|
266
|
+
return await this.withAutoReconnect(async () => {
|
|
267
|
+
const result = await this.client.callTool({ name, arguments: args ?? {} });
|
|
268
|
+
return result;
|
|
269
|
+
}, `callTool(${name})`);
|
|
172
270
|
}
|
|
173
271
|
catch (error) {
|
|
272
|
+
// If even the retry failed, return as error result
|
|
174
273
|
const message = error instanceof Error ? error.message : String(error);
|
|
175
274
|
return {
|
|
176
275
|
isError: true,
|
|
@@ -180,36 +279,41 @@ export class McpProxy {
|
|
|
180
279
|
});
|
|
181
280
|
// Handle listResources
|
|
182
281
|
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
282
|
+
return this.withAutoReconnect(async () => {
|
|
283
|
+
if (!this.capabilities.resources)
|
|
284
|
+
return { resources: [] };
|
|
285
|
+
return await this.client.listResources();
|
|
286
|
+
}, 'listResources');
|
|
187
287
|
});
|
|
188
288
|
// Handle listResourceTemplates
|
|
189
289
|
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
290
|
+
return this.withAutoReconnect(async () => {
|
|
291
|
+
if (!this.capabilities.resources)
|
|
292
|
+
return { resourceTemplates: [] };
|
|
293
|
+
return await this.client.listResourceTemplates();
|
|
294
|
+
}, 'listResourceTemplates');
|
|
194
295
|
});
|
|
195
296
|
// Handle readResource
|
|
196
297
|
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
197
|
-
await this.ensureConnected();
|
|
198
298
|
const { uri } = request.params;
|
|
199
|
-
return
|
|
299
|
+
return this.withAutoReconnect(async () => {
|
|
300
|
+
return await this.client.readResource({ uri });
|
|
301
|
+
}, 'readResource');
|
|
200
302
|
});
|
|
201
303
|
// Handle listPrompts
|
|
202
304
|
this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
305
|
+
return this.withAutoReconnect(async () => {
|
|
306
|
+
if (!this.capabilities.prompts)
|
|
307
|
+
return { prompts: [] };
|
|
308
|
+
return await this.client.listPrompts();
|
|
309
|
+
}, 'listPrompts');
|
|
207
310
|
});
|
|
208
311
|
// Handle getPrompt
|
|
209
312
|
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
210
|
-
await this.ensureConnected();
|
|
211
313
|
const { name, arguments: args } = request.params;
|
|
212
|
-
return
|
|
314
|
+
return this.withAutoReconnect(async () => {
|
|
315
|
+
return await this.client.getPrompt({ name, arguments: args });
|
|
316
|
+
}, `getPrompt(${name})`);
|
|
213
317
|
});
|
|
214
318
|
}
|
|
215
319
|
}
|
package/dist/proxy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACnG,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAUjF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,EACxB,0BAA0B,EAC1B,kCAAkC,EAClC,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,oCAAoC,CAAC;AAW5C,MAAM,OAAO,QAAQ;
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
|