npm package: @metalabdesign/mcp-client
Description: MCP client for connecting to Metalab's MCP Gateway with OAuth 2.1 authentication
Latest version: 1.2.0
Deprecated: Renamed to metalab-mcp-client. Update your MCP client config to use 'metalab-mcp-client'. See https://github.com/metalabdesign/metalab-core-mcps/blob/main/packages/mcp-local-proxy/README.md

# @metalabdesign/mcp-client

**Connect your AI assistant to Metalab's MCP services** - Figma, Notion, Jira, and more.

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.

```mermaid
flowchart LR
    A["Your AI Tool<br/>(Claude, etc.)"] -->|stdio| B["mcp-client<br/>(this package)"]
    B -->|"HTTPS + OAuth"| C["MCP Gateway<br/>(Figma, Notion, Jira)"]
```

## Quick Start

```bash
# Run directly - zero config needed!
npx @metalabdesign/mcp-client

# Generate config for your MCP client
npx @metalabdesign/mcp-client config --client claude
```

On first run, your browser will open for Okta authentication. Once authenticated, you can start using MCP tools in your AI assistant.

---

## Installation

The package is published on **npmjs.org** (public, no authentication needed).

```bash
# Run directly with npx (recommended)
npx @metalabdesign/mcp-client

# Or install globally
npm install -g @metalabdesign/mcp-client
metalab-mcp
```

### Registry Configuration

If your `~/.npmrc` maps `@metalabdesign` to GitHub Packages for other internal packages, you'll need to explicitly use the npmjs.org registry:

```bash
# Option 1: Use --registry flag
npx --registry=https://registry.npmjs.org @metalabdesign/mcp-client

# Option 2: Add to ~/.npmrc
@metalabdesign/mcp-client:registry=https://registry.npmjs.org
```

---

## MCP Client Configuration

### Claude Desktop (Recommended)

**Option 1: Auto-generate config**

```bash
npx @metalabdesign/mcp-client config --client claude --output
```

**Option 2: Manual configuration**

Add to your Claude Desktop config file:

| Platform | Config File Location |
|----------|---------------------|
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
| Linux | `~/.config/Claude/claude_desktop_config.json` |

```json
{
    "mcpServers": {
        "metalab": {
            "command": "npx",
            "args": ["@metalabdesign/mcp-client"]
        }
    }
}
```

**With service tokens** (for Figma, Notion, Jira, GitHub access):

```json
{
    "mcpServers": {
        "metalab": {
            "command": "npx",
            "args": ["@metalabdesign/mcp-client"],
            "env": {
                "FIGMA_ACCESS_TOKEN": "figd_YOUR_TOKEN_HERE",
                "NOTION_ACCESS_TOKEN": "secret_YOUR_TOKEN_HERE",
                "JIRA_API_TOKEN": "YOUR_JIRA_API_TOKEN",
                "JIRA_EMAIL": "your-email@company.com",
                "JIRA_INSTANCE_URL": "https://company.atlassian.net",
                "GITHUB_TOKEN": "ghp_YOUR_TOKEN_HERE"
            }
        }
    }
}
```

### Claude Code

**Option 1: Use `claude mcp add` command (recommended)**

```bash
# Basic installation
claude mcp add metalab --scope user -- npx @metalabdesign/mcp-client

# With service tokens
claude mcp add metalab --scope user \
    -e "FIGMA_ACCESS_TOKEN=figd_YOUR_TOKEN" \
    -e "NOTION_ACCESS_TOKEN=secret_YOUR_TOKEN" \
    -e "JIRA_API_TOKEN=YOUR_JIRA_TOKEN" \
    -e "JIRA_EMAIL=your-email@company.com" \
    -e "JIRA_INSTANCE_URL=https://company.atlassian.net" \
    -e "GITHUB_TOKEN=ghp_YOUR_TOKEN" \
    -- npx @metalabdesign/mcp-client
```

**Option 2: Manual configuration**

Add to `~/.claude.json`:

```json
{
    "mcpServers": {
        "metalab": {
            "command": "npx",
            "args": ["@metalabdesign/mcp-client"],
            "env": {
                "FIGMA_ACCESS_TOKEN": "figd_YOUR_TOKEN_HERE"
            }
        }
    }
}
```

### Windsurf

Add to `~/.codeium/windsurf/mcp_config.json`:

```json
{
    "mcpServers": {
        "metalab": {
            "command": "npx",
            "args": ["@metalabdesign/mcp-client"],
            "env": {
                "FIGMA_ACCESS_TOKEN": "figd_YOUR_TOKEN_HERE"
            }
        }
    }
}
```

### Cursor

Add to `~/.cursor/mcp.json`:

```json
{
    "mcpServers": {
        "metalab": {
            "command": "npx",
            "args": ["@metalabdesign/mcp-client"],
            "env": {
                "FIGMA_ACCESS_TOKEN": "figd_YOUR_TOKEN_HERE"
            }
        }
    }
}
```

### Cline (VS Code)

Add to `.vscode/cline_mcp_settings.json` in your project:

```json
{
    "mcpServers": {
        "metalab": {
            "command": "npx",
            "args": ["@metalabdesign/mcp-client"],
            "env": {
                "FIGMA_ACCESS_TOKEN": "figd_YOUR_TOKEN_HERE"
            }
        }
    }
}
```

### Using Development Environment

For testing against the dev gateway, add the `--dev` flag:

```json
{
    "mcpServers": {
        "metalab-dev": {
            "command": "npx",
            "args": ["@metalabdesign/mcp-client", "--dev"]
        }
    }
}
```

---

## Service Tokens

Each MCP service requires a personal access token for authentication. The tokens you provide are forwarded to the respective service APIs.

| Service | Required Credentials | How to Provide |
|---------|---------------------|----------------|
| **Figma** | Access Token | `FIGMA_ACCESS_TOKEN` env var or `--figma-token` flag |
| **Notion** | Integration Secret | `NOTION_ACCESS_TOKEN` env var or `--notion-token` flag |
| **Jira** | API Token + Email + Instance URL | See Jira section below |
| **GitHub** | Personal Access Token | `GITHUB_TOKEN` env var or `--github-token` flag |

### Getting Your Figma Token

1. Log in to your [Figma](https://figma.com) account
2. Go to **Settings** > **Account**
3. Scroll to **Personal access tokens**
4. Click **Generate new token**
5. Give it a descriptive name (e.g., "MCP Integration")
6. Copy the token (starts with `figd_`)

**Token format**: `figd_XXXXXXXXXXXXXXXXXXXXX`

### Getting Your Notion Token

1. Log in to your [Notion](https://notion.so) workspace
2. Go to **Settings & members** (gear icon in sidebar)
3. Click **My connections** in the left sidebar
4. Click **Develop or manage integrations** at the bottom
5. Click **+ New integration**
6. Configure the integration:
   - **Name**: Give it a descriptive name (e.g., "MCP Integration")
   - **Associated workspace**: Select your workspace
   - **Capabilities**: Enable read content, read comments, etc.
7. Click **Submit** to create the integration
8. Copy the **Internal Integration Secret** (starts with `secret_`)

**Token format**: `secret_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`

**Important**: After creating the integration, you must share pages/databases with it:
1. Open the page or database you want to access
2. Click **•••** (more menu) in the top-right corner
3. Click **Add connections**
4. Search for and select your integration

### Getting Your Jira Token

Jira requires three pieces of information:

1. **API Token**: Generated from Atlassian
2. **Email**: Your Atlassian account email (optional - auto-derived from Okta if not provided)
3. **Instance URL**: Your Jira Cloud URL

**Steps to get your API token:**

1. Go to https://id.atlassian.com/manage-profile/security/api-tokens
2. Click **Create API token**
3. Give it a label (e.g., "Metalab MCP Access")
4. Copy the token immediately (you won't see it again)

**Configuration example:**

```json
{
    "env": {
        "JIRA_API_TOKEN": "ATATT3xFfGF0...",
        "JIRA_EMAIL": "your-email@company.com",
        "JIRA_INSTANCE_URL": "https://company.atlassian.net"
    }
}
```

**Note**: The `JIRA_EMAIL` is optional. If not provided, the gateway uses your authenticated Okta email.

### Getting Your GitHub Token

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.

#### Required Repository Access

Your GitHub token must have read access to **all repositories** you want to work with:

| Use Case | Required Repo Access |
|----------|---------------------|
| Guides & Skills | `metalabdesign/librarian-dev-ai-assistants` |
| Dev Context Tools | Any repository you query (e.g., `metalabdesign/web-app`, `yourorg/backend-api`) |

> **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.

#### Creating a Token

**For Personal Access Tokens (Classic)**:

1. Go to GitHub > **Settings** > **Developer settings** > **Personal access tokens** > **Tokens (classic)**
2. Click **Generate new token**
3. Give it a descriptive name (e.g., "Metalab MCP Access")
4. Select scopes:
   - `repo` - Full control of private repositories (read access to code, commits, PRs)
   - Or `public_repo` - If only working with public repositories
5. Click **Generate token** and copy it immediately

**For Fine-Grained Personal Access Tokens** (recommended):

1. Go to GitHub > **Settings** > **Developer settings** > **Personal access tokens** > **Fine-grained tokens**
2. Click **Generate new token**
3. Give it a descriptive name (e.g., "Metalab MCP Access")
4. Select repository access:
   - **All repositories** (simplest, works with any repo)
   - Or **Select repositories** (more secure, but must include all repos you'll query)
5. Set permissions:
   - **Contents**: Read (for commits, branches, file content)
   - **Pull requests**: Read (for PR context, reviews, comments)
   - **Metadata**: Read (required by default)
6. Click **Generate token** and copy it immediately

**Token format**: `ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` (classic) or `github_pat_XXXXXX...` (fine-grained)

**Configuration example:**

```json
{
    "env": {
        "GITHUB_TOKEN": "ghp_YOUR_TOKEN_HERE"
    }
}
```

Or via CLI:

```bash
metalab-mcp --github-token ghp_YOUR_TOKEN_HERE
```

---

## CLI Reference

### Commands

#### `metalab-mcp` (default)

Connect to the MCP Gateway.

```bash
metalab-mcp [options]
```

| Option | Description | Default |
|--------|-------------|---------|
| `--gateway-url <url>` | Explicit gateway URL (highest priority) | Auto-detected |
| `--prod` | Use production environment | Default |
| `--dev` | Use development environment | - |
| `--profile <name>` | AWS profile for SSM lookup | Default profile |
| `--figma-token <token>` | Figma access token | `FIGMA_ACCESS_TOKEN` env |
| `--notion-token <token>` | Notion access token | `NOTION_ACCESS_TOKEN` env |
| `--jira-token <token>` | Jira API token | `JIRA_API_TOKEN` env |
| `--jira-email <email>` | Jira account email | `JIRA_EMAIL` env |
| `--jira-instance <url>` | Jira instance URL | `JIRA_INSTANCE_URL` env |
| `--github-token <token>` | GitHub personal access token | `GITHUB_TOKEN` env |
| `--inspect` | Run with MCP Inspector | false |
| `-h, --help` | Show help | - |

**Examples:**

```bash
# Basic usage (production, auto-detects gateway URL)
metalab-mcp

# Use development environment
metalab-mcp --dev

# With Figma token
metalab-mcp --figma-token figd_YOUR_TOKEN

# With MCP Inspector for debugging
metalab-mcp --inspect

# Using environment variables
FIGMA_ACCESS_TOKEN=figd_YOUR_TOKEN metalab-mcp
```

#### `metalab-mcp config`

Generate configuration for MCP clients.

```bash
metalab-mcp config [options]
```

| Option | Description |
|--------|-------------|
| `--client <name>` | Target client: `claude`, `claude-code`, `windsurf`, `cursor`, `cline` |
| `--output` | Write directly to client's config file |
| `--figma-token <token>` | Include Figma token in config |
| `--notion-token <token>` | Include Notion token in config |
| `--jira-token <token>` | Include Jira token in config |

**Examples:**

```bash
# Interactive mode
metalab-mcp config

# Generate config for Claude Desktop
metalab-mcp config --client claude

# Write directly to Claude Desktop config
metalab-mcp config --client claude --output

# Include tokens in generated config
metalab-mcp config --client claude --figma-token figd_YOUR_TOKEN
```

### Gateway URL Resolution

The gateway URL is resolved in this priority order:

1. `--gateway-url` option (highest priority)
2. AWS SSM Parameter Store (environment-specific path)
3. `MCP_SERVER_URL` environment variable
4. Hardcoded URL for the environment (fallback)

---

## How It Works

### Architecture

```mermaid
sequenceDiagram
    participant Client as MCP Client<br/>(Claude, etc.)
    participant Proxy as mcp-client<br/>(this package)
    participant Gateway as MCP Gateway
    participant Services as MCP Services<br/>(Figma, Notion, Jira)

    Client->>Proxy: stdio
    Note over Proxy: First request triggers<br/>OAuth authentication
    Proxy->>Gateway: HTTPS + Bearer Token
    Note over Gateway: Validates token,<br/>routes by tool prefix
    Gateway->>Services: Forward request
    Services-->>Gateway: Response
    Gateway-->>Proxy: MCP response
    Proxy-->>Client: stdio
```

### Key Features

1. **OAuth 2.1 with PKCE**: Secure authentication via Okta with browser-based login
2. **Token Management**: Automatically persists and refreshes access tokens
3. **Lazy Connection**: Only connects to gateway when first MCP request is made
4. **Service Token Forwarding**: Your personal tokens (Figma, Notion, Jira) are forwarded as HTTP headers
5. **Multiple Environments**: Supports production and development gateways
6. **Automatic Session Recovery**: Detects session expiration and reconnects automatically (see below)

### Automatic Session Recovery

Gateway sessions expire after a period of inactivity (default: 1 hour). When this happens, the proxy automatically:

1. Detects the session error (e.g., "session not found", "internal server error")
2. Resets the connection and establishes a new session
3. Retries the failed request once

This means you **don't need to restart Claude Code** when sessions expire - the proxy handles reconnection transparently.

**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.

### Token Flow

```mermaid
flowchart LR
    subgraph Client["mcp-client (with tokens)"]
        Tokens["X-Figma-Token<br/>X-Notion-Token<br/>X-Jira-Token"]
    end

    subgraph Gateway["MCP Gateway (forwards)"]
        Forward["Routes tokens<br/>to services"]
    end

    subgraph Services["MCP Services"]
        Figma["Figma MCP"]
        Notion["Notion MCP"]
        Jira["Jira MCP"]
    end

    Client -->|"Headers"| Gateway
    Gateway --> Figma
    Gateway --> Notion
    Gateway --> Jira
```

Your service tokens are included in every request to the gateway, which forwards them to the appropriate downstream service.

---

## Security

- **PKCE**: Uses SHA-256 code challenge for OAuth 2.1
- **Token Storage**: Tokens stored with 0600 permissions (owner read/write only) in `~/.metalab/mcp-local-proxy/tokens/`
- **Localhost Binding**: OAuth callback server binds to 127.0.0.1 only
- **Gateway Protected**: MCP Gateway requires Okta authentication
- **Public Package**: Safe because gateway still requires authentication

---

## AWS SSO Integration

For automatic gateway URL discovery via SSM Parameter Store:

```bash
# Configure AWS SSO profile
aws configure sso --profile metalab-dev

# Log in
aws sso login --profile metalab-dev

# Now mcp-client will auto-detect the gateway URL
metalab-mcp --profile metalab-dev
```

**SSM Parameter Paths:**
- Production: `/mcp/gateway-url` (us-east-1)
- Development: `/mcp/gateway-url` (us-east-2)

---

## Troubleshooting

### "No Figma/Notion/Jira access token available"

**Cause**: No token provided

**Solution**: Add the token to your MCP client config's `env` section or pass via CLI flag.

### OAuth callback timeout (5 minutes)

**Cause**: Browser didn't complete authentication in time

**Solutions**:
- Ensure your browser opened the Okta login page
- Complete authentication within 5 minutes
- Check if port 9876 is available (or the port in your callback URL)

### Token refresh fails

**Cause**: Refresh token expired or invalid

**Solution**: Clear stored tokens and re-authenticate:
```bash
rm -rf ~/.metalab/mcp-local-proxy/tokens/*
```

### Port already in use

**Cause**: Another process is using the OAuth callback port

**Solutions**:
- Kill the process using the port: `lsof -ti:9876 | xargs kill -9`
- Use a different callback URL: `--callback-url http://localhost:9877/callback`

### Tools not showing up

**Cause**: No token configured for that service

**Expected behavior**: Tools only appear when you have a valid token for the service

**Solution**: Configure your personal token for the service you want to use

---

## Development

### Requirements

- Node.js 20 or later
- (Optional) AWS CLI for SSM-based gateway URL discovery

### Building

```bash
# Install dependencies
pnpm install

# Build TypeScript
pnpm run build

# Watch mode
pnpm run dev
```

### Testing Locally

```bash
# Build and run CLI
pnpm run build
node dist/bin/cli.js --dev --inspect
```

### Publishing

```bash
# Build and publish
pnpm run build
npm publish --access public
```

---

## License

MIT