@nexus2520/jira-mcp-server 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 +114 -94
- package/build/handlers/attachment-handlers.d.ts +65 -0
- package/build/handlers/attachment-handlers.d.ts.map +1 -0
- package/build/handlers/attachment-handlers.js +176 -0
- package/build/handlers/attachment-handlers.js.map +1 -0
- package/build/index.js +49 -30
- package/build/index.js.map +1 -1
- package/build/tools/definitions.d.ts +58 -148
- package/build/tools/definitions.d.ts.map +1 -1
- package/build/tools/definitions.js +56 -209
- package/build/tools/definitions.js.map +1 -1
- package/build/utils/api-client.d.ts +6 -0
- package/build/utils/api-client.d.ts.map +1 -1
- package/build/utils/api-client.js +32 -0
- package/build/utils/api-client.js.map +1 -1
- package/build/utils/formatters.d.ts +1 -0
- package/build/utils/formatters.d.ts.map +1 -1
- package/build/utils/formatters.js +21 -0
- package/build/utils/formatters.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
# Jira MCP Server
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@nexus2520/jira-mcp-server)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
5
|
|
|
5
|
-
A Model Context Protocol (MCP) server for Jira API integration. This server enables AI assistants like Claude to interact with Jira Cloud instances for issue management, search, comments, and
|
|
6
|
+
A Model Context Protocol (MCP) server for Jira API integration. This server enables AI assistants like Claude to interact with Jira Cloud instances for issue management, search, comments, workflow transitions, and attachment handling.
|
|
6
7
|
|
|
7
8
|
## Features
|
|
8
9
|
|
|
9
10
|
- **Issue Management**: Get, create, update, and assign Jira issues with custom field support
|
|
10
11
|
- **JQL Search**: Search issues using Jira Query Language
|
|
11
|
-
- **Comments**: Add and retrieve comments on issues
|
|
12
|
+
- **Comments**: Add and retrieve comments on issues
|
|
12
13
|
- **Workflow**: Get available transitions and change issue status
|
|
13
14
|
- **Metadata Discovery**: Get field requirements and allowed values for projects
|
|
14
15
|
- **User Search**: Find users by email or name for assignments
|
|
15
16
|
- **Projects**: List all accessible projects
|
|
16
|
-
- **
|
|
17
|
+
- **Attachments**: List, upload, delete attachments and retrieve their content — text files returned as text, images rendered inline via Claude vision
|
|
18
|
+
- **Token Efficient**: 5 compound tools instead of 16 flat tools — ~50% fewer tokens per session
|
|
17
19
|
|
|
18
20
|
## Installation
|
|
19
21
|
|
|
@@ -51,8 +53,6 @@ npm install -g @nexus2520/jira-mcp-server
|
|
|
51
53
|
|
|
52
54
|
### Environment Variables
|
|
53
55
|
|
|
54
|
-
The server requires the following environment variables:
|
|
55
|
-
|
|
56
56
|
- `JIRA_EMAIL`: Your Atlassian account email
|
|
57
57
|
- `JIRA_API_TOKEN`: Your Jira API token
|
|
58
58
|
- `JIRA_BASE_URL`: Your Jira instance URL (e.g., `https://yourcompany.atlassian.net`)
|
|
@@ -91,12 +91,10 @@ Add the following to your Claude Desktop MCP settings file:
|
|
|
91
91
|
{
|
|
92
92
|
"mcpServers": {
|
|
93
93
|
"jira": {
|
|
94
|
-
"name": "jira",
|
|
95
94
|
"command": "node",
|
|
96
95
|
"args": [
|
|
97
96
|
"/absolute/path/to/jira-mcp-server/build/index.js"
|
|
98
97
|
],
|
|
99
|
-
"transport": "stdio",
|
|
100
98
|
"env": {
|
|
101
99
|
"JIRA_EMAIL": "your-email@company.com",
|
|
102
100
|
"JIRA_API_TOKEN": "your-api-token-here",
|
|
@@ -115,130 +113,152 @@ Add the following to your Claude Desktop MCP settings file:
|
|
|
115
113
|
4. Copy the generated token
|
|
116
114
|
5. Use it in your configuration
|
|
117
115
|
|
|
116
|
+
## Tool Architecture
|
|
117
|
+
|
|
118
|
+
This server exposes **5 compound tools** — each with an `action` parameter that selects the operation. This design reduces the token overhead of tool definitions by ~50% compared to 16 flat tools, leaving more context for actual work.
|
|
119
|
+
|
|
120
|
+
| Tool | Actions | Description |
|
|
121
|
+
|------|---------|-------------|
|
|
122
|
+
| `jira_issues` | `get` `create` `update` `assign` | Full issue lifecycle management |
|
|
123
|
+
| `jira_search` | `issues` `projects` `users` `create_metadata` | Search and discovery |
|
|
124
|
+
| `jira_comments` | `get` `add` | Read and write comments |
|
|
125
|
+
| `jira_workflow` | `get_transitions` `transition` | Status transitions |
|
|
126
|
+
| `jira_attachments` | `list` `get_content` `upload` `delete` | File attachments |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
118
130
|
## Available Tools
|
|
119
131
|
|
|
120
|
-
###
|
|
132
|
+
### `jira_issues`
|
|
121
133
|
|
|
122
|
-
|
|
123
|
-
Get detailed information about a Jira issue.
|
|
134
|
+
Manage the full lifecycle of Jira issues.
|
|
124
135
|
|
|
125
|
-
**
|
|
126
|
-
- `issueKey` (required): The issue key (e.g., "PROJ-123")
|
|
136
|
+
**Required**: `action`
|
|
127
137
|
|
|
128
|
-
|
|
138
|
+
| Action | Description | Required params | Optional params |
|
|
139
|
+
|--------|-------------|-----------------|-----------------|
|
|
140
|
+
| `get` | Fetch full issue details | `issueKey` | — |
|
|
141
|
+
| `create` | Create a new issue | `projectKey`, `summary`, `issueType` | `description`, `priority`, `assignee`, `labels`, `customFields` |
|
|
142
|
+
| `update` | Edit fields on an existing issue | `issueKey` | `summary`, `description`, `priority`, `assignee`, `labels`, `customFields` |
|
|
143
|
+
| `assign` | Set or clear the assignee | `issueKey`, `assignee` | — |
|
|
144
|
+
|
|
145
|
+
**Tips**:
|
|
146
|
+
- Always call `jira_search` with `action=create_metadata` before creating issues to discover required custom fields and allowed values.
|
|
147
|
+
- Pass `assignee: "-1"` to unassign an issue.
|
|
148
|
+
- `description` accepts plain text or an [Atlassian Document Format (ADF)](https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/) object.
|
|
149
|
+
- `customFields` is a key-value map: `{"customfield_10000": "value"}`.
|
|
150
|
+
|
|
151
|
+
**Examples**:
|
|
129
152
|
```
|
|
130
|
-
Get details for
|
|
153
|
+
Get details for PROJ-123
|
|
154
|
+
Create a Bug in project PROJ with summary "Login button broken"
|
|
155
|
+
Update PROJ-123 priority to High
|
|
156
|
+
Assign PROJ-123 to john.doe@company.com
|
|
131
157
|
```
|
|
132
158
|
|
|
133
|
-
|
|
134
|
-
Create a new Jira issue.
|
|
159
|
+
---
|
|
135
160
|
|
|
136
|
-
|
|
161
|
+
### `jira_search`
|
|
137
162
|
|
|
138
|
-
|
|
139
|
-
- `projectKey` (required): Project key (e.g., "PROJ", "DEV")
|
|
140
|
-
- `summary` (required): Issue title
|
|
141
|
-
- `issueType` (required): Type (e.g., "Bug", "Task", "Story")
|
|
142
|
-
- `description` (optional): Issue description
|
|
143
|
-
- `priority` (optional): Priority name
|
|
144
|
-
- `assignee` (optional): Assignee account ID or email
|
|
145
|
-
- `labels` (optional): Array of labels
|
|
146
|
-
- `customFields` (optional): Custom fields object
|
|
163
|
+
Search and discover Jira resources.
|
|
147
164
|
|
|
148
|
-
**
|
|
149
|
-
```
|
|
150
|
-
Create a bug in project PROJ with summary "Login button not working" and description "Users cannot log in"
|
|
151
|
-
```
|
|
165
|
+
**Required**: `action`
|
|
152
166
|
|
|
153
|
-
|
|
154
|
-
|
|
167
|
+
| Action | Description | Required params | Optional params |
|
|
168
|
+
|--------|-------------|-----------------|-----------------|
|
|
169
|
+
| `issues` | Search issues via JQL | `jql` | `maxResults` |
|
|
170
|
+
| `projects` | List all accessible projects | — | `maxResults` |
|
|
171
|
+
| `users` | Find users by name or email | `query` | `maxResults` |
|
|
172
|
+
| `create_metadata` | Get field requirements for creating issues | `projectKey` | `issueType` |
|
|
155
173
|
|
|
156
|
-
**
|
|
174
|
+
**Common JQL examples**:
|
|
175
|
+
```
|
|
176
|
+
project = PROJ AND status = Open
|
|
177
|
+
assignee = currentUser() AND status != Done
|
|
178
|
+
priority = High AND created >= -7d
|
|
179
|
+
```
|
|
157
180
|
|
|
158
|
-
**
|
|
159
|
-
- `
|
|
160
|
-
- `
|
|
161
|
-
- `description` (optional): New description
|
|
162
|
-
- `priority` (optional): New priority
|
|
163
|
-
- `assignee` (optional): New assignee
|
|
164
|
-
- `labels` (optional): New labels array
|
|
165
|
-
- `customFields` (optional): Custom fields object
|
|
181
|
+
**Tips**:
|
|
182
|
+
- Use `create_metadata` before `jira_issues` `create` to understand what fields are required for a project/issue type.
|
|
183
|
+
- Use `users` to look up account IDs for assignments — pass the returned account ID or email to `jira_issues` `assign`.
|
|
166
184
|
|
|
167
|
-
|
|
168
|
-
Assign an issue to a user.
|
|
185
|
+
---
|
|
169
186
|
|
|
170
|
-
|
|
171
|
-
- `issueKey` (required): Issue to assign
|
|
172
|
-
- `assignee` (required): User account ID, email, or "-1" to unassign
|
|
187
|
+
### `jira_comments`
|
|
173
188
|
|
|
174
|
-
|
|
189
|
+
Read and write comments on a Jira issue.
|
|
175
190
|
|
|
176
|
-
|
|
177
|
-
Get field requirements and metadata for creating issues in a project.
|
|
191
|
+
**Required**: `action`, `issueKey`
|
|
178
192
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
193
|
+
| Action | Description | Required params |
|
|
194
|
+
|--------|-------------|-----------------|
|
|
195
|
+
| `get` | Fetch all comments on an issue | — |
|
|
196
|
+
| `add` | Post a new comment | `comment` |
|
|
182
197
|
|
|
183
|
-
|
|
184
|
-
Search for users by name or email to get their account ID.
|
|
198
|
+
`comment` accepts plain text or an ADF object.
|
|
185
199
|
|
|
186
|
-
**
|
|
187
|
-
|
|
188
|
-
|
|
200
|
+
**Examples**:
|
|
201
|
+
```
|
|
202
|
+
Get all comments on PROJ-123
|
|
203
|
+
Add a comment to PROJ-123: "Fixed in PR #456"
|
|
204
|
+
```
|
|
189
205
|
|
|
190
|
-
|
|
206
|
+
---
|
|
191
207
|
|
|
192
|
-
|
|
193
|
-
Search for issues using JQL. Returns issue keys and titles.
|
|
208
|
+
### `jira_workflow`
|
|
194
209
|
|
|
195
|
-
|
|
196
|
-
- `jql` (required): JQL query string
|
|
197
|
-
- `maxResults` (optional): Max results (default: 50)
|
|
210
|
+
Manage issue status transitions.
|
|
198
211
|
|
|
199
|
-
**
|
|
200
|
-
- `"project = PROJ AND status = Open"`
|
|
201
|
-
- `"assignee = currentUser() AND status != Done"`
|
|
202
|
-
- `"priority = High AND created >= -7d"`
|
|
212
|
+
**Required**: `action`, `issueKey`
|
|
203
213
|
|
|
204
|
-
|
|
205
|
-
|
|
214
|
+
| Action | Description | Required params | Optional params |
|
|
215
|
+
|--------|-------------|-----------------|-----------------|
|
|
216
|
+
| `get_transitions` | List available status transitions | — | — |
|
|
217
|
+
| `transition` | Move issue to a new status | `transitionId` | `comment` |
|
|
206
218
|
|
|
207
|
-
**
|
|
208
|
-
- `maxResults` (optional): Max results (default: 50)
|
|
219
|
+
**Tip**: Always call `get_transitions` first — transition IDs vary per project and issue type. The `transitionId` from the response is what you pass to `transition`.
|
|
209
220
|
|
|
210
|
-
|
|
221
|
+
**Examples**:
|
|
222
|
+
```
|
|
223
|
+
Get available transitions for PROJ-123
|
|
224
|
+
Move PROJ-123 to "In Progress" (use get_transitions first to find the ID)
|
|
225
|
+
```
|
|
211
226
|
|
|
212
|
-
|
|
213
|
-
Add a comment to an issue.
|
|
227
|
+
---
|
|
214
228
|
|
|
215
|
-
|
|
216
|
-
- `issueKey` (required): Issue to comment on
|
|
217
|
-
- `comment` (required): Comment text
|
|
229
|
+
### `jira_attachments`
|
|
218
230
|
|
|
219
|
-
|
|
220
|
-
Get all comments for an issue.
|
|
231
|
+
Manage file attachments on Jira issues.
|
|
221
232
|
|
|
222
|
-
**
|
|
223
|
-
- `issueKey` (required): Issue key
|
|
233
|
+
**Required**: `action`
|
|
224
234
|
|
|
225
|
-
|
|
235
|
+
| Action | Description | Required params | Optional params |
|
|
236
|
+
|--------|-------------|-----------------|-----------------|
|
|
237
|
+
| `list` | List all attachments with metadata | `issueKey` | — |
|
|
238
|
+
| `get_content` | Download and return file content | `attachmentId` | `mimeType` |
|
|
239
|
+
| `upload` | Attach a local file to an issue | `issueKey`, `filePath` | `fileName` |
|
|
240
|
+
| `delete` | Remove an attachment by ID | `attachmentId` | — |
|
|
226
241
|
|
|
227
|
-
|
|
228
|
-
|
|
242
|
+
**Content types returned by `get_content`**:
|
|
243
|
+
- **Text files** (`text/*`, `application/json`, `application/xml`): returned as readable text
|
|
244
|
+
- **Images** (`image/*`): returned as base64 — Claude will render them inline
|
|
245
|
+
- **Other types** (PDF, zip, etc.): returns file metadata with a descriptive message
|
|
229
246
|
|
|
230
|
-
**
|
|
231
|
-
- `
|
|
247
|
+
**Tips**:
|
|
248
|
+
- Use `list` first to get attachment IDs before calling `get_content` or `delete`.
|
|
249
|
+
- `fileName` in `upload` overrides the filename shown in Jira (defaults to the file's basename).
|
|
232
250
|
|
|
233
|
-
|
|
234
|
-
|
|
251
|
+
**Examples**:
|
|
252
|
+
```
|
|
253
|
+
List attachments on PROJ-123
|
|
254
|
+
Get the content of attachment 136904
|
|
255
|
+
Upload /tmp/report.pdf to PROJ-123
|
|
256
|
+
Delete attachment 136904
|
|
257
|
+
```
|
|
235
258
|
|
|
236
|
-
|
|
237
|
-
- `issueKey` (required): Issue to transition
|
|
238
|
-
- `transitionId` (required): Transition ID (from get_transitions)
|
|
239
|
-
- `comment` (optional): Comment to add with transition
|
|
259
|
+
---
|
|
240
260
|
|
|
241
|
-
|
|
261
|
+
## API Reference
|
|
242
262
|
|
|
243
263
|
This server uses the [Jira REST API v3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/).
|
|
244
264
|
|
|
@@ -252,7 +272,7 @@ Make sure you've set the environment variables in your MCP configuration.
|
|
|
252
272
|
|
|
253
273
|
- Verify your API token is correct
|
|
254
274
|
- Ensure your email matches your Atlassian account
|
|
255
|
-
- Check that your JIRA_BASE_URL doesn't have a trailing slash
|
|
275
|
+
- Check that your `JIRA_BASE_URL` doesn't have a trailing slash
|
|
256
276
|
|
|
257
277
|
### Permission errors
|
|
258
278
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { JiraApiClient } from '../utils/api-client.js';
|
|
2
|
+
export declare class AttachmentHandlers {
|
|
3
|
+
private apiClient;
|
|
4
|
+
constructor(apiClient: JiraApiClient);
|
|
5
|
+
handleListAttachments(args: any): Promise<{
|
|
6
|
+
content: {
|
|
7
|
+
type: string;
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
isError?: undefined;
|
|
11
|
+
} | {
|
|
12
|
+
content: {
|
|
13
|
+
type: string;
|
|
14
|
+
text: string;
|
|
15
|
+
}[];
|
|
16
|
+
isError: boolean;
|
|
17
|
+
}>;
|
|
18
|
+
handleGetAttachmentContent(args: any): Promise<{
|
|
19
|
+
content: ({
|
|
20
|
+
type: string;
|
|
21
|
+
text: string;
|
|
22
|
+
data?: undefined;
|
|
23
|
+
mimeType?: undefined;
|
|
24
|
+
} | {
|
|
25
|
+
type: string;
|
|
26
|
+
data: string;
|
|
27
|
+
mimeType: string;
|
|
28
|
+
text?: undefined;
|
|
29
|
+
})[];
|
|
30
|
+
isError?: undefined;
|
|
31
|
+
} | {
|
|
32
|
+
content: {
|
|
33
|
+
type: string;
|
|
34
|
+
text: string;
|
|
35
|
+
}[];
|
|
36
|
+
isError: boolean;
|
|
37
|
+
}>;
|
|
38
|
+
handleUploadAttachment(args: any): Promise<{
|
|
39
|
+
content: {
|
|
40
|
+
type: string;
|
|
41
|
+
text: string;
|
|
42
|
+
}[];
|
|
43
|
+
isError?: undefined;
|
|
44
|
+
} | {
|
|
45
|
+
content: {
|
|
46
|
+
type: string;
|
|
47
|
+
text: string;
|
|
48
|
+
}[];
|
|
49
|
+
isError: boolean;
|
|
50
|
+
}>;
|
|
51
|
+
handleDeleteAttachment(args: any): Promise<{
|
|
52
|
+
content: {
|
|
53
|
+
type: string;
|
|
54
|
+
text: string;
|
|
55
|
+
}[];
|
|
56
|
+
isError?: undefined;
|
|
57
|
+
} | {
|
|
58
|
+
content: {
|
|
59
|
+
type: string;
|
|
60
|
+
text: string;
|
|
61
|
+
}[];
|
|
62
|
+
isError: boolean;
|
|
63
|
+
}>;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=attachment-handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-handlers.d.ts","sourceRoot":"","sources":["../../src/handlers/attachment-handlers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvD,qBAAa,kBAAkB;IACjB,OAAO,CAAC,SAAS;gBAAT,SAAS,EAAE,aAAa;IAEtC,qBAAqB,CAAC,IAAI,EAAE,GAAG;;;;;;;;;;;;;IAgC/B,0BAA0B,CAAC,IAAI,EAAE,GAAG;;;;;;;;;;;;;;;;;;;;IAsEpC,sBAAsB,CAAC,IAAI,EAAE,GAAG;;;;;;;;;;;;;IAwChC,sBAAsB,CAAC,IAAI,EAAE,GAAG;;;;;;;;;;;;;CAqCvC"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { basename } from 'path';
|
|
3
|
+
import { JiraFormatters } from '../utils/formatters.js';
|
|
4
|
+
export class AttachmentHandlers {
|
|
5
|
+
apiClient;
|
|
6
|
+
constructor(apiClient) {
|
|
7
|
+
this.apiClient = apiClient;
|
|
8
|
+
}
|
|
9
|
+
async handleListAttachments(args) {
|
|
10
|
+
try {
|
|
11
|
+
const { issueKey } = args;
|
|
12
|
+
if (!issueKey) {
|
|
13
|
+
throw new Error('issueKey is required');
|
|
14
|
+
}
|
|
15
|
+
const issue = await this.apiClient.get(`/issue/${issueKey}`, { fields: 'attachment' });
|
|
16
|
+
const attachments = issue.fields?.attachment || [];
|
|
17
|
+
return {
|
|
18
|
+
content: [
|
|
19
|
+
{
|
|
20
|
+
type: 'text',
|
|
21
|
+
text: JiraFormatters.formatAttachments(attachments),
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
return {
|
|
28
|
+
content: [
|
|
29
|
+
{
|
|
30
|
+
type: 'text',
|
|
31
|
+
text: JiraFormatters.formatError(error),
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
isError: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async handleGetAttachmentContent(args) {
|
|
39
|
+
try {
|
|
40
|
+
const { attachmentId } = args;
|
|
41
|
+
if (!attachmentId) {
|
|
42
|
+
throw new Error('attachmentId is required');
|
|
43
|
+
}
|
|
44
|
+
// Fetch metadata to get filename and MIME type
|
|
45
|
+
const metadata = await this.apiClient.get(`/attachment/${attachmentId}`);
|
|
46
|
+
const filename = metadata.filename || 'unknown';
|
|
47
|
+
const mimeType = args.mimeType || metadata.mimeType || 'application/octet-stream';
|
|
48
|
+
const sizeBytes = metadata.size || 0;
|
|
49
|
+
const sizeKB = (sizeBytes / 1024).toFixed(1);
|
|
50
|
+
// Download the actual file bytes
|
|
51
|
+
const { data, contentType } = await this.apiClient.downloadAttachment(attachmentId);
|
|
52
|
+
const effectiveMime = contentType.split(';')[0].trim() || mimeType;
|
|
53
|
+
if (effectiveMime.startsWith('text/') || effectiveMime === 'application/json' || effectiveMime === 'application/xml') {
|
|
54
|
+
const text = data.toString('utf-8');
|
|
55
|
+
return {
|
|
56
|
+
content: [
|
|
57
|
+
{
|
|
58
|
+
type: 'text',
|
|
59
|
+
text: `**Attachment**: ${filename} (${effectiveMime}, ${sizeKB} KB)\n\n${text}`,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (effectiveMime.startsWith('image/')) {
|
|
65
|
+
const base64String = data.toString('base64');
|
|
66
|
+
return {
|
|
67
|
+
content: [
|
|
68
|
+
{
|
|
69
|
+
type: 'text',
|
|
70
|
+
text: `**Attachment**: ${filename} (${effectiveMime}, ${sizeKB} KB)`,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: 'image',
|
|
74
|
+
data: base64String,
|
|
75
|
+
mimeType: effectiveMime,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Unsupported type (PDF, zip, etc.)
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: 'text',
|
|
85
|
+
text: `**Attachment**: ${filename}\n**Type**: ${effectiveMime}\n**Size**: ${sizeKB} KB\n\nThis attachment cannot be displayed as text or image. Download it directly from Jira.`,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
return {
|
|
92
|
+
content: [
|
|
93
|
+
{
|
|
94
|
+
type: 'text',
|
|
95
|
+
text: JiraFormatters.formatError(error),
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
isError: true,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async handleUploadAttachment(args) {
|
|
103
|
+
try {
|
|
104
|
+
const { issueKey, filePath, fileName } = args;
|
|
105
|
+
if (!issueKey) {
|
|
106
|
+
throw new Error('issueKey is required');
|
|
107
|
+
}
|
|
108
|
+
if (!filePath) {
|
|
109
|
+
throw new Error('filePath is required');
|
|
110
|
+
}
|
|
111
|
+
if (!existsSync(filePath)) {
|
|
112
|
+
throw new Error(`File not found: ${filePath}`);
|
|
113
|
+
}
|
|
114
|
+
const name = fileName || basename(filePath);
|
|
115
|
+
const result = await this.apiClient.uploadAttachment(issueKey, filePath, name);
|
|
116
|
+
const attachment = result[0];
|
|
117
|
+
const sizeKB = attachment.size ? (attachment.size / 1024).toFixed(1) : 'unknown';
|
|
118
|
+
return {
|
|
119
|
+
content: [
|
|
120
|
+
{
|
|
121
|
+
type: 'text',
|
|
122
|
+
text: `Attachment uploaded successfully to ${issueKey}.\n\n**ID**: ${attachment.id}\n**Filename**: ${attachment.filename}\n**Size**: ${sizeKB} KB\n**MIME type**: ${attachment.mimeType || 'unknown'}`,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
return {
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: 'text',
|
|
132
|
+
text: JiraFormatters.formatError(error),
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
isError: true,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async handleDeleteAttachment(args) {
|
|
140
|
+
try {
|
|
141
|
+
const { attachmentId } = args;
|
|
142
|
+
if (!attachmentId) {
|
|
143
|
+
throw new Error('attachmentId is required');
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
await this.apiClient.delete(`/attachment/${attachmentId}`);
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
if (err.message?.includes('(404)')) {
|
|
150
|
+
throw new Error(`Attachment not found: ${attachmentId}`);
|
|
151
|
+
}
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
content: [
|
|
156
|
+
{
|
|
157
|
+
type: 'text',
|
|
158
|
+
text: `Attachment ${attachmentId} deleted successfully.`,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
return {
|
|
165
|
+
content: [
|
|
166
|
+
{
|
|
167
|
+
type: 'text',
|
|
168
|
+
text: JiraFormatters.formatError(error),
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
isError: true,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=attachment-handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-handlers.js","sourceRoot":"","sources":["../../src/handlers/attachment-handlers.ts"],"names":[],"mappings":"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"}
|
package/build/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { TransitionHandlers } from './handlers/transition-handlers.js';
|
|
|
10
10
|
import { ProjectHandlers } from './handlers/project-handlers.js';
|
|
11
11
|
import { MetadataHandlers } from './handlers/metadata-handlers.js';
|
|
12
12
|
import { UserHandlers } from './handlers/user-handlers.js';
|
|
13
|
+
import { AttachmentHandlers } from './handlers/attachment-handlers.js';
|
|
13
14
|
import { toolDefinitions } from './tools/definitions.js';
|
|
14
15
|
// Get environment variables
|
|
15
16
|
const JIRA_EMAIL = process.env.JIRA_EMAIL;
|
|
@@ -36,6 +37,7 @@ class JiraMCPServer {
|
|
|
36
37
|
projectHandlers;
|
|
37
38
|
metadataHandlers;
|
|
38
39
|
userHandlers;
|
|
40
|
+
attachmentHandlers;
|
|
39
41
|
constructor() {
|
|
40
42
|
this.server = new Server({
|
|
41
43
|
name: 'jira-mcp-server',
|
|
@@ -55,6 +57,7 @@ class JiraMCPServer {
|
|
|
55
57
|
this.transitionHandlers = new TransitionHandlers(this.apiClient);
|
|
56
58
|
this.projectHandlers = new ProjectHandlers(this.apiClient);
|
|
57
59
|
this.metadataHandlers = new MetadataHandlers(this.apiClient);
|
|
60
|
+
this.attachmentHandlers = new AttachmentHandlers(this.apiClient);
|
|
58
61
|
this.setupToolHandlers();
|
|
59
62
|
// Error handling
|
|
60
63
|
this.server.onerror = (error) => console.error('[MCP Error]', error);
|
|
@@ -71,36 +74,52 @@ class JiraMCPServer {
|
|
|
71
74
|
// Handle tool calls
|
|
72
75
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
73
76
|
switch (request.params.name) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
case '
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
case '
|
|
103
|
-
|
|
77
|
+
case 'jira_issues': {
|
|
78
|
+
const args = request.params.arguments;
|
|
79
|
+
switch (args.action) {
|
|
80
|
+
case 'get': return this.issueHandlers.handleGetIssue(args);
|
|
81
|
+
case 'create': return this.issueHandlers.handleCreateIssue(args);
|
|
82
|
+
case 'update': return this.issueHandlers.handleUpdateIssue(args);
|
|
83
|
+
case 'assign': return this.issueHandlers.handleAssignIssue(args);
|
|
84
|
+
default: throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${args.action}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
case 'jira_search': {
|
|
88
|
+
const args = request.params.arguments;
|
|
89
|
+
switch (args.action) {
|
|
90
|
+
case 'issues': return this.searchHandlers.handleSearchIssues(args);
|
|
91
|
+
case 'projects': return this.projectHandlers.handleListProjects(args);
|
|
92
|
+
case 'users': return this.userHandlers.handleSearchUsers(args);
|
|
93
|
+
case 'create_metadata': return this.metadataHandlers.handleGetCreateMetadata(args);
|
|
94
|
+
default: throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${args.action}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
case 'jira_comments': {
|
|
98
|
+
const args = request.params.arguments;
|
|
99
|
+
switch (args.action) {
|
|
100
|
+
case 'get': return this.commentHandlers.handleGetComments(args);
|
|
101
|
+
case 'add': return this.commentHandlers.handleAddComment(args);
|
|
102
|
+
default: throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${args.action}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
case 'jira_workflow': {
|
|
106
|
+
const args = request.params.arguments;
|
|
107
|
+
switch (args.action) {
|
|
108
|
+
case 'get_transitions': return this.transitionHandlers.handleGetTransitions(args);
|
|
109
|
+
case 'transition': return this.transitionHandlers.handleTransitionIssue(args);
|
|
110
|
+
default: throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${args.action}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
case 'jira_attachments': {
|
|
114
|
+
const args = request.params.arguments;
|
|
115
|
+
switch (args.action) {
|
|
116
|
+
case 'list': return this.attachmentHandlers.handleListAttachments(args);
|
|
117
|
+
case 'get_content': return this.attachmentHandlers.handleGetAttachmentContent(args);
|
|
118
|
+
case 'upload': return this.attachmentHandlers.handleUploadAttachment(args);
|
|
119
|
+
case 'delete': return this.attachmentHandlers.handleDeleteAttachment(args);
|
|
120
|
+
default: throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${args.action}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
104
123
|
default:
|
|
105
124
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
|
106
125
|
}
|