// Command cursor-mcp-server is a Model Context Protocol (MCP) server that // exposes the cursor-adapter HTTP API as MCP tools for Claude Desktop. // // It communicates with Claude Desktop over stdio (JSON-RPC) and forwards // requests to a running cursor-adapter instance via HTTP. // // Usage (standalone): // // go run ./cmd/mcp-server // go run ./cmd/mcp-server --adapter-url http://127.0.0.1:8765 // // Usage (Claude Desktop config): // // { // "mcpServers": { // "cursor-bridge": { // "command": "/path/to/cursor-mcp-server", // "args": ["--adapter-url", "http://127.0.0.1:8765"] // } // } // } package main import ( "bytes" "context" "encoding/json" "flag" "fmt" "io" "log" "net/http" "os" "strings" "time" "github.com/modelcontextprotocol/go-sdk/mcp" ) var adapterURL string func init() { flag.StringVar(&adapterURL, "adapter-url", "http://127.0.0.1:8765", "cursor-adapter HTTP base URL") } // --- Tool input/output types --- type AskCursorInput struct { Prompt string `json:"prompt" mcp:"required"` Model string `json:"model"` } type EmptyInput struct{} type TextOutput struct { Text string `json:"text"` } // --- Tool handlers --- func askCursor(ctx context.Context, _ *mcp.CallToolRequest, input AskCursorInput) (*mcp.CallToolResult, TextOutput, error) { model := input.Model if model == "" { model = "claude-opus-4-7-high" } payload := map[string]interface{}{ "model": model, "max_tokens": 16384, "messages": []map[string]string{{"role": "user", "content": input.Prompt}}, "stream": false, } body, _ := json.Marshal(payload) httpReq, err := http.NewRequestWithContext(ctx, "POST", adapterURL+"/v1/messages", bytes.NewReader(body)) if err != nil { return nil, TextOutput{}, fmt.Errorf("build request: %w", err) } httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("x-api-key", "mcp-bridge") client := &http.Client{Timeout: 5 * time.Minute} resp, err := client.Do(httpReq) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{&mcp.TextContent{Text: "❌ Cannot connect to cursor-adapter at " + adapterURL + ". Make sure it is running."}}, IsError: true, }, TextOutput{}, nil } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) if resp.StatusCode != 200 { return &mcp.CallToolResult{ Content: []mcp.Content{&mcp.TextContent{Text: fmt.Sprintf("❌ cursor-adapter HTTP %d: %s", resp.StatusCode, string(respBody))}}, IsError: true, }, TextOutput{}, nil } var data struct { Content []struct { Type string `json:"type"` Text string `json:"text"` } `json:"content"` Error *struct { Message string `json:"message"` } `json:"error"` } if err := json.Unmarshal(respBody, &data); err != nil { return nil, TextOutput{Text: string(respBody)}, nil } if data.Error != nil { return &mcp.CallToolResult{ Content: []mcp.Content{&mcp.TextContent{Text: "❌ Cursor error: " + data.Error.Message}}, IsError: true, }, TextOutput{}, nil } var texts []string for _, block := range data.Content { if block.Type == "text" { texts = append(texts, block.Text) } } result := strings.Join(texts, "\n") if result == "" { result = string(respBody) } return &mcp.CallToolResult{ Content: []mcp.Content{&mcp.TextContent{Text: fmt.Sprintf("[Model: %s]\n\n%s", model, result)}}, }, TextOutput{Text: result}, nil } func listModels(ctx context.Context, _ *mcp.CallToolRequest, _ EmptyInput) (*mcp.CallToolResult, TextOutput, error) { httpReq, err := http.NewRequestWithContext(ctx, "GET", adapterURL+"/v1/models", nil) if err != nil { return nil, TextOutput{}, err } client := &http.Client{Timeout: 30 * time.Second} resp, err := client.Do(httpReq) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{&mcp.TextContent{Text: "❌ Cannot connect to cursor-adapter"}}, IsError: true, }, TextOutput{}, nil } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) var data struct { Data []struct { ID string `json:"id"` } `json:"data"` } if err := json.Unmarshal(respBody, &data); err != nil { return nil, TextOutput{Text: string(respBody)}, nil } var lines []string lines = append(lines, fmt.Sprintf("Available models (%d total):\n", len(data.Data))) for _, m := range data.Data { lines = append(lines, " "+m.ID) } text := strings.Join(lines, "\n") return &mcp.CallToolResult{ Content: []mcp.Content{&mcp.TextContent{Text: text}}, }, TextOutput{Text: text}, nil } func checkHealth(ctx context.Context, _ *mcp.CallToolRequest, _ EmptyInput) (*mcp.CallToolResult, TextOutput, error) { httpReq, err := http.NewRequestWithContext(ctx, "GET", adapterURL+"/health", nil) if err != nil { return nil, TextOutput{}, err } client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(httpReq) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{&mcp.TextContent{Text: "❌ cursor-adapter is not running"}}, IsError: true, }, TextOutput{}, nil } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) var pretty bytes.Buffer text := string(respBody) if err := json.Indent(&pretty, respBody, "", " "); err == nil { text = pretty.String() } return &mcp.CallToolResult{ Content: []mcp.Content{&mcp.TextContent{Text: text}}, }, TextOutput{Text: text}, nil } func main() { flag.Parse() if envURL := os.Getenv("CURSOR_ADAPTER_URL"); envURL != "" { adapterURL = envURL } server := mcp.NewServer( &mcp.Implementation{ Name: "cursor-bridge", Version: "1.0.0", }, &mcp.ServerOptions{ Instructions: "This server provides access to the Cursor AI coding agent via cursor-adapter. " + "Use ask_cursor to delegate coding tasks, code generation, debugging, or technical questions to Cursor.", }, ) mcp.AddTool(server, &mcp.Tool{ Name: "ask_cursor", Description: "Ask the Cursor AI agent a question or delegate a coding task. " + "Use this when you need code generation, review, debugging, or a second opinion. " + "The Cursor agent acts as a pure reasoning engine. " + "Available models: claude-opus-4-7-high (default), claude-opus-4-7-thinking-high, " + "claude-4.6-opus-high, claude-4.6-sonnet-medium, gpt-5.4-medium, gemini-3.1-pro. " + "Pass model name in the 'model' field.", }, askCursor) mcp.AddTool(server, &mcp.Tool{ Name: "list_cursor_models", Description: "List all available models from the Cursor adapter.", }, listModels) mcp.AddTool(server, &mcp.Tool{ Name: "cursor_health", Description: "Check the health status of the cursor-adapter service.", }, checkHealth) if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil { log.Fatal(err) } }