@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 +25 -30
- package/dist/client/atlassian.js +45 -12
- package/dist/config.d.ts +2 -0
- package/dist/config.js +31 -12
- package/dist/types.d.ts +15 -0
- package/package.json +4 -1
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
|
-
|
|
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": "
|
|
24
|
-
"args": ["@ama-work/mcp-atlassian"],
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["-y", "@ama-work/mcp-atlassian"],
|
|
25
25
|
"env": {
|
|
26
|
-
"
|
|
27
|
-
"
|
|
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
|
|
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
|
-
```
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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": "
|
|
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`
|
package/dist/client/atlassian.js
CHANGED
|
@@ -1,28 +1,61 @@
|
|
|
1
1
|
import { createCloudClient } from "jira.js";
|
|
2
2
|
import { createV1Client, createV2Client } from "confluence.js";
|
|
3
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
11
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
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
|
|
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
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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",
|