@ama-work/mcp-atlassian 1.0.1 → 1.2.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
@@ -5,7 +5,7 @@ Standalone TypeScript MCP server wrapping the Atlassian REST API (Jira + Conflue
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install -g @ama-work/mcp-atlassian
8
+ npx -y @ama-work/mcp-atlassian
9
9
  # or
10
10
  bunx @ama-work/mcp-atlassian
11
11
  ```
@@ -20,34 +20,38 @@ Add to your `claude_desktop_config.json`:
20
20
  {
21
21
  "mcpServers": {
22
22
  "atlassian": {
23
- "command": "bunx",
24
- "args": ["@ama-work/mcp-atlassian"],
23
+ "command": "npx",
24
+ "args": ["-y", "@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
  }
31
32
  }
32
33
  ```
33
34
 
35
+ > Works with `bunx` too — replace `npx` with `bunx` and remove `-y`.
36
+
34
37
  ### Standalone mode
35
38
 
36
- Provide an Atlassian OAuth access token and your cloud ID directly:
39
+ Provide your Atlassian credentials directly via environment variables.
40
+
41
+ **1. Create an API token:**
42
+
43
+ Go to [Atlassian API Tokens](https://id.atlassian.com/manage-profile/security/api-tokens) and create a new token.
44
+
45
+ **2. Find your Cloud ID:**
46
+
47
+ 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`.
48
+
49
+ **3. Configure the env vars:**
37
50
 
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
- }
51
+ ```
52
+ ATLASSIAN_EMAIL=you@company.com
53
+ ATLASSIAN_API_TOKEN=ATATT3xFfGF0...
54
+ ATLASSIAN_CLOUD_ID=mycompany
51
55
  ```
52
56
 
53
57
  ### Registry-integrated mode
@@ -58,8 +62,8 @@ If you run [ama-mcp-registry](https://github.com/Ask-Me-Anything-dot-work/ama-mc
58
62
  {
59
63
  "mcpServers": {
60
64
  "atlassian": {
61
- "command": "bunx",
62
- "args": ["@ama-work/mcp-atlassian"],
65
+ "command": "npx",
66
+ "args": ["-y", "@ama-work/mcp-atlassian"],
63
67
  "env": {
64
68
  "REGISTRY_URL": "https://your-registry.example.com",
65
69
  "INSTANCE_ID": "your-instance-id",
@@ -80,15 +84,6 @@ Import the canonical definition via URL:
80
84
  https://raw.githubusercontent.com/Ask-Me-Anything-dot-work/ama-mcp-atlassian/main/definitions/atlassian.json
81
85
  ```
82
86
 
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
87
  ## Tools
93
88
 
94
89
  **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,8 +1,11 @@
1
1
  {
2
2
  "name": "@ama-work/mcp-atlassian",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "Standalone TypeScript MCP server wrapping the Atlassian REST API (Jira + Confluence)",
5
5
  "main": "dist/index.js",
6
+ "bin": {
7
+ "mcp-atlassian": "dist/index.js"
8
+ },
6
9
  "type": "module",
7
10
  "scripts": {
8
11
  "start": "bun src/index.ts",