This commit is contained in:
ananta-tamboli 2026-01-30 11:55:39 -04:00 committed by GitHub
commit d98a78e62e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 2331 additions and 12 deletions

254
docs/plugins/cursor-mcp.md Normal file
View File

@ -0,0 +1,254 @@
---
title: Cursor IDE Integration
description: Use OpenClaw as an AI backend in Cursor IDE via MCP
---
# Cursor IDE Integration
OpenClaw provides a Model Context Protocol (MCP) server that integrates with [Cursor IDE](https://cursor.com), enabling you to use OpenClaw's AI capabilities directly in Cursor's Composer Agent.
## Overview
The Cursor MCP integration allows you to:
- **Chat with OpenClaw**: Use OpenClaw's AI agent directly in Cursor
- **Manage Sessions**: Create, list, and manage conversation sessions
- **Send Messages**: Route messages through WhatsApp, Telegram, Discord, and more
- **Access Models**: Use any AI model configured in OpenClaw
- **Code Assistance**: Built-in prompts for code review, debugging, and testing
## Quick Setup
### Prerequisites
1. [Install OpenClaw](/install)
2. Start the OpenClaw gateway:
```bash
openclaw gateway run
```
3. Install [Cursor IDE](https://cursor.com)
### Configure Cursor
#### Option 1: Cursor Settings UI
1. Open **Cursor Settings****Features** → **MCP**
2. Click **"+ Add New MCP Server"**
3. Configure:
- **Name**: `openclaw`
- **Type**: `stdio`
- **Command**: `openclaw`
- **Arguments**: `mcp serve`
#### Option 2: Manual Configuration
Create or edit `~/.cursor/mcp.json`:
```json
{
"mcpServers": {
"openclaw": {
"command": "openclaw",
"args": ["mcp", "serve"],
"env": {
"OPENCLAW_GATEWAY_URL": "ws://127.0.0.1:18789"
}
}
}
}
```
### Authentication
If your gateway requires authentication:
```json
{
"mcpServers": {
"openclaw": {
"command": "openclaw",
"args": ["mcp", "serve"],
"env": {
"OPENCLAW_GATEWAY_URL": "ws://127.0.0.1:18789",
"OPENCLAW_GATEWAY_TOKEN": "your-token-here",
"OPENCLAW_GATEWAY_PASSWORD": "your-password-here"
}
}
}
}
```
## Available Tools
The MCP server exposes these tools to Cursor:
| Tool | Description |
|------|-------------|
| `openclaw_chat` | Chat with the OpenClaw AI agent |
| `openclaw_list_sessions` | List all active chat sessions |
| `openclaw_get_session` | Get details about a specific session |
| `openclaw_clear_session` | Clear a session's conversation history |
| `openclaw_execute_command` | Execute OpenClaw control commands |
| `openclaw_send_message` | Send messages through channels |
| `openclaw_get_status` | Get gateway and channel status |
| `openclaw_list_models` | List available AI models |
### Tool Examples
#### Chat with OpenClaw
```
User: Ask OpenClaw to explain this Python code
Cursor Agent: [Uses openclaw_chat tool]
```
#### Send a Message
```
User: Send "Build completed" to my Telegram channel
Cursor Agent: [Uses openclaw_send_message tool]
```
## Available Resources
Access OpenClaw data via MCP resources:
| URI | Description |
|-----|-------------|
| `openclaw://status` | Gateway and channel status |
| `openclaw://models` | Available AI models |
| `openclaw://sessions` | Active chat sessions |
| `openclaw://config` | Current configuration (sanitized) |
## Available Prompts
Built-in prompts for common development tasks:
| Prompt | Description |
|--------|-------------|
| `code_review` | Review code for issues and improvements |
| `explain_code` | Explain how code works |
| `generate_tests` | Generate tests for code |
| `refactor_code` | Suggest refactoring improvements |
| `debug_help` | Help debug issues |
| `send_notification` | Send notification via channels |
## CLI Commands
```bash
# Start MCP server manually (usually done by Cursor)
openclaw mcp serve
# Show configuration help
openclaw mcp info
# Custom options
openclaw mcp serve --url ws://localhost:18789 --session agent:main:cursor
```
## Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `OPENCLAW_GATEWAY_URL` | Gateway WebSocket URL | `ws://127.0.0.1:18789` |
| `OPENCLAW_GATEWAY_TOKEN` | Authentication token | - |
| `OPENCLAW_GATEWAY_PASSWORD` | Authentication password | - |
| `OPENCLAW_SESSION_KEY` | Default session key | `agent:main:cursor` |
## Architecture
```
┌─────────────────┐ MCP Protocol ┌──────────────────┐
│ Cursor IDE │◄───────────────────►│ OpenClaw MCP │
│ (MCP Client) │ (stdio) │ Server │
└─────────────────┘ └────────┬─────────┘
│ WebSocket
┌──────────────────┐
│ OpenClaw │
│ Gateway │
└────────┬─────────┘
┌─────────────────────────────┼─────────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ AI Models │ │ Channels │ │ Sessions │
│ (Anthropic, │ │ (WhatsApp, │ │ │
│ OpenAI...) │ │ Telegram...) │ │ │
└───────────────┘ └───────────────┘ └───────────────┘
```
## Troubleshooting
### Gateway Connection Failed
1. Ensure the OpenClaw gateway is running:
```bash
openclaw gateway run
```
2. Check the gateway URL in your configuration
3. Verify authentication credentials if required
### Tools Not Appearing
1. Restart Cursor after adding the MCP server
2. Check Cursor's MCP logs for errors
3. Ensure `openclaw` is in your system PATH
### Session Issues
Clear and restart a session using the `openclaw_clear_session` tool or:
```bash
openclaw sessions clear agent:main:cursor
```
## Using Cursor's Models in OpenClaw
The integration is bidirectional - you can also use Cursor's AI models (Claude, GPT-4, etc.) as providers for OpenClaw.
### Setup
1. **Install Copilot Proxy extension** in Cursor (search for "Copilot Proxy" by AdrianGonz97)
2. **Check the proxy**:
```bash
openclaw mcp setup-models --check
```
3. **Configure OpenClaw**:
```bash
openclaw config set agents.defaults.model cursor/claude-sonnet-4
```
### Available Models
| Model | ID |
|-------|-----|
| Claude Sonnet 4 | `cursor/claude-sonnet-4` |
| Claude Sonnet 4 (Thinking) | `cursor/claude-sonnet-4-thinking` |
| GPT-4o | `cursor/gpt-4o` |
| GPT-4o Mini | `cursor/gpt-4o-mini` |
| o1 | `cursor/o1` |
| Gemini 2.5 Pro | `cursor/gemini-2.5-pro` |
### Usage
```bash
# Use Cursor's Claude in OpenClaw
openclaw agent --model cursor/claude-sonnet-4 "Help me debug this"
# In the TUI
openclaw tui --model cursor/gpt-4o
```
## See Also
- [Gateway Configuration](/gateway/configuration)
- [Model Providers](/concepts/model-providers)
- [Sessions](/concepts/sessions)
- [Messaging Channels](/channels)

View File

@ -0,0 +1,33 @@
# Changelog
## 2026.1.29
Initial release of OpenClaw Cursor MCP integration.
### Features
- **MCP Server**: Full Model Context Protocol server implementation for Cursor IDE
- **Tools**:
- `openclaw_chat`: Chat with OpenClaw AI agent
- `openclaw_list_sessions`: List active sessions
- `openclaw_get_session`: Get session details
- `openclaw_clear_session`: Clear session history
- `openclaw_execute_command`: Execute OpenClaw commands
- `openclaw_send_message`: Send messages through channels
- `openclaw_get_status`: Get gateway status
- `openclaw_list_models`: List available models
- **Resources**:
- `openclaw://status`: Gateway status
- `openclaw://models`: Available models
- `openclaw://sessions`: Active sessions
- `openclaw://config`: Configuration (sanitized)
- **Prompts**:
- `code_review`: Code review assistance
- `explain_code`: Code explanation
- `generate_tests`: Test generation
- `refactor_code`: Refactoring suggestions
- `debug_help`: Debugging assistance
- `send_notification`: Channel notifications
- **CLI Commands**:
- `openclaw mcp serve`: Start MCP server
- `openclaw mcp info`: Show configuration help

View File

@ -0,0 +1,286 @@
# OpenClaw Cursor MCP Integration
This extension provides Model Context Protocol (MCP) server integration for [Cursor IDE](https://cursor.com), enabling OpenClaw as an AI backend within Cursor's Composer Agent.
## Features
- **Chat with OpenClaw Agent**: Use OpenClaw's AI capabilities directly in Cursor
- **Session Management**: Create, list, and manage conversation sessions
- **Multi-Channel Messaging**: Send messages through WhatsApp, Telegram, Discord, Slack, and more
- **Model Selection**: Choose from any AI model configured in OpenClaw
- **Code Assistance Prompts**: Built-in prompts for code review, debugging, test generation, and more
## Quick Start
### Prerequisites
1. OpenClaw installed and gateway running:
```bash
npm install -g openclaw
openclaw gateway run
```
2. Cursor IDE installed
### Setup in Cursor
#### Option 1: Cursor Settings UI
1. Open **Cursor Settings****Features** → **MCP**
2. Click **"+ Add New MCP Server"**
3. Configure:
- **Name**: `openclaw`
- **Type**: `stdio`
- **Command**: `openclaw`
- **Arguments**: `mcp serve`
#### Option 2: Manual Configuration
Create or edit `~/.cursor/mcp.json`:
```json
{
"mcpServers": {
"openclaw": {
"command": "openclaw",
"args": ["mcp", "serve"],
"env": {
"OPENCLAW_GATEWAY_URL": "ws://127.0.0.1:18789"
}
}
}
}
```
### Authentication (Optional)
If your gateway requires authentication, add credentials to the environment:
```json
{
"mcpServers": {
"openclaw": {
"command": "openclaw",
"args": ["mcp", "serve"],
"env": {
"OPENCLAW_GATEWAY_URL": "ws://127.0.0.1:18789",
"OPENCLAW_GATEWAY_TOKEN": "your-token-here",
"OPENCLAW_GATEWAY_PASSWORD": "your-password-here"
}
}
}
}
```
## Available Tools
| Tool | Description |
|------|-------------|
| `openclaw_chat` | Chat with the OpenClaw AI agent |
| `openclaw_list_sessions` | List all active chat sessions |
| `openclaw_get_session` | Get details about a specific session |
| `openclaw_clear_session` | Clear a session's conversation history |
| `openclaw_execute_command` | Execute OpenClaw control commands |
| `openclaw_send_message` | Send messages through channels |
| `openclaw_get_status` | Get gateway and channel status |
| `openclaw_list_models` | List available AI models |
## Available Resources
| URI | Description |
|-----|-------------|
| `openclaw://status` | Gateway and channel status |
| `openclaw://models` | Available AI models |
| `openclaw://sessions` | Active chat sessions |
| `openclaw://config` | Current configuration (sanitized) |
## Available Prompts
| Prompt | Description |
|--------|-------------|
| `code_review` | Review code for issues and improvements |
| `explain_code` | Explain how code works |
| `generate_tests` | Generate tests for code |
| `refactor_code` | Suggest refactoring improvements |
| `debug_help` | Help debug issues |
| `send_notification` | Send notification via channels |
## Usage Examples
### Chat with OpenClaw
In Cursor's Composer, the Agent can use OpenClaw tools:
```
User: Use openclaw to help me debug this Python code
Agent: [Uses openclaw_chat tool to send your code to OpenClaw]
```
### Send a Message
```
User: Send "Build completed successfully" to my Telegram
Agent: [Uses openclaw_send_message with target and channel]
```
### Check Status
```
User: What's the status of my OpenClaw channels?
Agent: [Uses openclaw_get_status tool]
```
## CLI Commands
```bash
# Start MCP server (usually done automatically by Cursor)
openclaw mcp serve
# Show configuration help
openclaw mcp info
# With custom options
openclaw mcp serve --url ws://localhost:18789 --session agent:main:cursor
```
## Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `OPENCLAW_GATEWAY_URL` | Gateway WebSocket URL | `ws://127.0.0.1:18789` |
| `OPENCLAW_GATEWAY_TOKEN` | Authentication token | - |
| `OPENCLAW_GATEWAY_PASSWORD` | Authentication password | - |
| `OPENCLAW_SESSION_KEY` | Default session key | `agent:main:cursor` |
## Troubleshooting
### Gateway Connection Failed
1. Ensure the OpenClaw gateway is running:
```bash
openclaw gateway run
```
2. Check the gateway URL in your configuration
3. Verify authentication credentials if required
### Tools Not Appearing in Cursor
1. Restart Cursor after adding the MCP server
2. Check Cursor's MCP logs for errors
3. Ensure `openclaw` is in your PATH
### Session Issues
Clear and restart a session:
```
User: Clear my OpenClaw session
Agent: [Uses openclaw_clear_session tool]
```
## Development
```bash
# Install dependencies
cd extensions/cursor-mcp
pnpm install
# Build
pnpm build
# Test locally
node bin/server.js
```
## Architecture
```
┌─────────────────┐ MCP Protocol ┌──────────────────┐
│ Cursor IDE │◄───────────────────►│ OpenClaw MCP │
│ (MCP Client) │ (stdio) │ Server │
└─────────────────┘ └────────┬─────────┘
│ WebSocket
┌──────────────────┐
│ OpenClaw │
│ Gateway │
└────────┬─────────┘
┌─────────────────────────────┼─────────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ WhatsApp │ │ Telegram │ │ Discord │
└───────────────┘ └───────────────┘ └───────────────┘
```
## Using Cursor's Models with OpenClaw
You can also use Cursor's AI models (Claude, GPT-4, etc.) as providers for OpenClaw. This enables bidirectional integration:
- **OpenClaw → Cursor**: Use OpenClaw tools in Cursor (MCP server)
- **Cursor → OpenClaw**: Use Cursor's models in OpenClaw
### Setup Cursor Models
1. **Install Copilot Proxy extension** in Cursor:
- Search for "Copilot Proxy" by AdrianGonz97 in Extensions
- Install and restart Cursor
2. **Check the proxy is running**:
```bash
openclaw mcp setup-models --check
```
3. **Configure OpenClaw** to use Cursor models:
```bash
# Set a Cursor model as default
openclaw config set agents.defaults.model cursor/claude-sonnet-4
```
Or manually add to `~/.clawdbot/config.yaml`:
```yaml
models:
providers:
cursor:
baseUrl: "http://localhost:3000/v1"
apiKey: "cursor-proxy"
api: openai-completions
models:
- id: claude-sonnet-4
name: Claude Sonnet 4
contextWindow: 200000
```
### Available Cursor Models
| Model ID | Description |
|----------|-------------|
| `cursor/claude-sonnet-4` | Claude Sonnet 4 |
| `cursor/claude-sonnet-4-thinking` | Claude Sonnet 4 with extended thinking |
| `cursor/gpt-4o` | GPT-4o |
| `cursor/gpt-4o-mini` | GPT-4o Mini |
| `cursor/o1` | OpenAI o1 (reasoning) |
| `cursor/o1-mini` | OpenAI o1-mini |
| `cursor/gemini-2.5-pro` | Gemini 2.5 Pro |
> **Note**: Available models depend on your Cursor subscription tier.
### Usage Examples
```bash
# Chat using Cursor's Claude
openclaw agent --model cursor/claude-sonnet-4 "Explain this code"
# Send message via channels using GPT-4
openclaw message send --model cursor/gpt-4o "Hello from OpenClaw!"
# Use in TUI
openclaw tui --model cursor/claude-sonnet-4
```
## License
MIT - Part of the OpenClaw project.

View File

@ -0,0 +1,204 @@
/**
* OpenClaw Cursor MCP Plugin
*
* This plugin integrates OpenClaw with Cursor IDE through the Model Context Protocol (MCP).
* It allows Cursor to use OpenClaw as an AI backend and provides tools for managing
* conversations, sessions, and messaging channels.
*
* Usage in Cursor:
* 1. Add to Cursor's MCP configuration (~/.cursor/mcp.json or Cursor Settings > Features > MCP)
* 2. Use the openclaw_* tools in Cursor's Composer Agent
*
* Configuration example for mcp.json:
* {
* "mcpServers": {
* "openclaw": {
* "command": "openclaw",
* "args": ["mcp", "serve"],
* "env": {
* "OPENCLAW_GATEWAY_URL": "ws://127.0.0.1:18789",
* "OPENCLAW_GATEWAY_TOKEN": "your-token"
* }
* }
* }
* }
*/
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
export type CursorMcpPluginConfig = {
enabled?: boolean;
port?: number;
autoApproveTools?: string[];
};
const cursorMcpPlugin = {
id: "cursor-mcp",
name: "Cursor MCP Server",
description: "MCP server integration for Cursor IDE - enables OpenClaw as an AI agent in Cursor",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
// Register the CLI command for starting the MCP server
api.registerCli(
async (ctx) => {
const mcpCommand = ctx.program
.command("mcp")
.description("MCP server for IDE integration");
mcpCommand
.command("serve")
.description("Start the MCP server for Cursor IDE integration")
.option("--url <url>", "Gateway WebSocket URL", "ws://127.0.0.1:18789")
.option("--token <token>", "Gateway auth token")
.option("--password <password>", "Gateway auth password")
.option("--session <key>", "Default session key", "agent:main:cursor")
.action(async (opts) => {
// Dynamic import to avoid loading MCP SDK unless needed
const { OpenClawMcpServer } = await import("./src/server.js");
const server = new OpenClawMcpServer({
gatewayUrl: opts.url,
gatewayToken: opts.token,
gatewayPassword: opts.password,
defaultSessionKey: opts.session,
});
process.on("SIGINT", async () => {
await server.stop();
process.exit(0);
});
process.on("SIGTERM", async () => {
await server.stop();
process.exit(0);
});
await server.start();
});
// Command to set up Cursor models as a provider
mcpCommand
.command("setup-models")
.description("Configure OpenClaw to use Cursor's AI models via Copilot Proxy")
.option("--url <url>", "Copilot Proxy base URL", "http://localhost:3000/v1")
.option("--check", "Only check if proxy is running, don't configure")
.action(async (opts) => {
const {
checkCursorProxyHealth,
CURSOR_AVAILABLE_MODELS,
CURSOR_SETUP_INSTRUCTIONS,
} = await import("./src/cursor-models.js");
console.log("Checking Cursor Copilot Proxy...");
const health = await checkCursorProxyHealth(opts.url);
if (!health.ok) {
console.error(`\n❌ Copilot Proxy not accessible: ${health.error}`);
console.log(CURSOR_SETUP_INSTRUCTIONS);
process.exit(1);
}
console.log("✓ Copilot Proxy is running");
if (opts.check) {
console.log("\nAvailable models:");
for (const model of CURSOR_AVAILABLE_MODELS) {
console.log(` - cursor/${model.id} (${model.name})`);
}
return;
}
// Show configuration instructions
console.log("\nTo use Cursor models, add this to your OpenClaw config:\n");
console.log("```yaml");
console.log("models:");
console.log(" providers:");
console.log(" cursor:");
console.log(` baseUrl: "${opts.url}"`);
console.log(' apiKey: "cursor-proxy"');
console.log(" api: openai-completions");
console.log(" authHeader: false");
console.log(" models:");
for (const model of CURSOR_AVAILABLE_MODELS.slice(0, 4)) {
console.log(` - id: ${model.id}`);
console.log(` name: ${model.name}`);
console.log(` contextWindow: ${model.contextWindow}`);
}
console.log("```");
console.log("\nOr run: openclaw config set agents.defaults.model cursor/claude-sonnet-4");
console.log("\nAvailable models:");
for (const model of CURSOR_AVAILABLE_MODELS) {
console.log(` - cursor/${model.id}`);
}
});
mcpCommand
.command("info")
.description("Show MCP server configuration information for Cursor")
.action(() => {
console.log(`
OpenClaw MCP Server - Cursor IDE Integration
To use OpenClaw in Cursor, add the following to your Cursor MCP configuration:
1. Open Cursor Settings > Features > MCP
2. Click "+ Add New MCP Server"
3. Configure:
- Name: openclaw
- Type: stdio
- Command: openclaw mcp serve
Or manually edit ~/.cursor/mcp.json:
{
"mcpServers": {
"openclaw": {
"command": "openclaw",
"args": ["mcp", "serve"],
"env": {
"OPENCLAW_GATEWAY_URL": "ws://127.0.0.1:18789"
}
}
}
}
Environment Variables:
- OPENCLAW_GATEWAY_URL: Gateway WebSocket URL (default: ws://127.0.0.1:18789)
- OPENCLAW_GATEWAY_TOKEN: Authentication token
- OPENCLAW_GATEWAY_PASSWORD: Authentication password
- OPENCLAW_SESSION_KEY: Default session key (default: agent:main:cursor)
Available Tools:
- openclaw_chat: Chat with the OpenClaw AI agent
- openclaw_list_sessions: List active sessions
- openclaw_get_session: Get session details
- openclaw_clear_session: Clear session history
- openclaw_execute_command: Execute OpenClaw commands
- openclaw_send_message: Send messages through channels
- openclaw_get_status: Get gateway status
- openclaw_list_models: List available models
Available Resources:
- openclaw://status: Gateway status
- openclaw://models: Available models
- openclaw://sessions: Active sessions
- openclaw://config: Configuration (sanitized)
Available Prompts:
- code_review: Review code for issues
- explain_code: Explain how code works
- generate_tests: Generate tests
- refactor_code: Suggest refactoring
- debug_help: Help debug issues
- send_notification: Send notification via channels
`);
});
},
{ commands: ["mcp"] },
);
},
};
export default cursorMcpPlugin;

View File

@ -0,0 +1,25 @@
{
"id": "cursor-mcp",
"name": "Cursor MCP Server",
"description": "MCP server integration for Cursor IDE - enables OpenClaw as an AI agent in Cursor",
"providers": [],
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean",
"description": "Enable the Cursor MCP server"
},
"port": {
"type": "number",
"description": "Port for SSE transport (optional, uses stdio by default)"
},
"autoApproveTools": {
"type": "array",
"items": { "type": "string" },
"description": "List of tool names to auto-approve without user confirmation"
}
}
}
}

View File

@ -0,0 +1,17 @@
{
"name": "@openclaw/cursor-mcp",
"version": "2026.1.29",
"type": "module",
"description": "OpenClaw MCP server for Cursor IDE integration",
"openclaw": {
"extensions": [
"./index.ts"
]
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.25.3"
},
"devDependencies": {
"openclaw": "workspace:*"
}
}

View File

@ -0,0 +1,233 @@
/**
* Cursor Model Provider for OpenClaw
*
* This module provides integration with Cursor's Language Model API,
* allowing OpenClaw to use models available through Cursor's subscription
* (Claude, GPT-4, etc.).
*
* How it works:
* 1. Cursor exposes models via a local HTTP API when the Copilot Proxy extension is active
* 2. OpenClaw connects to this API as an OpenAI-compatible endpoint
* 3. You can then use Cursor's models in OpenClaw prompts
*
* Setup:
* 1. Install "Copilot Proxy" extension in Cursor
* 2. Run `openclaw setup cursor` to configure
* 3. Use models like `cursor/claude-sonnet-4` in OpenClaw
*/
import type { CursorMcpConfig } from "./types.js";
// Default Cursor proxy configuration
const DEFAULT_CURSOR_PROXY_URL = "http://localhost:3000/v1";
const DEFAULT_CURSOR_PROXY_PORT = 3000;
// Models typically available through Cursor
export const CURSOR_AVAILABLE_MODELS = [
{
id: "claude-sonnet-4",
name: "Claude Sonnet 4",
provider: "anthropic",
contextWindow: 200000,
reasoning: false,
},
{
id: "claude-sonnet-4-thinking",
name: "Claude Sonnet 4 (Thinking)",
provider: "anthropic",
contextWindow: 200000,
reasoning: true,
},
{
id: "gpt-4o",
name: "GPT-4o",
provider: "openai",
contextWindow: 128000,
reasoning: false,
},
{
id: "gpt-4o-mini",
name: "GPT-4o Mini",
provider: "openai",
contextWindow: 128000,
reasoning: false,
},
{
id: "o1",
name: "o1",
provider: "openai",
contextWindow: 200000,
reasoning: true,
},
{
id: "o1-mini",
name: "o1-mini",
provider: "openai",
contextWindow: 128000,
reasoning: true,
},
{
id: "gemini-2.5-pro",
name: "Gemini 2.5 Pro",
provider: "google",
contextWindow: 1000000,
reasoning: false,
},
] as const;
export type CursorModelId = (typeof CURSOR_AVAILABLE_MODELS)[number]["id"];
export type CursorModelConfig = {
baseUrl: string;
models: string[];
};
/**
* Check if the Cursor proxy is running and accessible
*/
export async function checkCursorProxyHealth(
baseUrl: string = DEFAULT_CURSOR_PROXY_URL,
): Promise<{ ok: boolean; error?: string }> {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
const response = await fetch(`${baseUrl}/models`, {
method: "GET",
signal: controller.signal,
});
clearTimeout(timeout);
if (response.ok) {
return { ok: true };
}
return { ok: false, error: `HTTP ${response.status}: ${response.statusText}` };
} catch (err) {
if (err instanceof Error && err.name === "AbortError") {
return { ok: false, error: "Connection timeout - is Copilot Proxy running in Cursor?" };
}
return { ok: false, error: String(err) };
}
}
/**
* Generate OpenClaw configuration patch for Cursor models
*/
export function generateCursorProviderConfig(opts: {
baseUrl?: string;
models?: string[];
}): Record<string, unknown> {
const baseUrl = opts.baseUrl ?? DEFAULT_CURSOR_PROXY_URL;
const modelIds = opts.models ?? CURSOR_AVAILABLE_MODELS.map((m) => m.id);
return {
models: {
providers: {
cursor: {
baseUrl,
apiKey: "cursor-proxy", // Placeholder - Copilot Proxy handles auth
api: "openai-completions",
authHeader: false,
models: modelIds.map((id) => {
const model = CURSOR_AVAILABLE_MODELS.find((m) => m.id === id);
return {
id,
name: model?.name ?? id,
api: "openai-completions",
reasoning: model?.reasoning ?? false,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: model?.contextWindow ?? 128000,
maxTokens: 8192,
};
}),
},
},
},
agents: {
defaults: {
models: Object.fromEntries(modelIds.map((id) => [`cursor/${id}`, {}])),
},
},
};
}
/**
* Instructions for setting up Cursor model integration
*/
export const CURSOR_SETUP_INSTRUCTIONS = `
# Using Cursor's Models with OpenClaw
## Prerequisites
1. Cursor IDE with an active subscription (Pro/Business)
2. The "Copilot Proxy" VS Code extension installed in Cursor
## Setup Steps
### Step 1: Install Copilot Proxy Extension
In Cursor, go to Extensions and search for "Copilot Proxy" by AdrianGonz97.
Install it and restart Cursor.
### Step 2: Start the Proxy Server
The extension should start automatically. Verify it's running:
- Look for "Copilot Proxy" in the status bar
- Or check http://localhost:3000/v1/models in your browser
### Step 3: Configure OpenClaw
Run the setup command:
openclaw setup cursor
Or manually add to your config (~/.clawdbot/config.yaml):
models:
providers:
cursor:
baseUrl: "http://localhost:3000/v1"
apiKey: "cursor-proxy"
api: openai-completions
authHeader: false
models:
- id: claude-sonnet-4
name: Claude Sonnet 4
contextWindow: 200000
- id: gpt-4o
name: GPT-4o
contextWindow: 128000
### Step 4: Use Cursor Models
Now you can use Cursor's models in OpenClaw:
# Set as default model
openclaw config set agents.defaults.model cursor/claude-sonnet-4
# Use in a specific message
openclaw message send --model cursor/gpt-4o "Hello!"
# Use in the TUI
openclaw tui --model cursor/claude-sonnet-4
## Available Models (depends on your Cursor subscription)
- cursor/claude-sonnet-4
- cursor/claude-sonnet-4-thinking
- cursor/gpt-4o
- cursor/gpt-4o-mini
- cursor/o1
- cursor/o1-mini
- cursor/gemini-2.5-pro
## Troubleshooting
### Proxy not responding
- Ensure Cursor is running with the Copilot Proxy extension active
- Check if http://localhost:3000/v1/models returns a response
- Try restarting Cursor
### Model not available
- Your Cursor subscription may not include all models
- Check Cursor's model selector to see which models you have access to
### Authentication errors
- Ensure you're logged into Cursor with your subscription account
- The proxy uses your Cursor session for authentication
`;

View File

@ -0,0 +1,302 @@
/**
* Gateway client for MCP server
*
* This module provides a standalone WebSocket client for communicating
* with the OpenClaw gateway. It implements the gateway's JSON-RPC protocol.
*/
import { randomUUID } from "node:crypto";
import { WebSocket } from "ws";
import type { CursorMcpConfig } from "./types.js";
type RequestFrame = {
type: "req";
id: string;
method: string;
params?: unknown;
};
type ResponseFrame = {
type: "res";
id: string;
ok: boolean;
payload?: unknown;
error?: { message?: string };
};
type EventFrame = {
type: "evt";
event: string;
payload?: unknown;
seq?: number;
};
type Pending = {
resolve: (value: unknown) => void;
reject: (err: Error) => void;
};
const PROTOCOL_VERSION = 6;
export class McpGatewayClient {
private ws: WebSocket | null = null;
private connected = false;
private config: CursorMcpConfig;
private pending = new Map<string, Pending>();
private eventHandlers: Map<string, ((payload: unknown) => void)[]> = new Map();
private connectResolve: ((value: void) => void) | null = null;
private connectReject: ((err: Error) => void) | null = null;
private connectNonce: string | null = null;
private connectSent = false;
private connectTimer: ReturnType<typeof setTimeout> | null = null;
constructor(config: CursorMcpConfig) {
this.config = config;
}
async connect(): Promise<void> {
return new Promise((resolve, reject) => {
this.connectResolve = resolve;
this.connectReject = reject;
const gatewayUrl = this.config.gatewayUrl ?? "ws://127.0.0.1:18789";
try {
this.ws = new WebSocket(gatewayUrl, {
maxPayload: 25 * 1024 * 1024,
});
} catch (err) {
reject(err instanceof Error ? err : new Error(String(err)));
return;
}
this.ws.on("open", () => {
this.queueConnect();
});
this.ws.on("message", (data) => {
const raw = typeof data === "string" ? data : data.toString("utf8");
this.handleMessage(raw);
});
this.ws.on("close", (code, reason) => {
const reasonText = typeof reason === "string" ? reason : reason.toString("utf8");
this.ws = null;
this.connected = false;
this.flushPendingErrors(new Error(`Gateway closed (${code}): ${reasonText}`));
});
this.ws.on("error", (err) => {
if (!this.connectSent && this.connectReject) {
this.connectReject(err instanceof Error ? err : new Error(String(err)));
this.connectResolve = null;
this.connectReject = null;
}
});
});
}
disconnect(): void {
if (this.connectTimer) {
clearTimeout(this.connectTimer);
this.connectTimer = null;
}
if (this.ws) {
this.ws.close();
this.ws = null;
}
this.connected = false;
this.flushPendingErrors(new Error("Client disconnected"));
}
isConnected(): boolean {
return this.connected;
}
private queueConnect(): void {
this.connectNonce = null;
this.connectSent = false;
if (this.connectTimer) clearTimeout(this.connectTimer);
// Wait a bit for optional challenge before sending connect
this.connectTimer = setTimeout(() => {
this.sendConnect();
}, 500);
}
private sendConnect(): void {
if (this.connectSent) return;
this.connectSent = true;
if (this.connectTimer) {
clearTimeout(this.connectTimer);
this.connectTimer = null;
}
const params = {
minProtocol: PROTOCOL_VERSION,
maxProtocol: PROTOCOL_VERSION,
client: {
id: "mcp",
displayName: "Cursor MCP",
version: "2026.1.29",
platform: process.platform,
mode: "backend",
},
caps: [],
auth: {
token: this.config.gatewayToken,
password: this.config.gatewayPassword,
},
role: "operator",
scopes: ["operator.admin"],
};
this.request("connect", params)
.then(() => {
this.connected = true;
if (this.connectResolve) {
this.connectResolve();
this.connectResolve = null;
this.connectReject = null;
}
})
.catch((err) => {
if (this.connectReject) {
this.connectReject(err instanceof Error ? err : new Error(String(err)));
this.connectResolve = null;
this.connectReject = null;
}
this.ws?.close(1008, "connect failed");
});
}
private handleMessage(raw: string): void {
try {
const parsed = JSON.parse(raw);
// Handle events
if (parsed.type === "evt" || parsed.event) {
const evt = parsed as EventFrame;
// Handle connect challenge
if (evt.event === "connect.challenge") {
const payload = evt.payload as { nonce?: unknown } | undefined;
const nonce = payload && typeof payload.nonce === "string" ? payload.nonce : null;
if (nonce) {
this.connectNonce = nonce;
this.sendConnect();
}
return;
}
// Dispatch to event handlers
const handlers = this.eventHandlers.get(evt.event);
if (handlers) {
for (const handler of handlers) {
try {
handler(evt.payload);
} catch (err) {
console.error(`Event handler error for ${evt.event}:`, err);
}
}
}
return;
}
// Handle responses
if (parsed.type === "res" || (parsed.id && (parsed.ok !== undefined || parsed.error))) {
const res = parsed as ResponseFrame;
const pending = this.pending.get(res.id);
if (!pending) return;
this.pending.delete(res.id);
if (res.ok) {
pending.resolve(res.payload);
} else {
pending.reject(new Error(res.error?.message ?? "Unknown error"));
}
}
} catch (err) {
console.error(`Gateway message parse error: ${String(err)}`);
}
}
private flushPendingErrors(err: Error): void {
for (const [, p] of this.pending) {
p.reject(err);
}
this.pending.clear();
}
onEvent(eventName: string, handler: (payload: unknown) => void): () => void {
const handlers = this.eventHandlers.get(eventName) ?? [];
handlers.push(handler);
this.eventHandlers.set(eventName, handlers);
return () => {
const idx = handlers.indexOf(handler);
if (idx >= 0) handlers.splice(idx, 1);
};
}
async request<T = unknown>(method: string, params?: unknown): Promise<T> {
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
throw new Error("Gateway not connected");
}
const id = randomUUID();
const frame: RequestFrame = { type: "req", id, method, params };
return new Promise<T>((resolve, reject) => {
this.pending.set(id, {
resolve: (value) => resolve(value as T),
reject,
});
this.ws!.send(JSON.stringify(frame));
});
}
// Convenience methods for common operations
async chat(params: {
message: string;
sessionKey?: string;
model?: string;
deliver?: boolean;
}): Promise<unknown> {
return this.request("chat.run", {
message: params.message,
sessionKey: params.sessionKey ?? this.config.defaultSessionKey ?? "agent:main:cursor",
model: params.model,
deliver: params.deliver ?? false,
});
}
async listSessions(): Promise<unknown> {
return this.request("sessions.list", {});
}
async getSessionInfo(sessionKey: string): Promise<unknown> {
return this.request("sessions.get", { sessionKey });
}
async clearSession(sessionKey: string): Promise<unknown> {
return this.request("sessions.clear", { sessionKey });
}
async getChannelStatus(): Promise<unknown> {
return this.request("channels.status", {});
}
async getHealth(): Promise<unknown> {
return this.request("health", {});
}
async getModels(): Promise<unknown> {
return this.request("models.list", {});
}
async executeCommand(command: string): Promise<unknown> {
return this.request("command", { command });
}
}

View File

@ -0,0 +1,818 @@
#!/usr/bin/env node
/**
* OpenClaw MCP Server for Cursor IDE Integration
*
* This server exposes OpenClaw functionality as an MCP server that can be used
* within Cursor IDE. It provides tools for chatting with the OpenClaw agent,
* managing sessions, and executing commands.
*
* Transport modes:
* - stdio (default): For local process-based integration
* - sse: For HTTP-based SSE integration (set OPENCLAW_MCP_PORT)
*/
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
ListPromptsRequestSchema,
GetPromptRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import { McpGatewayClient } from "./gateway-client.js";
import type { CursorMcpConfig, McpToolResult } from "./types.js";
// Tool input schemas
const ChatSchema = z.object({
message: z.string().describe("The message to send to the OpenClaw agent"),
sessionKey: z
.string()
.optional()
.describe("Session key for conversation continuity (e.g., 'agent:main:cursor')"),
model: z.string().optional().describe("Model to use (e.g., 'anthropic/claude-sonnet-4')"),
});
const SessionKeySchema = z.object({
sessionKey: z.string().describe("The session key to operate on"),
});
const CommandSchema = z.object({
command: z.string().describe("The command to execute (e.g., '/status', '/help')"),
});
const SendMessageSchema = z.object({
target: z.string().describe("Target identifier (phone number, group ID, or channel:target)"),
message: z.string().describe("Message content to send"),
channel: z
.string()
.optional()
.describe("Channel to use (whatsapp, telegram, discord, slack, etc.)"),
});
// MCP Server implementation
export class OpenClawMcpServer {
private server: Server;
private gatewayClient: McpGatewayClient;
private config: CursorMcpConfig;
constructor(config: CursorMcpConfig = {}) {
this.config = {
gatewayUrl: process.env.OPENCLAW_GATEWAY_URL ?? config.gatewayUrl ?? "ws://127.0.0.1:18789",
gatewayToken: process.env.OPENCLAW_GATEWAY_TOKEN ?? config.gatewayToken,
gatewayPassword: process.env.OPENCLAW_GATEWAY_PASSWORD ?? config.gatewayPassword,
defaultSessionKey: config.defaultSessionKey ?? "agent:main:cursor",
autoApproveTools: config.autoApproveTools ?? [],
...config,
};
this.gatewayClient = new McpGatewayClient(this.config);
this.server = new Server(
{
name: "openclaw-mcp",
version: "2026.1.29",
},
{
capabilities: {
tools: {},
resources: {},
prompts: {},
},
},
);
this.setupHandlers();
}
private setupHandlers(): void {
this.setupToolHandlers();
this.setupResourceHandlers();
this.setupPromptHandlers();
}
private setupToolHandlers(): void {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "openclaw_chat",
description:
"Chat with the OpenClaw AI agent. Use this to ask questions, request help with coding, or have a conversation. The agent can help with various tasks including code generation, explanations, and problem-solving.",
inputSchema: {
type: "object",
properties: {
message: {
type: "string",
description: "The message to send to the OpenClaw agent",
},
sessionKey: {
type: "string",
description:
"Session key for conversation continuity (e.g., 'agent:main:cursor'). Different session keys maintain separate conversation contexts.",
},
model: {
type: "string",
description:
"Model to use (e.g., 'anthropic/claude-sonnet-4', 'openai/gpt-4o'). Leave empty to use the default model.",
},
},
required: ["message"],
},
},
{
name: "openclaw_list_sessions",
description:
"List all active OpenClaw chat sessions. Returns information about ongoing conversations including session keys, message counts, and last activity times.",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "openclaw_get_session",
description:
"Get detailed information about a specific OpenClaw session, including conversation history summary and metadata.",
inputSchema: {
type: "object",
properties: {
sessionKey: {
type: "string",
description: "The session key to get information about",
},
},
required: ["sessionKey"],
},
},
{
name: "openclaw_clear_session",
description:
"Clear a specific OpenClaw session, removing all conversation history. Use this to start fresh with a new context.",
inputSchema: {
type: "object",
properties: {
sessionKey: {
type: "string",
description: "The session key to clear",
},
},
required: ["sessionKey"],
},
},
{
name: "openclaw_execute_command",
description:
"Execute an OpenClaw control command. Available commands include /status, /help, /models, /config, and more.",
inputSchema: {
type: "object",
properties: {
command: {
type: "string",
description:
"The command to execute (e.g., '/status', '/help', '/models list')",
},
},
required: ["command"],
},
},
{
name: "openclaw_send_message",
description:
"Send a message through OpenClaw to a specific target on a messaging channel (WhatsApp, Telegram, Discord, Slack, etc.).",
inputSchema: {
type: "object",
properties: {
target: {
type: "string",
description:
"Target identifier (phone number for WhatsApp, username/chat ID for Telegram, channel ID for Discord/Slack)",
},
message: {
type: "string",
description: "Message content to send",
},
channel: {
type: "string",
description:
"Channel to use: whatsapp, telegram, discord, slack, imessage, signal, line, etc.",
},
},
required: ["target", "message"],
},
},
{
name: "openclaw_get_status",
description:
"Get the current status of OpenClaw gateway, including connected channels, active sessions, and health information.",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "openclaw_list_models",
description:
"List all available AI models configured in OpenClaw. Shows provider, model ID, and capabilities.",
inputSchema: {
type: "object",
properties: {},
},
},
],
}));
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
// Ensure gateway is connected
if (!this.gatewayClient.isConnected()) {
try {
await this.gatewayClient.connect();
} catch (err) {
return this.errorResult(`Failed to connect to OpenClaw gateway: ${String(err)}`);
}
}
switch (name) {
case "openclaw_chat": {
const parsed = ChatSchema.safeParse(args);
if (!parsed.success) {
return this.errorResult(`Invalid arguments: ${parsed.error.message}`);
}
const result = await this.gatewayClient.chat({
message: parsed.data.message,
sessionKey: parsed.data.sessionKey ?? this.config.defaultSessionKey,
model: parsed.data.model,
deliver: false,
});
return this.formatChatResult(result);
}
case "openclaw_list_sessions": {
const result = await this.gatewayClient.listSessions();
return this.formatJsonResult(result);
}
case "openclaw_get_session": {
const parsed = SessionKeySchema.safeParse(args);
if (!parsed.success) {
return this.errorResult(`Invalid arguments: ${parsed.error.message}`);
}
const result = await this.gatewayClient.getSessionInfo(parsed.data.sessionKey);
return this.formatJsonResult(result);
}
case "openclaw_clear_session": {
const parsed = SessionKeySchema.safeParse(args);
if (!parsed.success) {
return this.errorResult(`Invalid arguments: ${parsed.error.message}`);
}
const result = await this.gatewayClient.clearSession(parsed.data.sessionKey);
return this.formatJsonResult(result);
}
case "openclaw_execute_command": {
const parsed = CommandSchema.safeParse(args);
if (!parsed.success) {
return this.errorResult(`Invalid arguments: ${parsed.error.message}`);
}
const result = await this.gatewayClient.executeCommand(parsed.data.command);
return this.formatJsonResult(result);
}
case "openclaw_send_message": {
const parsed = SendMessageSchema.safeParse(args);
if (!parsed.success) {
return this.errorResult(`Invalid arguments: ${parsed.error.message}`);
}
const target = parsed.data.channel
? `${parsed.data.channel}:${parsed.data.target}`
: parsed.data.target;
const result = await this.gatewayClient.request("message.send", {
target,
text: parsed.data.message,
});
return this.formatJsonResult(result);
}
case "openclaw_get_status": {
const [health, channels] = await Promise.all([
this.gatewayClient.getHealth(),
this.gatewayClient.getChannelStatus(),
]);
return this.formatJsonResult({ health, channels });
}
case "openclaw_list_models": {
const result = await this.gatewayClient.getModels();
return this.formatJsonResult(result);
}
default:
return this.errorResult(`Unknown tool: ${name}`);
}
} catch (err) {
return this.errorResult(`Tool execution failed: ${String(err)}`);
}
});
}
private setupResourceHandlers(): void {
// List available resources
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: "openclaw://status",
name: "OpenClaw Gateway Status",
description: "Current status of the OpenClaw gateway and connected channels",
mimeType: "application/json",
},
{
uri: "openclaw://models",
name: "Available Models",
description: "List of AI models configured in OpenClaw",
mimeType: "application/json",
},
{
uri: "openclaw://sessions",
name: "Active Sessions",
description: "List of active chat sessions",
mimeType: "application/json",
},
{
uri: "openclaw://config",
name: "OpenClaw Configuration",
description: "Current OpenClaw configuration (sanitized, no secrets)",
mimeType: "application/json",
},
],
}));
// Read resource content
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
try {
if (!this.gatewayClient.isConnected()) {
await this.gatewayClient.connect();
}
switch (uri) {
case "openclaw://status": {
const [health, channels] = await Promise.all([
this.gatewayClient.getHealth(),
this.gatewayClient.getChannelStatus(),
]);
return {
contents: [
{
uri,
mimeType: "application/json",
text: JSON.stringify({ health, channels }, null, 2),
},
],
};
}
case "openclaw://models": {
const models = await this.gatewayClient.getModels();
return {
contents: [
{
uri,
mimeType: "application/json",
text: JSON.stringify(models, null, 2),
},
],
};
}
case "openclaw://sessions": {
const sessions = await this.gatewayClient.listSessions();
return {
contents: [
{
uri,
mimeType: "application/json",
text: JSON.stringify(sessions, null, 2),
},
],
};
}
case "openclaw://config": {
const config = await this.gatewayClient.request("config.get", {});
return {
contents: [
{
uri,
mimeType: "application/json",
text: JSON.stringify(config, null, 2),
},
],
};
}
default:
throw new Error(`Unknown resource: ${uri}`);
}
} catch (err) {
return {
contents: [
{
uri,
mimeType: "text/plain",
text: `Error reading resource: ${String(err)}`,
},
],
};
}
});
}
private setupPromptHandlers(): void {
// List available prompts
this.server.setRequestHandler(ListPromptsRequestSchema, async () => ({
prompts: [
{
name: "code_review",
description: "Ask OpenClaw to review code for issues, improvements, and best practices",
arguments: [
{
name: "code",
description: "The code to review",
required: true,
},
{
name: "language",
description: "Programming language (optional, will be auto-detected)",
required: false,
},
],
},
{
name: "explain_code",
description: "Ask OpenClaw to explain how code works",
arguments: [
{
name: "code",
description: "The code to explain",
required: true,
},
],
},
{
name: "generate_tests",
description: "Ask OpenClaw to generate tests for code",
arguments: [
{
name: "code",
description: "The code to generate tests for",
required: true,
},
{
name: "framework",
description: "Testing framework to use (e.g., jest, pytest, vitest)",
required: false,
},
],
},
{
name: "refactor_code",
description: "Ask OpenClaw to suggest refactoring improvements",
arguments: [
{
name: "code",
description: "The code to refactor",
required: true,
},
{
name: "goal",
description: "Refactoring goal (e.g., 'improve readability', 'optimize performance')",
required: false,
},
],
},
{
name: "debug_help",
description: "Ask OpenClaw to help debug an issue",
arguments: [
{
name: "code",
description: "The code with the issue",
required: true,
},
{
name: "error",
description: "Error message or description of the problem",
required: true,
},
],
},
{
name: "send_notification",
description: "Send a notification message through OpenClaw channels",
arguments: [
{
name: "message",
description: "The notification message to send",
required: true,
},
{
name: "channel",
description: "Channel to use (whatsapp, telegram, discord, slack)",
required: false,
},
],
},
],
}));
// Get prompt content
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "code_review": {
const code = args?.code ?? "";
const language = args?.language ?? "";
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Please review the following${language ? ` ${language}` : ""} code for:
- Bugs and potential issues
- Code quality and readability
- Performance considerations
- Security concerns
- Best practices and improvements
Code to review:
\`\`\`${language}
${code}
\`\`\``,
},
},
],
};
}
case "explain_code": {
const code = args?.code ?? "";
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Please explain how the following code works, step by step:
\`\`\`
${code}
\`\`\`
Include:
- What the code does overall
- Key components and their purposes
- Control flow and logic
- Any important patterns or techniques used`,
},
},
],
};
}
case "generate_tests": {
const code = args?.code ?? "";
const framework = args?.framework ?? "";
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Please generate comprehensive tests for the following code${framework ? ` using ${framework}` : ""}:
\`\`\`
${code}
\`\`\`
Include:
- Unit tests for individual functions/methods
- Edge cases and boundary conditions
- Error handling scenarios
- Mock/stub setup where needed`,
},
},
],
};
}
case "refactor_code": {
const code = args?.code ?? "";
const goal = args?.goal ?? "improve code quality";
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Please refactor the following code to ${goal}:
\`\`\`
${code}
\`\`\`
Provide:
- The refactored code
- Explanation of changes made
- Benefits of the refactoring`,
},
},
],
};
}
case "debug_help": {
const code = args?.code ?? "";
const error = args?.error ?? "";
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `I'm encountering an issue with this code:
\`\`\`
${code}
\`\`\`
Error/Problem:
${error}
Please help me:
1. Identify the root cause
2. Explain why this is happening
3. Provide a fix with explanation`,
},
},
],
};
}
case "send_notification": {
const message = args?.message ?? "";
const channel = args?.channel ?? "default";
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Please send this notification through OpenClaw${channel !== "default" ? ` via ${channel}` : ""}:
"${message}"
Use the openclaw_send_message tool if a specific target is configured, or inform me that I need to specify a target.`,
},
},
],
};
}
default:
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Unknown prompt: ${name}`,
},
},
],
};
}
});
}
private formatChatResult(result: unknown): McpToolResult {
if (!result || typeof result !== "object") {
return {
content: [{ type: "text", text: "No response from OpenClaw agent" }],
};
}
const response = result as { payloads?: Array<{ text?: string }>; error?: string };
if (response.error) {
return this.errorResult(response.error);
}
const payloads = response.payloads;
if (!Array.isArray(payloads) || payloads.length === 0) {
return {
content: [{ type: "text", text: "No response from OpenClaw agent" }],
};
}
const text = payloads
.map((p) => (typeof p.text === "string" ? p.text : ""))
.filter(Boolean)
.join("\n\n");
return {
content: [{ type: "text", text: text || "No response from OpenClaw agent" }],
};
}
private formatJsonResult(result: unknown): McpToolResult {
return {
content: [
{
type: "text",
text: typeof result === "string" ? result : JSON.stringify(result, null, 2),
},
],
};
}
private errorResult(message: string): McpToolResult {
return {
content: [{ type: "text", text: `Error: ${message}` }],
isError: true,
};
}
async start(): Promise<void> {
// Connect to gateway
try {
await this.gatewayClient.connect();
console.error("[openclaw-mcp] Connected to OpenClaw gateway");
} catch (err) {
console.error(`[openclaw-mcp] Warning: Could not connect to gateway: ${String(err)}`);
console.error("[openclaw-mcp] Will attempt to connect when tools are called");
}
// Start stdio transport
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("[openclaw-mcp] MCP server started on stdio transport");
}
async stop(): Promise<void> {
this.gatewayClient.disconnect();
await this.server.close();
}
}
// Main entry point for standalone execution
async function main() {
const config: CursorMcpConfig = {
gatewayUrl: process.env.OPENCLAW_GATEWAY_URL,
gatewayToken: process.env.OPENCLAW_GATEWAY_TOKEN,
gatewayPassword: process.env.OPENCLAW_GATEWAY_PASSWORD,
defaultSessionKey: process.env.OPENCLAW_SESSION_KEY ?? "agent:main:cursor",
};
const server = new OpenClawMcpServer(config);
process.on("SIGINT", async () => {
console.error("[openclaw-mcp] Shutting down...");
await server.stop();
process.exit(0);
});
process.on("SIGTERM", async () => {
console.error("[openclaw-mcp] Shutting down...");
await server.stop();
process.exit(0);
});
await server.start();
}
// Run if this is the main module
const isMain =
typeof process !== "undefined" &&
process.argv[1] &&
(process.argv[1].endsWith("server.ts") ||
process.argv[1].endsWith("server.js") ||
process.argv[1].includes("cursor-mcp"));
if (isMain) {
main().catch((err) => {
console.error(`[openclaw-mcp] Fatal error: ${String(err)}`);
process.exit(1);
});
}
export { main };

View File

@ -0,0 +1,55 @@
/**
* Type definitions for the Cursor MCP server
*/
export type CursorMcpConfig = {
enabled?: boolean;
port?: number;
autoApproveTools?: string[];
gatewayUrl?: string;
gatewayToken?: string;
gatewayPassword?: string;
defaultSessionKey?: string;
};
export type McpToolResult = {
content: Array<{
type: "text" | "image" | "resource";
text?: string;
data?: string;
mimeType?: string;
uri?: string;
}>;
isError?: boolean;
};
export type ChatRequest = {
message: string;
sessionKey?: string;
model?: string;
stream?: boolean;
};
export type SessionInfo = {
sessionKey: string;
agentId: string;
messageCount: number;
lastActivity?: string;
model?: string;
};
export type ChannelStatus = {
channelId: string;
accountId: string;
status: "connected" | "disconnected" | "error";
lastHeartbeat?: string;
error?: string;
};
export type GatewayHealth = {
status: "healthy" | "degraded" | "unhealthy";
uptime: number;
version: string;
channels: ChannelStatus[];
activeSessionCount: number;
};

116
pnpm-lock.yaml generated
View File

@ -42,13 +42,13 @@ importers:
version: 1.2.0-beta.3
'@mariozechner/pi-agent-core':
specifier: 0.49.3
version: 0.49.3(ws@8.19.0)(zod@4.3.6)
version: 0.49.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.4)(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
'@mariozechner/pi-ai':
specifier: 0.49.3
version: 0.49.3(ws@8.19.0)(zod@4.3.6)
version: 0.49.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.4)(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
'@mariozechner/pi-coding-agent':
specifier: 0.49.3
version: 0.49.3(ws@8.19.0)(zod@4.3.6)
version: 0.49.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.4)(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
'@mariozechner/pi-tui':
specifier: 0.49.3
version: 0.49.3
@ -266,6 +266,16 @@ importers:
extensions/copilot-proxy: {}
extensions/cursor-mcp:
dependencies:
'@modelcontextprotocol/sdk':
specifier: ^1.25.3
version: 1.25.3(hono@4.11.4)(zod@4.3.6)
devDependencies:
openclaw:
specifier: workspace:*
version: link:../..
extensions/diagnostics-otel:
dependencies:
'@opentelemetry/api':
@ -1489,6 +1499,16 @@ packages:
'@mistralai/mistralai@1.10.0':
resolution: {integrity: sha512-tdIgWs4Le8vpvPiUEWne6tK0qbVc+jMenujnvTqOjogrJUsCSQhus0tHTU1avDDh5//Rq2dFgP9mWRAdIEoBqg==}
'@modelcontextprotocol/sdk@1.25.3':
resolution: {integrity: sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ==}
engines: {node: '>=18'}
peerDependencies:
'@cfworker/json-schema': ^4.1.1
zod: ^3.25 || ^4.0
peerDependenciesMeta:
'@cfworker/json-schema':
optional: true
'@mozilla/readability@0.6.0':
resolution: {integrity: sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ==}
engines: {node: '>=14.0.0'}
@ -3325,6 +3345,10 @@ packages:
core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
cors@2.8.6:
resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
engines: {node: '>= 0.10'}
croner@9.1.0:
resolution: {integrity: sha512-p9nwwR4qyT5W996vBZhdvBCnMhicY5ytZkR4D1Xj0wuTDEiMnjwR57Q3RXYY/s0EpX6Ay3vgIcfaR+ewGHsi+g==}
engines: {node: '>=18.0'}
@ -3531,10 +3555,24 @@ packages:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
eventsource-parser@3.0.6:
resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
engines: {node: '>=18.0.0'}
eventsource@3.0.7:
resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
engines: {node: '>=18.0.0'}
expect-type@1.3.0:
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
engines: {node: '>=12.0.0'}
express-rate-limit@7.5.1:
resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==}
engines: {node: '>= 16'}
peerDependencies:
express: '>= 4.11'
express@4.22.1:
resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==}
engines: {node: '>= 0.10.0'}
@ -3976,6 +4014,9 @@ packages:
jose@4.15.9:
resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==}
jose@6.1.3:
resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
js-base64@3.7.8:
resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==}
@ -4002,6 +4043,9 @@ packages:
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
json-schema-typed@8.0.2:
resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
json-schema@0.4.0:
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
@ -4697,6 +4741,10 @@ packages:
resolution: {integrity: sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==}
hasBin: true
pkce-challenge@5.0.1:
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
engines: {node: '>=16.20.0'}
playwright-core@1.58.0:
resolution: {integrity: sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw==}
engines: {node: '>=18'}
@ -6649,10 +6697,12 @@ snapshots:
'@glideapps/ts-necessities@2.2.3': {}
'@google/genai@1.34.0':
'@google/genai@1.34.0(@modelcontextprotocol/sdk@1.25.3(hono@4.11.4)(zod@4.3.6))':
dependencies:
google-auth-library: 10.5.0
ws: 8.19.0
optionalDependencies:
'@modelcontextprotocol/sdk': 1.25.3(hono@4.11.4)(zod@4.3.6)
transitivePeerDependencies:
- bufferutil
- supports-color
@ -6700,7 +6750,6 @@ snapshots:
'@hono/node-server@1.19.9(hono@4.11.4)':
dependencies:
hono: 4.11.4
optional: true
'@huggingface/jinja@0.5.3':
optional: true
@ -6994,9 +7043,9 @@ snapshots:
transitivePeerDependencies:
- tailwindcss
'@mariozechner/pi-agent-core@0.49.3(ws@8.19.0)(zod@4.3.6)':
'@mariozechner/pi-agent-core@0.49.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.4)(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)':
dependencies:
'@mariozechner/pi-ai': 0.49.3(ws@8.19.0)(zod@4.3.6)
'@mariozechner/pi-ai': 0.49.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.4)(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
'@mariozechner/pi-tui': 0.49.3
transitivePeerDependencies:
- '@modelcontextprotocol/sdk'
@ -7007,11 +7056,11 @@ snapshots:
- ws
- zod
'@mariozechner/pi-ai@0.49.3(ws@8.19.0)(zod@4.3.6)':
'@mariozechner/pi-ai@0.49.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.4)(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)':
dependencies:
'@anthropic-ai/sdk': 0.71.2(zod@4.3.6)
'@aws-sdk/client-bedrock-runtime': 3.972.0
'@google/genai': 1.34.0
'@google/genai': 1.34.0(@modelcontextprotocol/sdk@1.25.3(hono@4.11.4)(zod@4.3.6))
'@mistralai/mistralai': 1.10.0
'@sinclair/typebox': 0.34.47
ajv: 8.17.1
@ -7029,12 +7078,12 @@ snapshots:
- ws
- zod
'@mariozechner/pi-coding-agent@0.49.3(ws@8.19.0)(zod@4.3.6)':
'@mariozechner/pi-coding-agent@0.49.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.4)(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)':
dependencies:
'@mariozechner/clipboard': 0.3.0
'@mariozechner/jiti': 2.6.5
'@mariozechner/pi-agent-core': 0.49.3(ws@8.19.0)(zod@4.3.6)
'@mariozechner/pi-ai': 0.49.3(ws@8.19.0)(zod@4.3.6)
'@mariozechner/pi-agent-core': 0.49.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.4)(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
'@mariozechner/pi-ai': 0.49.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.4)(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
'@mariozechner/pi-tui': 0.49.3
'@silvia-odwyer/photon-node': 0.3.4
chalk: 5.6.2
@ -7111,6 +7160,28 @@ snapshots:
zod: 3.25.76
zod-to-json-schema: 3.25.1(zod@3.25.76)
'@modelcontextprotocol/sdk@1.25.3(hono@4.11.4)(zod@4.3.6)':
dependencies:
'@hono/node-server': 1.19.9(hono@4.11.4)
ajv: 8.17.1
ajv-formats: 3.0.1(ajv@8.17.1)
content-type: 1.0.5
cors: 2.8.6
cross-spawn: 7.0.6
eventsource: 3.0.7
eventsource-parser: 3.0.6
express: 5.2.1
express-rate-limit: 7.5.1(express@5.2.1)
jose: 6.1.3
json-schema-typed: 8.0.2
pkce-challenge: 5.0.1
raw-body: 3.0.2
zod: 4.3.6
zod-to-json-schema: 3.25.1(zod@4.3.6)
transitivePeerDependencies:
- hono
- supports-color
'@mozilla/readability@0.6.0': {}
'@napi-rs/canvas-android-arm64@0.1.88':
@ -9210,6 +9281,11 @@ snapshots:
core-util-is@1.0.3: {}
cors@2.8.6:
dependencies:
object-assign: 4.1.1
vary: 1.1.2
croner@9.1.0: {}
cross-fetch@4.1.0:
@ -9403,8 +9479,18 @@ snapshots:
events@3.3.0: {}
eventsource-parser@3.0.6: {}
eventsource@3.0.7:
dependencies:
eventsource-parser: 3.0.6
expect-type@1.3.0: {}
express-rate-limit@7.5.1(express@5.2.1):
dependencies:
express: 5.2.1
express@4.22.1:
dependencies:
accepts: 1.3.8
@ -9981,6 +10067,8 @@ snapshots:
jose@4.15.9: {}
jose@6.1.3: {}
js-base64@3.7.8: {}
js-tokens@9.0.1: {}
@ -10002,6 +10090,8 @@ snapshots:
json-schema-traverse@1.0.0: {}
json-schema-typed@8.0.2: {}
json-schema@0.4.0: {}
json-stringify-safe@5.0.1: {}
@ -10729,6 +10819,8 @@ snapshots:
dependencies:
pngjs: 7.0.0
pkce-challenge@5.0.1: {}
playwright-core@1.58.0: {}
playwright@1.58.0: