feat: add OpenClaw MCP integration for Cursor IDE

This commit introduces the OpenClaw Model Context Protocol (MCP) server integration for Cursor IDE, enabling users to leverage OpenClaw's AI capabilities directly within the IDE. Key features include session management, multi-channel messaging, and built-in code assistance prompts. The integration is documented in the new `cursor-mcp.md` file and includes a changelog for version 2026.1.29.

New files added:
- `cursor-mcp.md`: Documentation for MCP integration.
- `CHANGELOG.md`: Changelog for the integration.
- `index.ts`: Main plugin file for MCP server.
- `openclaw.plugin.json`: Plugin configuration.
- `README.md`: Detailed usage instructions.
- `src/gateway-client.ts`: WebSocket client for gateway communication.
- `src/server.ts`: MCP server implementation.
- `src/types.ts`: Type definitions for the MCP server.
- `tsconfig.json`: TypeScript configuration.
- `package.json`: Plugin dependencies and scripts.
This commit is contained in:
Ananta Tamboli 2026-01-30 13:00:01 +05:30
parent 9025da2296
commit c473a69f2d
10 changed files with 1864 additions and 0 deletions

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

@ -0,0 +1,215 @@
---
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
```
## 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,221 @@
# 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 │
└───────────────┘ └───────────────┘ └───────────────┘
```
## License
MIT - Part of the OpenClaw project.

View File

@ -0,0 +1,148 @@
/**
* 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();
});
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,29 @@
{
"name": "@openclaw/cursor-mcp",
"version": "2026.1.29",
"type": "module",
"description": "OpenClaw MCP server for Cursor IDE integration",
"main": "index.ts",
"openclaw": {
"extensions": [
"./index.ts"
]
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.25.3",
"ws": "^8.19.0",
"zod": "^3.25.0"
},
"peerDependencies": {
"openclaw": "*"
},
"devDependencies": {
"@types/node": "^22.0.0",
"@types/ws": "^8.18.1",
"typescript": "^5.9.0"
},
"scripts": {
"build": "tsc",
"start": "node --import tsx src/server.ts"
}
}

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;
};

View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": ".",
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["*.ts", "src/**/*.ts", "bin/**/*.ts"],
"exclude": ["node_modules", "dist"]
}