Merge 850c68cc92 into 09be5d45d5
This commit is contained in:
commit
d98a78e62e
254
docs/plugins/cursor-mcp.md
Normal file
254
docs/plugins/cursor-mcp.md
Normal 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)
|
||||
33
extensions/cursor-mcp/CHANGELOG.md
Normal file
33
extensions/cursor-mcp/CHANGELOG.md
Normal 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
|
||||
286
extensions/cursor-mcp/README.md
Normal file
286
extensions/cursor-mcp/README.md
Normal 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.
|
||||
204
extensions/cursor-mcp/index.ts
Normal file
204
extensions/cursor-mcp/index.ts
Normal 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;
|
||||
25
extensions/cursor-mcp/openclaw.plugin.json
Normal file
25
extensions/cursor-mcp/openclaw.plugin.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
extensions/cursor-mcp/package.json
Normal file
17
extensions/cursor-mcp/package.json
Normal 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:*"
|
||||
}
|
||||
}
|
||||
233
extensions/cursor-mcp/src/cursor-models.ts
Normal file
233
extensions/cursor-mcp/src/cursor-models.ts
Normal 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
|
||||
`;
|
||||
302
extensions/cursor-mcp/src/gateway-client.ts
Normal file
302
extensions/cursor-mcp/src/gateway-client.ts
Normal 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 });
|
||||
}
|
||||
}
|
||||
818
extensions/cursor-mcp/src/server.ts
Normal file
818
extensions/cursor-mcp/src/server.ts
Normal 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 };
|
||||
55
extensions/cursor-mcp/src/types.ts
Normal file
55
extensions/cursor-mcp/src/types.ts
Normal 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
116
pnpm-lock.yaml
generated
@ -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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user