From 77097ac65f5db83d75a2b640cd002eeaa2130757 Mon Sep 17 00:00:00 2001 From: Christian Gick Date: Wed, 11 Feb 2026 19:05:19 +0200 Subject: [PATCH] feat(API-11): Route API calls through AgilitonAPI gateway Add gateway-first pattern: when AGILITON_API_KEY is set, route all external API calls through the gateway with X-API-Key auth. Falls back to direct API access when gateway is unavailable. Co-Authored-By: Claude Opus 4.6 --- .env | 6 ++++- src/services/jira.ts | 60 ++++++++++++++++++++++++++------------------ 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/.env b/.env index 8d75ec2..6c0d3e6 100644 --- a/.env +++ b/.env @@ -28,7 +28,11 @@ POSTGRES_PORT=6432 LLM_API_URL=https://api.agiliton.cloud/llm LLM_API_KEY=sk-c02d41a118ce8330c428100afaa816c8 -# Jira Cloud (CF-762 migration) +# AgilitonAPI Gateway (API-11: centralized API access) +AGILITON_API_KEY=gw_92399e154f02730ebadec65ddbde9426c9378ec77093d1c9 +AGILITON_API_URL=https://api.agiliton.cloud + +# Jira Cloud (fallback if gateway unavailable) JIRA_URL=https://agiliton.atlassian.net JIRA_USERNAME=christian.gick@agiliton.eu JIRA_API_TOKEN=ATATT3xFfGF0tpaJTS4nJklW587McubEw-1SYbLWqfovkxI5320NdbFc-3fgHlw0HGTLOikgV082m9N-SIsYVZveGXa553_1LAyOevV6Qples93xF4hIExWGAvwvXPy_4pW2tH5FNusN5ieMca5_-YUP0i69SIN0RLIMQjfqDmQyhZXbkIvrm-I=A8A2A1FC diff --git a/src/services/jira.ts b/src/services/jira.ts index 984ea68..fc836ea 100644 --- a/src/services/jira.ts +++ b/src/services/jira.ts @@ -1,8 +1,9 @@ /** - * Jira Cloud REST API client for session-mcp. - * Creates/closes CF issues for sessions and posts session output as comments. + * Jira Cloud REST API client — routes through AgilitonAPI gateway. + * Falls back to direct Jira access if AGILITON_API_KEY is not set. * - * Uses JIRA_URL, JIRA_USERNAME, JIRA_API_TOKEN env vars. + * Gateway: AGILITON_API_KEY + AGILITON_API_URL + * Direct: JIRA_URL, JIRA_USERNAME, JIRA_API_TOKEN */ interface JiraIssue { @@ -16,35 +17,48 @@ interface JiraTransition { name: string; } -const getConfig = () => ({ - url: process.env.JIRA_URL || 'https://agiliton.atlassian.net', - username: process.env.JIRA_USERNAME || '', - token: process.env.JIRA_API_TOKEN || '', -}); +// Gateway config +const GATEWAY_URL = (process.env.AGILITON_API_URL || 'https://api.agiliton.cloud').replace(/\/$/, ''); +const GATEWAY_KEY = process.env.AGILITON_API_KEY || ''; -function getAuthHeader(): string { - const { username, token } = getConfig(); - return `Basic ${Buffer.from(`${username}:${token}`).toString('base64')}`; +// Direct config (fallback) +const JIRA_URL = process.env.JIRA_URL || 'https://agiliton.atlassian.net'; +const JIRA_USERNAME = process.env.JIRA_USERNAME || ''; +const JIRA_API_TOKEN = process.env.JIRA_API_TOKEN || ''; + +function useGateway(): boolean { + return !!GATEWAY_KEY; } function isConfigured(): boolean { - const { username, token } = getConfig(); - return !!(username && token); + if (useGateway()) return true; + return !!(JIRA_USERNAME && JIRA_API_TOKEN); } async function jiraFetch(path: string, options: RequestInit = {}): Promise { - const { url } = getConfig(); - const fullUrl = `${url}/rest/api/3${path}`; + let url: string; + let headers: Record; - return fetch(fullUrl, { - ...options, - headers: { - 'Authorization': getAuthHeader(), + if (useGateway()) { + url = `${GATEWAY_URL}/jira-cloud${path}`; + headers = { + 'X-API-Key': GATEWAY_KEY, 'Content-Type': 'application/json', 'Accept': 'application/json', - ...options.headers, - }, - }); + ...options.headers as Record, + }; + } else { + url = `${JIRA_URL}/rest/api/3${path}`; + const auth = Buffer.from(`${JIRA_USERNAME}:${JIRA_API_TOKEN}`).toString('base64'); + headers = { + 'Authorization': `Basic ${auth}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + ...options.headers as Record, + }; + } + + return fetch(url, { ...options, headers }); } /** @@ -165,7 +179,6 @@ export async function transitionToDone(issueKey: string): Promise { if (!isConfigured()) return false; try { - // Get available transitions const transResponse = await jiraFetch(`/issue/${issueKey}/transitions`); if (!transResponse.ok) { console.error(`session-mcp: Jira get transitions failed (${transResponse.status})`); @@ -182,7 +195,6 @@ export async function transitionToDone(issueKey: string): Promise { return false; } - // Execute transition const response = await jiraFetch(`/issue/${issueKey}/transitions`, { method: 'POST', body: JSON.stringify({