diff --git a/apps/docs/content/docs/de/copilot/index.mdx b/apps/docs/content/docs/de/copilot/index.mdx
index 116b107bee..e9a92f7e20 100644
--- a/apps/docs/content/docs/de/copilot/index.mdx
+++ b/apps/docs/content/docs/de/copilot/index.mdx
@@ -246,98 +246,3 @@ Die Copilot-Nutzung wird pro Token des zugrunde liegenden LLM abgerechnet. Wenn
Siehe die [Seite zur Kostenberechnung](/execution/costs) für Abrechnungsdetails.
-## Copilot MCP
-
-Sie können Copilot als MCP-Server in Ihrem bevorzugten Editor oder AI-Client verwenden. Damit können Sie Sim-Workflows direkt aus Tools wie Cursor, Claude Code, Claude Desktop und VS Code erstellen, testen, bereitstellen und verwalten.
-
-### Generieren eines Copilot-API-Schlüssels
-
-Um sich mit dem Copilot-MCP-Server zu verbinden, benötigen Sie einen **Copilot-API-Schlüssel**:
-
-1. Gehen Sie zu [sim.ai](https://sim.ai) und melden Sie sich an
-2. Navigieren Sie zu **Einstellungen** → **Copilot**
-3. Klicken Sie auf **API-Schlüssel generieren**
-4. Kopieren Sie den Schlüssel – er wird nur einmal angezeigt
-
-Der Schlüssel sieht aus wie `sk-sim-copilot-...`. Sie werden ihn in der folgenden Konfiguration verwenden.
-
-### Cursor
-
-Fügen Sie Folgendes zu Ihrer `.cursor/mcp.json` (Projektebene) oder den globalen Cursor-MCP-Einstellungen hinzu:
-
-```json
-{
- "mcpServers": {
- "sim-copilot": {
- "url": "https://www.sim.ai/api/mcp/copilot",
- "headers": {
- "X-API-Key": "YOUR_COPILOT_API_KEY"
- }
- }
- }
-}
-```
-
-Ersetzen Sie `YOUR_COPILOT_API_KEY` durch den oben generierten Schlüssel.
-
-### Claude Code
-
-Führen Sie den folgenden Befehl aus, um den Copilot MCP-Server hinzuzufügen:
-
-```bash
-claude mcp add sim-copilot \
- --transport http \
- https://www.sim.ai/api/mcp/copilot \
- --header "X-API-Key: YOUR_COPILOT_API_KEY"
-```
-
-Ersetzen Sie `YOUR_COPILOT_API_KEY` durch Ihren Schlüssel.
-
-### Claude Desktop
-
-Claude Desktop benötigt [`mcp-remote`](https://www.npmjs.com/package/mcp-remote), um sich mit HTTP-basierten MCP-Servern zu verbinden. Fügen Sie Folgendes zu Ihrer Claude Desktop-Konfigurationsdatei hinzu (`~/Library/Application Support/Claude/claude_desktop_config.json` unter macOS):
-
-```json
-{
- "mcpServers": {
- "sim-copilot": {
- "command": "npx",
- "args": [
- "-y",
- "mcp-remote",
- "https://www.sim.ai/api/mcp/copilot",
- "--header",
- "X-API-Key: YOUR_COPILOT_API_KEY"
- ]
- }
- }
-}
-```
-
-Ersetzen Sie `YOUR_COPILOT_API_KEY` durch Ihren Schlüssel.
-
-### VS Code
-
-Fügen Sie Folgendes zu Ihrer VS Code `settings.json` oder Workspace `.vscode/settings.json` hinzu:
-
-```json
-{
- "mcp": {
- "servers": {
- "sim-copilot": {
- "type": "http",
- "url": "https://www.sim.ai/api/mcp/copilot",
- "headers": {
- "X-API-Key": "YOUR_COPILOT_API_KEY"
- }
- }
- }
- }
-}
-```
-
-Ersetzen Sie `YOUR_COPILOT_API_KEY` durch Ihren Schlüssel.
-
-
- Für selbst gehostete Deployments ersetzen Sie `https://www.sim.ai` durch Ihre selbst gehostete Sim-URL.
-
diff --git a/apps/docs/content/docs/en/copilot/index.mdx b/apps/docs/content/docs/en/copilot/index.mdx
index 81f6dfe6a6..f30f3da691 100644
--- a/apps/docs/content/docs/en/copilot/index.mdx
+++ b/apps/docs/content/docs/en/copilot/index.mdx
@@ -50,90 +50,6 @@ For complex requests, Copilot may show its reasoning in an expandable thinking b
Copilot usage is billed per token and counts toward your plan's credit usage. If you reach your limit, enable on-demand billing from Settings → Subscription.
-## Copilot MCP
-
-You can use Copilot as an MCP server to build, test, and manage Sim workflows from external editors — Cursor, Claude Code, Claude Desktop, and VS Code.
-
-### Generating a Copilot API Key
-
-1. Go to [sim.ai](https://sim.ai) and sign in
-2. Navigate to **Settings** → **Copilot**
-3. Click **Generate API Key**
-4. Copy the key — it is only shown once
-
-The key will look like `sk-sim-copilot-...`.
-
-### Cursor
-
-Add to `.cursor/mcp.json`:
-
-```json
-{
- "mcpServers": {
- "sim-copilot": {
- "url": "https://www.sim.ai/api/mcp/copilot",
- "headers": {
- "X-API-Key": "YOUR_COPILOT_API_KEY"
- }
- }
- }
-}
-```
-
-### Claude Code
-
-```bash
-claude mcp add sim-copilot \
- --transport http \
- https://www.sim.ai/api/mcp/copilot \
- --header "X-API-Key: YOUR_COPILOT_API_KEY"
-```
-
-### Claude Desktop
-
-Claude Desktop requires [`mcp-remote`](https://www.npmjs.com/package/mcp-remote). Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
-
-```json
-{
- "mcpServers": {
- "sim-copilot": {
- "command": "npx",
- "args": [
- "-y",
- "mcp-remote",
- "https://www.sim.ai/api/mcp/copilot",
- "--header",
- "X-API-Key: YOUR_COPILOT_API_KEY"
- ]
- }
- }
-}
-```
-
-### VS Code
-
-Add to `settings.json` or `.vscode/settings.json`:
-
-```json
-{
- "mcp": {
- "servers": {
- "sim-copilot": {
- "type": "http",
- "url": "https://www.sim.ai/api/mcp/copilot",
- "headers": {
- "X-API-Key": "YOUR_COPILOT_API_KEY"
- }
- }
- }
- }
-}
-```
-
-
- For self-hosted deployments, replace `https://www.sim.ai` with your self-hosted Sim URL.
-
-
Consulta la [página de cálculo de costos](/execution/costs) para detalles de facturación.
-## Copilot MCP
-
-Puedes usar Copilot como servidor MCP en tu editor o cliente de IA favorito. Esto te permite construir, probar, desplegar y gestionar flujos de trabajo de Sim directamente desde herramientas como Cursor, Claude Code, Claude Desktop y VS Code.
-
-### Generar una clave API de Copilot
-
-Para conectarte al servidor MCP de Copilot, necesitas una **clave API de Copilot**:
-
-1. Ve a [sim.ai](https://sim.ai) e inicia sesión
-2. Navega a **Configuración** → **Copilot**
-3. Haz clic en **Generar clave API**
-4. Copia la clave — solo se muestra una vez
-
-La clave se verá como `sk-sim-copilot-...`. Usarás esto en la configuración a continuación.
-
-### Cursor
-
-Agrega lo siguiente a tu `.cursor/mcp.json` (nivel de proyecto) o configuración global de MCP de Cursor:
-
-```json
-{
- "mcpServers": {
- "sim-copilot": {
- "url": "https://www.sim.ai/api/mcp/copilot",
- "headers": {
- "X-API-Key": "YOUR_COPILOT_API_KEY"
- }
- }
- }
-}
-```
-
-Reemplaza `YOUR_COPILOT_API_KEY` con la clave que generaste anteriormente.
-
-### Claude Code
-
-Ejecuta el siguiente comando para añadir el servidor MCP de Copilot:
-
-```bash
-claude mcp add sim-copilot \
- --transport http \
- https://www.sim.ai/api/mcp/copilot \
- --header "X-API-Key: YOUR_COPILOT_API_KEY"
-```
-
-Reemplaza `YOUR_COPILOT_API_KEY` con tu clave.
-
-### Claude Desktop
-
-Claude Desktop requiere [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) para conectarse a servidores MCP basados en HTTP. Añade lo siguiente a tu archivo de configuración de Claude Desktop (`~/Library/Application Support/Claude/claude_desktop_config.json` en macOS):
-
-```json
-{
- "mcpServers": {
- "sim-copilot": {
- "command": "npx",
- "args": [
- "-y",
- "mcp-remote",
- "https://www.sim.ai/api/mcp/copilot",
- "--header",
- "X-API-Key: YOUR_COPILOT_API_KEY"
- ]
- }
- }
-}
-```
-
-Reemplaza `YOUR_COPILOT_API_KEY` con tu clave.
-
-### VS Code
-
-Añade lo siguiente a tu `settings.json` de VS Code o al `.vscode/settings.json` del espacio de trabajo:
-
-```json
-{
- "mcp": {
- "servers": {
- "sim-copilot": {
- "type": "http",
- "url": "https://www.sim.ai/api/mcp/copilot",
- "headers": {
- "X-API-Key": "YOUR_COPILOT_API_KEY"
- }
- }
- }
- }
-}
-```
-
-Reemplaza `YOUR_COPILOT_API_KEY` con tu clave.
-
-
- Para implementaciones auto-alojadas, reemplaza `https://www.sim.ai` con tu URL de Sim auto-alojada.
-
diff --git a/apps/docs/content/docs/fr/copilot/index.mdx b/apps/docs/content/docs/fr/copilot/index.mdx
index b092b26f08..53dc6c352c 100644
--- a/apps/docs/content/docs/fr/copilot/index.mdx
+++ b/apps/docs/content/docs/fr/copilot/index.mdx
@@ -246,98 +246,3 @@ L'utilisation de Copilot est facturée par jeton du LLM sous-jacent. Si vous att
Consultez la [page de calcul des coûts](/execution/costs) pour les détails de facturation.
-## Copilot MCP
-
-Vous pouvez utiliser Copilot comme serveur MCP dans votre éditeur ou client IA préféré. Cela vous permet de créer, tester, déployer et gérer des workflows Sim directement depuis des outils comme Cursor, Claude Code, Claude Desktop et VS Code.
-
-### Générer une clé API Copilot
-
-Pour vous connecter au serveur MCP Copilot, vous avez besoin d'une **clé API Copilot** :
-
-1. Rendez-vous sur [sim.ai](https://sim.ai) et connectez-vous
-2. Accédez à **Paramètres** → **Copilot**
-3. Cliquez sur **Générer une clé API**
-4. Copiez la clé — elle n'est affichée qu'une seule fois
-
-La clé ressemblera à `sk-sim-copilot-...`. Vous l'utiliserez dans la configuration ci-dessous.
-
-### Cursor
-
-Ajoutez ce qui suit à votre `.cursor/mcp.json` (niveau projet) ou aux paramètres MCP globaux de Cursor :
-
-```json
-{
- "mcpServers": {
- "sim-copilot": {
- "url": "https://www.sim.ai/api/mcp/copilot",
- "headers": {
- "X-API-Key": "YOUR_COPILOT_API_KEY"
- }
- }
- }
-}
-```
-
-Remplacez `YOUR_COPILOT_API_KEY` par la clé que vous avez générée ci-dessus.
-
-### Claude Code
-
-Exécutez la commande suivante pour ajouter le serveur MCP Copilot :
-
-```bash
-claude mcp add sim-copilot \
- --transport http \
- https://www.sim.ai/api/mcp/copilot \
- --header "X-API-Key: YOUR_COPILOT_API_KEY"
-```
-
-Remplacez `YOUR_COPILOT_API_KEY` par votre clé.
-
-### Claude Desktop
-
-Claude Desktop nécessite [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) pour se connecter aux serveurs MCP basés sur HTTP. Ajoutez ce qui suit à votre fichier de configuration Claude Desktop (`~/Library/Application Support/Claude/claude_desktop_config.json` sur macOS) :
-
-```json
-{
- "mcpServers": {
- "sim-copilot": {
- "command": "npx",
- "args": [
- "-y",
- "mcp-remote",
- "https://www.sim.ai/api/mcp/copilot",
- "--header",
- "X-API-Key: YOUR_COPILOT_API_KEY"
- ]
- }
- }
-}
-```
-
-Remplacez `YOUR_COPILOT_API_KEY` par votre clé.
-
-### VS Code
-
-Ajoutez ce qui suit à votre `settings.json` VS Code ou à votre `.vscode/settings.json` d'espace de travail :
-
-```json
-{
- "mcp": {
- "servers": {
- "sim-copilot": {
- "type": "http",
- "url": "https://www.sim.ai/api/mcp/copilot",
- "headers": {
- "X-API-Key": "YOUR_COPILOT_API_KEY"
- }
- }
- }
- }
-}
-```
-
-Remplacez `YOUR_COPILOT_API_KEY` par votre clé.
-
-
- Pour les déploiements auto-hébergés, remplacez `https://www.sim.ai` par votre URL Sim auto-hébergée.
-
diff --git a/apps/docs/content/docs/ja/copilot/index.mdx b/apps/docs/content/docs/ja/copilot/index.mdx
index 4714411d6b..d38f9a42cb 100644
--- a/apps/docs/content/docs/ja/copilot/index.mdx
+++ b/apps/docs/content/docs/ja/copilot/index.mdx
@@ -246,98 +246,3 @@ Copilotの使用量は、基盤となるLLMからのトークン単位で課金
課金の詳細については、[コスト計算ページ](/execution/costs)を参照してください。
-## Copilot MCP
-
-お気に入りのエディタやAIクライアントで、CopilotをMCPサーバーとして使用できます。これにより、Cursor、Claude Code、Claude Desktop、VS Codeなどのツールから直接、Simワークフローの構築、テスト、デプロイ、管理が可能になります。
-
-### Copilot APIキーの生成
-
-Copilot MCPサーバーに接続するには、**Copilot APIキー**が必要です。
-
-1. [sim.ai](https://sim.ai)にアクセスしてサインイン
-2. **設定** → **Copilot**に移動
-3. **APIキーを生成**をクリック
-4. キーをコピー(一度のみ表示されます)
-
-キーは`sk-sim-copilot-...`のような形式です。以下の設定で使用します。
-
-### Cursor
-
-`.cursor/mcp.json`(プロジェクトレベル)またはグローバルなCursor MCP設定に以下を追加してください。
-
-```json
-{
- "mcpServers": {
- "sim-copilot": {
- "url": "https://www.sim.ai/api/mcp/copilot",
- "headers": {
- "X-API-Key": "YOUR_COPILOT_API_KEY"
- }
- }
- }
-}
-```
-
-`YOUR_COPILOT_API_KEY`を上記で生成したキーに置き換えてください。
-
-### Claude Code
-
-次のコマンドを実行してCopilot MCPサーバーを追加します:
-
-```bash
-claude mcp add sim-copilot \
- --transport http \
- https://www.sim.ai/api/mcp/copilot \
- --header "X-API-Key: YOUR_COPILOT_API_KEY"
-```
-
-`YOUR_COPILOT_API_KEY`をあなたのキーに置き換えてください。
-
-### Claude Desktop
-
-Claude DesktopはHTTPベースのMCPサーバーに接続するために[`mcp-remote`](https://www.npmjs.com/package/mcp-remote)が必要です。Claude Desktopの設定ファイル(macOSでは`~/Library/Application Support/Claude/claude_desktop_config.json`)に以下を追加してください:
-
-```json
-{
- "mcpServers": {
- "sim-copilot": {
- "command": "npx",
- "args": [
- "-y",
- "mcp-remote",
- "https://www.sim.ai/api/mcp/copilot",
- "--header",
- "X-API-Key: YOUR_COPILOT_API_KEY"
- ]
- }
- }
-}
-```
-
-`YOUR_COPILOT_API_KEY`をあなたのキーに置き換えてください。
-
-### VS Code
-
-VS Codeの`settings.json`またはワークスペースの`.vscode/settings.json`に以下を追加してください:
-
-```json
-{
- "mcp": {
- "servers": {
- "sim-copilot": {
- "type": "http",
- "url": "https://www.sim.ai/api/mcp/copilot",
- "headers": {
- "X-API-Key": "YOUR_COPILOT_API_KEY"
- }
- }
- }
- }
-}
-```
-
-`YOUR_COPILOT_API_KEY`をあなたのキーに置き換えてください。
-
-
- セルフホスト型デプロイメントの場合は、`https://www.sim.ai`をあなたのセルフホスト型SimのURLに置き換えてください。
-
diff --git a/apps/docs/content/docs/zh/copilot/index.mdx b/apps/docs/content/docs/zh/copilot/index.mdx
index b3b54d134c..b37b1cdc6d 100644
--- a/apps/docs/content/docs/zh/copilot/index.mdx
+++ b/apps/docs/content/docs/zh/copilot/index.mdx
@@ -246,98 +246,3 @@ Copilot 的使用按底层 LLM 的 token 计费。如果达到使用上限,Cop
计费详情请参见 [成本计算页面](/execution/costs)。
-## Copilot MCP
-
-你可以在常用编辑器或 AI 客户端中将 Copilot 作为 MCP 服务器使用。这样可以直接在 Cursor、Claude Code、Claude Desktop 和 VS Code 等工具中构建、测试、部署和管理 Sim 工作流。
-
-### 生成 Copilot API 密钥
-
-要连接 Copilot MCP 服务器,你需要一个 **Copilot API 密钥**:
-
-1. 访问 [sim.ai](https://sim.ai) 并登录
-2. 进入 **设置** → **Copilot**
-3. 点击 **生成 API 密钥**
-4. 复制密钥 — 该密钥只显示一次
-
-密钥类似于 `sk-sim-copilot-...`。你将在下方配置中用到它。
-
-### Cursor
-
-将以下内容添加到你的 `.cursor/mcp.json`(项目级)或全局 Cursor MCP 设置中:
-
-```json
-{
- "mcpServers": {
- "sim-copilot": {
- "url": "https://www.sim.ai/api/mcp/copilot",
- "headers": {
- "X-API-Key": "YOUR_COPILOT_API_KEY"
- }
- }
- }
-}
-```
-
-将 `YOUR_COPILOT_API_KEY` 替换为你上面生成的密钥。
-
-### Claude Code
-
-运行以下命令以添加 Copilot MCP 服务器:
-
-```bash
-claude mcp add sim-copilot \
- --transport http \
- https://www.sim.ai/api/mcp/copilot \
- --header "X-API-Key: YOUR_COPILOT_API_KEY"
-```
-
-将 `YOUR_COPILOT_API_KEY` 替换为你的密钥。
-
-### Claude Desktop
-
-Claude Desktop 需要 [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) 以连接基于 HTTP 的 MCP 服务器。请将以下内容添加到你的 Claude Desktop 配置文件(macOS 上为 `~/Library/Application Support/Claude/claude_desktop_config.json`):
-
-```json
-{
- "mcpServers": {
- "sim-copilot": {
- "command": "npx",
- "args": [
- "-y",
- "mcp-remote",
- "https://www.sim.ai/api/mcp/copilot",
- "--header",
- "X-API-Key: YOUR_COPILOT_API_KEY"
- ]
- }
- }
-}
-```
-
-将 `YOUR_COPILOT_API_KEY` 替换为你的密钥。
-
-### VS Code
-
-请将以下内容添加到你的 VS Code `settings.json` 或工作区 `.vscode/settings.json`:
-
-```json
-{
- "mcp": {
- "servers": {
- "sim-copilot": {
- "type": "http",
- "url": "https://www.sim.ai/api/mcp/copilot",
- "headers": {
- "X-API-Key": "YOUR_COPILOT_API_KEY"
- }
- }
- }
- }
-}
-```
-
-将 `YOUR_COPILOT_API_KEY` 替换为你的密钥。
-
-
- 对于自托管部署,请将 `https://www.sim.ai` 替换为你的自托管 Sim URL。
-
diff --git a/apps/sim/app/.well-known/oauth-authorization-server/api/mcp/copilot/route.ts b/apps/sim/app/.well-known/oauth-authorization-server/api/mcp/copilot/route.ts
index d862fe277f..55d1b90a95 100644
--- a/apps/sim/app/.well-known/oauth-authorization-server/api/mcp/copilot/route.ts
+++ b/apps/sim/app/.well-known/oauth-authorization-server/api/mcp/copilot/route.ts
@@ -1,6 +1,6 @@
import type { NextResponse } from 'next/server'
-import { createMcpAuthorizationServerMetadataResponse } from '@/lib/mcp/oauth-discovery'
+import { copilotMcpDeprecatedResponse } from '@/lib/mcp/copilot-deprecated'
export async function GET(): Promise {
- return createMcpAuthorizationServerMetadataResponse()
+ return copilotMcpDeprecatedResponse()
}
diff --git a/apps/sim/app/.well-known/oauth-protected-resource/api/mcp/copilot/route.ts b/apps/sim/app/.well-known/oauth-protected-resource/api/mcp/copilot/route.ts
index a419ebda32..55d1b90a95 100644
--- a/apps/sim/app/.well-known/oauth-protected-resource/api/mcp/copilot/route.ts
+++ b/apps/sim/app/.well-known/oauth-protected-resource/api/mcp/copilot/route.ts
@@ -1,6 +1,6 @@
import type { NextResponse } from 'next/server'
-import { createMcpProtectedResourceMetadataResponse } from '@/lib/mcp/oauth-discovery'
+import { copilotMcpDeprecatedResponse } from '@/lib/mcp/copilot-deprecated'
export async function GET(): Promise {
- return createMcpProtectedResourceMetadataResponse()
+ return copilotMcpDeprecatedResponse()
}
diff --git a/apps/sim/app/api/mcp/copilot/.well-known/oauth-authorization-server/route.ts b/apps/sim/app/api/mcp/copilot/.well-known/oauth-authorization-server/route.ts
index 3f2976ff02..c54b72dfcb 100644
--- a/apps/sim/app/api/mcp/copilot/.well-known/oauth-authorization-server/route.ts
+++ b/apps/sim/app/api/mcp/copilot/.well-known/oauth-authorization-server/route.ts
@@ -1,12 +1,4 @@
-import type { NextRequest, NextResponse } from 'next/server'
-import { mcpOauthAuthorizationServerMetadataContract } from '@/lib/api/contracts/mcp-oauth'
-import { parseRequest } from '@/lib/api/server'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
-import { createMcpAuthorizationServerMetadataResponse } from '@/lib/mcp/oauth-discovery'
+import { copilotMcpDeprecatedResponse } from '@/lib/mcp/copilot-deprecated'
-export const GET = withRouteHandler(async (request: NextRequest): Promise => {
- const parsed = await parseRequest(mcpOauthAuthorizationServerMetadataContract, request, {})
- if (!parsed.success) return parsed.response as NextResponse
-
- return createMcpAuthorizationServerMetadataResponse()
-})
+export const GET = withRouteHandler(async () => copilotMcpDeprecatedResponse())
diff --git a/apps/sim/app/api/mcp/copilot/.well-known/oauth-protected-resource/route.ts b/apps/sim/app/api/mcp/copilot/.well-known/oauth-protected-resource/route.ts
index 1e17b126b3..c54b72dfcb 100644
--- a/apps/sim/app/api/mcp/copilot/.well-known/oauth-protected-resource/route.ts
+++ b/apps/sim/app/api/mcp/copilot/.well-known/oauth-protected-resource/route.ts
@@ -1,12 +1,4 @@
-import type { NextRequest, NextResponse } from 'next/server'
-import { mcpOauthProtectedResourceMetadataContract } from '@/lib/api/contracts/mcp-oauth'
-import { parseRequest } from '@/lib/api/server'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
-import { createMcpProtectedResourceMetadataResponse } from '@/lib/mcp/oauth-discovery'
+import { copilotMcpDeprecatedResponse } from '@/lib/mcp/copilot-deprecated'
-export const GET = withRouteHandler(async (request: NextRequest): Promise => {
- const parsed = await parseRequest(mcpOauthProtectedResourceMetadataContract, request, {})
- if (!parsed.success) return parsed.response as NextResponse
-
- return createMcpProtectedResourceMetadataResponse()
-})
+export const GET = withRouteHandler(async () => copilotMcpDeprecatedResponse())
diff --git a/apps/sim/app/api/mcp/copilot/route.test.ts b/apps/sim/app/api/mcp/copilot/route.test.ts
new file mode 100644
index 0000000000..7db9860fbc
--- /dev/null
+++ b/apps/sim/app/api/mcp/copilot/route.test.ts
@@ -0,0 +1,62 @@
+/**
+ * Tests for the deprecated Copilot MCP route
+ *
+ * @vitest-environment node
+ */
+import { NextRequest } from 'next/server'
+import { describe, expect, it } from 'vitest'
+import { GET as authServerDiscoveryGET } from '@/app/api/mcp/copilot/.well-known/oauth-authorization-server/route'
+import { GET as protectedResourceDiscoveryGET } from '@/app/api/mcp/copilot/.well-known/oauth-protected-resource/route'
+import { DELETE, GET, POST } from '@/app/api/mcp/copilot/route'
+
+const URL = 'http://localhost:3000/api/mcp/copilot'
+
+describe('Deprecated Copilot MCP route', () => {
+ it('GET returns 410', async () => {
+ const response = await GET(new NextRequest(URL))
+ expect(response.status).toBe(410)
+ })
+
+ it('POST returns 410 with a JSON-RPC error envelope', async () => {
+ const request = new NextRequest(URL, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize' }),
+ })
+ const response = await POST(request)
+ expect(response.status).toBe(410)
+
+ const body = (await response.json()) as { jsonrpc?: string; error?: { message?: string } }
+ expect(body.jsonrpc).toBe('2.0')
+ expect(body.error?.message).toContain('deprecated')
+ })
+
+ it('POST still returns 410 when an x-api-key header is present', async () => {
+ const request = new NextRequest(URL, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json', 'x-api-key': 'sk-sim-copilot-test' },
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list' }),
+ })
+ const response = await POST(request)
+ expect(response.status).toBe(410)
+ })
+
+ it('DELETE returns 410', async () => {
+ const response = await DELETE(new NextRequest(URL, { method: 'DELETE' }))
+ expect(response.status).toBe(410)
+ })
+
+ it('copilot OAuth authorization-server discovery returns 410', async () => {
+ const response = await authServerDiscoveryGET(
+ new NextRequest(`${URL}/.well-known/oauth-authorization-server`)
+ )
+ expect(response.status).toBe(410)
+ })
+
+ it('copilot OAuth protected-resource discovery returns 410', async () => {
+ const response = await protectedResourceDiscoveryGET(
+ new NextRequest(`${URL}/.well-known/oauth-protected-resource`)
+ )
+ expect(response.status).toBe(410)
+ })
+})
diff --git a/apps/sim/app/api/mcp/copilot/route.ts b/apps/sim/app/api/mcp/copilot/route.ts
index 2bda0f3670..6ec1475d2e 100644
--- a/apps/sim/app/api/mcp/copilot/route.ts
+++ b/apps/sim/app/api/mcp/copilot/route.ts
@@ -1,740 +1,13 @@
-import { Server } from '@modelcontextprotocol/sdk/server/index.js'
-import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js'
-import {
- CallToolRequestSchema,
- type CallToolResult,
- ErrorCode,
- type JSONRPCError,
- ListToolsRequestSchema,
- type ListToolsResult,
- McpError,
- type RequestId,
-} from '@modelcontextprotocol/sdk/types.js'
-import { createLogger } from '@sim/logger'
-import { toError } from '@sim/utils/errors'
-import { generateId } from '@sim/utils/id'
-import { authorizeWorkflowByWorkspacePermission } from '@sim/workflow-authz'
-import { type NextRequest, NextResponse } from 'next/server'
-import { mcpRequestBodySchema, mcpToolCallParamsSchema } from '@/lib/api/contracts/mcp'
-import { validateOAuthAccessToken } from '@/lib/auth/oauth-token'
-import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
-import { generateWorkspaceContext } from '@/lib/copilot/chat/workspace-context'
-import { ORCHESTRATION_TIMEOUT_MS, SIM_AGENT_API_URL } from '@/lib/copilot/constants'
-import { createRequestId } from '@/lib/copilot/request/http'
-import { runHeadlessCopilotLifecycle } from '@/lib/copilot/request/lifecycle/headless'
-import { orchestrateSubagentStream } from '@/lib/copilot/request/subagent'
-import { ensureHandlersRegistered, executeTool } from '@/lib/copilot/tool-executor'
-import { ensureWorkspaceAccess } from '@/lib/copilot/tools/handlers/access'
-import { prepareExecutionContext } from '@/lib/copilot/tools/handlers/context'
-import { DIRECT_TOOL_DEFS, SUBAGENT_TOOL_DEFS } from '@/lib/copilot/tools/mcp/definitions'
-import { env } from '@/lib/core/config/env'
-import { RateLimiter } from '@/lib/core/rate-limiter'
-import { getBaseUrl } from '@/lib/core/utils/urls'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
-import { resolveWorkflowIdForUser } from '@/lib/workflows/utils'
-
-const logger = createLogger('CopilotMcpAPI')
-const mcpRateLimiter = new RateLimiter()
-const DEFAULT_COPILOT_MODEL = 'claude-opus-4-6'
+import {
+ copilotMcpDeprecatedJsonRpcResponse,
+ copilotMcpDeprecatedResponse,
+} from '@/lib/mcp/copilot-deprecated'
export const dynamic = 'force-dynamic'
-export const runtime = 'nodejs'
-export const maxDuration = 3600
-
-interface CopilotKeyAuthResult {
- success: boolean
- userId?: string
- error?: string
-}
-
-/**
- * Validates a copilot API key by forwarding it to the Go copilot service's
- * `/api/validate-key` endpoint. Returns the associated userId on success.
- */
-async function authenticateCopilotApiKey(apiKey: string): Promise {
- try {
- const internalSecret = env.INTERNAL_API_SECRET
- if (!internalSecret) {
- logger.error('INTERNAL_API_SECRET not configured')
- return { success: false, error: 'Server configuration error' }
- }
-
- const { fetchGo } = await import('@/lib/copilot/request/go/fetch')
- const res = await fetchGo(`${SIM_AGENT_API_URL}/api/validate-key`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'x-api-key': internalSecret,
- },
- body: JSON.stringify({ targetApiKey: apiKey }),
- signal: AbortSignal.timeout(10_000),
- spanName: 'sim → go /api/validate-key (mcp)',
- operation: 'mcp_validate_key',
- })
-
- if (!res.ok) {
- const body = await res.json().catch(() => null)
- const upstream = (body as Record)?.message
- const status = res.status
-
- if (status === 401 || status === 403) {
- return {
- success: false,
- error: `Invalid Copilot API key. Generate a new key in Settings → Copilot and set it in the x-api-key header.`,
- }
- }
- if (status === 402) {
- return {
- success: false,
- error: `Usage limit exceeded for this Copilot API key. Upgrade your plan or wait for your quota to reset.`,
- }
- }
-
- return {
- success: false,
- error: String(upstream ?? 'Copilot API key validation failed'),
- }
- }
-
- const data = (await res.json()) as { ok?: boolean; userId?: string }
- if (!data.ok || !data.userId) {
- return {
- success: false,
- error: 'Invalid Copilot API key. Generate a new key in Settings → Copilot.',
- }
- }
-
- return { success: true, userId: data.userId }
- } catch (error) {
- logger.error('Copilot API key validation failed', { error })
- return {
- success: false,
- error:
- 'Could not validate Copilot API key — the authentication service is temporarily unreachable. This is NOT a problem with the API key itself; please retry shortly.',
- }
- }
-}
-
-/**
- * MCP Server instructions that guide LLMs on how to use the Sim copilot tools.
- * This is included in the initialize response to help external LLMs understand
- * the workflow lifecycle and best practices.
- */
-const MCP_SERVER_INSTRUCTIONS = `
-## Sim Workflow Copilot
-
-Sim is a workflow automation platform. Workflows are visual pipelines of connected blocks (Agent, Function, Condition, API, integrations, etc.). The Agent block is the core — an LLM with tools, memory, structured output, and knowledge bases.
-
-### Workflow Lifecycle (Happy Path)
-
-1. \`list_workspaces\` → know where to work
-2. \`create_workflow(name, workspaceId)\` → get a workflowId
-3. \`sim_workflow(request, workflowId)\` → plan and build in one pass
-4. \`sim_test(request, workflowId)\` → verify it works
-5. \`sim_deploy("deploy as api", workflowId)\` → make it accessible externally (optional)
-
-### Working with Existing Workflows
-
-When the user refers to a workflow by name or description ("the email one", "my Slack bot"):
-1. Use \`sim_discovery\` to find it by functionality
-2. Or use \`list_workflows\` and match by name
-3. Then pass the workflowId to other tools
-
-### Organization
-
-- \`rename_workflow\` — rename a workflow
-- \`move_workflow\` — move a workflow into a folder (or back to root by clearing the folder id)
-- \`move_folder\` — nest a folder inside another (or move it back to root by clearing the parent id)
-- \`create_folder(name, parentId)\` — create nested folder hierarchies
-
-### Key Rules
-
-- You can test workflows immediately after building — deployment is only needed for external access (API, chat, MCP).
-- Tools that operate on a specific workflow such as \`sim_workflow\`, \`sim_test\`, \`sim_deploy\`, and workflow-scoped \`sim_info\` requests require \`workflowId\`.
-- If the user reports errors, route through \`sim_workflow\` and ask it to reproduce, inspect logs, and fix the issue end to end.
-- Variable syntax: \`\` for block outputs, \`{{ENV_VAR}}\` for env vars.
-`
-
-type HeaderMap = Record
-
-function createError(id: RequestId, code: ErrorCode | number, message: string): JSONRPCError {
- return {
- jsonrpc: '2.0',
- id,
- error: { code, message },
- }
-}
-
-function readHeader(headers: HeaderMap | undefined, name: string): string | undefined {
- if (!headers) return undefined
- const value = headers[name.toLowerCase()]
- if (Array.isArray(value)) {
- return value[0]
- }
- return value
-}
-
-function buildMcpServer(abortSignal?: AbortSignal): Server {
- const server = new Server(
- {
- name: 'sim-copilot',
- version: '1.0.0',
- },
- {
- capabilities: { tools: {} },
- instructions: MCP_SERVER_INSTRUCTIONS,
- }
- )
-
- server.setRequestHandler(ListToolsRequestSchema, async () => {
- const directTools = DIRECT_TOOL_DEFS.map((tool) => ({
- name: tool.name,
- description: tool.description,
- inputSchema: tool.inputSchema,
- ...(tool.annotations && { annotations: tool.annotations }),
- }))
-
- const subagentTools = SUBAGENT_TOOL_DEFS.map((tool) => ({
- name: tool.name,
- description: tool.description,
- inputSchema: tool.inputSchema,
- ...(tool.annotations && { annotations: tool.annotations }),
- }))
-
- const result: ListToolsResult = {
- tools: [...directTools, ...subagentTools],
- }
-
- return result
- })
-
- server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
- const headers = (extra.requestInfo?.headers || {}) as HeaderMap
- const apiKeyHeader = readHeader(headers, 'x-api-key')
- const authorizationHeader = readHeader(headers, 'authorization')
-
- let authResult: CopilotKeyAuthResult = { success: false }
-
- if (authorizationHeader?.startsWith('Bearer ')) {
- const token = authorizationHeader.slice(7)
- const oauthResult = await validateOAuthAccessToken(token)
- if (oauthResult.success && oauthResult.userId) {
- if (!oauthResult.scopes?.includes('mcp:tools')) {
- return {
- content: [
- {
- type: 'text' as const,
- text: 'AUTHENTICATION ERROR: OAuth token is missing the required "mcp:tools" scope. Re-authorize with the correct scopes.',
- },
- ],
- isError: true,
- }
- }
- authResult = { success: true, userId: oauthResult.userId }
- } else {
- return {
- content: [
- {
- type: 'text' as const,
- text: `AUTHENTICATION ERROR: ${oauthResult.error ?? 'Invalid OAuth access token'} Do NOT retry — re-authorize via OAuth.`,
- },
- ],
- isError: true,
- }
- }
- } else if (apiKeyHeader) {
- authResult = await authenticateCopilotApiKey(apiKeyHeader)
- }
-
- if (!authResult.success || !authResult.userId) {
- const errorMsg = apiKeyHeader
- ? `AUTHENTICATION ERROR: ${authResult.error} Do NOT retry — this will fail until the user fixes their Copilot API key.`
- : 'AUTHENTICATION ERROR: No authentication provided. Provide a Bearer token (OAuth 2.1) or an x-api-key header. Generate a Copilot API key in Settings → Copilot.'
- logger.warn('MCP copilot auth failed', { method: request.method })
- return {
- content: [
- {
- type: 'text' as const,
- text: errorMsg,
- },
- ],
- isError: true,
- }
- }
-
- const rateLimitResult = await mcpRateLimiter.checkRateLimitWithSubscription(
- authResult.userId,
- await getHighestPrioritySubscription(authResult.userId),
- 'api-endpoint',
- false
- )
-
- if (!rateLimitResult.allowed) {
- return {
- content: [
- {
- type: 'text' as const,
- text: `RATE LIMIT: Too many requests. Please wait and retry after ${rateLimitResult.resetAt.toISOString()}.`,
- },
- ],
- isError: true,
- }
- }
-
- const paramsValidation = mcpToolCallParamsSchema.safeParse(request.params)
- if (!paramsValidation.success) {
- throw new McpError(ErrorCode.InvalidParams, 'Tool name required')
- }
- const params = paramsValidation.data
-
- const result = await handleToolsCall(
- {
- name: params.name,
- arguments: params.arguments,
- },
- authResult.userId,
- abortSignal
- )
-
- return result
- })
-
- return server
-}
-
-async function handleMcpRequestWithSdk(
- request: NextRequest,
- parsedBody: unknown
-): Promise {
- const server = buildMcpServer(request.signal)
- const transport = new WebStandardStreamableHTTPServerTransport({
- sessionIdGenerator: undefined,
- enableJsonResponse: true,
- })
-
- await server.connect(transport)
-
- try {
- return await transport.handleRequest(request, { parsedBody })
- } finally {
- await server.close().catch(() => {})
- await transport.close().catch(() => {})
- }
-}
-
-export const GET = withRouteHandler(async () => {
- // Return 405 to signal that server-initiated SSE notifications are not
- // supported. Without this, clients like mcp-remote will repeatedly
- // reconnect trying to open an SSE stream, flooding the logs with GETs.
- return new NextResponse(null, { status: 405 })
-})
-
-export const POST = withRouteHandler(async (request: NextRequest) => {
- const hasAuth = request.headers.has('authorization') || request.headers.has('x-api-key')
-
- if (!hasAuth) {
- const origin = getBaseUrl().replace(/\/$/, '')
- const resourceMetadataUrl = `${origin}/.well-known/oauth-protected-resource/api/mcp/copilot`
- return new NextResponse(JSON.stringify({ error: 'unauthorized' }), {
- status: 401,
- headers: {
- 'WWW-Authenticate': `Bearer resource_metadata="${resourceMetadataUrl}", scope="mcp:tools"`,
- 'Content-Type': 'application/json',
- },
- })
- }
-
- try {
- let parsedBody: unknown
-
- try {
- parsedBody = await request.json()
- } catch {
- return NextResponse.json(createError(0, ErrorCode.ParseError, 'Invalid JSON body'), {
- status: 400,
- })
- }
-
- const bodyValidation = mcpRequestBodySchema.safeParse(parsedBody)
- if (!bodyValidation.success) {
- return NextResponse.json(
- createError(0, ErrorCode.InvalidRequest, 'Invalid JSON-RPC message'),
- {
- status: 400,
- }
- )
- }
-
- return await handleMcpRequestWithSdk(request, bodyValidation.data)
- } catch (error) {
- if (request.signal.aborted || (error as Error)?.name === 'AbortError') {
- return NextResponse.json(
- createError(0, ErrorCode.ConnectionClosed, 'Client cancelled request'),
- { status: 499 }
- )
- }
-
- logger.error('Error handling MCP request', { error })
- return NextResponse.json(createError(0, ErrorCode.InternalError, 'Internal error'), {
- status: 500,
- })
- }
-})
-
-export const DELETE = withRouteHandler(async (request: NextRequest) => {
- void request
- return NextResponse.json(createError(0, -32000, 'Method not allowed.'), { status: 405 })
-})
-
-async function handleToolsCall(
- params: { name: string; arguments?: Record },
- userId: string,
- abortSignal?: AbortSignal
-): Promise {
- const args = params.arguments || {}
-
- const directTool = DIRECT_TOOL_DEFS.find((tool) => tool.name === params.name)
- if (directTool) {
- return handleDirectToolCall(directTool, args, userId)
- }
-
- const subagentTool = SUBAGENT_TOOL_DEFS.find((tool) => tool.name === params.name)
- if (subagentTool) {
- return handleSubagentToolCall(subagentTool, args, userId, abortSignal)
- }
-
- throw new McpError(ErrorCode.MethodNotFound, `Tool not found: ${params.name}`)
-}
-
-async function handleDirectToolCall(
- toolDef: (typeof DIRECT_TOOL_DEFS)[number],
- args: Record,
- userId: string
-): Promise {
- try {
- const rawWorkflowId = (args.workflowId as string) || ''
- let resolvedWorkspaceId: string | undefined
- if (rawWorkflowId) {
- const authorization = await authorizeWorkflowByWorkspacePermission({
- workflowId: rawWorkflowId,
- userId,
- action: 'read',
- })
- if (!authorization.allowed) {
- return {
- content: [
- {
- type: 'text',
- text: JSON.stringify(
- { success: false, error: 'Workflow not found or access denied' },
- null,
- 2
- ),
- },
- ],
- isError: true,
- }
- }
- resolvedWorkspaceId = authorization.workflow?.workspaceId || undefined
- }
- const execContext = await prepareExecutionContext(
- userId,
- rawWorkflowId,
- (args.chatId as string) || undefined,
- { workspaceId: resolvedWorkspaceId }
- )
-
- const toolCall = {
- id: generateId(),
- name: toolDef.toolId,
- status: 'pending' as const,
- params: args as Record,
- startTime: Date.now(),
- }
-
- ensureHandlersRegistered()
- const result = await executeTool(toolCall.name, toolCall.params || {}, execContext)
-
- return {
- content: [
- {
- type: 'text',
- text: JSON.stringify(result.output ?? result, null, 2),
- },
- ],
- isError: !result.success,
- }
- } catch (error) {
- logger.error('Direct tool execution failed', { tool: toolDef.name, error })
- return {
- content: [
- {
- type: 'text',
- text: `Tool execution failed: ${toError(error).message}`,
- },
- ],
- isError: true,
- }
- }
-}
-
-/**
- * Build mode uses the main /api/mcp orchestrator instead of /api/subagent/workflow.
- * The main agent still delegates workflow work to the workflow subagent inside Go;
- * this helper simply uses the full headless lifecycle so build requests behave like
- * the primary MCP chat flow.
- */
-async function handleBuildToolCall(
- args: Record,
- userId: string,
- abortSignal?: AbortSignal
-): Promise {
- try {
- const requestText = (args.request as string) || JSON.stringify(args)
- const workflowId = args.workflowId as string | undefined
- let resolvedWorkflowName: string | undefined
- let resolvedWorkspaceId: string | undefined
-
- const resolved = workflowId
- ? await (async () => {
- const authorization = await authorizeWorkflowByWorkspacePermission({
- workflowId,
- userId,
- action: 'read',
- })
- resolvedWorkflowName = authorization.workflow?.name || undefined
- resolvedWorkspaceId = authorization.workflow?.workspaceId || undefined
- return authorization.allowed
- ? {
- status: 'resolved' as const,
- workflowId,
- workflowName: resolvedWorkflowName,
- }
- : {
- status: 'not_found' as const,
- message: 'workflowId is required for build. Call create_workflow first.',
- }
- })()
- : await resolveWorkflowIdForUser(userId)
-
- if (resolved.status === 'resolved') {
- resolvedWorkflowName ||= resolved.workflowName
- }
-
- if (!resolved || resolved.status !== 'resolved') {
- return {
- content: [
- {
- type: 'text',
- text: JSON.stringify(
- {
- success: false,
- error:
- resolved?.message ??
- 'workflowId is required for build. Call create_workflow first.',
- },
- null,
- 2
- ),
- },
- ],
- isError: true,
- }
- }
-
- const chatId = generateId()
- const executionContext = await prepareExecutionContext(userId, resolved.workflowId, chatId, {
- workspaceId: resolvedWorkspaceId,
- })
- resolvedWorkspaceId = executionContext.workspaceId
- let workspaceContext: string | undefined
- if (resolvedWorkspaceId) {
- try {
- workspaceContext = await generateWorkspaceContext(resolvedWorkspaceId, userId)
- } catch (error) {
- logger.warn('Failed to generate workspace context for build tool call', {
- workflowId: resolved.workflowId,
- workspaceId: resolvedWorkspaceId,
- error: toError(error).message,
- })
- }
- }
-
- const requestPayload = {
- message: requestText,
- workflowId: resolved.workflowId,
- ...(resolvedWorkflowName ? { workflowName: resolvedWorkflowName } : {}),
- ...(resolvedWorkspaceId ? { workspaceId: resolvedWorkspaceId } : {}),
- ...(workspaceContext ? { workspaceContext } : {}),
- userId,
- model: DEFAULT_COPILOT_MODEL,
- mode: 'agent',
- commands: ['fast'],
- messageId: generateId(),
- chatId,
- }
-
- const result = await runHeadlessCopilotLifecycle(requestPayload, {
- userId,
- workflowId: resolved.workflowId,
- workspaceId: resolvedWorkspaceId,
- chatId,
- goRoute: '/api/mcp',
- executionContext,
- autoExecuteTools: true,
- timeout: ORCHESTRATION_TIMEOUT_MS,
- interactive: false,
- abortSignal,
- })
-
- const responseData = {
- success: result.success,
- content: result.content,
- toolCalls: result.toolCalls,
- error: result.error,
- }
-
- return {
- content: [{ type: 'text', text: JSON.stringify(responseData, null, 2) }],
- isError: !result.success,
- }
- } catch (error) {
- logger.error('Build tool call failed', { error })
- return {
- content: [
- {
- type: 'text',
- text: `Build failed: ${toError(error).message}`,
- },
- ],
- isError: true,
- }
- }
-}
-
-async function handleSubagentToolCall(
- toolDef: (typeof SUBAGENT_TOOL_DEFS)[number],
- args: Record,
- userId: string,
- abortSignal?: AbortSignal
-): Promise {
- if (toolDef.agentId === 'workflow') {
- return handleBuildToolCall(args, userId, abortSignal)
- }
-
- try {
- const requestText =
- (args.request as string) ||
- (args.message as string) ||
- (args.error as string) ||
- JSON.stringify(args)
- const simRequestId = createRequestId()
-
- const context = (args.context as Record) || {}
- if (args.plan && !context.plan) {
- context.plan = args.plan
- }
-
- // Authorize user-supplied workflowId / workspaceId before forwarding downstream
- const rawWorkflowId = args.workflowId as string | undefined
- const rawWorkspaceId = args.workspaceId as string | undefined
- let resolvedWorkflowId: string | undefined
- let resolvedWorkspaceId: string | undefined
-
- if (rawWorkflowId) {
- const authorization = await authorizeWorkflowByWorkspacePermission({
- workflowId: rawWorkflowId,
- userId,
- action: 'read',
- })
- if (!authorization.allowed) {
- return {
- content: [
- {
- type: 'text',
- text: JSON.stringify(
- { success: false, error: 'Workflow not found or access denied' },
- null,
- 2
- ),
- },
- ],
- isError: true,
- }
- }
- resolvedWorkflowId = rawWorkflowId
- resolvedWorkspaceId = authorization.workflow?.workspaceId || undefined
- } else if (rawWorkspaceId) {
- await ensureWorkspaceAccess(rawWorkspaceId, userId, 'read')
- resolvedWorkspaceId = rawWorkspaceId
- }
-
- const result = await orchestrateSubagentStream(
- toolDef.agentId,
- {
- message: requestText,
- workflowId: resolvedWorkflowId,
- workspaceId: resolvedWorkspaceId,
- context,
- model: DEFAULT_COPILOT_MODEL,
- headless: true,
- source: 'mcp',
- },
- {
- userId,
- workflowId: resolvedWorkflowId,
- workspaceId: resolvedWorkspaceId,
- simRequestId,
- abortSignal,
- }
- )
-
- let responseData: unknown
- if (result.structuredResult) {
- responseData = {
- success: result.structuredResult.success ?? result.success,
- type: result.structuredResult.type,
- summary: result.structuredResult.summary,
- data: result.structuredResult.data,
- }
- } else if (result.error) {
- responseData = {
- success: false,
- error: result.error,
- errors: result.errors,
- }
- } else {
- responseData = {
- success: result.success,
- content: result.content,
- }
- }
+export const GET = withRouteHandler(async () => copilotMcpDeprecatedResponse())
- return {
- content: [
- {
- type: 'text',
- text: JSON.stringify(responseData, null, 2),
- },
- ],
- isError: !result.success,
- }
- } catch (error) {
- logger.error('Subagent tool call failed', {
- tool: toolDef.name,
- agentId: toolDef.agentId,
- error,
- })
+export const POST = withRouteHandler(async () => copilotMcpDeprecatedJsonRpcResponse())
- return {
- content: [
- {
- type: 'text',
- text: `Subagent call failed: ${toError(error).message}`,
- },
- ],
- isError: true,
- }
- }
-}
+export const DELETE = withRouteHandler(async () => copilotMcpDeprecatedJsonRpcResponse())
diff --git a/apps/sim/app/api/v1/copilot/chat/route.test.ts b/apps/sim/app/api/v1/copilot/chat/route.test.ts
new file mode 100644
index 0000000000..fef0a7eec8
--- /dev/null
+++ b/apps/sim/app/api/v1/copilot/chat/route.test.ts
@@ -0,0 +1,26 @@
+/**
+ * Tests for the deprecated v1 copilot chat API route
+ *
+ * @vitest-environment node
+ */
+import { NextRequest } from 'next/server'
+import { describe, expect, it } from 'vitest'
+import { POST } from '@/app/api/v1/copilot/chat/route'
+
+const URL = 'http://localhost:3000/api/v1/copilot/chat'
+
+describe('Deprecated v1 copilot chat route', () => {
+ it('POST returns 410 with a success:false error body', async () => {
+ const request = new NextRequest(URL, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json', 'x-api-key': 'sk-test' },
+ body: JSON.stringify({ message: 'hello' }),
+ })
+ const response = await POST(request)
+ expect(response.status).toBe(410)
+
+ const body = (await response.json()) as { success?: boolean; error?: string }
+ expect(body.success).toBe(false)
+ expect(body.error).toContain('deprecated')
+ })
+})
diff --git a/apps/sim/app/api/v1/copilot/chat/route.ts b/apps/sim/app/api/v1/copilot/chat/route.ts
index 6eaa1424b7..ac3759d96f 100644
--- a/apps/sim/app/api/v1/copilot/chat/route.ts
+++ b/apps/sim/app/api/v1/copilot/chat/route.ts
@@ -1,151 +1,18 @@
-import { createLogger } from '@sim/logger'
-import { toError } from '@sim/utils/errors'
-import { generateId } from '@sim/utils/id'
-import { type NextRequest, NextResponse } from 'next/server'
-import { v1CopilotChatContract } from '@/lib/api/contracts/v1/copilot'
-import { getValidationErrorMessage, parseRequest } from '@/lib/api/server'
-import { runHeadlessCopilotLifecycle } from '@/lib/copilot/request/lifecycle/headless'
+import { NextResponse } from 'next/server'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
-import { getWorkflowById, resolveWorkflowIdForUser } from '@/lib/workflows/utils'
-import { authenticateRequest } from '@/app/api/v1/middleware'
-
-export const maxDuration = 3600
-
-const logger = createLogger('CopilotHeadlessAPI')
-const DEFAULT_COPILOT_MODEL = 'claude-opus-4-6'
/**
* POST /api/v1/copilot/chat
- * Headless copilot endpoint for server-side orchestration.
*
- * workflowId is optional - if not provided:
- * - If workflowName is provided, finds that workflow
- * - If exactly one workflow is available, uses that workflow as context
- * - Otherwise requires workflowId or workflowName to disambiguate
+ * Deprecated: the v1 headless copilot chat API has been removed. The endpoint
+ * returns 410 Gone for all callers.
*/
-export const POST = withRouteHandler(async (req: NextRequest) => {
- let messageId: string | undefined
- const authorized = await authenticateRequest(req, 'copilot-chat')
- if (authorized instanceof NextResponse) {
- return authorized
- }
- const { userId, rateLimit } = authorized
- const auth = {
- authenticated: true as const,
- userId,
- keyType: rateLimit.keyType,
- workspaceId: rateLimit.workspaceId,
- }
-
- try {
- const parsedRequest = await parseRequest(
- v1CopilotChatContract,
- req,
- {},
- {
- validationErrorResponse: (error) =>
- NextResponse.json(
- {
- success: false,
- error: getValidationErrorMessage(error, 'Invalid request'),
- details: error.issues,
- },
- { status: 400 }
- ),
- invalidJsonResponse: () =>
- NextResponse.json({ success: false, error: 'Invalid request' }, { status: 400 }),
- }
- )
- if (!parsedRequest.success) return parsedRequest.response
-
- const parsed = parsedRequest.data.body
- const selectedModel = parsed.model || DEFAULT_COPILOT_MODEL
-
- // Resolve workflow ID
- const resolved = await resolveWorkflowIdForUser(
- auth.userId,
- parsed.workflowId,
- parsed.workflowName,
- auth.keyType === 'workspace' ? auth.workspaceId : undefined
- )
- if (resolved.status !== 'resolved') {
- return NextResponse.json(
- {
- success: false,
- error: resolved.message,
- },
- { status: 400 }
- )
- }
-
- if (auth.keyType === 'workspace' && auth.workspaceId) {
- const workflow = await getWorkflowById(resolved.workflowId)
- if (!workflow?.workspaceId || workflow.workspaceId !== auth.workspaceId) {
- return NextResponse.json(
- { success: false, error: 'API key is not authorized for this workspace' },
- { status: 403 }
- )
- }
- }
-
- // Transform mode to transport mode (same as client API)
- // build and agent both map to 'agent' on the backend
- const effectiveMode = parsed.mode === 'agent' ? 'build' : parsed.mode
- const transportMode = effectiveMode === 'build' ? 'agent' : effectiveMode
-
- // Always generate a chatId - required for artifacts system to work with subagents
- const chatId = parsed.chatId || generateId()
-
- messageId = generateId()
- logger.info(
- messageId
- ? `Received headless copilot chat start request [messageId:${messageId}]`
- : 'Received headless copilot chat start request',
- {
- workflowId: resolved.workflowId,
- workflowName: parsed.workflowName,
- chatId,
- mode: transportMode,
- autoExecuteTools: parsed.autoExecuteTools,
- timeout: parsed.timeout,
- }
- )
- const requestPayload = {
- message: parsed.message,
- workflowId: resolved.workflowId,
- userId: auth.userId,
- model: selectedModel,
- mode: transportMode,
- messageId,
- chatId,
- }
-
- const result = await runHeadlessCopilotLifecycle(requestPayload, {
- userId: auth.userId,
- workflowId: resolved.workflowId,
- chatId,
- goRoute: '/api/mcp',
- autoExecuteTools: parsed.autoExecuteTools,
- timeout: parsed.timeout,
- interactive: false,
- })
-
- return NextResponse.json({
- success: result.success,
- content: result.content,
- toolCalls: result.toolCalls,
- chatId: result.chatId || chatId,
- error: result.error,
- })
- } catch (error) {
- logger.error(
- messageId
- ? `Headless copilot request failed [messageId:${messageId}]`
- : 'Headless copilot request failed',
- {
- error: toError(error).message,
- }
- )
- return NextResponse.json({ success: false, error: 'Internal server error' }, { status: 500 })
- }
-})
+export const POST = withRouteHandler(async () =>
+ NextResponse.json(
+ {
+ success: false,
+ error: 'The v1 copilot chat API has been deprecated and is no longer available.',
+ },
+ { status: 410, headers: { 'Cache-Control': 'no-store' } }
+ )
+)
diff --git a/apps/sim/lib/mcp/copilot-deprecated.ts b/apps/sim/lib/mcp/copilot-deprecated.ts
new file mode 100644
index 0000000000..a3be7e7ebe
--- /dev/null
+++ b/apps/sim/lib/mcp/copilot-deprecated.ts
@@ -0,0 +1,35 @@
+import { NextResponse } from 'next/server'
+
+const DEPRECATION_MESSAGE = 'Copilot MCP has been deprecated and is no longer available.'
+
+/**
+ * Standard 410 response for the deprecated Copilot MCP surface. Used by the
+ * copilot MCP resource route and its copilot-specific OAuth discovery routes.
+ */
+export function copilotMcpDeprecatedResponse(): NextResponse {
+ return NextResponse.json(
+ { error: 'gone', message: DEPRECATION_MESSAGE },
+ {
+ status: 410,
+ headers: { 'Cache-Control': 'no-store' },
+ }
+ )
+}
+
+/**
+ * JSON-RPC flavored 410 response for the deprecated Copilot MCP `POST` endpoint,
+ * so MCP clients surface a clean error envelope instead of an opaque body.
+ */
+export function copilotMcpDeprecatedJsonRpcResponse(): NextResponse {
+ return NextResponse.json(
+ {
+ jsonrpc: '2.0',
+ id: null,
+ error: { code: -32000, message: DEPRECATION_MESSAGE },
+ },
+ {
+ status: 410,
+ headers: { 'Cache-Control': 'no-store' },
+ }
+ )
+}
diff --git a/packages/db/.env.swp b/packages/db/.env.swp
new file mode 100644
index 0000000000..27153b0737
Binary files /dev/null and b/packages/db/.env.swp differ
diff --git a/scripts/check-api-validation-contracts.ts b/scripts/check-api-validation-contracts.ts
index 5b321a818d..4fb99356ee 100644
--- a/scripts/check-api-validation-contracts.ts
+++ b/scripts/check-api-validation-contracts.ts
@@ -83,6 +83,14 @@ const INDIRECT_ZOD_ROUTES = new Set([
// that closes the popup, so the JSON-mode contract framework doesn't fit.
// Validation is enforced via state lookup + session-vs-row userId match.
'apps/sim/app/api/mcp/oauth/callback/route.ts',
+ // Deprecated Copilot MCP surface: these routes are gated to always return
+ // 410 Gone and consume no client-supplied input.
+ 'apps/sim/app/api/mcp/copilot/route.ts',
+ 'apps/sim/app/api/mcp/copilot/.well-known/oauth-authorization-server/route.ts',
+ 'apps/sim/app/api/mcp/copilot/.well-known/oauth-protected-resource/route.ts',
+ // Deprecated v1 headless copilot chat API: gated to always return 410 Gone
+ // and consumes no client-supplied input.
+ 'apps/sim/app/api/v1/copilot/chat/route.ts',
])
/**
@@ -110,7 +118,6 @@ const RAW_JSON_BASELINE_ROUTES = new Set([
'apps/sim/app/api/invitations/[id]/route.ts',
'apps/sim/app/api/knowledge/[id]/documents/route.ts',
'apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts',
- 'apps/sim/app/api/mcp/copilot/route.ts',
'apps/sim/app/api/mcp/serve/[serverId]/route.ts',
'apps/sim/app/api/mcp/servers/route.ts',
'apps/sim/app/api/mcp/servers/[id]/route.ts',