diff --git a/registry/coder/modules/cursor/README.md b/registry/coder/modules/cursor/README.md index 9dba52b51..3551e831b 100644 --- a/registry/coder/modules/cursor/README.md +++ b/registry/coder/modules/cursor/README.md @@ -16,7 +16,7 @@ Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder) module "cursor" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/cursor/coder" - version = "1.2.1" + version = "1.3.0" agent_id = coder_agent.example.id } ``` @@ -29,8 +29,33 @@ module "cursor" { module "cursor" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/cursor/coder" - version = "1.2.1" + version = "1.3.0" agent_id = coder_agent.example.id folder = "/home/coder/project" } ``` + +### Configure MCP servers for Cursor + +Provide a JSON-encoded string via the `mcp` input. When set, the module writes the value to `~/.cursor/mcp.json` using a `coder_script` on workspace start. + +```tf +module "cursor" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/cursor/coder" + version = "1.3.0" + agent_id = coder_agent.example.id + mcp = jsonencode({ + mcpServers = { + coder = { + command = "coder" + args = ["exp", "mcp", "server"] + env = { + CODER_MCP_APP_STATUS_SLUG = "cursor" + CODER_MCP_AI_AGENTAPI_URL = "http://localhost:3284" + } + } + } + }) +} +``` diff --git a/registry/coder/modules/cursor/main.test.ts b/registry/coder/modules/cursor/main.test.ts index ed92b9c93..5d10c823a 100644 --- a/registry/coder/modules/cursor/main.test.ts +++ b/registry/coder/modules/cursor/main.test.ts @@ -1,8 +1,13 @@ -import { describe, expect, it } from "bun:test"; +import { describe, it, expect } from "bun:test"; import { runTerraformApply, runTerraformInit, testRequiredVariables, + runContainer, + execContainer, + removeContainer, + findResourceInstance, + readFileContainer, } from "~test"; describe("cursor", async () => { @@ -85,4 +90,26 @@ describe("cursor", async () => { expect(coder_app?.instances.length).toBe(1); expect(coder_app?.instances[0].attributes.order).toBe(22); }); + + it("writes ~/.cursor/mcp.json when mcp provided", async () => { + const id = await runContainer("alpine"); + try { + const mcp = JSON.stringify({ servers: { demo: { url: "http://localhost:1234" } } }); + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + mcp, + }); + const script = findResourceInstance(state, "coder_script", "cursor_mcp").script; + const resp = await execContainer(id, ["sh", "-c", script]); + if (resp.exitCode !== 0) { + console.log(resp.stdout); + console.log(resp.stderr); + } + expect(resp.exitCode).toBe(0); + const content = await readFileContainer(id, "/root/.cursor/mcp.json"); + expect(content).toBe(mcp); + } finally { + await removeContainer(id); + } + }); }); diff --git a/registry/coder/modules/cursor/main.tf b/registry/coder/modules/cursor/main.tf index 47d35b623..71aff72f1 100644 --- a/registry/coder/modules/cursor/main.tf +++ b/registry/coder/modules/cursor/main.tf @@ -50,9 +50,20 @@ variable "display_name" { default = "Cursor Desktop" } +variable "mcp" { + type = string + description = "JSON-encoded string to configure MCP servers for Cursor. When set, writes ~/.cursor/mcp.json." + default = "" +} + data "coder_workspace" "me" {} + data "coder_workspace_owner" "me" {} +locals { + mcp_b64 = var.mcp != "" ? base64encode(var.mcp) : "" +} + resource "coder_app" "cursor" { agent_id = var.agent_id external = true @@ -75,6 +86,21 @@ resource "coder_app" "cursor" { ]) } +resource "coder_script" "cursor_mcp" { + count = var.mcp != "" ? 1 : 0 + agent_id = var.agent_id + display_name = "Cursor MCP" + icon = "/icon/cursor.svg" + run_on_start = true + start_blocks_login = false + script = <<-EOT + #!/bin/sh + set -eu + mkdir -p "$HOME/.cursor" + echo -n "${local.mcp_b64}" | base64 -d > "$HOME/.cursor/mcp.json" + EOT +} + output "cursor_url" { value = coder_app.cursor.url description = "Cursor IDE Desktop URL."