diff --git a/k8s/40-langgraph.values.yaml b/k8s/40-langgraph.values.yaml index 9d1738d..970483d 100644 --- a/k8s/40-langgraph.values.yaml +++ b/k8s/40-langgraph.values.yaml @@ -51,7 +51,7 @@ queue: # --- External Postgres (managed) --------------------------------------- # Disable the chart's bundled postgres in favor of a managed instance -# (RDS / Cloud SQL / Azure DB / etc). +# (RDS / Cloud SQL / Azure DB / SAP HANA Cloud Postgres / etc). # The connection string lives in `codevibe-langgraph-database` (see # 10-secrets.example.yaml) under the key `connection_url`. Use a database # name distinct from Prisma's so the two schemas can't collide. diff --git a/k8s/README.md b/k8s/README.md index ae892d1..ceb942b 100644 --- a/k8s/README.md +++ b/k8s/README.md @@ -17,7 +17,7 @@ expected to run on Vercel / a separate platform. - Kubernetes 1.27+ with a default StorageClass (only needed if you keep `yjs-server` PVCs — there are none today) - Helm 3 - A container registry you can push to (ECR / GAR / GHCR / Docker Hub) -- A managed Postgres instance reachable from the cluster (RDS / Cloud SQL / Azure DB / etc). Create two databases up-front: +- A managed Postgres instance reachable from the cluster (RDS / Cloud SQL / Azure DB / SAP HANA Cloud Postgres). Create two databases up-front: ```sql CREATE DATABASE codevibe; -- for Prisma CREATE DATABASE langgraph; -- for LangGraph checkpoints diff --git a/package-lock.json b/package-lock.json index ffa458e..7a440e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@hookform/resolvers": "^5.1.1", "@langchain/anthropic": "^1.3.28", "@langchain/core": "^1.1.42", - "@langchain/langgraph": "^1.2.9", + "@langchain/langgraph": "^1.3.4", "@langchain/langgraph-checkpoint-postgres": "^1.0.1", "@langchain/mcp-adapters": "^1.0.3", "@modelcontextprotocol/sdk": "^1.25.1", @@ -94,7 +94,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", - "@langchain/langgraph-cli": "^1.1.17", + "@langchain/langgraph-cli": "^1.2.4", "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/pg": "^8.20.0", @@ -2612,16 +2612,16 @@ } }, "node_modules/@langchain/langgraph": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-1.3.2.tgz", - "integrity": "sha512-SL7Ktsr681R7da+1b2MVOWEbaCoFJOXEJPTGOjg4JIG4C7quWbTYC8DzxhcCxte6D/8cGp0rYDBnbKLXEpNqlA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-1.3.4.tgz", + "integrity": "sha512-i5nlhy2INcX326sTJ+dAkvSe+sYO8DoGHn4OwxcI7U6OSm/n8xek24yvL5ahX09M1PES9hfooKz1lYn+cyPULw==", "license": "MIT", "dependencies": { - "@langchain/langgraph-checkpoint": "^1.0.2", - "@langchain/langgraph-sdk": "~1.9.4", - "@langchain/protocol": "^0.0.15", + "@langchain/langgraph-checkpoint": "^1.0.4", + "@langchain/langgraph-sdk": "~1.9.12", + "@langchain/protocol": "^0.0.16", "@standard-schema/spec": "1.1.0", - "uuid": "^10.0.0" + "uuid": "^14.0.0" }, "engines": { "node": ">=18" @@ -2638,9 +2638,9 @@ } }, "node_modules/@langchain/langgraph-api": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@langchain/langgraph-api/-/langgraph-api-1.2.2.tgz", - "integrity": "sha512-+2nZTHaMHYLfEhwD2cxMOlZ6gf1y1/pJfVetl+Tj0HDVM9UmwuGTPnDgWl4+L89LTMrAICR0EMQ2x3Pk/qam6A==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-api/-/langgraph-api-1.2.4.tgz", + "integrity": "sha512-S1GljWbALREm23eyTnAoJUi96jmUEIV3dbtLPHHWTnfti/ibsqktPKY0Kl2f6WS7PjduenL70txTyC9pSz228Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2648,8 +2648,8 @@ "@hono/node-server": "^1.19.13", "@hono/node-ws": "^1.3.0", "@hono/zod-validator": "^0.7.6", - "@langchain/langgraph-ui": "1.2.2", - "@langchain/protocol": "^0.0.15", + "@langchain/langgraph-ui": "1.2.4", + "@langchain/protocol": "^0.0.16", "@types/json-schema": "^7.0.15", "@typescript/vfs": "^1.6.0", "dedent": "^1.5.3", @@ -2662,7 +2662,7 @@ "stacktrace-parser": "^0.1.10", "superjson": "^2.2.2", "tsx": "^4.19.3", - "uuid": "^10.0.0", + "uuid": "^14.0.0", "winston": "^3.17.0", "winston-console-format": "^1.0.8", "zod": "^3.25.76 || ^4" @@ -2697,12 +2697,12 @@ } }, "node_modules/@langchain/langgraph-checkpoint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-1.0.2.tgz", - "integrity": "sha512-F4E5Tr0nt8FGghgdscJtHw+ABzChOHeI80R7Y1pjIHdiJom6c2ieo76vL+FWiny80JmoGqhrVAEIWrw0cXKPxg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-1.0.4.tgz", + "integrity": "sha512-1y5MgZ0gXXrtmoy56e3kaBChI3GwFPIKl27xkrHwN+VE/3iUsyr9gO3Jtp7kdKAe6diZGbcas5bdC/r0yUwTZA==", "license": "MIT", "dependencies": { - "uuid": "^10.0.0" + "uuid": "^14.0.0" }, "engines": { "node": ">=18" @@ -2728,15 +2728,15 @@ } }, "node_modules/@langchain/langgraph-cli": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@langchain/langgraph-cli/-/langgraph-cli-1.2.2.tgz", - "integrity": "sha512-DNa1MQ5cCVoFW5f1zrjPE3SYdOw4r9qVLy2O9IoJGxBXXhNIWFMN9KG70Vbi7UcN6GobskuNt+1qlmqgDmGowg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-cli/-/langgraph-cli-1.2.4.tgz", + "integrity": "sha512-2INxq7JNrZqfUb9oEMgBnEqhwReX6nkwMzSaHKvvbQYGsH2zsTIGfew4HznbWsyTuALgWaTlDHm3Vk2C9ndksQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.26.2", "@commander-js/extra-typings": "^13.0.0", - "@langchain/langgraph-api": "1.2.2", + "@langchain/langgraph-api": "1.2.4", "chokidar": "^4.0.3", "commander": "^13.0.0", "create-langgraph": "1.1.5", @@ -2810,16 +2810,16 @@ } }, "node_modules/@langchain/langgraph-sdk": { - "version": "1.9.8", - "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-1.9.8.tgz", - "integrity": "sha512-t7HcTy5QL0taI4Zm096yWK5m99n0uW898E7fE8ub4h2+0Ylype5I9Ph3oAB2kCRzTtT/fVwDhsj90t8/c+aNAw==", + "version": "1.9.12", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-1.9.12.tgz", + "integrity": "sha512-zdALHeAQ6//ZIGPKC58tf9n8ofBzB+6ABKF3ySYZZonshGhTYxAMKJ3sqDxWyKlBJRIei0wq91GcuYY0bqCIew==", "license": "MIT", "dependencies": { - "@langchain/protocol": "^0.0.15", + "@langchain/protocol": "^0.0.16", "@types/json-schema": "^7.0.15", "p-queue": "^9.0.1", "p-retry": "^7.1.1", - "uuid": "^13.0.0" + "uuid": "^14.0.0" }, "peerDependencies": { "@langchain/core": "^1.1.44", @@ -2877,23 +2877,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@langchain/langgraph-sdk/node_modules/uuid": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.2.tgz", - "integrity": "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, "node_modules/@langchain/langgraph-ui": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@langchain/langgraph-ui/-/langgraph-ui-1.2.2.tgz", - "integrity": "sha512-W+P2DgJm9Pb5378iwqjaigwGENW4dZ6Zp8EzUbPZ4iNT8VQ3BDpkpJgNNYFj+EsZ3TRZc9KhP1vgHtU6y1EOQw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-ui/-/langgraph-ui-1.2.4.tgz", + "integrity": "sha512-7GK/MfAo8yy2xSbRegoB4+uNhUbLe7MH88ikud5NbbdbUyArTspQzG0SwXwanvkIj4pau90uGj2O9AK6kfApVw==", "dev": true, "license": "MIT", "dependencies": { @@ -2940,9 +2927,9 @@ } }, "node_modules/@langchain/protocol": { - "version": "0.0.15", - "resolved": "https://registry.npmjs.org/@langchain/protocol/-/protocol-0.0.15.tgz", - "integrity": "sha512-MllvbpMjqHevUm+v94M422mH7XKN+wGCvJRBVROTWBotEDOATYB4Ktk2UheYP859y9o2LlhtPek5t1T9eyfAbQ==", + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@langchain/protocol/-/protocol-0.0.16.tgz", + "integrity": "sha512-ws+J7MaHyhO5dG7f0vdyHQiUn9hoCnki0f3crJPa4MCTGzcRC39jYSCghyrGtBPYQnZbUQiGyRVpW3z3M8IpJg==", "license": "MIT" }, "node_modules/@lifeomic/attempt": { @@ -17913,17 +17900,16 @@ "license": "MIT" }, "node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", + "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist-node/bin/uuid" } }, "node_modules/valibot": { diff --git a/package.json b/package.json index 9087faa..e9c1004 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@hookform/resolvers": "^5.1.1", "@langchain/anthropic": "^1.3.28", "@langchain/core": "^1.1.42", - "@langchain/langgraph": "^1.2.9", + "@langchain/langgraph": "^1.3.4", "@langchain/langgraph-checkpoint-postgres": "^1.0.1", "@langchain/mcp-adapters": "^1.0.3", "@modelcontextprotocol/sdk": "^1.25.1", @@ -104,7 +104,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", - "@langchain/langgraph-cli": "^1.1.17", + "@langchain/langgraph-cli": "^1.2.4", "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/pg": "^8.20.0", diff --git a/src/app/api/mcp/oauth/callback/route.ts b/src/app/api/mcp/oauth/callback/route.ts index 8cf9936..fdc0635 100644 --- a/src/app/api/mcp/oauth/callback/route.ts +++ b/src/app/api/mcp/oauth/callback/route.ts @@ -14,8 +14,8 @@ export const dynamic = 'force-dynamic'; // in the `state` param (set by the provider's state() method); we look it up // to find the right server, validate the user owns it, and finish the token // exchange. This lets us register exactly ONE redirect URI per environment -// upstream — important for OAuth servers that reject any URI not on a -// strict allowlist. +// upstream — important for OAuth servers (e.g. SAP IAS) that reject any URI +// not on a strict allowlist. export async function GET(req: NextRequest) { const { userId } = await auth(); const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000'; diff --git a/src/app/api/mcp/servers/[id]/auth/route.ts b/src/app/api/mcp/servers/[id]/auth/route.ts index c926a93..1469b68 100644 --- a/src/app/api/mcp/servers/[id]/auth/route.ts +++ b/src/app/api/mcp/servers/[id]/auth/route.ts @@ -23,9 +23,9 @@ export async function GET(_req: NextRequest, { params }: { params: Promise<{ id: const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000'; - // Loopback servers can't use the standard browser-redirect flow because - // the IdP doesn't allowlist our host. Send the user to the settings modal - // which will open the paste-style connect dialog. + // Loopback servers (e.g. SAP Jira) can't use the standard browser-redirect + // flow because the IdP doesn't allowlist our host. Send the user to the + // settings modal which will open the paste-style connect dialog. if (isLoopbackServer(row.url)) { return NextResponse.redirect(new URL(`/?settings=apps&connectLoopback=${id}`, appUrl)); } diff --git a/src/app/api/mcp/servers/[id]/auth/start/route.ts b/src/app/api/mcp/servers/[id]/auth/start/route.ts index 4e99455..ba9aa12 100644 --- a/src/app/api/mcp/servers/[id]/auth/start/route.ts +++ b/src/app/api/mcp/servers/[id]/auth/start/route.ts @@ -17,8 +17,8 @@ export const dynamic = 'force-dynamic'; // Step 1 of the loopback OAuth flow. Frontend POSTs here, we run DCR + build // the authorize URL, return it as JSON. Frontend opens that URL in a new tab. -// Used for OAuth servers whose IdP doesn't allowlist our real redirect -// URI — we register loopback instead. +// Used for OAuth servers (e.g. SAP Jira) whose IdP doesn't allowlist our +// real redirect URI — we register loopback instead. export async function POST(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) { const { userId } = await auth(); if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); diff --git a/src/components/settings/McpServerSettings.tsx b/src/components/settings/McpServerSettings.tsx index e77e2fb..6bda4f3 100644 --- a/src/components/settings/McpServerSettings.tsx +++ b/src/components/settings/McpServerSettings.tsx @@ -28,10 +28,6 @@ import { } from "@/components/ui/alert-dialog"; import { cn } from "@/lib/utils"; -// URLs of MCP servers that require loopback OAuth (RFC 8252). -const LOOPBACK_HOST_URLS = new Set([]); -function isLoopbackServer(url: string) { return LOOPBACK_HOST_URLS.has(url); } - interface UserMcpServer { id: string; name: string; @@ -200,10 +196,10 @@ function ServerRow({ server, onDelete, onConnected, autoOpen, onAutoOpenHandled const [status, setStatus] = useState<{ ok: boolean; toolCount?: number; error?: string } | null>(null); const [loopbackOpen, setLoopbackOpen] = useState(false); - // Servers whose IdP doesn't allowlist our host use a loopback OAuth flow - // (RFC 8252). The IdP redirects to a dead localhost URL; the user pastes - // it back here, server completes the exchange. - const isLoopback = isLoopbackServer(server.url); + // SAP Jira and any other server whose IdP doesn't allowlist our host uses + // a loopback OAuth flow (RFC 8252). The IdP redirects to a dead localhost + // URL; the user pastes it back here, server completes the exchange. + const isLoopback = server.url === "https://mcp.jira.tools.sap/mcp"; // When the parent passes autoOpen (because of ?connectLoopback=), // pop the loopback dialog automatically. Only acts on loopback rows. diff --git a/src/lib/mcp-user-store.ts b/src/lib/mcp-user-store.ts index baaafd7..ea3ae1e 100644 --- a/src/lib/mcp-user-store.ts +++ b/src/lib/mcp-user-store.ts @@ -41,7 +41,7 @@ function appUrl(): string { const LOOPBACK_PORT = 33418; export const LOOPBACK_REDIRECT_URI = `http://127.0.0.1:${LOOPBACK_PORT}/callback`; const LOOPBACK_HOST_URLS = new Set([ - // Add loopback-only MCP server URLs here + 'https://mcp.jira.tools.sap/mcp', ]); export function isLoopbackServer(url: string): boolean { @@ -53,7 +53,7 @@ export function oauthRedirectUrl(_serverId: string, serverUrl?: string): string return LOOPBACK_REDIRECT_URI; } // Single shared callback path so we only need to allowlist ONE redirect URI - // per environment in upstream OAuth servers (they reject unregistered + // per environment in upstream OAuth servers (SAP IAS rejects unregistered // hosts/paths). The serverId travels in the OAuth `state` param instead. return `${appUrl()}/api/mcp/oauth/callback`; }