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 <noreply@anthropic.com>
This commit is contained in:
6
.env
6
.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
|
||||
|
||||
@@ -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<Response> {
|
||||
const { url } = getConfig();
|
||||
const fullUrl = `${url}/rest/api/3${path}`;
|
||||
let url: string;
|
||||
let headers: Record<string, string>;
|
||||
|
||||
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<string, string>,
|
||||
};
|
||||
} 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<string, string>,
|
||||
};
|
||||
}
|
||||
|
||||
return fetch(url, { ...options, headers });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,7 +179,6 @@ export async function transitionToDone(issueKey: string): Promise<boolean> {
|
||||
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<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Execute transition
|
||||
const response = await jiraFetch(`/issue/${issueKey}/transitions`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
|
||||
Reference in New Issue
Block a user