@ama-work/mcp-atlassian 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,8 +23,9 @@ Add to your `claude_desktop_config.json`:
23
23
  "command": "bunx",
24
24
  "args": ["@ama-work/mcp-atlassian"],
25
25
  "env": {
26
- "ATLASSIAN_ACCESS_TOKEN": "your-oauth-access-token",
27
- "ATLASSIAN_CLOUD_ID": "your-atlassian-cloud-id"
26
+ "ATLASSIAN_EMAIL": "you@company.com",
27
+ "ATLASSIAN_API_TOKEN": "your-api-token",
28
+ "ATLASSIAN_CLOUD_ID": "your-cloud-id"
28
29
  }
29
30
  }
30
31
  }
@@ -33,21 +34,22 @@ Add to your `claude_desktop_config.json`:
33
34
 
34
35
  ### Standalone mode
35
36
 
36
- Provide an Atlassian OAuth access token and your cloud ID directly:
37
+ Provide your Atlassian credentials directly via environment variables.
37
38
 
38
- ```json
39
- {
40
- "mcpServers": {
41
- "atlassian": {
42
- "command": "bunx",
43
- "args": ["@ama-work/mcp-atlassian"],
44
- "env": {
45
- "ATLASSIAN_ACCESS_TOKEN": "your-oauth-access-token",
46
- "ATLASSIAN_CLOUD_ID": "your-atlassian-cloud-id"
47
- }
48
- }
49
- }
50
- }
39
+ **1. Create an API token:**
40
+
41
+ Go to [Atlassian API Tokens](https://id.atlassian.com/manage-profile/security/api-tokens) and create a new token.
42
+
43
+ **2. Find your Cloud ID:**
44
+
45
+ Your Cloud ID is the first part of your Atlassian URL. For example, if your Jira is at `https://mycompany.atlassian.net`, your Cloud ID is `mycompany`.
46
+
47
+ **3. Configure the env vars:**
48
+
49
+ ```
50
+ ATLASSIAN_EMAIL=you@company.com
51
+ ATLASSIAN_API_TOKEN=ATATT3xFfGF0...
52
+ ATLASSIAN_CLOUD_ID=mycompany
51
53
  ```
52
54
 
53
55
  ### Registry-integrated mode
@@ -80,15 +82,6 @@ Import the canonical definition via URL:
80
82
  https://raw.githubusercontent.com/Ask-Me-Anything-dot-work/ama-mcp-atlassian/main/definitions/atlassian.json
81
83
  ```
82
84
 
83
- ### OAuth Setup
84
-
85
- Before using this server, register an OAuth 2.0 app at [developer.atlassian.com](https://developer.atlassian.com):
86
-
87
- 1. Create a new OAuth 2.0 app
88
- 2. Add the required scopes (see `definitions/atlassian.json`)
89
- 3. Copy the **Client ID** and replace `YOUR_ATLASSIAN_CLIENT_ID` in the definition
90
- 4. Generate an access token with the required scopes
91
-
92
85
  ## Tools
93
86
 
94
87
  **Jira:** `jira_search_issues`, `jira_get_issue`, `jira_create_issue`, `jira_update_issue`, `jira_transition_issue`, `jira_add_comment`, `jira_get_projects`
@@ -1,28 +1,61 @@
1
1
  import { createCloudClient } from "jira.js";
2
2
  import { createV1Client, createV2Client } from "confluence.js";
3
- import { getToken, getCloudId } from "../config.js";
3
+ import { getAuthConfig } from "../config.js";
4
+ function getHost(authConfig) {
5
+ if (authConfig.mode === "basic") {
6
+ return `https://${authConfig.cloudId}.atlassian.net`;
7
+ }
8
+ return "https://api.atlassian.com";
9
+ }
4
10
  export async function createJiraClient() {
5
- const [token, cloudId] = await Promise.all([getToken(), getCloudId()]);
11
+ const authConfig = await getAuthConfig();
12
+ const host = getHost(authConfig);
13
+ if (authConfig.mode === "basic") {
14
+ return createCloudClient({
15
+ host,
16
+ auth: { type: "basic", email: authConfig.email, apiToken: authConfig.apiToken },
17
+ });
18
+ }
19
+ const token = authConfig.mode === "registry" ? authConfig.token : authConfig.token;
6
20
  return createCloudClient({
7
- host: "https://api.atlassian.com",
8
- auth: { type: "oauth2", accessToken: token, cloudId },
21
+ host,
22
+ auth: { type: "oauth2", accessToken: token, cloudId: authConfig.cloudId },
9
23
  getAuthOn401: async () => {
10
- const fresh = await getToken();
11
- return { type: "oauth2", accessToken: fresh, cloudId };
24
+ const fresh = await getAuthConfig();
25
+ if (fresh.mode === "basic") {
26
+ return { type: "basic", email: fresh.email, apiToken: fresh.apiToken };
27
+ }
28
+ return { type: "oauth2", accessToken: fresh.token, cloudId: fresh.cloudId };
12
29
  },
13
30
  });
14
31
  }
15
32
  export async function createConfluenceClient() {
16
- const [token, cloudId] = await Promise.all([getToken(), getCloudId()]);
33
+ const authConfig = await getAuthConfig();
34
+ const host = getHost(authConfig);
35
+ if (authConfig.mode === "basic") {
36
+ return createV2Client({
37
+ host,
38
+ auth: { type: "basic", email: authConfig.email, apiToken: authConfig.apiToken },
39
+ });
40
+ }
41
+ const token = authConfig.mode === "registry" ? authConfig.token : authConfig.token;
17
42
  return createV2Client({
18
- host: "https://api.atlassian.com",
19
- auth: { type: "oauth2", accessToken: token, cloudId },
43
+ host,
44
+ auth: { type: "oauth2", accessToken: token, cloudId: authConfig.cloudId },
20
45
  });
21
46
  }
22
47
  export async function createConfluenceV1Client() {
23
- const [token, cloudId] = await Promise.all([getToken(), getCloudId()]);
48
+ const authConfig = await getAuthConfig();
49
+ const host = getHost(authConfig);
50
+ if (authConfig.mode === "basic") {
51
+ return createV1Client({
52
+ host,
53
+ auth: { type: "basic", email: authConfig.email, apiToken: authConfig.apiToken },
54
+ });
55
+ }
56
+ const token = authConfig.mode === "registry" ? authConfig.token : authConfig.token;
24
57
  return createV1Client({
25
- host: "https://api.atlassian.com",
26
- auth: { type: "oauth2", accessToken: token, cloudId },
58
+ host,
59
+ auth: { type: "oauth2", accessToken: token, cloudId: authConfig.cloudId },
27
60
  });
28
61
  }
package/dist/config.d.ts CHANGED
@@ -1,2 +1,4 @@
1
+ import type { AuthConfig } from "./types.js";
2
+ export declare function getAuthConfig(): Promise<AuthConfig>;
1
3
  export declare function getToken(): Promise<string>;
2
4
  export declare function getCloudId(): Promise<string>;
package/dist/config.js CHANGED
@@ -25,35 +25,54 @@ async function fetchFromRegistry() {
25
25
  expiresAt: data.oauth.expires_at,
26
26
  };
27
27
  }
28
- function resolveStandalone() {
28
+ function resolveBasicAuth() {
29
+ const email = env("ATLASSIAN_EMAIL");
30
+ const apiToken = env("ATLASSIAN_API_TOKEN");
31
+ const cloudId = env("ATLASSIAN_CLOUD_ID");
32
+ if (!email || !apiToken || !cloudId)
33
+ return null;
34
+ return { mode: "basic", email, apiToken, cloudId };
35
+ }
36
+ function resolveOAuth() {
29
37
  const token = env("ATLASSIAN_ACCESS_TOKEN");
30
38
  const cloudId = env("ATLASSIAN_CLOUD_ID");
31
39
  if (!token || !cloudId)
32
40
  return null;
33
- return {
34
- token,
35
- cloudId,
36
- expiresAt: Number.MAX_SAFE_INTEGER,
37
- };
41
+ return { mode: "oauth2", token, cloudId };
38
42
  }
39
43
  function isExpired(c) {
40
44
  return Date.now() >= c.expiresAt - REFRESH_BUFFER_MS;
41
45
  }
42
- async function load() {
43
- const standalone = resolveStandalone();
44
- if (standalone)
45
- return standalone;
46
+ async function loadTokenCache() {
47
+ const basicAuth = resolveBasicAuth();
48
+ if (basicAuth && basicAuth.mode === "basic") {
49
+ return { token: basicAuth.apiToken, cloudId: basicAuth.cloudId, expiresAt: Number.MAX_SAFE_INTEGER };
50
+ }
51
+ const oauth = resolveOAuth();
52
+ if (oauth && oauth.mode === "oauth2") {
53
+ return { token: oauth.token, cloudId: oauth.cloudId, expiresAt: Number.MAX_SAFE_INTEGER };
54
+ }
46
55
  return fetchFromRegistry();
47
56
  }
57
+ export async function getAuthConfig() {
58
+ const basicAuth = resolveBasicAuth();
59
+ if (basicAuth)
60
+ return basicAuth;
61
+ const oauth = resolveOAuth();
62
+ if (oauth)
63
+ return oauth;
64
+ const cached = await loadTokenCache();
65
+ return { mode: "registry", token: cached.token, cloudId: cached.cloudId, expiresAt: cached.expiresAt };
66
+ }
48
67
  export async function getToken() {
49
68
  if (!cache || isExpired(cache)) {
50
- cache = await load();
69
+ cache = await loadTokenCache();
51
70
  }
52
71
  return cache.token;
53
72
  }
54
73
  export async function getCloudId() {
55
74
  if (!cache || isExpired(cache)) {
56
- cache = await load();
75
+ cache = await loadTokenCache();
57
76
  }
58
77
  return cache.cloudId;
59
78
  }
package/dist/types.d.ts CHANGED
@@ -12,3 +12,18 @@ export interface TokenCache {
12
12
  cloudId: string;
13
13
  expiresAt: number;
14
14
  }
15
+ export type AuthConfig = {
16
+ mode: "basic";
17
+ email: string;
18
+ apiToken: string;
19
+ cloudId: string;
20
+ } | {
21
+ mode: "oauth2";
22
+ token: string;
23
+ cloudId: string;
24
+ } | {
25
+ mode: "registry";
26
+ token: string;
27
+ cloudId: string;
28
+ expiresAt: number;
29
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ama-work/mcp-atlassian",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Standalone TypeScript MCP server wrapping the Atlassian REST API (Jira + Confluence)",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",