npm package: @flxbl-dev/mcp
Description: FLXBL Model Context Protocol Server - Bridge between LLM-assisted IDEs and FLXBL
Author: FLXBL
Latest version: 0.9.1-beta

# FLXBL MCP Server

Model Context Protocol (MCP) server for integrating FLXBL with LLM-assisted IDEs like Cursor, Windsurf, Kiro, and Antigravity.

## Overview

The FLXBL MCP Server enables AI assistants in your IDE to:

- **Understand your schema**: Read your current FLXBL schema, entities, fields, and relationships
- **Design optimized schemas**: Get guidance on FLXBL best practices (graph relationships, field types, naming conventions)
- **Publish schema versions**: Create new schema versions directly from your IDE
- **Monitor migrations**: Track the status of schema migrations
- **Generate client code**: Create TypeScript interfaces, Zod schemas, or Python models from your schema
- **Write queries**: Get help with FLXBL's Query DSL and GraphQL

## Prerequisites

- Node.js 18 or later
- A FLXBL account with an active tenant

## Installation

### Global Installation

```bash
npm install -g @flxbl-dev/mcp
```

### Using npx (No Installation)

```bash
npx @flxbl-dev/mcp
```

## Authentication

The MCP server supports two authentication methods. OAuth is recommended for interactive use; API keys are available for CI/CD and headless environments.

### Interactive Login via OAuth (Recommended)

No API key needed. On first use, ask the AI assistant to authenticate with FLXBL:

> "Authenticate with FLXBL using OAuth"

The assistant calls the `authenticate` tool with `action: "oauth_login"`, which:

1. Opens your browser to the FLXBL consent page
2. You approve access with your existing FLXBL session
3. Tokens are stored locally in `~/.flxbl/tokens.json`
4. Access tokens auto-refresh (24h access, 30-day refresh)

Credentials are shared between the MCP server and the FLXBL CLI (`@flxbl-dev/cli`), so logging in once works for both.

**Required environment variable:**

| Variable | Description | Example |
|----------|-------------|---------|
| `FLXBL_INSTANCE_URL` | URL of your FLXBL instance | `https://api.flxbl.dev` |

### API Key (CI/CD & Non-Interactive)

For automation and headless environments, set an API key:

| Variable | Description | Example |
|----------|-------------|---------|
| `FLXBL_INSTANCE_URL` | URL of your FLXBL instance | `https://api.flxbl.dev` |
| `FLXBL_API_KEY` | Your FLXBL API key | `flxbl_ab12cd34_...` |

**Getting an API Key:**

1. Log in to your FLXBL dashboard
2. Navigate to **Settings** → **API Keys**
3. Click **Generate New Key**
4. Copy the key (it won't be shown again)

When `FLXBL_API_KEY` is set, the server uses it automatically without interactive login.

## IDE Setup

With OAuth, you only need `FLXBL_INSTANCE_URL`. Add `FLXBL_API_KEY` if you prefer API key auth.

### Cursor

Add to your Cursor MCP configuration (`~/.cursor/mcp.json`):

```json
{
  "mcpServers": {
    "flxbl": {
      "command": "npx",
      "args": ["@flxbl-dev/mcp"],
      "env": {
        "FLXBL_INSTANCE_URL": "https://api.flxbl.dev"
      }
    }
  }
}
```

### Windsurf

Add to your Windsurf MCP settings:

```json
{
  "mcp": {
    "servers": {
      "flxbl": {
        "command": "npx",
        "args": ["@flxbl-dev/mcp"],
        "env": {
          "FLXBL_INSTANCE_URL": "https://api.flxbl.dev"
        }
      }
    }
  }
}
```

### VS Code with Continue

Add to your Continue configuration:

```json
{
  "mcpServers": [
    {
      "name": "flxbl",
      "command": "npx",
      "args": ["@flxbl-dev/mcp"],
      "env": {
        "FLXBL_INSTANCE_URL": "https://api.flxbl.dev"
      }
    }
  ]
}
```

## Available Capabilities

### Resources (Context)

| Resource URI | Description |
|--------------|-------------|
| `flxbl://about` | Product information about FLXBL - graph-based BaaS architecture |
| `flxbl://tenant` | Your tenant context including `tenantId` (required for GraphQL API) |
| `flxbl://schema/active` | Current active schema with all entities, fields, and relationships |
| `flxbl://context` | Formatted schema summary for LLM context (Markdown) |
| `flxbl://api/openapi/{schemaId}` | OpenAPI 3.0 specification for REST API |
| `flxbl://api/graphql` | GraphQL SDL schema with usage instructions |
| `flxbl://api/context` | Unified API context combining OpenAPI, GraphQL SDL, and schema details |
| `flxbl://templates` | Available schema templates |
| `flxbl://identity/config` | End-user auth settings (requires identity entity in schema) |

### Tools (Actions)

| Tool | Description |
|------|-------------|
| `authenticate` | Manage authentication: `oauth_login` (browser), `login` (email/password), `status`, `logout` |
| `validate_schema` | Validate a schema without publishing (dry run) |
| `publish_schema_version` | Create a new schema version with entities and relationships |
| `check_migration_status` | Check the status of a running migration by ID |
| `generate_client_types` | Generate TypeScript/Python types or language-agnostic API contract schema |
| `list_schema_templates` | Browse available pre-built schema templates |
| `get_api_spec` | Get OpenAPI 3.0 specification for the active schema (json or summary format) |
| `get_graphql_schema` | Get GraphQL SDL for the active schema |

#### generate_client_types Options

| Language | Format | Description |
|----------|--------|-------------|
| `typescript` | `interface` | TypeScript interfaces |
| `typescript` | `zod` | Zod schemas with full API client |
| `typescript` | `class` | TypeScript classes |
| `python` | `pydantic` | Pydantic models |
| `python` | `dataclass` | Python dataclasses |
| `any` | `schema` | Language-agnostic JSON API contract |

The `schema` format returns a comprehensive JSON structure that includes:
- Entity definitions with field types and constraints
- Complete REST endpoint documentation
- Query DSL operators and examples
- Relationship operation endpoints
- GraphQL endpoint information
- Type mapping for multiple languages (TypeScript, Python, Go, Rust)

### Prompts (Guidance)

| Prompt | Description |
|--------|-------------|
| `design_flxbl_schema` | Guide for designing optimal FLXBL schemas |
| `schema_design_wizard` | Interactive wizard for step-by-step schema design |
| `generate_query` | Help writing FLXBL Query DSL or GraphQL |
| `query_builder` | Interactive wizard for building queries |

## Key Concepts

### Tenant Context for GraphQL

The GraphQL API requires your `tenantId` in the URL. Get it from the `flxbl://tenant` resource:

```json
{
  "tenantId": "your-tenant-uuid",
  "endpoints": {
    "graphql": "/api/v1/dynamic-gql/your-tenant-uuid"
  }
}
```

### FLXBL Architecture

FLXBL is a **graph-relational backend-as-a-service** that combines:
- **PostgreSQL** for schema management and control plane
- **Neo4j** for data storage and graph traversal

When you define a schema, FLXBL automatically generates REST and GraphQL APIs. Use the `flxbl://about` resource for detailed architecture information.

## Example Workflows

### Designing a New Schema

1. Open your IDE chat
2. Ask: "Help me design a schema for a blog platform with posts, authors, and comments"
3. The AI will use the `design_flxbl_schema` prompt to guide you through best practices
4. Review the suggested schema
5. Use `publish_schema_version` to create it in FLXBL

### Adding Features to Existing Schema

1. The AI reads your current schema via `flxbl://schema/active`
2. Ask: "Add a 'Reviews' feature to my Product entity"
3. The AI suggests schema changes using relationships
4. Publish the new version with `publish_schema_version`
5. Monitor with `check_migration_status`

### Generating Client Code

1. Ask: "Generate TypeScript interfaces for my schema"
2. Use `generate_client_types` with `language: typescript, format: interface`
3. Copy the generated code to your project

**Available formats:**

```
# TypeScript interfaces
Generate TypeScript interfaces for my FLXBL schema

# Zod schemas with full API client
Generate Zod schemas for my FLXBL schema

# Python Pydantic models
Generate Pydantic models for my schema

# Python dataclasses
Generate Python dataclasses for my schema

# Language-agnostic API contract (for any language)
Generate API contract schema for my FLXBL schema
```

The `schema` format is especially useful when you need to:
- Generate code in Go, Rust, Java, or other languages
- Create custom API clients
- Understand the full API contract including Query DSL and relationships

## Using Generated Types with the Dynamic API

Once you've generated types using the MCP, you'll need to connect your application to the FLXBL Dynamic API.

### API Base URL

The Dynamic REST API is available at:
```
{YOUR_FLXBL_URL}/api/v1/dynamic
```

For local development:
```
http://localhost:3001/api/v1/dynamic
```

### Authentication

All requests to the Dynamic API must include your API key in the `Authorization` header:

```
Authorization: Bearer your_api_key_here
```

### REST Endpoints

For each entity in your schema, FLXBL automatically generates these endpoints:

#### Entity CRUD Operations

| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/v1/dynamic/{entity}` | List all records |
| `GET` | `/api/v1/dynamic/{entity}/:id` | Get a record by ID |
| `POST` | `/api/v1/dynamic/{entity}` | Create a new record |
| `PUT` | `/api/v1/dynamic/{entity}/:id` | Full update (replace) |
| `PATCH` | `/api/v1/dynamic/{entity}/:id` | Partial update |
| `DELETE` | `/api/v1/dynamic/{entity}/:id` | Delete a record |
| `POST` | `/api/v1/dynamic/{entity}/query` | Query with filters (Query DSL) |

#### Relationship Operations

| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/api/v1/dynamic/{entity}/:id/relationships/{rel}` | Create a relationship |
| `GET` | `/api/v1/dynamic/{entity}/:id/relationships/{rel}?direction=` | Get relationships |
| `PATCH` | `/api/v1/dynamic/{entity}/:id/relationships/{rel}/:targetId` | Update relationship properties |
| `DELETE` | `/api/v1/dynamic/{entity}/:id/relationships/{rel}/:targetId` | Delete a relationship |

#### GraphQL Endpoint

| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/api/v1/dynamic-gql/{tenantId}` | GraphQL queries and mutations |
| `GET` | `/api/v1/dynamic-gql/{tenantId}/schema.graphql` | Download GraphQL schema |

#### Identity API (End-User Authentication)

If your schema has an **identity entity** (`isIdentity: true`), these endpoints are automatically enabled for your application's end-users:

| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/api/v1/identity/{tenantId}/register` | User registration |
| `POST` | `/api/v1/identity/{tenantId}/login` | User authentication |
| `GET` | `/api/v1/identity/{tenantId}/me` | Get authenticated user profile |
| `POST` | `/api/v1/identity/{tenantId}/verify` | Verify email |
| `POST` | `/api/v1/identity/{tenantId}/forgot-password` | Request password reset |
| `POST` | `/api/v1/identity/{tenantId}/reset-password` | Reset password |
| `POST` | `/api/v1/identity/{tenantId}/refresh` | Refresh access token |
| `POST` | `/api/v1/identity/{tenantId}/logout` | Revoke refresh token |

**Important - Dual-Access Pattern**: Identity entities are accessible via BOTH:
- **Identity API** (`/api/v1/identity/...`) - For end-user self-service (registration, login)
- **Dynamic API** (`/api/v1/dynamic/Customer`) - For admin operations (list users, bulk updates)

**Creating an Identity Entity**:
```json
{
  "name": "Customer",
  "isIdentity": true,
  "identifierField": "email",
  "fields": [
    { "name": "email", "type": "STRING", "required": true },
    { "name": "password", "type": "PASSWORD", "required": true },
    { "name": "role", "type": "STRING", "required": false }
  ]
}
```

The `PASSWORD` field type is automatically hashed on write and redacted in all API responses.

### Query DSL

FLXBL supports a powerful JSON-based Query DSL for complex graph queries:

```json
{
  "where": {
    "age": { "$gte": 18 },
    "status": { "$eq": "active" }
  },
  "orderBy": "createdAt",
  "orderDirection": "DESC",
  "limit": 10
}
```

**Available Operators:**
- Comparison: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`
- Array: `$contains`
- Logical: `$and`, `$or`

**Graph Traversal for Filtering** (related data NOT in response):
```json
{
  "where": { "name": { "$eq": "Alice" } },
  "traverse": [{
    "relationship": "WROTE",
    "direction": "out",
    "where": { "published": { "$eq": true } }
  }]
}
```

**Graph Traversal for Projection** (related data IS included in response):
```json
{
  "where": { "name": { "$eq": "Alice" } },
  "traverse": [{
    "relationship": "WROTE",
    "direction": "out",
    "include": true,
    "limit": 10,
    "orderBy": "createdAt",
    "orderDirection": "DESC"
  }]
}
```
Response: `[{ id, name, ..., WROTE: [{ id, title, published, ... }, ...] }]`

### Important: DELETE Requests

**Do NOT include `Content-Type: application/json` header for DELETE requests without a body.** This will cause a 400 error.

### Example: TypeScript/React

**Important:** Entity names in URLs are **case-sensitive**. Use the exact entity name from your schema (e.g., `List` not `lists`).

```typescript
const API_BASE = 'http://localhost:3001/api/v1/dynamic';
const API_KEY = 'your_api_key_here';

// Headers for requests WITH body
const postHeaders = {
  'Authorization': `Bearer ${API_KEY}`,
  'Content-Type': 'application/json',
};

// Headers for requests WITHOUT body (GET, DELETE)
const getHeaders = {
  'Authorization': `Bearer ${API_KEY}`,
};

// Fetch all items from an entity (note: entity name is case-sensitive!)
async function getLists(): Promise<List[]> {
  const response = await fetch(`${API_BASE}/List`, {
    headers: getHeaders,  // No Content-Type for GET
  });
  
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

// Create a new item
async function createList(data: CreateListDto): Promise<List> {
  const response = await fetch(`${API_BASE}/List`, {
    method: 'POST',
    headers: postHeaders,
    body: JSON.stringify(data),
  });
  
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

// Partial update (PATCH)
async function updateListField(id: string, field: string, value: unknown): Promise<List> {
  const response = await fetch(`${API_BASE}/List/${id}`, {
    method: 'PATCH',
    headers: postHeaders,
    body: JSON.stringify({ [field]: value }),
  });
  
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

// DELETE - Important: No Content-Type header!
async function deleteList(id: string): Promise<void> {
  const response = await fetch(`${API_BASE}/List/${id}`, {
    method: 'DELETE',
    headers: getHeaders,  // No Content-Type for DELETE without body!
  });
  
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
}

// Query with filters
async function findActiveLists(): Promise<List[]> {
  const response = await fetch(`${API_BASE}/List/query`, {
    method: 'POST',
    headers: postHeaders,
    body: JSON.stringify({
      where: { status: { $eq: 'active' } },
      orderBy: 'createdAt',
      orderDirection: 'DESC',
      limit: 10
    }),
  });
  
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}
```

### Example: Python

**Important:** Entity names in URLs are **case-sensitive**. Use the exact entity name from your schema.

```python
import requests

API_BASE = 'http://localhost:3001/api/v1/dynamic'
API_KEY = 'your_api_key_here'

# Headers for requests WITH body
post_headers = {
    'Authorization': f'Bearer {API_KEY}',
    'Content-Type': 'application/json'
}

# Headers for requests WITHOUT body (GET, DELETE)
get_headers = {
    'Authorization': f'Bearer {API_KEY}'
}

# Fetch all items (note: entity name is case-sensitive!)
response = requests.get(f'{API_BASE}/List', headers=get_headers)
items = response.json()

# Create a new item
new_item = {'name': 'My List', 'description': 'A test list'}
response = requests.post(f'{API_BASE}/List', headers=post_headers, json=new_item)
created = response.json()

# Partial update (PATCH)
updates = {'name': 'Updated Name'}
response = requests.patch(f'{API_BASE}/List/{id}', headers=post_headers, json=updates)

# DELETE - Important: No Content-Type header!
response = requests.delete(f'{API_BASE}/List/{id}', headers=get_headers)

# Query with filters
query = {
    'where': {'status': {'$eq': 'active'}},
    'orderBy': 'createdAt',
    'orderDirection': 'DESC',
    'limit': 10
}
response = requests.post(f'{API_BASE}/List/query', headers=post_headers, json=query)
results = response.json()

# Create a relationship
rel_data = {'targetId': 'node_target_id', 'properties': {'role': 'owner'}}
response = requests.post(
    f'{API_BASE}/List/{id}/relationships/BELONGS_TO',
    headers=post_headers,
    json=rel_data
)
```

### Zod Format Includes Full API Client

When you generate types with `format: zod`, the output includes a comprehensive type-safe API client:

```typescript
import { createFlxblClient } from './schemas';

const client = createFlxblClient({
  baseUrl: 'http://localhost:3001',
  apiKey: 'your_api_key_here'
});

// Entity CRUD Operations (entity names are case-sensitive!)
const lists = await client.list('List', { limit: 10, orderBy: 'createdAt' });
const list = await client.get('List', 'node_123');
const newList = await client.create('List', { name: 'My List' });
const updated = await client.update('List', 'node_123', { name: 'Updated' });
const patched = await client.patch('List', 'node_123', { status: 'active' });
await client.delete('List', 'node_123');

// Query DSL with type safety
const results = await client.query('List', {
  where: { status: { $eq: 'active' } },
  orderBy: 'createdAt',
  orderDirection: 'DESC',
  limit: 10
});

// Graph traversal for FILTERING (find users who wrote published posts)
const filteredUsers = await client.query('User', {
  where: { name: { $eq: 'Alice' } },
  traverse: [{
    relationship: 'WROTE',
    direction: 'out',
    where: { published: { $eq: true } }
  }]
});

// Graph traversal for PROJECTION (find users AND include their posts)
const usersWithPosts = await client.query('User', {
  where: { name: { $eq: 'Alice' } },
  traverse: [{
    relationship: 'WROTE',
    direction: 'out',
    include: true,  // Include related posts in response
    limit: 10,
    orderBy: 'createdAt',
    orderDirection: 'DESC'
  }]
});
// Response: [{ id, name, WROTE: [{ id, title, published, ... }, ...] }]

// Relationship operations
await client.createRelationship('User', 'user_id', 'MEMBER_OF', {
  targetId: 'group_id',
  properties: { role: 'admin', joinedAt: new Date().toISOString() }
});

const relationships = await client.getRelationships('User', 'user_id', 'MEMBER_OF', 'out');

await client.updateRelationship('User', 'user_id', 'MEMBER_OF', 'group_id', {
  role: 'owner'
});

await client.deleteRelationship('User', 'user_id', 'MEMBER_OF', 'group_id');
```

### Language-Agnostic Schema Format

For generating code in languages other than TypeScript or Python, use the `schema` format:

```
Generate API contract schema for my FLXBL schema
```

This returns a comprehensive JSON structure that can be used to generate code in any language, including:
- Complete entity and field definitions
- All REST endpoint specifications
- Query DSL documentation with examples
- Type mappings for TypeScript, Python, Go, and Rust

## Team Workflow

When working with a team, each team member should have their own API key for IDE integration. This ensures proper audit trails and allows granular permission control.

### Setting Up API Keys for Team Members

1. **Tenant Admin Creates API Keys**:
   - Log in to the FLXBL dashboard as an admin
   - Navigate to **Settings** → **API Keys**
   - Create a dedicated API key for each team member

2. **Recommended Scope Configuration by Role**:

   | Role | Recommended Scopes | Description |
   |------|-------------------|-------------|
   | **Developer** | `entity:*:read`, `entity:*:create`, `entity:*:update` | Full read access, create and update data |
   | **Designer** | `entity:*:read`, `relationship:*:read` | Read-only access to explore schema |
   | **Admin** | `entity:*:*`, `relationship:*:*` | Full access to all operations |
   | **CI/CD** | Specific entity scopes only | Minimal scopes for automation |

3. **Team Members Configure Their IDE**:
   - Each team member adds the MCP configuration to their IDE with their personal API key
   - Never share API keys between team members

### API Key Scopes and Schema Evolution

When you publish a new schema version that removes or renames entities/relationships, existing API keys may have "stale" scopes that reference the old schema:

- **Detection**: The API Keys page shows a warning badge next to keys with stale scopes
- **Impact**: Stale scopes don't grant access to anything—they're simply ignored
- **Resolution**: Update the API key scopes to match the new schema

**Best Practice**: Use wildcard scopes (e.g., `entity:*:read`) when possible. Wildcards automatically adapt to schema changes.

### Permission Implications

The MCP server operates with the permissions of the API key used:

| Operation | Required Permission |
|-----------|-------------------|
| Read schema (`flxbl://schema/active`) | Any valid API key |
| Validate schema (`validate_schema`) | Any valid API key |
| Publish schema (`publish_schema_version`) | `entity:*:create` or admin role |
| Generate types (`generate_client_types`) | Any valid API key |
| Get API specs (`get_api_spec`, `get_graphql_schema`) | Any valid API key |
| Manage access keys (`manage_access_keys`) | Admin role only |
| Configure identity (`configure_identity`) | Admin role only |

### Best Practices for MCP Key Scopes

1. **Principle of Least Privilege**: Only grant the scopes team members need
2. **Use Descriptive Names**: Name keys by team member and purpose (e.g., "alice-dev-macbook")
3. **Set Expiration**: Use expiration dates for contractor or temporary access
4. **Regular Audits**: Review API keys periodically and revoke unused ones
5. **Wildcard Scopes**: Prefer wildcards for developers who need to work across the whole schema

## Security

- **API Key Isolation**: Your API key only has access to your tenant's data
- **No Database Access**: The MCP server communicates only via FLXBL's public API
- **Key Redaction**: API keys are automatically redacted in logs
- **Read-Only Resources**: Resources only read data; mutations require explicit tool calls

## Troubleshooting

### "Configuration Error: Missing required environment variables"

Ensure both `FLXBL_INSTANCE_URL` and `FLXBL_API_KEY` are set in your MCP configuration.

### "Invalid or expired API key"

Your API key may have expired or been revoked. Generate a new one from the FLXBL dashboard.

### "Could not verify FLXBL connectivity"

Check that:
1. Your `FLXBL_INSTANCE_URL` is correct
2. Your network can reach the FLXBL API
3. The FLXBL service is operational

### MCP Server Not Starting

Check the logs in your IDE's MCP output panel. Common issues:
- Node.js version too old (requires 18+)
- npm/npx not in PATH
- Missing environment variables

## Development

### Building from Source

```bash
cd mcp
npm install
npm run build
```

### Running Locally

```bash
FLXBL_INSTANCE_URL=http://localhost:3000 \
FLXBL_API_KEY=your-dev-key \
npm start
```

### Type Checking

```bash
npm run typecheck
```

## License

MIT

## Support

- GitHub Issues: [flxbl-monolith/issues](https://github.com/flxbl/flxbl-monolith/issues)
- Documentation: [docs.flxbl.dev](https://docs.flxbl.dev)