mcp-jira-cloud 3.0.0 → 3.1.1

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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [3.1.1](https://github.com/tezaswi7222/jira-mcp/compare/v3.1.0...v3.1.1) (2026-02-17)
2
+
3
+ ### 🐛 Bug Fixes
4
+
5
+ * remove .default() from zod schema in jira_get_user_worklogs ([7620b45](https://github.com/tezaswi7222/jira-mcp/commit/7620b451b581a50023c2d8ee896cc02f8ee3064d))
6
+
7
+ ## [3.1.0](https://github.com/tezaswi7222/jira-mcp/compare/v3.0.0...v3.1.0) (2026-02-17)
8
+
9
+ ### ✨ Features
10
+
11
+ * add jira_get_user_worklogs tool for querying worklogs by user and date range ([fa653ba](https://github.com/tezaswi7222/jira-mcp/commit/fa653baa24b18afc1faf6dbe56c46315ee6f63ff))
12
+
1
13
  ## [3.0.0](https://github.com/tezaswi7222/jira-mcp/compare/v2.0.5...v3.0.0) (2026-02-17)
2
14
 
3
15
  ### ⚠ BREAKING CHANGES
package/README.md CHANGED
@@ -113,6 +113,7 @@ A **Model Context Protocol (MCP)** server that enables AI assistants like **GitH
113
113
  ### ⏱️ Time Tracking
114
114
  - Log work on issues
115
115
  - View work logs
116
+ - Query worklogs by user & date range
116
117
  - Flexible time formats
117
118
 
118
119
  </td>
@@ -146,7 +147,7 @@ A **Model Context Protocol (MCP)** server that enables AI assistants like **GitH
146
147
  </table>
147
148
 
148
149
  <p align="center">
149
- <strong>73 Tools</strong> for comprehensive Jira management
150
+ <strong>74 Tools</strong> for comprehensive Jira management
150
151
  </p>
151
152
 
152
153
  ## 🚀 Quick Start
@@ -157,10 +158,10 @@ A **Model Context Protocol (MCP)** server that enables AI assistants like **GitH
157
158
  npm install -g mcp-jira-cloud
158
159
  ```
159
160
 
160
- Or use directly with `npx`:
161
+ Or use directly with `npx` (always gets latest version):
161
162
 
162
163
  ```bash
163
- npx mcp-jira-cloud
164
+ npx -y mcp-jira-cloud@latest
164
165
  ```
165
166
 
166
167
  ### Get Your API Token
@@ -175,7 +176,7 @@ There are two ways to run the MCP server:
175
176
 
176
177
  | Method | Command | Best For |
177
178
  |--------|---------|----------|
178
- | **npx** (no install) | `npx mcp-jira-cloud` | Quick setup, always latest version |
179
+ | **npx** (no install) | `npx -y mcp-jira-cloud@latest` | Quick setup, always latest version |
179
180
  | **Global install** | `jira-mcp` | Faster startup, offline usage |
180
181
 
181
182
  ---
@@ -191,7 +192,7 @@ Create or edit `.vscode/mcp.json` in your workspace:
191
192
  "jira": {
192
193
  "type": "stdio",
193
194
  "command": "npx",
194
- "args": ["mcp-jira-cloud"],
195
+ "args": ["-y", "mcp-jira-cloud@latest"],
195
196
  "env": {
196
197
  "JIRA_BASE_URL": "https://your-domain.atlassian.net",
197
198
  "JIRA_EMAIL": "your-email@example.com",
@@ -232,7 +233,7 @@ Add to your Claude configuration (`claude_desktop_config.json`):
232
233
  "mcpServers": {
233
234
  "jira": {
234
235
  "command": "npx",
235
- "args": ["mcp-jira-cloud"],
236
+ "args": ["-y", "mcp-jira-cloud@latest"],
236
237
  "env": {
237
238
  "JIRA_BASE_URL": "https://your-domain.atlassian.net",
238
239
  "JIRA_EMAIL": "your-email@example.com",
@@ -272,7 +273,7 @@ Create `.cursor/mcp.json` in your project or home directory:
272
273
  "mcpServers": {
273
274
  "jira": {
274
275
  "command": "npx",
275
- "args": ["mcp-jira-cloud"],
276
+ "args": ["-y", "mcp-jira-cloud@latest"],
276
277
  "env": {
277
278
  "JIRA_BASE_URL": "https://your-domain.atlassian.net",
278
279
  "JIRA_EMAIL": "your-email@example.com",
@@ -312,7 +313,7 @@ Add to your Windsurf MCP configuration:
312
313
  "mcpServers": {
313
314
  "jira": {
314
315
  "command": "npx",
315
- "args": ["mcp-jira-cloud"],
316
+ "args": ["-y", "mcp-jira-cloud@latest"],
316
317
  "env": {
317
318
  "JIRA_BASE_URL": "https://your-domain.atlassian.net",
318
319
  "JIRA_EMAIL": "your-email@example.com",
@@ -384,7 +385,7 @@ For OAuth authentication:
384
385
 
385
386
  ## 🛠️ Available Tools
386
387
 
387
- > **73 tools** organised into 14 categories
388
+ > **74 tools** organised into 14 categories
388
389
 
389
390
  ### 🔐 Authentication (7 tools)
390
391
 
@@ -419,7 +420,7 @@ For OAuth authentication:
419
420
  | `jira_get_my_open_issues` | Get your open/in-progress issues |
420
421
  | `jira_resolve` | Smart routing tool for common intents |
421
422
 
422
- ### 💬 Comments & Work Logs (4 tools)
423
+ ### 💬 Comments & Work Logs (5 tools)
423
424
 
424
425
  | Tool | Description |
425
426
  |------|-------------|
@@ -427,6 +428,7 @@ For OAuth authentication:
427
428
  | `jira_add_comment` | Add a comment to an issue |
428
429
  | `jira_add_worklog` | Log time spent on an issue |
429
430
  | `jira_get_worklogs` | Get work logs for an issue |
431
+ | `jira_get_user_worklogs` | Get worklogs by user and date range |
430
432
 
431
433
  ### ⚙️ Configuration & Metadata (9 tools)
432
434
 
@@ -718,12 +720,12 @@ The issue or project doesn't exist, or you don't have access to view it.
718
720
  | Attribute | Value |
719
721
  |-----------|-------|
720
722
  | Package name | [`mcp-jira-cloud`](https://www.npmjs.com/package/mcp-jira-cloud) |
721
- | Version | **2.2.0** |
723
+ | Latest Version | ![npm version](https://img.shields.io/npm/v/mcp-jira-cloud?style=flat-square) |
722
724
  | License | [MIT](LICENSE) |
723
725
  | Node.js | ≥18.0.0 |
724
726
  | TypeScript | ≥5.0.0 |
725
727
  | Module | ES Modules |
726
- | Tools | **78** |
728
+ | Tools | **74** |
727
729
 
728
730
  ### Dependencies
729
731
 
@@ -734,13 +736,57 @@ The issue or project doesn't exist, or you don't have access to view it.
734
736
  | [`keytar`](https://www.npmjs.com/package/keytar) | Secure credential storage |
735
737
  | [`zod`](https://www.npmjs.com/package/zod) | Schema validation |
736
738
 
737
- ## 🆕 What's New in v2.0.0
739
+ ## 🆕 What's New
740
+
741
+ ### 🚀 v3.1.0 (Latest)
742
+
743
+ | Feature | Description |
744
+ |---------|-------------|
745
+ | 🔍 **User Worklogs Query** | New `jira_get_user_worklogs` tool to query worklogs by user and date range |
746
+ | 📊 **Time Summary** | Returns total time logged with formatted hours/minutes |
747
+
748
+ **Total tools: 74**
749
+
750
+ ---
751
+
752
+ ### ⚠️ v3.0.0 (Breaking Changes)
753
+
754
+ <table>
755
+ <tr><td>
756
+
757
+ **Removed for Safety:**
758
+ - `jira_delete_issue`
759
+ - `jira_delete_sprint`
760
+ - `jira_delete_attachment`
761
+ - `jira_delete_filter`
762
+ - `jira_delete_issue_link`
763
+
764
+ </td><td>
765
+
766
+ **Improvements:**
767
+ - 🔒 Security patches (axios, qs)
768
+ - 🔄 Deprecated API migration
769
+ - ✅ 0 known vulnerabilities
770
+
771
+ </td></tr>
772
+ </table>
773
+
774
+ ---
738
775
 
739
776
  <details>
740
- <summary><strong>Click to see full changelog</strong></summary>
777
+ <summary><strong>📅 v2.x Changelog</strong></summary>
778
+
779
+ ### v2.2.0
780
+ - **Time Tracking Reports** - Get user worklogs for any date range with summaries
781
+
782
+ ### v2.1.0
783
+ - **Dashboard Management** - View and manage Jira dashboards and gadgets
784
+ - **Enhanced Attachments** - Upload attachments, get metadata and content
785
+ - **Labels Management** - Get all labels, bulk add/remove/set labels
786
+ - **JQL Tools** - Autocomplete, validate, and parse JQL queries
741
787
 
742
- ### Added
743
- - **Issue CRUD** - Create, update, delete issues with full field support
788
+ ### v2.0.0
789
+ - **Issue CRUD** - Create, update issues with full field support
744
790
  - **Workflow Transitions** - Move issues through workflow states
745
791
  - **Agile/Scrum** - Complete sprint and board management (15 tools)
746
792
  - **Issue Linking** - Blocks, relates, duplicates relationships
@@ -749,16 +795,6 @@ The issue or project doesn't exist, or you don't have access to view it.
749
795
  - **Filters** - Create and manage saved JQL filters
750
796
  - **Metadata** - Access field configurations and create metadata
751
797
  - **Bulk Operations** - Edit, watch, unwatch multiple issues at once
752
- - **Dashboard Management** - View and manage Jira dashboards and gadgets
753
- - **Enhanced Attachments** - Upload attachments, get metadata and content
754
- - **Labels Management** - Get all labels, bulk add/remove/set labels
755
- - **JQL Tools** - Autocomplete, validate, and parse JQL queries
756
- - **Time Tracking Reports** - Get user worklogs for any date range with summaries
757
-
758
- ### Changed
759
- - Total tools increased from 18 to 78
760
- - Improved TypeScript strict mode compliance
761
- - Enhanced error handling and validation
762
798
 
763
799
  </details>
764
800
 
package/SECURITY.md CHANGED
@@ -6,8 +6,9 @@ We release patches for security vulnerabilities in the following versions:
6
6
 
7
7
  | Version | Supported |
8
8
  | ------- | ------------------ |
9
+ | 3.x.x | :white_check_mark: |
9
10
  | 2.x.x | :white_check_mark: |
10
- | 1.x.x | :white_check_mark: |
11
+ | 1.x.x | :x: |
11
12
  | < 1.0 | :x: |
12
13
 
13
14
  ## Reporting a Vulnerability
@@ -128,6 +129,15 @@ When using OAuth 2.0:
128
129
 
129
130
  ## Audit Trail
130
131
 
132
+ ### Version 3.0.0
133
+
134
+ - **Removed destructive delete tools** for safety (jira_delete_issue, jira_delete_sprint, etc.)
135
+ - **Security patches** - Updated axios and qs dependencies to latest secure versions
136
+ - **Deprecated API migration** - Replaced deprecated `/createmeta` endpoint
137
+ - **0 known vulnerabilities** - Verified with `npm audit`
138
+ - Enhanced `.gitignore` and `.npmignore` with comprehensive sensitive file patterns
139
+ - Added protection for SSH keys, OAuth tokens, certificates, and credential files
140
+
131
141
  ### Version 2.0.0
132
142
 
133
143
  - Security review for bulk operations
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import e,{AxiosError as t}from"axios";import{McpServer as s}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as a}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as r}from"zod";import{readFileSync as i}from"fs";import{fileURLToPath as n}from"url";import{dirname as o,join as c}from"path";const u=o(n(import.meta.url));function d(){try{const e=c(u,"..","package.json");return JSON.parse(i(e,"utf-8"))}catch{try{const e=c(u,"package.json");return JSON.parse(i(e,"utf-8"))}catch{return{name:"mcp-jira-cloud",version:"2.0.4",description:"Jira MCP Server"}}}}const l=process.argv.slice(2);(l.includes("-h")||l.includes("--help"))&&(!function(){const e=d();console.log(`\n${e.name} v${e.version}\n${e.description}\n\nUSAGE:\n jira-mcp [OPTIONS]\n mcp-jira-cloud [OPTIONS]\n\nOPTIONS:\n -h, --help Show this help message and exit\n -v, --version Show version number and exit\n\nENVIRONMENT VARIABLES:\n Basic Auth (recommended for most users):\n JIRA_BASE_URL Your Jira instance URL (e.g., https://your-domain.atlassian.net)\n JIRA_EMAIL Your Atlassian account email\n JIRA_API_TOKEN API token from https://id.atlassian.com/manage-profile/security/api-tokens\n\n OAuth 2.0 (for advanced integrations):\n JIRA_OAUTH_CLIENT_ID OAuth app client ID\n JIRA_OAUTH_CLIENT_SECRET OAuth app client secret\n JIRA_OAUTH_ACCESS_TOKEN Access token\n JIRA_OAUTH_REFRESH_TOKEN Refresh token (optional)\n JIRA_CLOUD_ID Jira Cloud ID\n\n Optional:\n JIRA_ACCEPTANCE_CRITERIA_FIELD Custom field ID for acceptance criteria\n\nEXAMPLES:\n # Run as MCP server (typical usage via AI assistant config)\n jira-mcp\n\n # Check version\n jira-mcp --version\n\nMCP CONFIGURATION:\n Add to your AI assistant's MCP configuration:\n\n VS Code (settings.json):\n "mcp": {\n "servers": {\n "jira": {\n "command": "npx",\n "args": ["-y", "mcp-jira-cloud"],\n "env": {\n "JIRA_BASE_URL": "https://your-domain.atlassian.net",\n "JIRA_EMAIL": "your-email@example.com",\n "JIRA_API_TOKEN": "your-api-token"\n }\n }\n }\n }\n\nDOCUMENTATION:\n https://github.com/tezaswi7222/jira-mcp#readme\n\nISSUES:\n https://github.com/tezaswi7222/jira-mcp/issues\n`)}(),process.exit(0)),(l.includes("-v")||l.includes("--version"))&&(!function(){const e=d();console.log(`${e.name} v${e.version}`)}(),process.exit(0));const p="jira-mcp",m="default",y=(process.env.JIRA_ACCEPTANCE_CRITERIA_FIELD||"").trim(),h="https://auth.atlassian.com/oauth/token";let f,I=null;async function w(){if(void 0!==f)return f;try{f=await import("keytar")}catch{f=null}return f}function g(e){let t;try{t=new URL(e)}catch{throw new Error("baseUrl must be a valid URL like https://your-domain.atlassian.net")}const s=t.pathname.replace(/\/+$/,"");return`${t.origin}${s}`}async function _(t,s,a){const r=await e.post(h,{grant_type:"refresh_token",client_id:t,client_secret:s,refresh_token:a},{headers:{"Content-Type":"application/json"}});return{accessToken:r.data.access_token,refreshToken:r.data.refresh_token,expiresIn:r.data.expires_in}}async function k(t){return(await e.get("https://api.atlassian.com/oauth/token/accessible-resources",{headers:{Authorization:`Bearer ${t}`,Accept:"application/json"}})).data}async function b(e,t){const s=await k(e);if(0===s.length)throw new Error("No accessible Jira sites found. Make sure your OAuth app has the correct scopes and you have granted access.");if(t){const e=g(t),a=s.find(t=>g(t.url)===e);if(a)return{cloudId:a.id,siteName:a.name,siteUrl:a.url};throw new Error(`Site ${t} not found in accessible resources. Available sites: ${s.map(e=>e.url).join(", ")}`)}const a=s[0];if(!a)throw new Error("No accessible Jira resources found");return{cloudId:a.id,siteName:a.name,siteUrl:a.url}}async function v(){if(I){if("oauth"===I.type&&I.expiresAt&&I.refreshToken){if(Date.now()>=I.expiresAt-3e5)try{const e=await _(I.clientId,I.clientSecret,I.refreshToken);I={...I,accessToken:e.accessToken,refreshToken:e.refreshToken||I.refreshToken,expiresAt:Date.now()+1e3*e.expiresIn}}catch(e){console.error("Failed to refresh OAuth token:",e)}}return I}const e=function(){const e=process.env.JIRA_OAUTH_CLIENT_ID,t=process.env.JIRA_OAUTH_CLIENT_SECRET,s=process.env.JIRA_OAUTH_ACCESS_TOKEN,a=process.env.JIRA_OAUTH_REFRESH_TOKEN,r=process.env.JIRA_CLOUD_ID;return e&&t&&s&&r?{type:"oauth",clientId:e,clientSecret:t,accessToken:s,refreshToken:a,cloudId:r}:null}();if(e)return e;const t=function(){const e=process.env.JIRA_BASE_URL,t=process.env.JIRA_EMAIL,s=process.env.JIRA_API_TOKEN;return e&&t&&s?{type:"basic",baseUrl:g(e),email:t,apiToken:s}:null}();if(t)return t;const s=await async function(){const e=await w();if(!e)return null;const t=await e.getPassword(p,m);if(!t)return null;try{const e=JSON.parse(t);return"basic"===e.type?{...e,baseUrl:g(e.baseUrl)}:e}catch{return null}}();if(s)return s;throw new Error("MISSING_AUTH")}async function j(e,t){if(I=e,!t)return;const s=await w();if(!s)throw new Error("Keytar is not available to persist credentials.");await s.setPassword(p,m,JSON.stringify(e))}function A(t){return"basic"===t.type?e.create({baseURL:t.baseUrl,auth:{username:t.email,password:t.apiToken},headers:{Accept:"application/json"}}):e.create({baseURL:`https://api.atlassian.com/ex/jira/${t.cloudId}`,headers:{Authorization:`Bearer ${t.accessToken}`,Accept:"application/json"}})}function S(e){if(!e)return"";if(Array.isArray(e))return e.map(S).filter(Boolean).join("\n").trim();if("string"==typeof e.text)return e.text;if(Array.isArray(e.content)){return e.content.map(S).filter(Boolean).join("paragraph"===e.type?"\n":" ").trim()}return""}function D(e){if("string"==typeof e)return e;if("number"==typeof e)return String(e);if(e&&"object"==typeof e){const t=S(e);if(t)return t}return""}function R(e){return{type:"doc",version:1,content:[{type:"paragraph",content:[{type:"text",text:e}]}]}}function x(e){const t=e?.fields||{},s=D(t.description),a=y?D(t[y]):"";return{key:e?.key??"",summary:t.summary??"",description:s,acceptanceCriteria:a||null}}function O(e){const t=e?.fields||{};return{key:e?.key??"",summary:t.summary??"",status:t.status?.name??""}}function U(){const e=["summary","description"];return y&&e.push(y),e}function K(e){if(e instanceof t){const t=e.response?.status,s=e.response?.data;return`Jira API error${t?` (${t})`:""}: ${("string"==typeof s?s:JSON.stringify(s,null,2))||e.message}`}return e instanceof Error?e.message:"Unknown error"}function $(e){if(e instanceof Error&&"MISSING_AUTH"===e.message)return{error:"unauthorized",message:"Jira credentials are missing. Provide credentials explicitly to authenticate."};if(e instanceof t){const t=e.response?.status;return 401===t?{error:"unauthorized",message:"Jira credentials are missing or invalid. If using OAuth, the token may have expired."}:403===t?{error:"forbidden",message:"You do not have permission to access this Jira resource."}:404===t?{error:"not_found",message:"The Jira resource does not exist or is not visible."}:429===t?{error:"rate_limited",message:"Jira rate limit exceeded. Please retry later."}:t&&t>=500?{error:"server_error",message:"Jira server error. Please retry later."}:{error:"jira_error",message:K(e)}}return e instanceof Error?{error:"unknown",message:e.message}:{error:"unknown",message:"Unknown error"}}function C(e){return{content:[{type:"text",text:"string"==typeof e?e:JSON.stringify(e,null,2)}]}}const T=new s({name:"jira-mcp",version:"2.0.0"});T.registerTool("_internal_jira_set_auth",{title:"Set Jira Auth (Basic)",description:"Use when the user wants to connect Jira using Basic Auth (email + API token). This tool should only be called when the user explicitly provides credentials.",inputSchema:r.object({baseUrl:r.string(),email:r.string().email(),apiToken:r.string().min(1),persist:r.boolean().optional().default(!1)})},async({baseUrl:e,email:t,apiToken:s,persist:a})=>{const r=g(e);return await j({type:"basic",baseUrl:r,email:t,apiToken:s},a??!1),C("Jira credentials loaded (Basic Auth).")}),T.registerTool("jira_oauth_get_auth_url",{title:"Get OAuth Authorization URL",description:"Generate the OAuth 2.0 authorization URL that the user should visit to grant access. Returns the URL and required state parameter.",inputSchema:r.object({clientId:r.string().min(1).describe("OAuth Client ID from Atlassian Developer Console"),redirectUri:r.string().url().describe("Callback URL configured in your OAuth app"),scopes:r.array(r.string()).optional().default(["read:jira-work","read:jira-user","write:jira-work","offline_access"]).describe("OAuth scopes to request")})},async({clientId:e,redirectUri:t,scopes:s})=>{const a=Math.random().toString(36).substring(2,15),r=function(e,t,s,a){return`https://auth.atlassian.com/authorize?${new URLSearchParams({audience:"api.atlassian.com",client_id:e,scope:s.join(" "),redirect_uri:t,state:a,response_type:"code",prompt:"consent"}).toString()}`}(e,t,s,a);return C({authUrl:r,state:a,instructions:"1. Visit the authUrl in your browser\n2. Grant access to your Jira site\n3. Copy the 'code' parameter from the redirect URL\n4. Use jira_oauth_exchange_code to exchange it for tokens"})}),T.registerTool("jira_oauth_exchange_code",{title:"Exchange OAuth Code for Tokens",description:"Exchange the authorization code for access tokens after the user has completed the OAuth flow.",inputSchema:r.object({clientId:r.string().min(1),clientSecret:r.string().min(1),code:r.string().min(1).describe("Authorization code from the OAuth callback"),redirectUri:r.string().url(),siteUrl:r.string().url().optional().describe("Optional: specific Jira site URL (e.g., https://yoursite.atlassian.net)"),persist:r.boolean().optional().default(!1)})},async({clientId:t,clientSecret:s,code:a,redirectUri:r,siteUrl:i,persist:n})=>{try{const o=await async function(t,s,a,r){const i=await e.post(h,{grant_type:"authorization_code",client_id:t,client_secret:s,code:a,redirect_uri:r},{headers:{"Content-Type":"application/json"}});return{accessToken:i.data.access_token,refreshToken:i.data.refresh_token,expiresIn:i.data.expires_in}}(t,s,a,r),{cloudId:c,siteName:u,siteUrl:d}=await b(o.accessToken,i),l={type:"oauth",clientId:t,clientSecret:s,accessToken:o.accessToken,refreshToken:o.refreshToken,cloudId:c,expiresAt:o.expiresIn?Date.now()+1e3*o.expiresIn:void 0};return await j(l,n),C({success:!0,message:`Successfully authenticated with OAuth to ${u}`,site:{name:u,url:d,cloudId:c},hasRefreshToken:!!o.refreshToken})}catch(e){return C($(e))}}),T.registerTool("jira_oauth_set_tokens",{title:"Set OAuth Tokens Directly",description:"Set OAuth tokens directly if you already have them (e.g., from a previous session or external OAuth flow).",inputSchema:r.object({clientId:r.string().min(1),clientSecret:r.string().min(1),accessToken:r.string().min(1),refreshToken:r.string().optional(),cloudId:r.string().optional().describe("Cloud ID of the Jira site. If not provided, will be fetched automatically."),siteUrl:r.string().url().optional().describe("Jira site URL to find the correct cloudId"),persist:r.boolean().optional().default(!1)})},async({clientId:e,clientSecret:t,accessToken:s,refreshToken:a,cloudId:r,siteUrl:i,persist:n})=>{try{let o=r,c="",u=i||"";if(!o){const e=await b(s,i);o=e.cloudId,c=e.siteName,u=e.siteUrl}const d={type:"oauth",clientId:e,clientSecret:t,accessToken:s,refreshToken:a,cloudId:o};return await j(d,n),C({success:!0,message:c?`OAuth tokens set for ${c}`:"OAuth tokens set successfully",cloudId:o,siteUrl:u})}catch(e){return C($(e))}}),T.registerTool("jira_oauth_refresh",{title:"Refresh OAuth Token",description:"Manually refresh the OAuth access token using the refresh token.",inputSchema:r.object({})},async()=>{try{const e=await v();if("oauth"!==e.type)return C({error:"invalid_auth_type",message:"Current authentication is not OAuth. Use basic auth credentials directly."});if(!e.refreshToken)return C({error:"no_refresh_token",message:"No refresh token available. You need to re-authenticate with 'offline_access' scope."});const t=await _(e.clientId,e.clientSecret,e.refreshToken),s={...e,accessToken:t.accessToken,refreshToken:t.refreshToken||e.refreshToken,expiresAt:Date.now()+1e3*t.expiresIn};return await j(s,!1),C({success:!0,message:"OAuth token refreshed successfully",expiresIn:t.expiresIn})}catch(e){return C($(e))}}),T.registerTool("jira_oauth_list_sites",{title:"List Accessible Jira Sites",description:"List all Jira sites accessible with the current OAuth token.",inputSchema:r.object({})},async()=>{try{const e=await v();if("oauth"!==e.type)return C({error:"invalid_auth_type",message:"This tool requires OAuth authentication. Current auth is basic auth."});const t=await k(e.accessToken);return C({currentCloudId:e.cloudId,sites:t.map(e=>({cloudId:e.id,name:e.name,url:e.url,scopes:e.scopes}))})}catch(e){return C($(e))}}),T.registerTool("jira_clear_auth",{title:"Clear Jira Auth",description:"Use when the user asks to remove or reset stored Jira credentials.",inputSchema:r.object({})},async()=>(await async function(){I=null;const e=await w();e&&await e.deletePassword(p,m)}(),C("Jira credentials cleared."))),T.registerTool("jira_auth_status",{title:"Get Auth Status",description:"Check the current authentication status and type.",inputSchema:r.object({})},async()=>{try{const e=await v();return"basic"===e.type?C({authenticated:!0,type:"basic",baseUrl:e.baseUrl,email:e.email}):C({authenticated:!0,type:"oauth",cloudId:e.cloudId,hasRefreshToken:!!e.refreshToken,expiresAt:e.expiresAt?new Date(e.expiresAt).toISOString():null})}catch(e){return e instanceof Error&&"MISSING_AUTH"===e.message?C({authenticated:!1,message:"No authentication configured. Use basic auth or OAuth to authenticate."}):C($(e))}}),T.registerTool("jira_whoami",{title:"Get Jira Profile",description:"Use when the user asks who they are in Jira or wants to verify the Jira account in use."},async()=>{try{const e=A(await v());return C((await e.get("/rest/api/3/myself")).data)}catch(e){return C($(e))}}),T.registerTool("jira_get_issue",{title:"Get Jira Issue",description:"Get the full details of a Jira issue when the user mentions an issue key like PROJ-123 or asks about a specific ticket.",inputSchema:r.object({issueIdOrKey:r.string().min(1),fields:r.array(r.string()).optional(),expand:r.string().optional()})},async({issueIdOrKey:e,fields:t,expand:s})=>{try{const a=A(await v()),r=t?.length?t:U();return C(x((await a.get(`/rest/api/3/issue/${encodeURIComponent(e)}`,{params:{fields:r.join(","),expand:s}})).data))}catch(e){return C($(e))}}),T.registerTool("jira_search_issues",{title:"Search Jira Issues",description:"Use when the user asks to find issues matching criteria (JQL), like 'my open bugs' or 'tickets updated this week'.",inputSchema:r.object({jql:r.string().min(1),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(200).optional(),fields:r.array(r.string()).optional(),expand:r.string().optional(),nextPageToken:r.string().optional(),reconcileIssues:r.boolean().optional()})},async({jql:e,startAt:t,maxResults:s,fields:a,expand:r,nextPageToken:i,reconcileIssues:n})=>{try{const o=A(await v()),c=a?.length?a:U(),u=await o.get("/rest/api/3/search/jql",{params:{jql:e,startAt:t,maxResults:s,fields:c.join(","),expand:r,nextPageToken:i,reconcileIssues:n}}),d=Array.isArray(u.data?.issues)?u.data.issues.map(x):[];return C({total:u.data?.total??d.length,issues:d})}catch(e){return C($(e))}}),T.registerTool("jira_search_issues_summary",{title:"Search Jira Issues (Summary)",description:"Use when the user wants the top results for a Jira search and only needs key, summary, and status.",inputSchema:r.object({jql:r.string().min(1),maxResults:r.number().int().positive().max(50).optional()})},async({jql:e,maxResults:t})=>{try{const s=A(await v()),a=await s.get("/rest/api/3/search/jql",{params:{jql:e,maxResults:t??10,fields:["summary","status"].join(",")}});return C(Array.isArray(a.data?.issues)?a.data.issues.map(O):[])}catch(e){return C($(e))}}),T.registerTool("jira_resolve",{title:"Resolve Jira Intent",description:"Primary routing tool. Use this tool first when the user intent is clear (get issue, search, or my issues) but the exact Jira tool to call is uncertain.",inputSchema:r.object({intent:r.enum(["get_issue","search","my_issues"]),issueKey:r.string().optional(),jql:r.string().optional(),maxResults:r.number().int().positive().max(50).optional()})},async({intent:e,issueKey:t,jql:s,maxResults:a})=>{try{const r=A(await v());if("get_issue"===e){if(!t)return C({error:"invalid_input",message:"issueKey is required when intent is get_issue."});return C(x((await r.get(`/rest/api/3/issue/${encodeURIComponent(t)}`,{params:{fields:U().join(",")}})).data))}if("search"===e){if(!s)return C({error:"invalid_input",message:"jql is required when intent is search."});const e=await r.get("/rest/api/3/search/jql",{params:{jql:s,maxResults:a??10,fields:["summary","status"].join(",")}});return C(Array.isArray(e.data?.issues)?e.data.issues.map(O):[])}const i=await r.get("/rest/api/3/search/jql",{params:{jql:"assignee = currentUser() AND statusCategory != Done ORDER BY updated DESC",maxResults:a??20,fields:U().join(",")}}),n=Array.isArray(i.data?.issues)?i.data.issues.map(x):[];return C({total:i.data?.total??n.length,issues:n})}catch(e){return C($(e))}}),T.registerTool("jira_get_issue_summary",{title:"Get Issue Summary",description:"Use when the user wants the summary, description, and acceptance criteria for a specific issue key.",inputSchema:r.object({issueIdOrKey:r.string().min(1)})},async({issueIdOrKey:e})=>{try{const t=A(await v());return C(x((await t.get(`/rest/api/3/issue/${encodeURIComponent(e)}`,{params:{fields:U().join(",")}})).data))}catch(e){return C($(e))}}),T.registerTool("jira_get_my_open_issues",{title:"Get My Open Issues",description:"Use when the user asks for their open tickets or what they should work on next.",inputSchema:r.object({maxResults:r.number().int().positive().max(50).optional()})},async({maxResults:e})=>{try{const t=A(await v()),s=await t.get("/rest/api/3/search/jql",{params:{jql:"assignee = currentUser() AND statusCategory != Done ORDER BY updated DESC",maxResults:e??20,fields:U().join(",")}}),a=Array.isArray(s.data?.issues)?s.data.issues.map(x):[];return C({total:s.data?.total??a.length,issues:a})}catch(e){return C($(e))}}),T.registerTool("jira_get_issue_comments",{title:"Get Issue Comments",description:"Use when the user asks for the discussion or comments on a specific ticket; returns a clean list.",inputSchema:r.object({issueIdOrKey:r.string().min(1),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(100).optional()})},async({issueIdOrKey:e,startAt:t,maxResults:s})=>{try{const a=A(await v()),r=await a.get(`/rest/api/3/issue/${encodeURIComponent(e)}/comment`,{params:{startAt:t,maxResults:s}});return C(Array.isArray(r.data?.comments)?r.data.comments.map(e=>({author:e?.author?.displayName||e?.author?.emailAddress||e?.author?.accountId||"",created:e?.created??"",body:D(e?.body)})):[])}catch(e){return C($(e))}}),T.registerTool("jira_add_comment",{title:"Add Jira Comment",description:"Use when the user asks to add a comment to a specific ticket; confirm intent before posting.",inputSchema:r.object({issueIdOrKey:r.string().min(1),body:r.string().min(1)})},async({issueIdOrKey:e,body:t})=>{try{const s=A(await v()),a=await s.post(`/rest/api/3/issue/${encodeURIComponent(e)}/comment`,{body:R(t)});return C({id:a.data?.id??"",created:a.data?.created??""})}catch(e){return C($(e))}}),T.registerTool("jira_add_worklog",{title:"Add Work Log",description:"Use when the user wants to log time/work on a specific Jira ticket. Allows specifying time spent, start date/time, and an optional description.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("The issue key (e.g., PROJ-123) to log work against"),timeSpent:r.string().min(1).describe("Time spent in Jira format (e.g., '1h', '30m', '1h 30m', '1d')"),started:r.string().optional().describe("When the work started in ISO 8601 format (e.g., '2026-02-13T14:00:00.000+0000'). Defaults to now if not provided."),comment:r.string().optional().describe("Optional description of the work performed")})},async({issueIdOrKey:e,timeSpent:t,started:s,comment:a})=>{try{const r=A(await v()),i={timeSpent:t};s&&(i.started=s),a&&(i.comment=R(a));const n=await r.post(`/rest/api/3/issue/${encodeURIComponent(e)}/worklog`,i);return C({id:n.data?.id??"",issueId:n.data?.issueId??"",timeSpent:n.data?.timeSpent??"",started:n.data?.started??"",author:n.data?.author?.displayName??n.data?.author?.emailAddress??"",created:n.data?.created??""})}catch(e){return C($(e))}}),T.registerTool("jira_get_worklogs",{title:"Get Work Logs",description:"Use when the user wants to see work logs recorded on a specific Jira ticket.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("The issue key (e.g., PROJ-123) to get work logs for"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(100).optional()})},async({issueIdOrKey:e,startAt:t,maxResults:s})=>{try{const a=A(await v()),r=await a.get(`/rest/api/3/issue/${encodeURIComponent(e)}/worklog`,{params:{startAt:t,maxResults:s}}),i=Array.isArray(r.data?.worklogs)?r.data.worklogs.map(e=>({id:e?.id??"",author:e?.author?.displayName||e?.author?.emailAddress||"",timeSpent:e?.timeSpent??"",timeSpentSeconds:e?.timeSpentSeconds??0,started:e?.started??"",created:e?.created??"",comment:D(e?.comment)})):[];return C({total:r.data?.total??i.length,worklogs:i})}catch(e){return C($(e))}}),T.registerTool("jira_list_projects",{title:"List Jira Projects",description:"Use when the user asks which Jira projects they can access or wants a list of projects.",inputSchema:r.object({startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(50).optional()})},async({startAt:e,maxResults:t})=>{try{const s=A(await v());return C((await s.get("/rest/api/3/project/search",{params:{startAt:e,maxResults:t}})).data)}catch(e){return C($(e))}}),T.registerTool("jira_get_project",{title:"Get Jira Project",description:"Use when the user mentions a project key and asks for project details or metadata.",inputSchema:r.object({projectIdOrKey:r.string().min(1)})},async({projectIdOrKey:e})=>{try{const t=A(await v());return C((await t.get(`/rest/api/3/project/${encodeURIComponent(e)}`)).data)}catch(e){return C($(e))}}),T.registerTool("jira_create_issue",{title:"Create Jira Issue",description:"Create a new Jira issue. Requires project key, issue type, and summary at minimum.",inputSchema:r.object({projectKey:r.string().min(1).describe("Project key (e.g., 'MXTS')"),issueType:r.string().min(1).describe("Issue type name or ID (e.g., 'Bug', 'Task', 'Story')"),summary:r.string().min(1).describe("Issue title/summary"),description:r.string().optional().describe("Issue description (plain text, will be converted to ADF)"),assignee:r.string().optional().describe("Assignee account ID. Use '-1' for automatic assignment."),reporter:r.string().optional().describe("Reporter account ID"),priority:r.string().optional().describe("Priority name or ID (e.g., 'High', 'Medium', 'Low')"),labels:r.array(r.string()).optional().describe("Array of label strings"),components:r.array(r.string()).optional().describe("Array of component names or IDs"),fixVersions:r.array(r.string()).optional().describe("Array of fix version names or IDs"),affectsVersions:r.array(r.string()).optional().describe("Array of affected version names or IDs"),dueDate:r.string().optional().describe("Due date in YYYY-MM-DD format"),parentKey:r.string().optional().describe("Parent issue key for subtasks"),environment:r.string().optional().describe("Environment description"),originalEstimate:r.string().optional().describe("Original time estimate (e.g., '2h', '1d')"),customFields:r.record(r.string(),r.unknown()).optional().describe("Custom field values as key-value pairs")})},async e=>{try{const t=A(await v()),s=function(e){const t={};if(e.projectKey&&(t.project={key:e.projectKey}),e.issueType&&(t.issuetype=/^\d+$/.test(e.issueType)?{id:e.issueType}:{name:e.issueType}),void 0!==e.summary&&(t.summary=e.summary),void 0!==e.description&&(t.description=e.description?R(e.description):null),void 0!==e.assignee&&(t.assignee=null===e.assignee?null:{accountId:e.assignee}),e.reporter&&(t.reporter={accountId:e.reporter}),e.priority&&(t.priority=/^\d+$/.test(e.priority)?{id:e.priority}:{name:e.priority}),e.labels&&e.labels.length>0&&(t.labels=e.labels),e.components&&e.components.length>0&&(t.components=e.components.map(e=>/^\d+$/.test(e)?{id:e}:{name:e})),e.fixVersions&&e.fixVersions.length>0&&(t.fixVersions=e.fixVersions.map(e=>/^\d+$/.test(e)?{id:e}:{name:e})),e.affectsVersions&&e.affectsVersions.length>0&&(t.versions=e.affectsVersions.map(e=>/^\d+$/.test(e)?{id:e}:{name:e})),void 0!==e.dueDate&&(t.duedate=e.dueDate),e.parentKey&&(t.parent={key:e.parentKey}),e.environment&&(t.environment=R(e.environment)),(e.originalEstimate||e.remainingEstimate)&&(t.timetracking={},e.originalEstimate&&(t.timetracking.originalEstimate=e.originalEstimate),e.remainingEstimate&&(t.timetracking.remainingEstimate=e.remainingEstimate)),e.customFields)for(const[s,a]of Object.entries(e.customFields)){const e=s.startsWith("customfield_")?s:`customfield_${s}`;"string"==typeof a&&a.length,t[e]=a}return t}({projectKey:e.projectKey,issueType:e.issueType,summary:e.summary,description:e.description,assignee:e.assignee,reporter:e.reporter,priority:e.priority,labels:e.labels,components:e.components,fixVersions:e.fixVersions,affectsVersions:e.affectsVersions,dueDate:e.dueDate,parentKey:e.parentKey,environment:e.environment,originalEstimate:e.originalEstimate,customFields:e.customFields}),a=await t.post("/rest/api/3/issue",{fields:s});return C({success:!0,id:a.data?.id??"",key:a.data?.key??"",self:a.data?.self??"",message:`Issue ${a.data?.key} created successfully`})}catch(e){return C($(e))}}),T.registerTool("jira_update_issue",{title:"Update Jira Issue",description:"Update an existing Jira issue. Only provided fields will be modified.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID (e.g., 'MXTS-123')"),summary:r.string().optional().describe("New summary/title"),description:r.string().optional().describe("New description (plain text)"),assignee:r.string().nullable().optional().describe("Assignee account ID. Use null to unassign."),priority:r.string().optional().describe("Priority name or ID"),dueDate:r.string().nullable().optional().describe("Due date (YYYY-MM-DD) or null to clear"),labels:r.object({add:r.array(r.string()).optional(),remove:r.array(r.string()).optional(),set:r.array(r.string()).optional()}).optional().describe("Label operations: add, remove, or set"),components:r.object({add:r.array(r.string()).optional(),remove:r.array(r.string()).optional(),set:r.array(r.string()).optional()}).optional().describe("Component operations: add, remove, or set"),fixVersions:r.object({add:r.array(r.string()).optional(),remove:r.array(r.string()).optional(),set:r.array(r.string()).optional()}).optional().describe("Fix version operations: add, remove, or set"),customFields:r.record(r.string(),r.unknown()).optional().describe("Custom field values"),notifyUsers:r.boolean().optional().default(!0).describe("Send notifications to watchers")})},async e=>{try{const t=A(await v()),s={},a={};if(void 0!==e.summary&&(a.summary=e.summary),void 0!==e.description&&(a.description=e.description?R(e.description):null),void 0!==e.assignee&&(a.assignee=null===e.assignee?null:{accountId:e.assignee}),void 0!==e.priority&&(a.priority=/^\d+$/.test(e.priority)?{id:e.priority}:{name:e.priority}),void 0!==e.dueDate&&(a.duedate=e.dueDate),e.customFields)for(const[t,s]of Object.entries(e.customFields)){a[t.startsWith("customfield_")?t:`customfield_${t}`]=s}Object.keys(a).length>0&&(s.fields=a);const r=function(e){const t={};if(e.labels){const s=[];e.labels.add&&e.labels.add.forEach(e=>s.push({add:e})),e.labels.remove&&e.labels.remove.forEach(e=>s.push({remove:e})),e.labels.set&&s.push({set:e.labels.set}),t.labels=s}if(e.components){const s=[];e.components.add&&e.components.add.forEach(e=>s.push({add:/^\d+$/.test(e)?{id:e}:{name:e}})),e.components.remove&&e.components.remove.forEach(e=>s.push({remove:/^\d+$/.test(e)?{id:e}:{name:e}})),e.components.set&&s.push({set:e.components.set.map(e=>/^\d+$/.test(e)?{id:e}:{name:e})}),t.components=s}if(e.fixVersions){const s=[];e.fixVersions.add&&e.fixVersions.add.forEach(e=>s.push({add:/^\d+$/.test(e)?{id:e}:{name:e}})),e.fixVersions.remove&&e.fixVersions.remove.forEach(e=>s.push({remove:/^\d+$/.test(e)?{id:e}:{name:e}})),e.fixVersions.set&&s.push({set:e.fixVersions.set.map(e=>/^\d+$/.test(e)?{id:e}:{name:e})}),t.fixVersions=s}if(e.affectsVersions){const s=[];e.affectsVersions.add&&e.affectsVersions.add.forEach(e=>s.push({add:/^\d+$/.test(e)?{id:e}:{name:e}})),e.affectsVersions.remove&&e.affectsVersions.remove.forEach(e=>s.push({remove:/^\d+$/.test(e)?{id:e}:{name:e}})),e.affectsVersions.set&&s.push({set:e.affectsVersions.set.map(e=>/^\d+$/.test(e)?{id:e}:{name:e})}),t.versions=s}return t}({labels:e.labels,components:e.components,fixVersions:e.fixVersions});return Object.keys(r).length>0&&(s.update=r),0===Object.keys(s).length?C({error:"no_changes",message:"No fields provided to update"}):(await t.put(`/rest/api/3/issue/${encodeURIComponent(e.issueIdOrKey)}`,s,{params:{notifyUsers:e.notifyUsers??!0}}),C({success:!0,key:e.issueIdOrKey,message:`Issue ${e.issueIdOrKey} updated successfully`}))}catch(e){return C($(e))}}),T.registerTool("jira_assign_issue",{title:"Assign Jira Issue",description:"Assign or unassign a Jira issue to a user.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID"),accountId:r.string().nullable().describe("User account ID to assign, '-1' for automatic, or null to unassign")})},async({issueIdOrKey:e,accountId:t})=>{try{const s=A(await v());await s.put(`/rest/api/3/issue/${encodeURIComponent(e)}/assignee`,{accountId:t});return C({success:!0,key:e,message:`Issue ${e} ${null===t?"unassigned":"assigned"} successfully`,assignee:t})}catch(e){return C($(e))}}),T.registerTool("jira_get_transitions",{title:"Get Issue Transitions",description:"Get available workflow transitions for an issue. Use before transitioning to see valid options.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID"),expand:r.string().optional().describe("Expand options: 'transitions.fields' to include required fields")})},async({issueIdOrKey:e,expand:t})=>{try{const s=A(await v()),a=await s.get(`/rest/api/3/issue/${encodeURIComponent(e)}/transitions`,{params:{expand:t}});return C({issueKey:e,transitions:Array.isArray(a.data?.transitions)?a.data.transitions.map(e=>({id:e.id,name:e.name,to:{id:e.to?.id,name:e.to?.name,statusCategory:e.to?.statusCategory?.name},hasScreen:e.hasScreen??!1,isGlobal:e.isGlobal??!1,isInitial:e.isInitial??!1,isConditional:e.isConditional??!1,fields:e.fields?Object.keys(e.fields):[]})):[]})}catch(e){return C($(e))}}),T.registerTool("jira_transition_issue",{title:"Transition Jira Issue",description:"Move a Jira issue to a different status by executing a workflow transition.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID"),transitionId:r.string().min(1).describe("Transition ID (get from jira_get_transitions)"),comment:r.string().optional().describe("Comment to add during transition"),resolution:r.string().optional().describe("Resolution name for closing transitions (e.g., 'Done', 'Fixed')"),fields:r.record(r.string(),r.unknown()).optional().describe("Additional fields required by the transition")})},async({issueIdOrKey:e,transitionId:t,comment:s,resolution:a,fields:r})=>{try{const i=A(await v()),n={transition:{id:t}};if(r||a){const e={...r};a&&(e.resolution={name:a}),n.fields=e}return s&&(n.update={comment:[{add:{body:R(s)}}]}),await i.post(`/rest/api/3/issue/${encodeURIComponent(e)}/transitions`,n),C({success:!0,key:e,transitionId:t,message:`Issue ${e} transitioned successfully`})}catch(e){return C($(e))}}),T.registerTool("jira_get_issue_types",{title:"Get Issue Types",description:"Get available issue types, optionally filtered by project.",inputSchema:r.object({projectKey:r.string().optional().describe("Filter issue types for a specific project")})},async({projectKey:e})=>{try{const t=A(await v());let s;if(e){const a=await t.get(`/rest/api/3/project/${encodeURIComponent(e)}`);s=a.data?.issueTypes||[]}else{s=(await t.get("/rest/api/3/issuetype")).data||[]}return C(s.map(e=>({id:e.id,name:e.name,description:e.description||"",subtask:e.subtask??!1,hierarchyLevel:e.hierarchyLevel})))}catch(e){return C($(e))}}),T.registerTool("jira_get_priorities",{title:"Get Priorities",description:"Get available priority levels for issues.",inputSchema:r.object({})},async()=>{try{const e=A(await v());return C(((await e.get("/rest/api/3/priority")).data||[]).map(e=>({id:e.id,name:e.name,description:e.description||"",iconUrl:e.iconUrl})))}catch(e){return C($(e))}}),T.registerTool("jira_get_statuses",{title:"Get Statuses",description:"Get available statuses, optionally filtered by project.",inputSchema:r.object({projectKey:r.string().optional().describe("Filter statuses for a specific project")})},async({projectKey:e})=>{try{const t=A(await v());if(e){return C((await t.get(`/rest/api/3/project/${encodeURIComponent(e)}/statuses`)).data||[])}return C(((await t.get("/rest/api/3/status")).data||[]).map(e=>({id:e.id,name:e.name,description:e.description||"",statusCategory:e.statusCategory?.name})))}catch(e){return C($(e))}}),T.registerTool("jira_get_components",{title:"Get Project Components",description:"Get components for a specific project.",inputSchema:r.object({projectKey:r.string().min(1).describe("Project key")})},async({projectKey:e})=>{try{const t=A(await v());return C(((await t.get(`/rest/api/3/project/${encodeURIComponent(e)}/components`)).data||[]).map(e=>({id:e.id,name:e.name,description:e.description||"",lead:e.lead?.displayName,assigneeType:e.assigneeType})))}catch(e){return C($(e))}}),T.registerTool("jira_get_versions",{title:"Get Project Versions",description:"Get versions for a specific project.",inputSchema:r.object({projectKey:r.string().min(1).describe("Project key"),released:r.boolean().optional().describe("Filter by released status")})},async({projectKey:e,released:t})=>{try{const s=A(await v());let a=(await s.get(`/rest/api/3/project/${encodeURIComponent(e)}/versions`)).data||[];return void 0!==t&&(a=a.filter(e=>e.released===t)),C(a.map(e=>({id:e.id,name:e.name,description:e.description||"",released:e.released??!1,archived:e.archived??!1,releaseDate:e.releaseDate,startDate:e.startDate})))}catch(e){return C($(e))}}),T.registerTool("jira_search_users",{title:"Search Jira Users",description:"Search for Jira users by name, email, or username.",inputSchema:r.object({query:r.string().min(1).describe("Search query (name, email, or username)"),projectKey:r.string().optional().describe("Filter users with access to this project"),maxResults:r.number().int().positive().max(50).optional().default(10)})},async({query:e,projectKey:t,maxResults:s})=>{try{const a=A(await v());let r=(await a.get("/rest/api/3/user/search",{params:{query:e,maxResults:s??10}})).data||[];if(t&&r.length>0)try{r=(await a.get("/rest/api/3/user/assignable/search",{params:{query:e,project:t,maxResults:s??10}})).data||[]}catch{}return C(r.map(e=>({accountId:e.accountId,displayName:e.displayName,emailAddress:e.emailAddress,active:e.active??!0,avatarUrl:e.avatarUrls?.["48x48"]})))}catch(e){return C($(e))}}),T.registerTool("jira_get_changelog",{title:"Get Issue Changelog",description:"Get the history of changes for an issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(100).optional().default(20)})},async({issueIdOrKey:e,startAt:t,maxResults:s})=>{try{const a=A(await v()),r=await a.get(`/rest/api/3/issue/${encodeURIComponent(e)}/changelog`,{params:{startAt:t,maxResults:s??20}}),i=Array.isArray(r.data?.values)?r.data.values.map(e=>({id:e.id,author:e.author?.displayName||e.author?.emailAddress||"",created:e.created,items:(e.items||[]).map(e=>({field:e.field,fieldtype:e.fieldtype,from:e.fromString||e.from,to:e.toString||e.to}))})):[];return C({total:r.data?.total??i.length,startAt:r.data?.startAt??0,changes:i})}catch(e){return C($(e))}}),T.registerTool("jira_get_boards",{title:"Get Jira Boards",description:"Get all Scrum and Kanban boards, optionally filtered by project or type.",inputSchema:r.object({projectKeyOrId:r.string().optional().describe("Filter boards by project"),type:r.enum(["scrum","kanban","simple"]).optional().describe("Filter by board type"),name:r.string().optional().describe("Filter boards by name (contains)"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(50).optional().default(50)})},async({projectKeyOrId:e,type:t,name:s,startAt:a,maxResults:r})=>{try{const i=A(await v()),n=await i.get("/rest/agile/1.0/board",{params:{projectKeyOrId:e,type:t,name:s,startAt:a,maxResults:r??50}}),o=Array.isArray(n.data?.values)?n.data.values.map(e=>({id:e.id,name:e.name,type:e.type,projectKey:e.location?.projectKey,projectName:e.location?.displayName})):[];return C({total:n.data?.total??o.length,startAt:n.data?.startAt??0,boards:o})}catch(e){return C($(e))}}),T.registerTool("jira_get_board",{title:"Get Board Details",description:"Get details of a specific board including configuration.",inputSchema:r.object({boardId:r.number().int().positive().describe("Board ID")})},async({boardId:e})=>{try{const t=A(await v()),s=await t.get(`/rest/agile/1.0/board/${e}`);return C({id:s.data?.id,name:s.data?.name,type:s.data?.type,self:s.data?.self,location:s.data?.location})}catch(e){return C($(e))}}),T.registerTool("jira_get_board_configuration",{title:"Get Board Configuration",description:"Get the configuration of a board including columns, estimation, and ranking.",inputSchema:r.object({boardId:r.number().int().positive().describe("Board ID")})},async({boardId:e})=>{try{const t=A(await v()),s=await t.get(`/rest/agile/1.0/board/${e}/configuration`);return C({id:s.data?.id,name:s.data?.name,type:s.data?.type,filter:s.data?.filter,columnConfig:s.data?.columnConfig,estimation:s.data?.estimation,ranking:s.data?.ranking})}catch(e){return C($(e))}}),T.registerTool("jira_get_sprints",{title:"Get Sprints",description:"Get sprints for a board, optionally filtered by state.",inputSchema:r.object({boardId:r.number().int().positive().describe("Board ID"),state:r.enum(["future","active","closed"]).optional().describe("Filter by sprint state"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(50).optional().default(50)})},async({boardId:e,state:t,startAt:s,maxResults:a})=>{try{const r=A(await v()),i=await r.get(`/rest/agile/1.0/board/${e}/sprint`,{params:{state:t,startAt:s,maxResults:a??50}}),n=Array.isArray(i.data?.values)?i.data.values.map(e=>({id:e.id,name:e.name,state:e.state,startDate:e.startDate,endDate:e.endDate,completeDate:e.completeDate,originBoardId:e.originBoardId,goal:e.goal})):[];return C({total:i.data?.total??n.length,startAt:i.data?.startAt??0,sprints:n})}catch(e){return C($(e))}}),T.registerTool("jira_get_sprint",{title:"Get Sprint Details",description:"Get details of a specific sprint.",inputSchema:r.object({sprintId:r.number().int().positive().describe("Sprint ID")})},async({sprintId:e})=>{try{const t=A(await v()),s=await t.get(`/rest/agile/1.0/sprint/${e}`);return C({id:s.data?.id,name:s.data?.name,state:s.data?.state,startDate:s.data?.startDate,endDate:s.data?.endDate,completeDate:s.data?.completeDate,originBoardId:s.data?.originBoardId,goal:s.data?.goal})}catch(e){return C($(e))}}),T.registerTool("jira_create_sprint",{title:"Create Sprint",description:"Create a new sprint on a board.",inputSchema:r.object({boardId:r.number().int().positive().describe("Board ID"),name:r.string().min(1).describe("Sprint name"),startDate:r.string().optional().describe("Start date (ISO 8601)"),endDate:r.string().optional().describe("End date (ISO 8601)"),goal:r.string().optional().describe("Sprint goal")})},async({boardId:e,name:t,startDate:s,endDate:a,goal:r})=>{try{const i=A(await v()),n=await i.post("/rest/agile/1.0/sprint",{originBoardId:e,name:t,startDate:s,endDate:a,goal:r});return C({success:!0,id:n.data?.id,name:n.data?.name,state:n.data?.state,message:`Sprint "${t}" created successfully`})}catch(e){return C($(e))}}),T.registerTool("jira_update_sprint",{title:"Update Sprint",description:"Update sprint details including name, dates, and goal.",inputSchema:r.object({sprintId:r.number().int().positive().describe("Sprint ID"),name:r.string().optional().describe("New sprint name"),state:r.enum(["future","active","closed"]).optional().describe("Sprint state"),startDate:r.string().optional().describe("Start date (ISO 8601)"),endDate:r.string().optional().describe("End date (ISO 8601)"),goal:r.string().optional().describe("Sprint goal")})},async({sprintId:e,name:t,state:s,startDate:a,endDate:r,goal:i})=>{try{const n=A(await v()),o={};if(void 0!==t&&(o.name=t),void 0!==s&&(o.state=s),void 0!==a&&(o.startDate=a),void 0!==r&&(o.endDate=r),void 0!==i&&(o.goal=i),0===Object.keys(o).length)return C({error:"no_changes",message:"No fields provided to update"});const c=await n.put(`/rest/agile/1.0/sprint/${e}`,o);return C({success:!0,id:c.data?.id??e,name:c.data?.name,state:c.data?.state,message:"Sprint updated successfully"})}catch(e){return C($(e))}}),T.registerTool("jira_start_sprint",{title:"Start Sprint",description:"Start a sprint that is in 'future' state.",inputSchema:r.object({sprintId:r.number().int().positive().describe("Sprint ID"),startDate:r.string().optional().describe("Start date (defaults to now)"),endDate:r.string().describe("End date (required for starting a sprint)")})},async({sprintId:e,startDate:t,endDate:s})=>{try{const a=A(await v()),r=await a.post(`/rest/agile/1.0/sprint/${e}`,{state:"active",startDate:t||(new Date).toISOString(),endDate:s});return C({success:!0,id:r.data?.id??e,state:"active",message:"Sprint started successfully"})}catch(e){return C($(e))}}),T.registerTool("jira_complete_sprint",{title:"Complete Sprint",description:"Complete an active sprint. Optionally move incomplete issues to another sprint or backlog.",inputSchema:r.object({sprintId:r.number().int().positive().describe("Sprint ID to complete"),moveIncompleteIssuesTo:r.number().int().positive().optional().describe("Sprint ID to move incomplete issues to (omit to move to backlog)")})},async({sprintId:e,moveIncompleteIssuesTo:t})=>{try{const s=A(await v());return await s.post(`/rest/agile/1.0/sprint/${e}`,{state:"closed"}),C({success:!0,id:e,state:"closed",message:"Sprint completed successfully",incompleteIssuesMovedTo:t||"backlog"})}catch(e){return C($(e))}}),T.registerTool("jira_get_sprint_issues",{title:"Get Sprint Issues",description:"Get all issues in a sprint.",inputSchema:r.object({sprintId:r.number().int().positive().describe("Sprint ID"),jql:r.string().optional().describe("Additional JQL filter"),fields:r.array(r.string()).optional().describe("Fields to return"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(100).optional().default(50)})},async({sprintId:e,jql:t,fields:s,startAt:a,maxResults:r})=>{try{const i=A(await v()),n=await i.get(`/rest/agile/1.0/sprint/${e}/issue`,{params:{jql:t,fields:s?.join(","),startAt:a,maxResults:r??50}}),o=Array.isArray(n.data?.issues)?n.data.issues.map(e=>({key:e.key,summary:e.fields?.summary,status:e.fields?.status?.name,assignee:e.fields?.assignee?.displayName,issueType:e.fields?.issuetype?.name,priority:e.fields?.priority?.name,storyPoints:e.fields?.customfield_10016})):[];return C({total:n.data?.total??o.length,startAt:n.data?.startAt??0,sprintId:e,issues:o})}catch(e){return C($(e))}}),T.registerTool("jira_move_issues_to_sprint",{title:"Move Issues to Sprint",description:"Move issues to a sprint.",inputSchema:r.object({sprintId:r.number().int().positive().describe("Target sprint ID"),issueKeys:r.array(r.string().min(1)).min(1).describe("Issue keys to move")})},async({sprintId:e,issueKeys:t})=>{try{const s=A(await v());return await s.post(`/rest/agile/1.0/sprint/${e}/issue`,{issues:t}),C({success:!0,sprintId:e,issuesMoved:t,message:`${t.length} issue(s) moved to sprint ${e}`})}catch(e){return C($(e))}}),T.registerTool("jira_get_backlog_issues",{title:"Get Backlog Issues",description:"Get issues in the backlog (not in any active sprint) for a board.",inputSchema:r.object({boardId:r.number().int().positive().describe("Board ID"),jql:r.string().optional().describe("Additional JQL filter"),fields:r.array(r.string()).optional().describe("Fields to return"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(100).optional().default(50)})},async({boardId:e,jql:t,fields:s,startAt:a,maxResults:r})=>{try{const i=A(await v()),n=await i.get(`/rest/agile/1.0/board/${e}/backlog`,{params:{jql:t,fields:s?.join(","),startAt:a,maxResults:r??50}}),o=Array.isArray(n.data?.issues)?n.data.issues.map(e=>({key:e.key,summary:e.fields?.summary,status:e.fields?.status?.name,assignee:e.fields?.assignee?.displayName,issueType:e.fields?.issuetype?.name,priority:e.fields?.priority?.name})):[];return C({total:n.data?.total??o.length,startAt:n.data?.startAt??0,boardId:e,issues:o})}catch(e){return C($(e))}}),T.registerTool("jira_move_issues_to_backlog",{title:"Move Issues to Backlog",description:"Move issues from a sprint back to the backlog.",inputSchema:r.object({issueKeys:r.array(r.string().min(1)).min(1).describe("Issue keys to move to backlog")})},async({issueKeys:e})=>{try{const t=A(await v());return await t.post("/rest/agile/1.0/backlog/issue",{issues:e}),C({success:!0,issuesMoved:e,message:`${e.length} issue(s) moved to backlog`})}catch(e){return C($(e))}}),T.registerTool("jira_rank_issues",{title:"Rank Issues",description:"Change the rank of issues on a board by placing them before or after another issue.",inputSchema:r.object({issueKeys:r.array(r.string().min(1)).min(1).describe("Issue keys to rank"),rankBeforeIssue:r.string().optional().describe("Issue key to rank before"),rankAfterIssue:r.string().optional().describe("Issue key to rank after")})},async({issueKeys:e,rankBeforeIssue:t,rankAfterIssue:s})=>{try{if(!t&&!s)return C({error:"invalid_parameters",message:"Either rankBeforeIssue or rankAfterIssue must be provided"});const a=A(await v()),r={issues:e};return t?r.rankBeforeIssue=t:s&&(r.rankAfterIssue=s),await a.put("/rest/agile/1.0/issue/rank",r),C({success:!0,issuesRanked:e,message:`${e.length} issue(s) ranked successfully`})}catch(e){return C($(e))}}),T.registerTool("jira_get_issue_links",{title:"Get Issue Links",description:"Get all linked issues for a specific issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID")})},async({issueIdOrKey:e})=>{try{const t=A(await v()),s=await t.get(`/rest/api/3/issue/${encodeURIComponent(e)}`,{params:{fields:"issuelinks"}});return C({issueKey:e,links:Array.isArray(s.data?.fields?.issuelinks)?s.data.fields.issuelinks.map(e=>{const t=!!e.inwardIssue,s=t?e.inwardIssue:e.outwardIssue;return{id:e.id,type:e.type?.name,direction:t?"inward":"outward",description:t?e.type?.inward:e.type?.outward,linkedIssue:{key:s?.key,summary:s?.fields?.summary,status:s?.fields?.status?.name,issueType:s?.fields?.issuetype?.name}}}):[]})}catch(e){return C($(e))}}),T.registerTool("jira_create_issue_link",{title:"Link Issues",description:"Create a link between two issues.",inputSchema:r.object({inwardIssue:r.string().min(1).describe("Inward issue key (the 'from' issue)"),outwardIssue:r.string().min(1).describe("Outward issue key (the 'to' issue)"),linkType:r.string().min(1).describe("Link type name (e.g., 'Blocks', 'Relates', 'Duplicates')"),comment:r.string().optional().describe("Comment to add with the link")})},async({inwardIssue:e,outwardIssue:t,linkType:s,comment:a})=>{try{const r=A(await v()),i={type:{name:s},inwardIssue:{key:e},outwardIssue:{key:t}};return a&&(i.comment={body:R(a)}),await r.post("/rest/api/3/issueLink",i),C({success:!0,message:`Link created: ${e} ${s} ${t}`,inwardIssue:e,outwardIssue:t,linkType:s})}catch(e){return C($(e))}}),T.registerTool("jira_get_link_types",{title:"Get Issue Link Types",description:"Get available link types for linking issues.",inputSchema:r.object({})},async()=>{try{const e=A(await v()),t=await e.get("/rest/api/3/issueLinkType");return C((t.data?.issueLinkTypes||[]).map(e=>({id:e.id,name:e.name,inward:e.inward,outward:e.outward})))}catch(e){return C($(e))}}),T.registerTool("jira_get_watchers",{title:"Get Issue Watchers",description:"Get the list of users watching an issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID")})},async({issueIdOrKey:e})=>{try{const t=A(await v()),s=await t.get(`/rest/api/3/issue/${encodeURIComponent(e)}/watchers`),a=Array.isArray(s.data?.watchers)?s.data.watchers.map(e=>({accountId:e.accountId,displayName:e.displayName,emailAddress:e.emailAddress})):[];return C({issueKey:e,watchCount:s.data?.watchCount??a.length,isWatching:s.data?.isWatching??!1,watchers:a})}catch(e){return C($(e))}}),T.registerTool("jira_add_watcher",{title:"Add Issue Watcher",description:"Add a user to watch an issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID"),accountId:r.string().min(1).describe("User account ID to add as watcher")})},async({issueIdOrKey:e,accountId:t})=>{try{const s=A(await v());return await s.post(`/rest/api/3/issue/${encodeURIComponent(e)}/watchers`,JSON.stringify(t),{headers:{"Content-Type":"application/json"}}),C({success:!0,issueKey:e,accountId:t,message:"User added as watcher"})}catch(e){return C($(e))}}),T.registerTool("jira_remove_watcher",{title:"Remove Issue Watcher",description:"Remove a user from watching an issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID"),accountId:r.string().min(1).describe("User account ID to remove")})},async({issueIdOrKey:e,accountId:t})=>{try{const s=A(await v());return await s.delete(`/rest/api/3/issue/${encodeURIComponent(e)}/watchers`,{params:{accountId:t}}),C({success:!0,issueKey:e,accountId:t,message:"User removed from watchers"})}catch(e){return C($(e))}}),T.registerTool("jira_get_votes",{title:"Get Issue Votes",description:"Get the vote count and voters for an issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID")})},async({issueIdOrKey:e})=>{try{const t=A(await v()),s=await t.get(`/rest/api/3/issue/${encodeURIComponent(e)}/votes`),a=Array.isArray(s.data?.voters)?s.data.voters.map(e=>({accountId:e.accountId,displayName:e.displayName})):[];return C({issueKey:e,votes:s.data?.votes??0,hasVoted:s.data?.hasVoted??!1,voters:a})}catch(e){return C($(e))}}),T.registerTool("jira_add_vote",{title:"Vote for Issue",description:"Add your vote to an issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID")})},async({issueIdOrKey:e})=>{try{const t=A(await v());return await t.post(`/rest/api/3/issue/${encodeURIComponent(e)}/votes`),C({success:!0,issueKey:e,message:"Vote added successfully"})}catch(e){return C($(e))}}),T.registerTool("jira_remove_vote",{title:"Remove Vote",description:"Remove your vote from an issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID")})},async({issueIdOrKey:e})=>{try{const t=A(await v());return await t.delete(`/rest/api/3/issue/${encodeURIComponent(e)}/votes`),C({success:!0,issueKey:e,message:"Vote removed successfully"})}catch(e){return C($(e))}}),T.registerTool("jira_get_attachments",{title:"Get Issue Attachments",description:"Get all attachments for an issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID")})},async({issueIdOrKey:e})=>{try{const t=A(await v()),s=await t.get(`/rest/api/3/issue/${encodeURIComponent(e)}`,{params:{fields:"attachment"}});return C({issueKey:e,attachments:Array.isArray(s.data?.fields?.attachment)?s.data.fields.attachment.map(e=>({id:e.id,filename:e.filename,size:e.size,mimeType:e.mimeType,content:e.content,thumbnail:e.thumbnail,author:e.author?.displayName,created:e.created})):[]})}catch(e){return C($(e))}}),T.registerTool("jira_get_epics",{title:"Get Epics",description:"Get epics for a board.",inputSchema:r.object({boardId:r.number().int().positive().describe("Board ID"),done:r.enum(["true","false"]).optional().describe("Filter by completion status"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(50).optional().default(50)})},async({boardId:e,done:t,startAt:s,maxResults:a})=>{try{const r=A(await v()),i=await r.get(`/rest/agile/1.0/board/${e}/epic`,{params:{done:t,startAt:s,maxResults:a??50}}),n=Array.isArray(i.data?.values)?i.data.values.map(e=>({id:e.id,key:e.key,name:e.name,summary:e.summary,done:e.done??!1})):[];return C({total:i.data?.total??n.length,startAt:i.data?.startAt??0,boardId:e,epics:n})}catch(e){return C($(e))}}),T.registerTool("jira_get_epic_issues",{title:"Get Epic Issues",description:"Get all issues belonging to an epic.",inputSchema:r.object({epicIdOrKey:r.string().min(1).describe("Epic ID or key"),jql:r.string().optional().describe("Additional JQL filter"),fields:r.array(r.string()).optional().describe("Fields to return"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(100).optional().default(50)})},async({epicIdOrKey:e,jql:t,fields:s,startAt:a,maxResults:r})=>{try{const i=A(await v()),n=await i.get(`/rest/agile/1.0/epic/${e}/issue`,{params:{jql:t,fields:s?.join(","),startAt:a,maxResults:r??50}}),o=Array.isArray(n.data?.issues)?n.data.issues.map(e=>({key:e.key,summary:e.fields?.summary,status:e.fields?.status?.name,assignee:e.fields?.assignee?.displayName,issueType:e.fields?.issuetype?.name})):[];return C({total:n.data?.total??o.length,startAt:n.data?.startAt??0,epicKey:e,issues:o})}catch(e){return C($(e))}}),T.registerTool("jira_move_issues_to_epic",{title:"Move Issues to Epic",description:"Move issues to an epic.",inputSchema:r.object({epicIdOrKey:r.string().min(1).describe("Epic ID or key"),issueKeys:r.array(r.string().min(1)).min(1).describe("Issue keys to move")})},async({epicIdOrKey:e,issueKeys:t})=>{try{const s=A(await v());return await s.post(`/rest/agile/1.0/epic/${e}/issue`,{issues:t}),C({success:!0,epicKey:e,issuesMoved:t,message:`${t.length} issue(s) moved to epic ${e}`})}catch(e){return C($(e))}}),T.registerTool("jira_remove_issues_from_epic",{title:"Remove Issues from Epic",description:"Remove issues from their epic (move to no epic).",inputSchema:r.object({issueKeys:r.array(r.string().min(1)).min(1).describe("Issue keys to remove from epic")})},async({issueKeys:e})=>{try{const t=A(await v());return await t.post("/rest/agile/1.0/epic/none/issue",{issues:e}),C({success:!0,issuesRemoved:e,message:`${e.length} issue(s) removed from epic`})}catch(e){return C($(e))}}),T.registerTool("jira_get_fields",{title:"Get All Fields",description:"Get all available fields including custom fields.",inputSchema:r.object({})},async()=>{try{const e=A(await v());return C(((await e.get("/rest/api/3/field")).data||[]).map(e=>({id:e.id,key:e.key,name:e.name,custom:e.custom??!1,orderable:e.orderable??!1,navigable:e.navigable??!1,searchable:e.searchable??!1,clauseNames:e.clauseNames||[],schema:e.schema})))}catch(e){return C($(e))}}),T.registerTool("jira_get_create_metadata",{title:"Get Create Issue Metadata",description:"Get metadata for creating issues in a project, including available issue types and their fields. Uses the modern non-deprecated API endpoints.",inputSchema:r.object({projectIdOrKey:r.string().min(1).describe("Project key or ID (required)"),issueTypeId:r.string().optional().describe("Issue type ID to get field metadata for (optional - if not provided, returns available issue types)"),startAt:r.number().optional().describe("Starting index for pagination"),maxResults:r.number().optional().describe("Maximum results (default 50, max 50)")})},async({projectIdOrKey:e,issueTypeId:t,startAt:s,maxResults:a})=>{try{const r=A(await v());if(t){return C((await r.get(`/rest/api/3/issue/createmeta/${encodeURIComponent(e)}/issuetypes/${encodeURIComponent(t)}`,{params:{startAt:s||0,maxResults:a||50}})).data)}return C((await r.get(`/rest/api/3/issue/createmeta/${encodeURIComponent(e)}/issuetypes`,{params:{startAt:s||0,maxResults:a||50}})).data)}catch(e){return C($(e))}}),T.registerTool("jira_get_edit_metadata",{title:"Get Edit Issue Metadata",description:"Get metadata for editing a specific issue, including editable fields.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID")})},async({issueIdOrKey:e})=>{try{const t=A(await v());return C((await t.get(`/rest/api/3/issue/${encodeURIComponent(e)}/editmeta`)).data)}catch(e){return C($(e))}}),T.registerTool("jira_get_filters",{title:"Get Filters",description:"Get saved filters, optionally filtered by name.",inputSchema:r.object({filterName:r.string().optional().describe("Filter by name (contains)"),owner:r.string().optional().describe("Filter by owner account ID"),expand:r.string().optional().describe("Expand options: description, owner, jql, viewUrl, searchUrl, favourite, favouritedCount, sharePermissions"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(50).optional().default(50)})},async({filterName:e,owner:t,expand:s,startAt:a,maxResults:r})=>{try{const i=A(await v()),n=await i.get("/rest/api/3/filter/search",{params:{filterName:e,owner:t,expand:s,startAt:a,maxResults:r??50}}),o=Array.isArray(n.data?.values)?n.data.values.map(e=>({id:e.id,name:e.name,description:e.description,owner:e.owner?.displayName,jql:e.jql,favourite:e.favourite??!1,favouritedCount:e.favouritedCount??0})):[];return C({total:n.data?.total??o.length,startAt:n.data?.startAt??0,filters:o})}catch(e){return C($(e))}}),T.registerTool("jira_get_filter",{title:"Get Filter Details",description:"Get details of a specific filter.",inputSchema:r.object({filterId:r.string().min(1).describe("Filter ID"),expand:r.string().optional().describe("Expand options")})},async({filterId:e,expand:t})=>{try{const s=A(await v()),a=await s.get(`/rest/api/3/filter/${e}`,{params:{expand:t}});return C({id:a.data?.id,name:a.data?.name,description:a.data?.description,owner:a.data?.owner?.displayName,jql:a.data?.jql,favourite:a.data?.favourite??!1,sharePermissions:a.data?.sharePermissions})}catch(e){return C($(e))}}),T.registerTool("jira_create_filter",{title:"Create Filter",description:"Create a new saved filter.",inputSchema:r.object({name:r.string().min(1).describe("Filter name"),jql:r.string().min(1).describe("JQL query"),description:r.string().optional().describe("Filter description"),favourite:r.boolean().optional().describe("Mark as favourite")})},async({name:e,jql:t,description:s,favourite:a})=>{try{const r=A(await v()),i=await r.post("/rest/api/3/filter",{name:e,jql:t,description:s,favourite:a});return C({success:!0,id:i.data?.id,name:i.data?.name,jql:i.data?.jql,message:`Filter "${e}" created successfully`})}catch(e){return C($(e))}}),T.registerTool("jira_update_filter",{title:"Update Filter",description:"Update an existing filter.",inputSchema:r.object({filterId:r.string().min(1).describe("Filter ID"),name:r.string().optional().describe("New filter name"),jql:r.string().optional().describe("New JQL query"),description:r.string().optional().describe("New description"),favourite:r.boolean().optional().describe("Favourite status")})},async({filterId:e,name:t,jql:s,description:a,favourite:r})=>{try{const i=A(await v()),n={};if(void 0!==t&&(n.name=t),void 0!==s&&(n.jql=s),void 0!==a&&(n.description=a),void 0!==r&&(n.favourite=r),0===Object.keys(n).length)return C({error:"no_changes",message:"No fields provided to update"});const o=await i.put(`/rest/api/3/filter/${e}`,n);return C({success:!0,id:o.data?.id??e,name:o.data?.name,message:"Filter updated successfully"})}catch(e){return C($(e))}}),T.registerTool("jira_get_my_filters",{title:"Get My Filters",description:"Get filters owned by the current user.",inputSchema:r.object({expand:r.string().optional().describe("Expand options")})},async({expand:e})=>{try{const t=A(await v());return C(((await t.get("/rest/api/3/filter/my",{params:{expand:e}})).data||[]).map(e=>({id:e.id,name:e.name,description:e.description,jql:e.jql,favourite:e.favourite??!1})))}catch(e){return C($(e))}}),T.registerTool("jira_get_favourite_filters",{title:"Get Favourite Filters",description:"Get filters marked as favourite by the current user.",inputSchema:r.object({expand:r.string().optional().describe("Expand options")})},async({expand:e})=>{try{const t=A(await v());return C(((await t.get("/rest/api/3/filter/favourite",{params:{expand:e}})).data||[]).map(e=>({id:e.id,name:e.name,description:e.description,owner:e.owner?.displayName,jql:e.jql})))}catch(e){return C($(e))}}),T.registerTool("jira_bulk_edit_issues",{title:"Bulk Edit Issues",description:"Edit multiple issues at once. Supports bulk editing of labels, assignee, priority, components, and fix versions. Returns a taskId to track progress.",inputSchema:r.object({issueIdsOrKeys:r.array(r.string()).min(1).describe("Array of issue IDs or keys to edit"),editedFieldsInput:r.object({labels:r.object({add:r.array(r.string()).optional().describe("Labels to add"),remove:r.array(r.string()).optional().describe("Labels to remove"),set:r.array(r.string()).optional().describe("Labels to set (replaces all)")}).optional(),assignee:r.object({accountId:r.string().describe("Account ID of the assignee")}).optional(),priority:r.object({id:r.string().describe("Priority ID")}).optional(),components:r.object({add:r.array(r.object({id:r.string()})).optional(),remove:r.array(r.object({id:r.string()})).optional()}).optional(),fixVersions:r.object({add:r.array(r.object({id:r.string()})).optional(),remove:r.array(r.object({id:r.string()})).optional()}).optional()}).describe("Fields to edit with their operations"),sendNotifications:r.boolean().optional().default(!0).describe("Whether to send email notifications")})},async({issueIdsOrKeys:e,editedFieldsInput:t,sendNotifications:s})=>{try{const a=A(await v());return C({success:!0,taskId:(await a.post("/rest/api/3/bulk/issues/fields",{issueIdsOrKeys:e,editedFieldsInput:t,sendNotifications:s})).data.taskId,message:`Bulk edit initiated for ${e.length} issues. Use jira_get_bulk_operation_progress with taskId to track progress.`})}catch(e){return C($(e))}}),T.registerTool("jira_bulk_watch_issues",{title:"Bulk Watch Issues",description:"Add watchers to multiple issues at once. Returns a taskId to track progress.",inputSchema:r.object({issueIdsOrKeys:r.array(r.string()).min(1).describe("Array of issue IDs or keys to watch"),accountIds:r.array(r.string()).optional().describe("Account IDs to add as watchers (defaults to current user)")})},async({issueIdsOrKeys:e,accountIds:t})=>{try{const s=A(await v());return C({success:!0,taskId:(await s.post("/rest/api/3/bulk/issues/watch",{issueIdsOrKeys:e,...t&&{accountIds:t}})).data.taskId,message:`Bulk watch initiated for ${e.length} issues. Use jira_get_bulk_operation_progress with taskId to track progress.`})}catch(e){return C($(e))}}),T.registerTool("jira_bulk_unwatch_issues",{title:"Bulk Unwatch Issues",description:"Remove watchers from multiple issues at once. Returns a taskId to track progress.",inputSchema:r.object({issueIdsOrKeys:r.array(r.string()).min(1).describe("Array of issue IDs or keys to unwatch"),accountIds:r.array(r.string()).optional().describe("Account IDs to remove as watchers (defaults to current user)")})},async({issueIdsOrKeys:e,accountIds:t})=>{try{const s=A(await v());return C({success:!0,taskId:(await s.post("/rest/api/3/bulk/issues/unwatch",{issueIdsOrKeys:e,...t&&{accountIds:t}})).data.taskId,message:`Bulk unwatch initiated for ${e.length} issues. Use jira_get_bulk_operation_progress with taskId to track progress.`})}catch(e){return C($(e))}}),T.registerTool("jira_get_bulk_operation_progress",{title:"Get Bulk Operation Progress",description:"Check the progress of an async bulk operation using its taskId.",inputSchema:r.object({taskId:r.string().describe("The task ID returned from a bulk operation")})},async({taskId:e})=>{try{const t=A(await v()),s=(await t.get(`/rest/api/3/bulk/queue/${e}`)).data;return C({taskId:s.taskId,status:s.status,progress:s.progress,submittedBy:s.submittedBy,created:s.created,started:s.started,finished:s.finished,successfulIssues:s.successfulIssues||[],failedIssues:s.failedIssues||[],totalIssues:(s.successfulIssues?.length||0)+(s.failedIssues?.length||0)})}catch(e){return C($(e))}}),T.registerTool("jira_get_dashboards",{title:"Get Dashboards",description:"Get a list of dashboards. Can filter by favourite or owned dashboards.",inputSchema:r.object({filter:r.enum(["favourite","my"]).optional().describe("Filter dashboards: 'favourite' for favourited, 'my' for owned"),startAt:r.number().optional().default(0).describe("Index of first result"),maxResults:r.number().optional().default(50).describe("Maximum results to return")})},async({filter:e,startAt:t,maxResults:s})=>{try{const a=A(await v()),r=await a.get("/rest/api/3/dashboard",{params:{filter:e,startAt:t,maxResults:s}});return C({total:r.data.total,startAt:r.data.startAt,maxResults:r.data.maxResults,dashboards:(r.data.dashboards||[]).map(e=>({id:e.id,name:e.name,self:e.self,isFavourite:e.isFavourite,view:e.view}))})}catch(e){return C($(e))}}),T.registerTool("jira_search_dashboards",{title:"Search Dashboards",description:"Search for dashboards by name, owner, or other criteria.",inputSchema:r.object({dashboardName:r.string().optional().describe("Filter by dashboard name (case insensitive contains)"),accountId:r.string().optional().describe("Filter by owner account ID"),groupname:r.string().optional().describe("Filter by group permission"),orderBy:r.enum(["name","-name","id","-id","owner","-owner","favourite_count","-favourite_count"]).optional().describe("Order results by field (prefix with - for descending)"),startAt:r.number().optional().default(0).describe("Index of first result"),maxResults:r.number().optional().default(50).describe("Maximum results"),expand:r.string().optional().describe("Expand options: description, owner, viewUrl, favourite, favouritedCount, sharePermissions, editPermissions")})},async({dashboardName:e,accountId:t,groupname:s,orderBy:a,startAt:r,maxResults:i,expand:n})=>{try{const o=A(await v()),c=await o.get("/rest/api/3/dashboard/search",{params:{dashboardName:e,accountId:t,groupname:s,orderBy:a,startAt:r,maxResults:i,expand:n}});return C({total:c.data.total,startAt:c.data.startAt,maxResults:c.data.maxResults,dashboards:(c.data.values||[]).map(e=>({id:e.id,name:e.name,description:e.description,owner:e.owner?{accountId:e.owner.accountId,displayName:e.owner.displayName}:void 0,isFavourite:e.isFavourite,popularity:e.popularity,view:e.view}))})}catch(e){return C($(e))}}),T.registerTool("jira_get_dashboard",{title:"Get Dashboard",description:"Get details of a specific dashboard by ID.",inputSchema:r.object({id:r.string().describe("Dashboard ID")})},async({id:e})=>{try{const t=A(await v()),s=(await t.get(`/rest/api/3/dashboard/${e}`)).data;return C({id:s.id,name:s.name,description:s.description,self:s.self,isFavourite:s.isFavourite,owner:s.owner?{accountId:s.owner.accountId,displayName:s.owner.displayName}:void 0,popularity:s.popularity,view:s.view,editPermissions:s.editPermissions,sharePermissions:s.sharePermissions})}catch(e){return C($(e))}}),T.registerTool("jira_get_dashboard_gadgets",{title:"Get Dashboard Gadgets",description:"Get all gadgets on a dashboard.",inputSchema:r.object({dashboardId:r.string().describe("Dashboard ID"),moduleKey:r.array(r.string()).optional().describe("Filter by gadget module keys"),uri:r.string().optional().describe("Filter by gadget URI"),gadgetId:r.array(r.string()).optional().describe("Filter by gadget IDs")})},async({dashboardId:e,moduleKey:t,uri:s,gadgetId:a})=>{try{const r=A(await v());return C({gadgets:((await r.get(`/rest/api/3/dashboard/${e}/gadget`,{params:{moduleKey:t?.join(","),uri:s,gadgetId:a?.join(",")}})).data.gadgets||[]).map(e=>({id:e.id,moduleKey:e.moduleKey,uri:e.uri,color:e.color,position:e.position,title:e.title}))})}catch(e){return C($(e))}}),T.registerTool("jira_add_dashboard_gadget",{title:"Add Dashboard Gadget",description:"Add a gadget to a dashboard. Provide either moduleKey or uri to specify the gadget type.",inputSchema:r.object({dashboardId:r.string().describe("Dashboard ID"),moduleKey:r.string().optional().describe("Module key of the gadget type (e.g., com.atlassian.jira.gadgets:filter-results-gadget)"),uri:r.string().optional().describe("URI of the gadget type"),color:r.enum(["blue","red","yellow","green","cyan","purple","gray","white"]).optional().describe("Gadget colour"),position:r.object({row:r.number().describe("Row position (0-indexed)"),column:r.number().describe("Column position (0-indexed)")}).optional().describe("Position on dashboard grid"),title:r.string().optional().describe("Gadget title"),ignoreUriAndModuleKeyValidation:r.boolean().optional().default(!1).describe("Skip validation of moduleKey/uri")})},async({dashboardId:e,moduleKey:t,uri:s,color:a,position:r,title:i,ignoreUriAndModuleKeyValidation:n})=>{try{if(!t&&!s)return C({error:!0,message:"Either moduleKey or uri must be provided"});const o=A(await v()),c=await o.post(`/rest/api/3/dashboard/${e}/gadget`,{moduleKey:t,uri:s,color:a,position:r,title:i,ignoreUriAndModuleKeyValidation:n});return C({success:!0,gadget:{id:c.data.id,moduleKey:c.data.moduleKey,uri:c.data.uri,color:c.data.color,position:c.data.position,title:c.data.title}})}catch(e){return C($(e))}}),T.registerTool("jira_upload_attachment",{title:"Upload Attachment",description:"Upload a file attachment to an issue. Requires the file path on the local filesystem.",inputSchema:r.object({issueIdOrKey:r.string().describe("Issue ID or key"),filePath:r.string().describe("Absolute path to the file to upload"),filename:r.string().optional().describe("Override the filename (defaults to original filename)")})},async({issueIdOrKey:e,filePath:t,filename:s})=>{try{const a=await import("fs"),r=await import("path"),i=(await import("form-data")).default;if(!a.existsSync(t))return C({error:!0,message:`File not found: ${t}`});const n=A(await v()),o=new i,c=a.createReadStream(t),u=s||r.basename(t);o.append("file",c,u);const d=await n.post(`/rest/api/3/issue/${e}/attachments`,o,{headers:{...o.getHeaders(),"X-Atlassian-Token":"no-check"}});return C({success:!0,attachments:(d.data||[]).map(e=>({id:e.id,filename:e.filename,size:e.size,mimeType:e.mimeType,created:e.created,author:e.author?.displayName,content:e.content}))})}catch(e){return C($(e))}}),T.registerTool("jira_get_attachment_metadata",{title:"Get Attachment Metadata",description:"Get metadata for a specific attachment by ID.",inputSchema:r.object({id:r.string().describe("Attachment ID")})},async({id:e})=>{try{const t=A(await v()),s=(await t.get(`/rest/api/3/attachment/${e}`)).data;return C({id:s.id,filename:s.filename,size:s.size,mimeType:s.mimeType,created:s.created,author:s.author?{accountId:s.author.accountId,displayName:s.author.displayName}:void 0,content:s.content,thumbnail:s.thumbnail,self:s.self})}catch(e){return C($(e))}}),T.registerTool("jira_get_attachment_content",{title:"Get Attachment Content",description:"Get the content/download URL for an attachment. Returns the redirect URL or content depending on redirect setting.",inputSchema:r.object({id:r.string().describe("Attachment ID"),redirect:r.boolean().optional().default(!0).describe("Whether to return redirect URL (true) or follow redirect (false)")})},async({id:e,redirect:t})=>{try{const s=A(await v()),a=await s.get(`/rest/api/3/attachment/content/${e}`,{params:{redirect:t},maxRedirects:t?0:5,validateStatus:e=>e<400||302===e});return 302===a.status||a.headers.location?C({downloadUrl:a.headers.location||a.data,message:"Use this URL to download the attachment content"}):C({contentType:a.headers["content-type"],contentLength:a.headers["content-length"],message:"Content retrieved. For binary files, use the download URL instead."})}catch(e){return C($(e))}}),T.registerTool("jira_get_all_labels",{title:"Get All Labels",description:"Get all labels used across all issues in the Jira instance.",inputSchema:r.object({startAt:r.number().optional().default(0).describe("Index of first result"),maxResults:r.number().optional().default(1e3).describe("Maximum results to return")})},async({startAt:e,maxResults:t})=>{try{const s=A(await v()),a=await s.get("/rest/api/3/label",{params:{startAt:e,maxResults:t}});return C({total:a.data.total,maxResults:a.data.maxResults,startAt:a.data.startAt,labels:a.data.values||[]})}catch(e){return C($(e))}}),T.registerTool("jira_add_labels",{title:"Add/Set/Remove Labels",description:"Add, set, or remove labels on an issue. Use 'add' to append, 'set' to replace all, or 'remove' to delete specific labels.",inputSchema:r.object({issueIdOrKey:r.string().describe("Issue ID or key"),labels:r.array(r.string()).min(1).describe("Labels to add/set/remove"),operation:r.enum(["add","set","remove"]).default("add").describe("Operation: 'add' appends, 'set' replaces all, 'remove' deletes specified labels")})},async({issueIdOrKey:e,labels:t,operation:s})=>{try{const a=A(await v());let r;return r="set"===s?{fields:{labels:t}}:{update:{labels:t.map(e=>({[s]:e}))}},await a.put(`/rest/api/3/issue/${e}`,r),C({success:!0,message:`Labels ${"add"===s?"added to":"remove"===s?"removed from":"set on"} issue ${e}`,labels:t,operation:s})}catch(e){return C($(e))}}),T.registerTool("jira_autocomplete_jql",{title:"Autocomplete JQL",description:"Get autocomplete suggestions for JQL field values. Useful for building JQL queries interactively.",inputSchema:r.object({fieldName:r.string().optional().describe("Field name to get value suggestions for (e.g., status, priority, assignee)"),fieldValue:r.string().optional().describe("Partial value to autocomplete"),predicateName:r.string().optional().describe("Predicate name for function suggestions"),predicateValue:r.string().optional().describe("Partial predicate value to autocomplete")})},async({fieldName:e,fieldValue:t,predicateName:s,predicateValue:a})=>{try{const r=A(await v());return C({results:((await r.get("/rest/api/3/jql/autocompletedata/suggestions",{params:{fieldName:e,fieldValue:t,predicateName:s,predicateValue:a}})).data.results||[]).map(e=>({value:e.value,displayName:e.displayName}))})}catch(e){return C($(e))}}),T.registerTool("jira_validate_jql",{title:"Validate JQL",description:"Validate one or more JQL queries for syntax and semantic correctness.",inputSchema:r.object({queries:r.array(r.string()).min(1).describe("JQL queries to validate"),validation:r.enum(["strict","warn","none"]).optional().default("strict").describe("Validation level: strict (errors only), warn (errors and warnings), none (no validation)")})},async({queries:e,validation:t})=>{try{const s=A(await v());return C({queries:((await s.post("/rest/api/3/jql/parse",{queries:e,validation:t})).data.queries||[]).map(e=>({query:e.query,errors:e.errors||[],warnings:e.warnings||[],isValid:!e.errors||0===e.errors.length}))})}catch(e){return C($(e))}}),T.registerTool("jira_parse_jql",{title:"Parse JQL",description:"Parse JQL queries and return their abstract syntax tree (AST) structure. Useful for understanding query structure.",inputSchema:r.object({queries:r.array(r.string()).min(1).describe("JQL queries to parse"),validation:r.enum(["strict","warn","none"]).optional().default("none").describe("Validation level")})},async({queries:e,validation:t})=>{try{const s=A(await v());return C({queries:((await s.post("/rest/api/3/jql/parse",{queries:e,validation:t})).data.queries||[]).map(e=>({query:e.query,structure:e.structure,errors:e.errors||[]}))})}catch(e){return C($(e))}}),T.registerTool("jira_get_updated_worklog_ids",{title:"Get Updated Worklog IDs",description:"Get IDs of worklogs that were created or updated since a specific date/time. Use this to discover worklogs for reporting purposes.",inputSchema:r.object({since:r.string().describe("Get worklogs updated since this date. Can be ISO 8601 format (e.g., '2026-01-15T00:00:00.000Z') or Unix timestamp in milliseconds."),expand:r.string().optional().describe("Expand options for additional worklog properties")})},async({since:e,expand:t})=>{try{const s=A(await v());let a;a=/^\d+$/.test(e)?parseInt(e,10):new Date(e).getTime();const r={since:a};t&&(r.expand=t);const i=await s.get("/rest/api/3/worklog/updated",{params:r});return C({since:a,sinceDate:new Date(a).toISOString(),until:i.data.until,untilDate:i.data.until?new Date(i.data.until).toISOString():null,lastPage:i.data.lastPage,nextPage:i.data.nextPage,worklogIds:(i.data.values||[]).map(e=>({worklogId:e.worklogId,updatedTime:e.updatedTime,updatedDate:new Date(e.updatedTime).toISOString()})),total:i.data.values?.length||0})}catch(e){return C($(e))}}),T.registerTool("jira_get_worklogs_by_ids",{title:"Get Worklogs by IDs",description:"Get full worklog details for a list of worklog IDs. Use after getting IDs from jira_get_updated_worklog_ids.",inputSchema:r.object({ids:r.array(r.number().int().positive()).min(1).max(1e3).describe("Array of worklog IDs to fetch (max 1000)"),expand:r.string().optional().describe("Expand options")})},async({ids:e,expand:t})=>{try{const s=A(await v()),a={};t&&(a.expand=t);const r=await s.post("/rest/api/3/worklog/list",{ids:e},{params:a}),i=Array.isArray(r.data)?r.data.map(e=>({id:e.id,issueId:e.issueId,author:{accountId:e.author?.accountId,displayName:e.author?.displayName,emailAddress:e.author?.emailAddress},updateAuthor:{accountId:e.updateAuthor?.accountId,displayName:e.updateAuthor?.displayName},timeSpent:e.timeSpent,timeSpentSeconds:e.timeSpentSeconds,started:e.started,created:e.created,updated:e.updated,comment:D(e.comment)})):[];return C({worklogs:i,total:i.length})}catch(e){return C($(e))}}),T.registerTool("jira_get_user_worklogs",{title:"Get User Worklogs",description:"Get all worklogs for a specific user within a date range. Combines worklog discovery and filtering to provide a complete time tracking report for a person.",inputSchema:r.object({accountId:r.string().optional().describe("User account ID to filter worklogs for. If not provided, returns worklogs for the current user."),since:r.string().describe("Start date for the report. ISO 8601 format (e.g., '2026-01-15') or relative like '30 days ago' will be parsed."),until:r.string().optional().describe("End date for the report. Defaults to now if not provided."),includeIssueDetails:r.boolean().optional().default(!1).describe("Whether to fetch issue details (key, summary) for each worklog")})},async({accountId:e,since:t,until:s,includeIssueDetails:a})=>{try{const r=A(await v());let i,n=e,o="";if(!n){const e=await r.get("/rest/api/3/myself");n=e.data.accountId,o=e.data.displayName||e.data.emailAddress}const c=t.match(/^(\d+)\s*(days?|weeks?|months?)\s*ago$/i);if(c&&c[1]&&c[2]){const e=parseInt(c[1],10),t=c[2].toLowerCase(),s=new Date;t.startsWith("day")?s.setDate(s.getDate()-e):t.startsWith("week")?s.setDate(s.getDate()-7*e):t.startsWith("month")&&s.setMonth(s.getMonth()-e),i=s.getTime()}else i=new Date(t).getTime();let u;s&&(u=new Date(s).getTime());const d=[];let l=`/rest/api/3/worklog/updated?since=${i}`,p=0;const m=10;for(;l&&p<m;){const e=await r.get(l),t=e.data.values||[];for(const e of t)u&&e.updatedTime>u||d.push(e.worklogId);if(e.data.lastPage)break;l=e.data.nextPage,p++}if(0===d.length)return C({user:o||n,accountId:n,period:{since:new Date(i).toISOString(),until:u?new Date(u).toISOString():(new Date).toISOString()},worklogs:[],summary:{totalWorklogs:0,totalTimeSpentSeconds:0,totalTimeSpent:"0h"}});const y=[];for(let e=0;e<d.length;e+=1e3){const t=d.slice(e,e+1e3),s=await r.post("/rest/api/3/worklog/list",{ids:t});Array.isArray(s.data)&&y.push(...s.data)}const h=y.filter(e=>e.author?.accountId===n),f={};if(a&&h.length>0){const e=[...new Set(h.map(e=>e.issueId))];for(let t=0;t<e.length;t+=50){const s=e.slice(t,t+50);try{const e=await r.get("/rest/api/3/search",{params:{jql:`id in (${s.join(",")})`,fields:"summary",maxResults:50}});for(const t of e.data.issues||[])f[t.id]={key:t.key,summary:t.fields?.summary||""}}catch{}}}const I=h.map(e=>{const t={id:e.id,issueId:e.issueId,timeSpent:e.timeSpent,timeSpentSeconds:e.timeSpentSeconds,started:e.started,created:e.created,comment:D(e.comment)},s=f[e.issueId];return a&&s&&(t.issueKey=s.key,t.issueSummary=s.summary),t});I.sort((e,t)=>new Date(e.started).getTime()-new Date(t.started).getTime());const w=h.reduce((e,t)=>e+(t.timeSpentSeconds||0),0),g=Math.floor(w/3600),_=Math.floor(w%3600/60),k=_>0?`${g}h ${_}m`:`${g}h`;let b;if(a){b={};for(const e of I){const t=e.issueId;b[t]||(b[t]={key:e.issueKey||t,summary:e.issueSummary||"",totalSeconds:0,totalTime:""}),b[t].totalSeconds+=e.timeSpentSeconds||0}for(const e of Object.keys(b)){const t=b[e];if(t){const e=t.totalSeconds,s=Math.floor(e/3600),a=Math.floor(e%3600/60);t.totalTime=a>0?`${s}h ${a}m`:`${s}h`}}}return C({user:o||n,accountId:n,period:{since:new Date(i).toISOString(),until:u?new Date(u).toISOString():(new Date).toISOString()},worklogs:I,summary:{totalWorklogs:I.length,totalTimeSpentSeconds:w,totalTimeSpent:k,...b&&{byIssue:Object.values(b)}}})}catch(e){return C($(e))}}),T.registerTool("jira_get_deleted_worklog_ids",{title:"Get Deleted Worklog IDs",description:"Get IDs of worklogs that were deleted since a specific date/time. Useful for audit and sync purposes.",inputSchema:r.object({since:r.string().describe("Get worklogs deleted since this date. ISO 8601 format or Unix timestamp in milliseconds.")})},async({since:e})=>{try{const t=A(await v());let s;s=/^\d+$/.test(e)?parseInt(e,10):new Date(e).getTime();const a=await t.get("/rest/api/3/worklog/deleted",{params:{since:s}});return C({since:s,sinceDate:new Date(s).toISOString(),until:a.data.until,lastPage:a.data.lastPage,nextPage:a.data.nextPage,deletedWorklogIds:(a.data.values||[]).map(e=>({worklogId:e.worklogId,updatedTime:e.updatedTime})),total:a.data.values?.length||0})}catch(e){return C($(e))}});const G=new a;await T.connect(G);
2
+ import e,{AxiosError as t}from"axios";import{McpServer as s}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as a}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as r}from"zod";import{readFileSync as i}from"fs";import{fileURLToPath as n}from"url";import{dirname as o,join as c}from"path";const u=o(n(import.meta.url));function d(){try{const e=c(u,"..","package.json");return JSON.parse(i(e,"utf-8"))}catch{try{const e=c(u,"package.json");return JSON.parse(i(e,"utf-8"))}catch{return{name:"mcp-jira-cloud",version:"2.0.4",description:"Jira MCP Server"}}}}const l=process.argv.slice(2);(l.includes("-h")||l.includes("--help"))&&(!function(){const e=d();console.log(`\n${e.name} v${e.version}\n${e.description}\n\nUSAGE:\n jira-mcp [OPTIONS]\n mcp-jira-cloud [OPTIONS]\n\nOPTIONS:\n -h, --help Show this help message and exit\n -v, --version Show version number and exit\n\nENVIRONMENT VARIABLES:\n Basic Auth (recommended for most users):\n JIRA_BASE_URL Your Jira instance URL (e.g., https://your-domain.atlassian.net)\n JIRA_EMAIL Your Atlassian account email\n JIRA_API_TOKEN API token from https://id.atlassian.com/manage-profile/security/api-tokens\n\n OAuth 2.0 (for advanced integrations):\n JIRA_OAUTH_CLIENT_ID OAuth app client ID\n JIRA_OAUTH_CLIENT_SECRET OAuth app client secret\n JIRA_OAUTH_ACCESS_TOKEN Access token\n JIRA_OAUTH_REFRESH_TOKEN Refresh token (optional)\n JIRA_CLOUD_ID Jira Cloud ID\n\n Optional:\n JIRA_ACCEPTANCE_CRITERIA_FIELD Custom field ID for acceptance criteria\n\nEXAMPLES:\n # Run as MCP server (typical usage via AI assistant config)\n jira-mcp\n\n # Check version\n jira-mcp --version\n\nMCP CONFIGURATION:\n Add to your AI assistant's MCP configuration:\n\n VS Code (settings.json):\n "mcp": {\n "servers": {\n "jira": {\n "command": "npx",\n "args": ["-y", "mcp-jira-cloud"],\n "env": {\n "JIRA_BASE_URL": "https://your-domain.atlassian.net",\n "JIRA_EMAIL": "your-email@example.com",\n "JIRA_API_TOKEN": "your-api-token"\n }\n }\n }\n }\n\nDOCUMENTATION:\n https://github.com/tezaswi7222/jira-mcp#readme\n\nISSUES:\n https://github.com/tezaswi7222/jira-mcp/issues\n`)}(),process.exit(0)),(l.includes("-v")||l.includes("--version"))&&(!function(){const e=d();console.log(`${e.name} v${e.version}`)}(),process.exit(0));const p="jira-mcp",m="default",y=(process.env.JIRA_ACCEPTANCE_CRITERIA_FIELD||"").trim(),h="https://auth.atlassian.com/oauth/token";let f,w=null;async function g(){if(void 0!==f)return f;try{f=await import("keytar")}catch{f=null}return f}function I(e){let t;try{t=new URL(e)}catch{throw new Error("baseUrl must be a valid URL like https://your-domain.atlassian.net")}const s=t.pathname.replace(/\/+$/,"");return`${t.origin}${s}`}async function k(t,s,a){const r=await e.post(h,{grant_type:"refresh_token",client_id:t,client_secret:s,refresh_token:a},{headers:{"Content-Type":"application/json"}});return{accessToken:r.data.access_token,refreshToken:r.data.refresh_token,expiresIn:r.data.expires_in}}async function _(t){return(await e.get("https://api.atlassian.com/oauth/token/accessible-resources",{headers:{Authorization:`Bearer ${t}`,Accept:"application/json"}})).data}async function b(e,t){const s=await _(e);if(0===s.length)throw new Error("No accessible Jira sites found. Make sure your OAuth app has the correct scopes and you have granted access.");if(t){const e=I(t),a=s.find(t=>I(t.url)===e);if(a)return{cloudId:a.id,siteName:a.name,siteUrl:a.url};throw new Error(`Site ${t} not found in accessible resources. Available sites: ${s.map(e=>e.url).join(", ")}`)}const a=s[0];if(!a)throw new Error("No accessible Jira resources found");return{cloudId:a.id,siteName:a.name,siteUrl:a.url}}async function v(){if(w){if("oauth"===w.type&&w.expiresAt&&w.refreshToken){if(Date.now()>=w.expiresAt-3e5)try{const e=await k(w.clientId,w.clientSecret,w.refreshToken);w={...w,accessToken:e.accessToken,refreshToken:e.refreshToken||w.refreshToken,expiresAt:Date.now()+1e3*e.expiresIn}}catch(e){console.error("Failed to refresh OAuth token:",e)}}return w}const e=function(){const e=process.env.JIRA_OAUTH_CLIENT_ID,t=process.env.JIRA_OAUTH_CLIENT_SECRET,s=process.env.JIRA_OAUTH_ACCESS_TOKEN,a=process.env.JIRA_OAUTH_REFRESH_TOKEN,r=process.env.JIRA_CLOUD_ID;return e&&t&&s&&r?{type:"oauth",clientId:e,clientSecret:t,accessToken:s,refreshToken:a,cloudId:r}:null}();if(e)return e;const t=function(){const e=process.env.JIRA_BASE_URL,t=process.env.JIRA_EMAIL,s=process.env.JIRA_API_TOKEN;return e&&t&&s?{type:"basic",baseUrl:I(e),email:t,apiToken:s}:null}();if(t)return t;const s=await async function(){const e=await g();if(!e)return null;const t=await e.getPassword(p,m);if(!t)return null;try{const e=JSON.parse(t);return"basic"===e.type?{...e,baseUrl:I(e.baseUrl)}:e}catch{return null}}();if(s)return s;throw new Error("MISSING_AUTH")}async function j(e,t){if(w=e,!t)return;const s=await g();if(!s)throw new Error("Keytar is not available to persist credentials.");await s.setPassword(p,m,JSON.stringify(e))}function A(t){return"basic"===t.type?e.create({baseURL:t.baseUrl,auth:{username:t.email,password:t.apiToken},headers:{Accept:"application/json"}}):e.create({baseURL:`https://api.atlassian.com/ex/jira/${t.cloudId}`,headers:{Authorization:`Bearer ${t.accessToken}`,Accept:"application/json"}})}function S(e){if(!e)return"";if(Array.isArray(e))return e.map(S).filter(Boolean).join("\n").trim();if("string"==typeof e.text)return e.text;if(Array.isArray(e.content)){return e.content.map(S).filter(Boolean).join("paragraph"===e.type?"\n":" ").trim()}return""}function D(e){if("string"==typeof e)return e;if("number"==typeof e)return String(e);if(e&&"object"==typeof e){const t=S(e);if(t)return t}return""}function R(e){return{type:"doc",version:1,content:[{type:"paragraph",content:[{type:"text",text:e}]}]}}function x(e){const t=e?.fields||{},s=D(t.description),a=y?D(t[y]):"";return{key:e?.key??"",summary:t.summary??"",description:s,acceptanceCriteria:a||null}}function O(e){const t=e?.fields||{};return{key:e?.key??"",summary:t.summary??"",status:t.status?.name??""}}function U(){const e=["summary","description"];return y&&e.push(y),e}function $(e){if(e instanceof t){const t=e.response?.status,s=e.response?.data;return`Jira API error${t?` (${t})`:""}: ${("string"==typeof s?s:JSON.stringify(s,null,2))||e.message}`}return e instanceof Error?e.message:"Unknown error"}function K(e){if(e instanceof Error&&"MISSING_AUTH"===e.message)return{error:"unauthorized",message:"Jira credentials are missing. Provide credentials explicitly to authenticate."};if(e instanceof t){const t=e.response?.status;return 401===t?{error:"unauthorized",message:"Jira credentials are missing or invalid. If using OAuth, the token may have expired."}:403===t?{error:"forbidden",message:"You do not have permission to access this Jira resource."}:404===t?{error:"not_found",message:"The Jira resource does not exist or is not visible."}:429===t?{error:"rate_limited",message:"Jira rate limit exceeded. Please retry later."}:t&&t>=500?{error:"server_error",message:"Jira server error. Please retry later."}:{error:"jira_error",message:$(e)}}return e instanceof Error?{error:"unknown",message:e.message}:{error:"unknown",message:"Unknown error"}}function T(e){return{content:[{type:"text",text:"string"==typeof e?e:JSON.stringify(e,null,2)}]}}const C=new s({name:"jira-mcp",version:"2.0.0"});C.registerTool("_internal_jira_set_auth",{title:"Set Jira Auth (Basic)",description:"Use when the user wants to connect Jira using Basic Auth (email + API token). This tool should only be called when the user explicitly provides credentials.",inputSchema:r.object({baseUrl:r.string(),email:r.string().email(),apiToken:r.string().min(1),persist:r.boolean().optional().default(!1)})},async({baseUrl:e,email:t,apiToken:s,persist:a})=>{const r=I(e);return await j({type:"basic",baseUrl:r,email:t,apiToken:s},a??!1),T("Jira credentials loaded (Basic Auth).")}),C.registerTool("jira_oauth_get_auth_url",{title:"Get OAuth Authorization URL",description:"Generate the OAuth 2.0 authorization URL that the user should visit to grant access. Returns the URL and required state parameter.",inputSchema:r.object({clientId:r.string().min(1).describe("OAuth Client ID from Atlassian Developer Console"),redirectUri:r.string().url().describe("Callback URL configured in your OAuth app"),scopes:r.array(r.string()).optional().default(["read:jira-work","read:jira-user","write:jira-work","offline_access"]).describe("OAuth scopes to request")})},async({clientId:e,redirectUri:t,scopes:s})=>{const a=Math.random().toString(36).substring(2,15),r=function(e,t,s,a){return`https://auth.atlassian.com/authorize?${new URLSearchParams({audience:"api.atlassian.com",client_id:e,scope:s.join(" "),redirect_uri:t,state:a,response_type:"code",prompt:"consent"}).toString()}`}(e,t,s,a);return T({authUrl:r,state:a,instructions:"1. Visit the authUrl in your browser\n2. Grant access to your Jira site\n3. Copy the 'code' parameter from the redirect URL\n4. Use jira_oauth_exchange_code to exchange it for tokens"})}),C.registerTool("jira_oauth_exchange_code",{title:"Exchange OAuth Code for Tokens",description:"Exchange the authorization code for access tokens after the user has completed the OAuth flow.",inputSchema:r.object({clientId:r.string().min(1),clientSecret:r.string().min(1),code:r.string().min(1).describe("Authorization code from the OAuth callback"),redirectUri:r.string().url(),siteUrl:r.string().url().optional().describe("Optional: specific Jira site URL (e.g., https://yoursite.atlassian.net)"),persist:r.boolean().optional().default(!1)})},async({clientId:t,clientSecret:s,code:a,redirectUri:r,siteUrl:i,persist:n})=>{try{const o=await async function(t,s,a,r){const i=await e.post(h,{grant_type:"authorization_code",client_id:t,client_secret:s,code:a,redirect_uri:r},{headers:{"Content-Type":"application/json"}});return{accessToken:i.data.access_token,refreshToken:i.data.refresh_token,expiresIn:i.data.expires_in}}(t,s,a,r),{cloudId:c,siteName:u,siteUrl:d}=await b(o.accessToken,i),l={type:"oauth",clientId:t,clientSecret:s,accessToken:o.accessToken,refreshToken:o.refreshToken,cloudId:c,expiresAt:o.expiresIn?Date.now()+1e3*o.expiresIn:void 0};return await j(l,n),T({success:!0,message:`Successfully authenticated with OAuth to ${u}`,site:{name:u,url:d,cloudId:c},hasRefreshToken:!!o.refreshToken})}catch(e){return T(K(e))}}),C.registerTool("jira_oauth_set_tokens",{title:"Set OAuth Tokens Directly",description:"Set OAuth tokens directly if you already have them (e.g., from a previous session or external OAuth flow).",inputSchema:r.object({clientId:r.string().min(1),clientSecret:r.string().min(1),accessToken:r.string().min(1),refreshToken:r.string().optional(),cloudId:r.string().optional().describe("Cloud ID of the Jira site. If not provided, will be fetched automatically."),siteUrl:r.string().url().optional().describe("Jira site URL to find the correct cloudId"),persist:r.boolean().optional().default(!1)})},async({clientId:e,clientSecret:t,accessToken:s,refreshToken:a,cloudId:r,siteUrl:i,persist:n})=>{try{let o=r,c="",u=i||"";if(!o){const e=await b(s,i);o=e.cloudId,c=e.siteName,u=e.siteUrl}const d={type:"oauth",clientId:e,clientSecret:t,accessToken:s,refreshToken:a,cloudId:o};return await j(d,n),T({success:!0,message:c?`OAuth tokens set for ${c}`:"OAuth tokens set successfully",cloudId:o,siteUrl:u})}catch(e){return T(K(e))}}),C.registerTool("jira_oauth_refresh",{title:"Refresh OAuth Token",description:"Manually refresh the OAuth access token using the refresh token.",inputSchema:r.object({})},async()=>{try{const e=await v();if("oauth"!==e.type)return T({error:"invalid_auth_type",message:"Current authentication is not OAuth. Use basic auth credentials directly."});if(!e.refreshToken)return T({error:"no_refresh_token",message:"No refresh token available. You need to re-authenticate with 'offline_access' scope."});const t=await k(e.clientId,e.clientSecret,e.refreshToken),s={...e,accessToken:t.accessToken,refreshToken:t.refreshToken||e.refreshToken,expiresAt:Date.now()+1e3*t.expiresIn};return await j(s,!1),T({success:!0,message:"OAuth token refreshed successfully",expiresIn:t.expiresIn})}catch(e){return T(K(e))}}),C.registerTool("jira_oauth_list_sites",{title:"List Accessible Jira Sites",description:"List all Jira sites accessible with the current OAuth token.",inputSchema:r.object({})},async()=>{try{const e=await v();if("oauth"!==e.type)return T({error:"invalid_auth_type",message:"This tool requires OAuth authentication. Current auth is basic auth."});const t=await _(e.accessToken);return T({currentCloudId:e.cloudId,sites:t.map(e=>({cloudId:e.id,name:e.name,url:e.url,scopes:e.scopes}))})}catch(e){return T(K(e))}}),C.registerTool("jira_clear_auth",{title:"Clear Jira Auth",description:"Use when the user asks to remove or reset stored Jira credentials.",inputSchema:r.object({})},async()=>(await async function(){w=null;const e=await g();e&&await e.deletePassword(p,m)}(),T("Jira credentials cleared."))),C.registerTool("jira_auth_status",{title:"Get Auth Status",description:"Check the current authentication status and type.",inputSchema:r.object({})},async()=>{try{const e=await v();return"basic"===e.type?T({authenticated:!0,type:"basic",baseUrl:e.baseUrl,email:e.email}):T({authenticated:!0,type:"oauth",cloudId:e.cloudId,hasRefreshToken:!!e.refreshToken,expiresAt:e.expiresAt?new Date(e.expiresAt).toISOString():null})}catch(e){return e instanceof Error&&"MISSING_AUTH"===e.message?T({authenticated:!1,message:"No authentication configured. Use basic auth or OAuth to authenticate."}):T(K(e))}}),C.registerTool("jira_whoami",{title:"Get Jira Profile",description:"Use when the user asks who they are in Jira or wants to verify the Jira account in use."},async()=>{try{const e=A(await v());return T((await e.get("/rest/api/3/myself")).data)}catch(e){return T(K(e))}}),C.registerTool("jira_get_issue",{title:"Get Jira Issue",description:"Get the full details of a Jira issue when the user mentions an issue key like PROJ-123 or asks about a specific ticket.",inputSchema:r.object({issueIdOrKey:r.string().min(1),fields:r.array(r.string()).optional(),expand:r.string().optional()})},async({issueIdOrKey:e,fields:t,expand:s})=>{try{const a=A(await v()),r=t?.length?t:U();return T(x((await a.get(`/rest/api/3/issue/${encodeURIComponent(e)}`,{params:{fields:r.join(","),expand:s}})).data))}catch(e){return T(K(e))}}),C.registerTool("jira_search_issues",{title:"Search Jira Issues",description:"Use when the user asks to find issues matching criteria (JQL), like 'my open bugs' or 'tickets updated this week'.",inputSchema:r.object({jql:r.string().min(1),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(200).optional(),fields:r.array(r.string()).optional(),expand:r.string().optional(),nextPageToken:r.string().optional(),reconcileIssues:r.boolean().optional()})},async({jql:e,startAt:t,maxResults:s,fields:a,expand:r,nextPageToken:i,reconcileIssues:n})=>{try{const o=A(await v()),c=a?.length?a:U(),u=await o.get("/rest/api/3/search/jql",{params:{jql:e,startAt:t,maxResults:s,fields:c.join(","),expand:r,nextPageToken:i,reconcileIssues:n}}),d=Array.isArray(u.data?.issues)?u.data.issues.map(x):[];return T({total:u.data?.total??d.length,issues:d})}catch(e){return T(K(e))}}),C.registerTool("jira_search_issues_summary",{title:"Search Jira Issues (Summary)",description:"Use when the user wants the top results for a Jira search and only needs key, summary, and status.",inputSchema:r.object({jql:r.string().min(1),maxResults:r.number().int().positive().max(50).optional()})},async({jql:e,maxResults:t})=>{try{const s=A(await v()),a=await s.get("/rest/api/3/search/jql",{params:{jql:e,maxResults:t??10,fields:["summary","status"].join(",")}});return T(Array.isArray(a.data?.issues)?a.data.issues.map(O):[])}catch(e){return T(K(e))}}),C.registerTool("jira_resolve",{title:"Resolve Jira Intent",description:"Primary routing tool. Use this tool first when the user intent is clear (get issue, search, or my issues) but the exact Jira tool to call is uncertain.",inputSchema:r.object({intent:r.enum(["get_issue","search","my_issues"]),issueKey:r.string().optional(),jql:r.string().optional(),maxResults:r.number().int().positive().max(50).optional()})},async({intent:e,issueKey:t,jql:s,maxResults:a})=>{try{const r=A(await v());if("get_issue"===e){if(!t)return T({error:"invalid_input",message:"issueKey is required when intent is get_issue."});return T(x((await r.get(`/rest/api/3/issue/${encodeURIComponent(t)}`,{params:{fields:U().join(",")}})).data))}if("search"===e){if(!s)return T({error:"invalid_input",message:"jql is required when intent is search."});const e=await r.get("/rest/api/3/search/jql",{params:{jql:s,maxResults:a??10,fields:["summary","status"].join(",")}});return T(Array.isArray(e.data?.issues)?e.data.issues.map(O):[])}const i=await r.get("/rest/api/3/search/jql",{params:{jql:"assignee = currentUser() AND statusCategory != Done ORDER BY updated DESC",maxResults:a??20,fields:U().join(",")}}),n=Array.isArray(i.data?.issues)?i.data.issues.map(x):[];return T({total:i.data?.total??n.length,issues:n})}catch(e){return T(K(e))}}),C.registerTool("jira_get_issue_summary",{title:"Get Issue Summary",description:"Use when the user wants the summary, description, and acceptance criteria for a specific issue key.",inputSchema:r.object({issueIdOrKey:r.string().min(1)})},async({issueIdOrKey:e})=>{try{const t=A(await v());return T(x((await t.get(`/rest/api/3/issue/${encodeURIComponent(e)}`,{params:{fields:U().join(",")}})).data))}catch(e){return T(K(e))}}),C.registerTool("jira_get_my_open_issues",{title:"Get My Open Issues",description:"Use when the user asks for their open tickets or what they should work on next.",inputSchema:r.object({maxResults:r.number().int().positive().max(50).optional()})},async({maxResults:e})=>{try{const t=A(await v()),s=await t.get("/rest/api/3/search/jql",{params:{jql:"assignee = currentUser() AND statusCategory != Done ORDER BY updated DESC",maxResults:e??20,fields:U().join(",")}}),a=Array.isArray(s.data?.issues)?s.data.issues.map(x):[];return T({total:s.data?.total??a.length,issues:a})}catch(e){return T(K(e))}}),C.registerTool("jira_get_issue_comments",{title:"Get Issue Comments",description:"Use when the user asks for the discussion or comments on a specific ticket; returns a clean list.",inputSchema:r.object({issueIdOrKey:r.string().min(1),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(100).optional()})},async({issueIdOrKey:e,startAt:t,maxResults:s})=>{try{const a=A(await v()),r=await a.get(`/rest/api/3/issue/${encodeURIComponent(e)}/comment`,{params:{startAt:t,maxResults:s}});return T(Array.isArray(r.data?.comments)?r.data.comments.map(e=>({author:e?.author?.displayName||e?.author?.emailAddress||e?.author?.accountId||"",created:e?.created??"",body:D(e?.body)})):[])}catch(e){return T(K(e))}}),C.registerTool("jira_add_comment",{title:"Add Jira Comment",description:"Use when the user asks to add a comment to a specific ticket; confirm intent before posting.",inputSchema:r.object({issueIdOrKey:r.string().min(1),body:r.string().min(1)})},async({issueIdOrKey:e,body:t})=>{try{const s=A(await v()),a=await s.post(`/rest/api/3/issue/${encodeURIComponent(e)}/comment`,{body:R(t)});return T({id:a.data?.id??"",created:a.data?.created??""})}catch(e){return T(K(e))}}),C.registerTool("jira_add_worklog",{title:"Add Work Log",description:"Use when the user wants to log time/work on a specific Jira ticket. Allows specifying time spent, start date/time, and an optional description.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("The issue key (e.g., PROJ-123) to log work against"),timeSpent:r.string().min(1).describe("Time spent in Jira format (e.g., '1h', '30m', '1h 30m', '1d')"),started:r.string().optional().describe("When the work started in ISO 8601 format (e.g., '2026-02-13T14:00:00.000+0000'). Defaults to now if not provided."),comment:r.string().optional().describe("Optional description of the work performed")})},async({issueIdOrKey:e,timeSpent:t,started:s,comment:a})=>{try{const r=A(await v()),i={timeSpent:t};s&&(i.started=s),a&&(i.comment=R(a));const n=await r.post(`/rest/api/3/issue/${encodeURIComponent(e)}/worklog`,i);return T({id:n.data?.id??"",issueId:n.data?.issueId??"",timeSpent:n.data?.timeSpent??"",started:n.data?.started??"",author:n.data?.author?.displayName??n.data?.author?.emailAddress??"",created:n.data?.created??""})}catch(e){return T(K(e))}}),C.registerTool("jira_get_worklogs",{title:"Get Work Logs",description:"Use when the user wants to see work logs recorded on a specific Jira ticket.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("The issue key (e.g., PROJ-123) to get work logs for"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(100).optional()})},async({issueIdOrKey:e,startAt:t,maxResults:s})=>{try{const a=A(await v()),r=await a.get(`/rest/api/3/issue/${encodeURIComponent(e)}/worklog`,{params:{startAt:t,maxResults:s}}),i=Array.isArray(r.data?.worklogs)?r.data.worklogs.map(e=>({id:e?.id??"",author:e?.author?.displayName||e?.author?.emailAddress||"",timeSpent:e?.timeSpent??"",timeSpentSeconds:e?.timeSpentSeconds??0,started:e?.started??"",created:e?.created??"",comment:D(e?.comment)})):[];return T({total:r.data?.total??i.length,worklogs:i})}catch(e){return T(K(e))}}),C.registerTool("jira_get_user_worklogs",{title:"Get User Work Logs by Date Range",description:"Use when the user wants to see work logs recorded by a specific user within a date range. Searches for issues with worklogs by the specified user and date range using JQL worklogAuthor and worklogDate filters.",inputSchema:r.object({username:r.string().min(1).describe("The username or account ID of the user whose worklogs to retrieve"),startDate:r.string().describe("Start date in YYYY-MM-DD format"),endDate:r.string().describe("End date in YYYY-MM-DD format"),projectKey:r.string().optional().describe("Optional project key to filter results (e.g., PROJ)"),maxResults:r.number().int().positive().max(100).optional().describe("Maximum number of issues to search (default 50)")})},async({username:e,startDate:t,endDate:s,projectKey:a,maxResults:r})=>{const i=r??50;try{const r=A(await v());let n=`worklogAuthor = "${e}" AND worklogDate >= "${t}" AND worklogDate <= "${s}"`;a&&(n=`project = ${a} AND ${n}`),n+=" ORDER BY updated DESC";const o=await r.get("/rest/api/3/search",{params:{jql:n,maxResults:i,fields:"key,summary"}}),c=Array.isArray(o.data?.issues)?o.data.issues:[];if(0===c.length)return T({message:`No worklogs found for user "${e}" between ${t} and ${s}`,totalWorklogs:0,totalTimeSpentSeconds:0,worklogs:[]});const u=[],d=new Date(t),l=new Date(s);l.setHours(23,59,59,999);for(const t of c){const s=t.key,a=t.fields?.summary??"";try{const t=await r.get(`/rest/api/3/issue/${encodeURIComponent(s)}/worklog`,{params:{maxResults:1e3}}),i=Array.isArray(t.data?.worklogs)?t.data.worklogs:[];for(const t of i){const r=t?.author?.accountId||t?.author?.name||"",i=t?.author?.displayName||t?.author?.emailAddress||"",n=new Date(t?.started),o=r===e||i.toLowerCase().includes(e.toLowerCase())||(t?.author?.emailAddress||"").toLowerCase().includes(e.toLowerCase());o&&(n>=d&&n<=l)&&u.push({issueKey:s,issueSummary:a,id:t?.id??"",author:i,timeSpent:t?.timeSpent??"",timeSpentSeconds:t?.timeSpentSeconds??0,started:t?.started??"",comment:D(t?.comment)})}}catch{}}u.sort((e,t)=>new Date(t.started).getTime()-new Date(e.started).getTime());const p=u.reduce((e,t)=>e+t.timeSpentSeconds,0),m=Math.floor(p/3600),y=Math.floor(p%3600/60);return T({user:e,dateRange:{startDate:t,endDate:s},totalWorklogs:u.length,totalTimeSpentSeconds:p,totalTimeFormatted:`${m}h ${y}m`,worklogs:u})}catch(e){return T(K(e))}}),C.registerTool("jira_list_projects",{title:"List Jira Projects",description:"Use when the user asks which Jira projects they can access or wants a list of projects.",inputSchema:r.object({startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(50).optional()})},async({startAt:e,maxResults:t})=>{try{const s=A(await v());return T((await s.get("/rest/api/3/project/search",{params:{startAt:e,maxResults:t}})).data)}catch(e){return T(K(e))}}),C.registerTool("jira_get_project",{title:"Get Jira Project",description:"Use when the user mentions a project key and asks for project details or metadata.",inputSchema:r.object({projectIdOrKey:r.string().min(1)})},async({projectIdOrKey:e})=>{try{const t=A(await v());return T((await t.get(`/rest/api/3/project/${encodeURIComponent(e)}`)).data)}catch(e){return T(K(e))}}),C.registerTool("jira_create_issue",{title:"Create Jira Issue",description:"Create a new Jira issue. Requires project key, issue type, and summary at minimum.",inputSchema:r.object({projectKey:r.string().min(1).describe("Project key (e.g., 'MXTS')"),issueType:r.string().min(1).describe("Issue type name or ID (e.g., 'Bug', 'Task', 'Story')"),summary:r.string().min(1).describe("Issue title/summary"),description:r.string().optional().describe("Issue description (plain text, will be converted to ADF)"),assignee:r.string().optional().describe("Assignee account ID. Use '-1' for automatic assignment."),reporter:r.string().optional().describe("Reporter account ID"),priority:r.string().optional().describe("Priority name or ID (e.g., 'High', 'Medium', 'Low')"),labels:r.array(r.string()).optional().describe("Array of label strings"),components:r.array(r.string()).optional().describe("Array of component names or IDs"),fixVersions:r.array(r.string()).optional().describe("Array of fix version names or IDs"),affectsVersions:r.array(r.string()).optional().describe("Array of affected version names or IDs"),dueDate:r.string().optional().describe("Due date in YYYY-MM-DD format"),parentKey:r.string().optional().describe("Parent issue key for subtasks"),environment:r.string().optional().describe("Environment description"),originalEstimate:r.string().optional().describe("Original time estimate (e.g., '2h', '1d')"),customFields:r.record(r.string(),r.unknown()).optional().describe("Custom field values as key-value pairs")})},async e=>{try{const t=A(await v()),s=function(e){const t={};if(e.projectKey&&(t.project={key:e.projectKey}),e.issueType&&(t.issuetype=/^\d+$/.test(e.issueType)?{id:e.issueType}:{name:e.issueType}),void 0!==e.summary&&(t.summary=e.summary),void 0!==e.description&&(t.description=e.description?R(e.description):null),void 0!==e.assignee&&(t.assignee=null===e.assignee?null:{accountId:e.assignee}),e.reporter&&(t.reporter={accountId:e.reporter}),e.priority&&(t.priority=/^\d+$/.test(e.priority)?{id:e.priority}:{name:e.priority}),e.labels&&e.labels.length>0&&(t.labels=e.labels),e.components&&e.components.length>0&&(t.components=e.components.map(e=>/^\d+$/.test(e)?{id:e}:{name:e})),e.fixVersions&&e.fixVersions.length>0&&(t.fixVersions=e.fixVersions.map(e=>/^\d+$/.test(e)?{id:e}:{name:e})),e.affectsVersions&&e.affectsVersions.length>0&&(t.versions=e.affectsVersions.map(e=>/^\d+$/.test(e)?{id:e}:{name:e})),void 0!==e.dueDate&&(t.duedate=e.dueDate),e.parentKey&&(t.parent={key:e.parentKey}),e.environment&&(t.environment=R(e.environment)),(e.originalEstimate||e.remainingEstimate)&&(t.timetracking={},e.originalEstimate&&(t.timetracking.originalEstimate=e.originalEstimate),e.remainingEstimate&&(t.timetracking.remainingEstimate=e.remainingEstimate)),e.customFields)for(const[s,a]of Object.entries(e.customFields)){const e=s.startsWith("customfield_")?s:`customfield_${s}`;"string"==typeof a&&a.length,t[e]=a}return t}({projectKey:e.projectKey,issueType:e.issueType,summary:e.summary,description:e.description,assignee:e.assignee,reporter:e.reporter,priority:e.priority,labels:e.labels,components:e.components,fixVersions:e.fixVersions,affectsVersions:e.affectsVersions,dueDate:e.dueDate,parentKey:e.parentKey,environment:e.environment,originalEstimate:e.originalEstimate,customFields:e.customFields}),a=await t.post("/rest/api/3/issue",{fields:s});return T({success:!0,id:a.data?.id??"",key:a.data?.key??"",self:a.data?.self??"",message:`Issue ${a.data?.key} created successfully`})}catch(e){return T(K(e))}}),C.registerTool("jira_update_issue",{title:"Update Jira Issue",description:"Update an existing Jira issue. Only provided fields will be modified.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID (e.g., 'MXTS-123')"),summary:r.string().optional().describe("New summary/title"),description:r.string().optional().describe("New description (plain text)"),assignee:r.string().nullable().optional().describe("Assignee account ID. Use null to unassign."),priority:r.string().optional().describe("Priority name or ID"),dueDate:r.string().nullable().optional().describe("Due date (YYYY-MM-DD) or null to clear"),labels:r.object({add:r.array(r.string()).optional(),remove:r.array(r.string()).optional(),set:r.array(r.string()).optional()}).optional().describe("Label operations: add, remove, or set"),components:r.object({add:r.array(r.string()).optional(),remove:r.array(r.string()).optional(),set:r.array(r.string()).optional()}).optional().describe("Component operations: add, remove, or set"),fixVersions:r.object({add:r.array(r.string()).optional(),remove:r.array(r.string()).optional(),set:r.array(r.string()).optional()}).optional().describe("Fix version operations: add, remove, or set"),customFields:r.record(r.string(),r.unknown()).optional().describe("Custom field values"),notifyUsers:r.boolean().optional().default(!0).describe("Send notifications to watchers")})},async e=>{try{const t=A(await v()),s={},a={};if(void 0!==e.summary&&(a.summary=e.summary),void 0!==e.description&&(a.description=e.description?R(e.description):null),void 0!==e.assignee&&(a.assignee=null===e.assignee?null:{accountId:e.assignee}),void 0!==e.priority&&(a.priority=/^\d+$/.test(e.priority)?{id:e.priority}:{name:e.priority}),void 0!==e.dueDate&&(a.duedate=e.dueDate),e.customFields)for(const[t,s]of Object.entries(e.customFields)){a[t.startsWith("customfield_")?t:`customfield_${t}`]=s}Object.keys(a).length>0&&(s.fields=a);const r=function(e){const t={};if(e.labels){const s=[];e.labels.add&&e.labels.add.forEach(e=>s.push({add:e})),e.labels.remove&&e.labels.remove.forEach(e=>s.push({remove:e})),e.labels.set&&s.push({set:e.labels.set}),t.labels=s}if(e.components){const s=[];e.components.add&&e.components.add.forEach(e=>s.push({add:/^\d+$/.test(e)?{id:e}:{name:e}})),e.components.remove&&e.components.remove.forEach(e=>s.push({remove:/^\d+$/.test(e)?{id:e}:{name:e}})),e.components.set&&s.push({set:e.components.set.map(e=>/^\d+$/.test(e)?{id:e}:{name:e})}),t.components=s}if(e.fixVersions){const s=[];e.fixVersions.add&&e.fixVersions.add.forEach(e=>s.push({add:/^\d+$/.test(e)?{id:e}:{name:e}})),e.fixVersions.remove&&e.fixVersions.remove.forEach(e=>s.push({remove:/^\d+$/.test(e)?{id:e}:{name:e}})),e.fixVersions.set&&s.push({set:e.fixVersions.set.map(e=>/^\d+$/.test(e)?{id:e}:{name:e})}),t.fixVersions=s}if(e.affectsVersions){const s=[];e.affectsVersions.add&&e.affectsVersions.add.forEach(e=>s.push({add:/^\d+$/.test(e)?{id:e}:{name:e}})),e.affectsVersions.remove&&e.affectsVersions.remove.forEach(e=>s.push({remove:/^\d+$/.test(e)?{id:e}:{name:e}})),e.affectsVersions.set&&s.push({set:e.affectsVersions.set.map(e=>/^\d+$/.test(e)?{id:e}:{name:e})}),t.versions=s}return t}({labels:e.labels,components:e.components,fixVersions:e.fixVersions});return Object.keys(r).length>0&&(s.update=r),0===Object.keys(s).length?T({error:"no_changes",message:"No fields provided to update"}):(await t.put(`/rest/api/3/issue/${encodeURIComponent(e.issueIdOrKey)}`,s,{params:{notifyUsers:e.notifyUsers??!0}}),T({success:!0,key:e.issueIdOrKey,message:`Issue ${e.issueIdOrKey} updated successfully`}))}catch(e){return T(K(e))}}),C.registerTool("jira_assign_issue",{title:"Assign Jira Issue",description:"Assign or unassign a Jira issue to a user.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID"),accountId:r.string().nullable().describe("User account ID to assign, '-1' for automatic, or null to unassign")})},async({issueIdOrKey:e,accountId:t})=>{try{const s=A(await v());await s.put(`/rest/api/3/issue/${encodeURIComponent(e)}/assignee`,{accountId:t});return T({success:!0,key:e,message:`Issue ${e} ${null===t?"unassigned":"assigned"} successfully`,assignee:t})}catch(e){return T(K(e))}}),C.registerTool("jira_get_transitions",{title:"Get Issue Transitions",description:"Get available workflow transitions for an issue. Use before transitioning to see valid options.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID"),expand:r.string().optional().describe("Expand options: 'transitions.fields' to include required fields")})},async({issueIdOrKey:e,expand:t})=>{try{const s=A(await v()),a=await s.get(`/rest/api/3/issue/${encodeURIComponent(e)}/transitions`,{params:{expand:t}});return T({issueKey:e,transitions:Array.isArray(a.data?.transitions)?a.data.transitions.map(e=>({id:e.id,name:e.name,to:{id:e.to?.id,name:e.to?.name,statusCategory:e.to?.statusCategory?.name},hasScreen:e.hasScreen??!1,isGlobal:e.isGlobal??!1,isInitial:e.isInitial??!1,isConditional:e.isConditional??!1,fields:e.fields?Object.keys(e.fields):[]})):[]})}catch(e){return T(K(e))}}),C.registerTool("jira_transition_issue",{title:"Transition Jira Issue",description:"Move a Jira issue to a different status by executing a workflow transition.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID"),transitionId:r.string().min(1).describe("Transition ID (get from jira_get_transitions)"),comment:r.string().optional().describe("Comment to add during transition"),resolution:r.string().optional().describe("Resolution name for closing transitions (e.g., 'Done', 'Fixed')"),fields:r.record(r.string(),r.unknown()).optional().describe("Additional fields required by the transition")})},async({issueIdOrKey:e,transitionId:t,comment:s,resolution:a,fields:r})=>{try{const i=A(await v()),n={transition:{id:t}};if(r||a){const e={...r};a&&(e.resolution={name:a}),n.fields=e}return s&&(n.update={comment:[{add:{body:R(s)}}]}),await i.post(`/rest/api/3/issue/${encodeURIComponent(e)}/transitions`,n),T({success:!0,key:e,transitionId:t,message:`Issue ${e} transitioned successfully`})}catch(e){return T(K(e))}}),C.registerTool("jira_get_issue_types",{title:"Get Issue Types",description:"Get available issue types, optionally filtered by project.",inputSchema:r.object({projectKey:r.string().optional().describe("Filter issue types for a specific project")})},async({projectKey:e})=>{try{const t=A(await v());let s;if(e){const a=await t.get(`/rest/api/3/project/${encodeURIComponent(e)}`);s=a.data?.issueTypes||[]}else{s=(await t.get("/rest/api/3/issuetype")).data||[]}return T(s.map(e=>({id:e.id,name:e.name,description:e.description||"",subtask:e.subtask??!1,hierarchyLevel:e.hierarchyLevel})))}catch(e){return T(K(e))}}),C.registerTool("jira_get_priorities",{title:"Get Priorities",description:"Get available priority levels for issues.",inputSchema:r.object({})},async()=>{try{const e=A(await v());return T(((await e.get("/rest/api/3/priority")).data||[]).map(e=>({id:e.id,name:e.name,description:e.description||"",iconUrl:e.iconUrl})))}catch(e){return T(K(e))}}),C.registerTool("jira_get_statuses",{title:"Get Statuses",description:"Get available statuses, optionally filtered by project.",inputSchema:r.object({projectKey:r.string().optional().describe("Filter statuses for a specific project")})},async({projectKey:e})=>{try{const t=A(await v());if(e){return T((await t.get(`/rest/api/3/project/${encodeURIComponent(e)}/statuses`)).data||[])}return T(((await t.get("/rest/api/3/status")).data||[]).map(e=>({id:e.id,name:e.name,description:e.description||"",statusCategory:e.statusCategory?.name})))}catch(e){return T(K(e))}}),C.registerTool("jira_get_components",{title:"Get Project Components",description:"Get components for a specific project.",inputSchema:r.object({projectKey:r.string().min(1).describe("Project key")})},async({projectKey:e})=>{try{const t=A(await v());return T(((await t.get(`/rest/api/3/project/${encodeURIComponent(e)}/components`)).data||[]).map(e=>({id:e.id,name:e.name,description:e.description||"",lead:e.lead?.displayName,assigneeType:e.assigneeType})))}catch(e){return T(K(e))}}),C.registerTool("jira_get_versions",{title:"Get Project Versions",description:"Get versions for a specific project.",inputSchema:r.object({projectKey:r.string().min(1).describe("Project key"),released:r.boolean().optional().describe("Filter by released status")})},async({projectKey:e,released:t})=>{try{const s=A(await v());let a=(await s.get(`/rest/api/3/project/${encodeURIComponent(e)}/versions`)).data||[];return void 0!==t&&(a=a.filter(e=>e.released===t)),T(a.map(e=>({id:e.id,name:e.name,description:e.description||"",released:e.released??!1,archived:e.archived??!1,releaseDate:e.releaseDate,startDate:e.startDate})))}catch(e){return T(K(e))}}),C.registerTool("jira_search_users",{title:"Search Jira Users",description:"Search for Jira users by name, email, or username.",inputSchema:r.object({query:r.string().min(1).describe("Search query (name, email, or username)"),projectKey:r.string().optional().describe("Filter users with access to this project"),maxResults:r.number().int().positive().max(50).optional().default(10)})},async({query:e,projectKey:t,maxResults:s})=>{try{const a=A(await v());let r=(await a.get("/rest/api/3/user/search",{params:{query:e,maxResults:s??10}})).data||[];if(t&&r.length>0)try{r=(await a.get("/rest/api/3/user/assignable/search",{params:{query:e,project:t,maxResults:s??10}})).data||[]}catch{}return T(r.map(e=>({accountId:e.accountId,displayName:e.displayName,emailAddress:e.emailAddress,active:e.active??!0,avatarUrl:e.avatarUrls?.["48x48"]})))}catch(e){return T(K(e))}}),C.registerTool("jira_get_changelog",{title:"Get Issue Changelog",description:"Get the history of changes for an issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(100).optional().default(20)})},async({issueIdOrKey:e,startAt:t,maxResults:s})=>{try{const a=A(await v()),r=await a.get(`/rest/api/3/issue/${encodeURIComponent(e)}/changelog`,{params:{startAt:t,maxResults:s??20}}),i=Array.isArray(r.data?.values)?r.data.values.map(e=>({id:e.id,author:e.author?.displayName||e.author?.emailAddress||"",created:e.created,items:(e.items||[]).map(e=>({field:e.field,fieldtype:e.fieldtype,from:e.fromString||e.from,to:e.toString||e.to}))})):[];return T({total:r.data?.total??i.length,startAt:r.data?.startAt??0,changes:i})}catch(e){return T(K(e))}}),C.registerTool("jira_get_boards",{title:"Get Jira Boards",description:"Get all Scrum and Kanban boards, optionally filtered by project or type.",inputSchema:r.object({projectKeyOrId:r.string().optional().describe("Filter boards by project"),type:r.enum(["scrum","kanban","simple"]).optional().describe("Filter by board type"),name:r.string().optional().describe("Filter boards by name (contains)"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(50).optional().default(50)})},async({projectKeyOrId:e,type:t,name:s,startAt:a,maxResults:r})=>{try{const i=A(await v()),n=await i.get("/rest/agile/1.0/board",{params:{projectKeyOrId:e,type:t,name:s,startAt:a,maxResults:r??50}}),o=Array.isArray(n.data?.values)?n.data.values.map(e=>({id:e.id,name:e.name,type:e.type,projectKey:e.location?.projectKey,projectName:e.location?.displayName})):[];return T({total:n.data?.total??o.length,startAt:n.data?.startAt??0,boards:o})}catch(e){return T(K(e))}}),C.registerTool("jira_get_board",{title:"Get Board Details",description:"Get details of a specific board including configuration.",inputSchema:r.object({boardId:r.number().int().positive().describe("Board ID")})},async({boardId:e})=>{try{const t=A(await v()),s=await t.get(`/rest/agile/1.0/board/${e}`);return T({id:s.data?.id,name:s.data?.name,type:s.data?.type,self:s.data?.self,location:s.data?.location})}catch(e){return T(K(e))}}),C.registerTool("jira_get_board_configuration",{title:"Get Board Configuration",description:"Get the configuration of a board including columns, estimation, and ranking.",inputSchema:r.object({boardId:r.number().int().positive().describe("Board ID")})},async({boardId:e})=>{try{const t=A(await v()),s=await t.get(`/rest/agile/1.0/board/${e}/configuration`);return T({id:s.data?.id,name:s.data?.name,type:s.data?.type,filter:s.data?.filter,columnConfig:s.data?.columnConfig,estimation:s.data?.estimation,ranking:s.data?.ranking})}catch(e){return T(K(e))}}),C.registerTool("jira_get_sprints",{title:"Get Sprints",description:"Get sprints for a board, optionally filtered by state.",inputSchema:r.object({boardId:r.number().int().positive().describe("Board ID"),state:r.enum(["future","active","closed"]).optional().describe("Filter by sprint state"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(50).optional().default(50)})},async({boardId:e,state:t,startAt:s,maxResults:a})=>{try{const r=A(await v()),i=await r.get(`/rest/agile/1.0/board/${e}/sprint`,{params:{state:t,startAt:s,maxResults:a??50}}),n=Array.isArray(i.data?.values)?i.data.values.map(e=>({id:e.id,name:e.name,state:e.state,startDate:e.startDate,endDate:e.endDate,completeDate:e.completeDate,originBoardId:e.originBoardId,goal:e.goal})):[];return T({total:i.data?.total??n.length,startAt:i.data?.startAt??0,sprints:n})}catch(e){return T(K(e))}}),C.registerTool("jira_get_sprint",{title:"Get Sprint Details",description:"Get details of a specific sprint.",inputSchema:r.object({sprintId:r.number().int().positive().describe("Sprint ID")})},async({sprintId:e})=>{try{const t=A(await v()),s=await t.get(`/rest/agile/1.0/sprint/${e}`);return T({id:s.data?.id,name:s.data?.name,state:s.data?.state,startDate:s.data?.startDate,endDate:s.data?.endDate,completeDate:s.data?.completeDate,originBoardId:s.data?.originBoardId,goal:s.data?.goal})}catch(e){return T(K(e))}}),C.registerTool("jira_create_sprint",{title:"Create Sprint",description:"Create a new sprint on a board.",inputSchema:r.object({boardId:r.number().int().positive().describe("Board ID"),name:r.string().min(1).describe("Sprint name"),startDate:r.string().optional().describe("Start date (ISO 8601)"),endDate:r.string().optional().describe("End date (ISO 8601)"),goal:r.string().optional().describe("Sprint goal")})},async({boardId:e,name:t,startDate:s,endDate:a,goal:r})=>{try{const i=A(await v()),n=await i.post("/rest/agile/1.0/sprint",{originBoardId:e,name:t,startDate:s,endDate:a,goal:r});return T({success:!0,id:n.data?.id,name:n.data?.name,state:n.data?.state,message:`Sprint "${t}" created successfully`})}catch(e){return T(K(e))}}),C.registerTool("jira_update_sprint",{title:"Update Sprint",description:"Update sprint details including name, dates, and goal.",inputSchema:r.object({sprintId:r.number().int().positive().describe("Sprint ID"),name:r.string().optional().describe("New sprint name"),state:r.enum(["future","active","closed"]).optional().describe("Sprint state"),startDate:r.string().optional().describe("Start date (ISO 8601)"),endDate:r.string().optional().describe("End date (ISO 8601)"),goal:r.string().optional().describe("Sprint goal")})},async({sprintId:e,name:t,state:s,startDate:a,endDate:r,goal:i})=>{try{const n=A(await v()),o={};if(void 0!==t&&(o.name=t),void 0!==s&&(o.state=s),void 0!==a&&(o.startDate=a),void 0!==r&&(o.endDate=r),void 0!==i&&(o.goal=i),0===Object.keys(o).length)return T({error:"no_changes",message:"No fields provided to update"});const c=await n.put(`/rest/agile/1.0/sprint/${e}`,o);return T({success:!0,id:c.data?.id??e,name:c.data?.name,state:c.data?.state,message:"Sprint updated successfully"})}catch(e){return T(K(e))}}),C.registerTool("jira_start_sprint",{title:"Start Sprint",description:"Start a sprint that is in 'future' state.",inputSchema:r.object({sprintId:r.number().int().positive().describe("Sprint ID"),startDate:r.string().optional().describe("Start date (defaults to now)"),endDate:r.string().describe("End date (required for starting a sprint)")})},async({sprintId:e,startDate:t,endDate:s})=>{try{const a=A(await v()),r=await a.post(`/rest/agile/1.0/sprint/${e}`,{state:"active",startDate:t||(new Date).toISOString(),endDate:s});return T({success:!0,id:r.data?.id??e,state:"active",message:"Sprint started successfully"})}catch(e){return T(K(e))}}),C.registerTool("jira_complete_sprint",{title:"Complete Sprint",description:"Complete an active sprint. Optionally move incomplete issues to another sprint or backlog.",inputSchema:r.object({sprintId:r.number().int().positive().describe("Sprint ID to complete"),moveIncompleteIssuesTo:r.number().int().positive().optional().describe("Sprint ID to move incomplete issues to (omit to move to backlog)")})},async({sprintId:e,moveIncompleteIssuesTo:t})=>{try{const s=A(await v());return await s.post(`/rest/agile/1.0/sprint/${e}`,{state:"closed"}),T({success:!0,id:e,state:"closed",message:"Sprint completed successfully",incompleteIssuesMovedTo:t||"backlog"})}catch(e){return T(K(e))}}),C.registerTool("jira_get_sprint_issues",{title:"Get Sprint Issues",description:"Get all issues in a sprint.",inputSchema:r.object({sprintId:r.number().int().positive().describe("Sprint ID"),jql:r.string().optional().describe("Additional JQL filter"),fields:r.array(r.string()).optional().describe("Fields to return"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(100).optional().default(50)})},async({sprintId:e,jql:t,fields:s,startAt:a,maxResults:r})=>{try{const i=A(await v()),n=await i.get(`/rest/agile/1.0/sprint/${e}/issue`,{params:{jql:t,fields:s?.join(","),startAt:a,maxResults:r??50}}),o=Array.isArray(n.data?.issues)?n.data.issues.map(e=>({key:e.key,summary:e.fields?.summary,status:e.fields?.status?.name,assignee:e.fields?.assignee?.displayName,issueType:e.fields?.issuetype?.name,priority:e.fields?.priority?.name,storyPoints:e.fields?.customfield_10016})):[];return T({total:n.data?.total??o.length,startAt:n.data?.startAt??0,sprintId:e,issues:o})}catch(e){return T(K(e))}}),C.registerTool("jira_move_issues_to_sprint",{title:"Move Issues to Sprint",description:"Move issues to a sprint.",inputSchema:r.object({sprintId:r.number().int().positive().describe("Target sprint ID"),issueKeys:r.array(r.string().min(1)).min(1).describe("Issue keys to move")})},async({sprintId:e,issueKeys:t})=>{try{const s=A(await v());return await s.post(`/rest/agile/1.0/sprint/${e}/issue`,{issues:t}),T({success:!0,sprintId:e,issuesMoved:t,message:`${t.length} issue(s) moved to sprint ${e}`})}catch(e){return T(K(e))}}),C.registerTool("jira_get_backlog_issues",{title:"Get Backlog Issues",description:"Get issues in the backlog (not in any active sprint) for a board.",inputSchema:r.object({boardId:r.number().int().positive().describe("Board ID"),jql:r.string().optional().describe("Additional JQL filter"),fields:r.array(r.string()).optional().describe("Fields to return"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(100).optional().default(50)})},async({boardId:e,jql:t,fields:s,startAt:a,maxResults:r})=>{try{const i=A(await v()),n=await i.get(`/rest/agile/1.0/board/${e}/backlog`,{params:{jql:t,fields:s?.join(","),startAt:a,maxResults:r??50}}),o=Array.isArray(n.data?.issues)?n.data.issues.map(e=>({key:e.key,summary:e.fields?.summary,status:e.fields?.status?.name,assignee:e.fields?.assignee?.displayName,issueType:e.fields?.issuetype?.name,priority:e.fields?.priority?.name})):[];return T({total:n.data?.total??o.length,startAt:n.data?.startAt??0,boardId:e,issues:o})}catch(e){return T(K(e))}}),C.registerTool("jira_move_issues_to_backlog",{title:"Move Issues to Backlog",description:"Move issues from a sprint back to the backlog.",inputSchema:r.object({issueKeys:r.array(r.string().min(1)).min(1).describe("Issue keys to move to backlog")})},async({issueKeys:e})=>{try{const t=A(await v());return await t.post("/rest/agile/1.0/backlog/issue",{issues:e}),T({success:!0,issuesMoved:e,message:`${e.length} issue(s) moved to backlog`})}catch(e){return T(K(e))}}),C.registerTool("jira_rank_issues",{title:"Rank Issues",description:"Change the rank of issues on a board by placing them before or after another issue.",inputSchema:r.object({issueKeys:r.array(r.string().min(1)).min(1).describe("Issue keys to rank"),rankBeforeIssue:r.string().optional().describe("Issue key to rank before"),rankAfterIssue:r.string().optional().describe("Issue key to rank after")})},async({issueKeys:e,rankBeforeIssue:t,rankAfterIssue:s})=>{try{if(!t&&!s)return T({error:"invalid_parameters",message:"Either rankBeforeIssue or rankAfterIssue must be provided"});const a=A(await v()),r={issues:e};return t?r.rankBeforeIssue=t:s&&(r.rankAfterIssue=s),await a.put("/rest/agile/1.0/issue/rank",r),T({success:!0,issuesRanked:e,message:`${e.length} issue(s) ranked successfully`})}catch(e){return T(K(e))}}),C.registerTool("jira_get_issue_links",{title:"Get Issue Links",description:"Get all linked issues for a specific issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID")})},async({issueIdOrKey:e})=>{try{const t=A(await v()),s=await t.get(`/rest/api/3/issue/${encodeURIComponent(e)}`,{params:{fields:"issuelinks"}});return T({issueKey:e,links:Array.isArray(s.data?.fields?.issuelinks)?s.data.fields.issuelinks.map(e=>{const t=!!e.inwardIssue,s=t?e.inwardIssue:e.outwardIssue;return{id:e.id,type:e.type?.name,direction:t?"inward":"outward",description:t?e.type?.inward:e.type?.outward,linkedIssue:{key:s?.key,summary:s?.fields?.summary,status:s?.fields?.status?.name,issueType:s?.fields?.issuetype?.name}}}):[]})}catch(e){return T(K(e))}}),C.registerTool("jira_create_issue_link",{title:"Link Issues",description:"Create a link between two issues.",inputSchema:r.object({inwardIssue:r.string().min(1).describe("Inward issue key (the 'from' issue)"),outwardIssue:r.string().min(1).describe("Outward issue key (the 'to' issue)"),linkType:r.string().min(1).describe("Link type name (e.g., 'Blocks', 'Relates', 'Duplicates')"),comment:r.string().optional().describe("Comment to add with the link")})},async({inwardIssue:e,outwardIssue:t,linkType:s,comment:a})=>{try{const r=A(await v()),i={type:{name:s},inwardIssue:{key:e},outwardIssue:{key:t}};return a&&(i.comment={body:R(a)}),await r.post("/rest/api/3/issueLink",i),T({success:!0,message:`Link created: ${e} ${s} ${t}`,inwardIssue:e,outwardIssue:t,linkType:s})}catch(e){return T(K(e))}}),C.registerTool("jira_get_link_types",{title:"Get Issue Link Types",description:"Get available link types for linking issues.",inputSchema:r.object({})},async()=>{try{const e=A(await v()),t=await e.get("/rest/api/3/issueLinkType");return T((t.data?.issueLinkTypes||[]).map(e=>({id:e.id,name:e.name,inward:e.inward,outward:e.outward})))}catch(e){return T(K(e))}}),C.registerTool("jira_get_watchers",{title:"Get Issue Watchers",description:"Get the list of users watching an issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID")})},async({issueIdOrKey:e})=>{try{const t=A(await v()),s=await t.get(`/rest/api/3/issue/${encodeURIComponent(e)}/watchers`),a=Array.isArray(s.data?.watchers)?s.data.watchers.map(e=>({accountId:e.accountId,displayName:e.displayName,emailAddress:e.emailAddress})):[];return T({issueKey:e,watchCount:s.data?.watchCount??a.length,isWatching:s.data?.isWatching??!1,watchers:a})}catch(e){return T(K(e))}}),C.registerTool("jira_add_watcher",{title:"Add Issue Watcher",description:"Add a user to watch an issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID"),accountId:r.string().min(1).describe("User account ID to add as watcher")})},async({issueIdOrKey:e,accountId:t})=>{try{const s=A(await v());return await s.post(`/rest/api/3/issue/${encodeURIComponent(e)}/watchers`,JSON.stringify(t),{headers:{"Content-Type":"application/json"}}),T({success:!0,issueKey:e,accountId:t,message:"User added as watcher"})}catch(e){return T(K(e))}}),C.registerTool("jira_remove_watcher",{title:"Remove Issue Watcher",description:"Remove a user from watching an issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID"),accountId:r.string().min(1).describe("User account ID to remove")})},async({issueIdOrKey:e,accountId:t})=>{try{const s=A(await v());return await s.delete(`/rest/api/3/issue/${encodeURIComponent(e)}/watchers`,{params:{accountId:t}}),T({success:!0,issueKey:e,accountId:t,message:"User removed from watchers"})}catch(e){return T(K(e))}}),C.registerTool("jira_get_votes",{title:"Get Issue Votes",description:"Get the vote count and voters for an issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID")})},async({issueIdOrKey:e})=>{try{const t=A(await v()),s=await t.get(`/rest/api/3/issue/${encodeURIComponent(e)}/votes`),a=Array.isArray(s.data?.voters)?s.data.voters.map(e=>({accountId:e.accountId,displayName:e.displayName})):[];return T({issueKey:e,votes:s.data?.votes??0,hasVoted:s.data?.hasVoted??!1,voters:a})}catch(e){return T(K(e))}}),C.registerTool("jira_add_vote",{title:"Vote for Issue",description:"Add your vote to an issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID")})},async({issueIdOrKey:e})=>{try{const t=A(await v());return await t.post(`/rest/api/3/issue/${encodeURIComponent(e)}/votes`),T({success:!0,issueKey:e,message:"Vote added successfully"})}catch(e){return T(K(e))}}),C.registerTool("jira_remove_vote",{title:"Remove Vote",description:"Remove your vote from an issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID")})},async({issueIdOrKey:e})=>{try{const t=A(await v());return await t.delete(`/rest/api/3/issue/${encodeURIComponent(e)}/votes`),T({success:!0,issueKey:e,message:"Vote removed successfully"})}catch(e){return T(K(e))}}),C.registerTool("jira_get_attachments",{title:"Get Issue Attachments",description:"Get all attachments for an issue.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID")})},async({issueIdOrKey:e})=>{try{const t=A(await v()),s=await t.get(`/rest/api/3/issue/${encodeURIComponent(e)}`,{params:{fields:"attachment"}});return T({issueKey:e,attachments:Array.isArray(s.data?.fields?.attachment)?s.data.fields.attachment.map(e=>({id:e.id,filename:e.filename,size:e.size,mimeType:e.mimeType,content:e.content,thumbnail:e.thumbnail,author:e.author?.displayName,created:e.created})):[]})}catch(e){return T(K(e))}}),C.registerTool("jira_get_epics",{title:"Get Epics",description:"Get epics for a board.",inputSchema:r.object({boardId:r.number().int().positive().describe("Board ID"),done:r.enum(["true","false"]).optional().describe("Filter by completion status"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(50).optional().default(50)})},async({boardId:e,done:t,startAt:s,maxResults:a})=>{try{const r=A(await v()),i=await r.get(`/rest/agile/1.0/board/${e}/epic`,{params:{done:t,startAt:s,maxResults:a??50}}),n=Array.isArray(i.data?.values)?i.data.values.map(e=>({id:e.id,key:e.key,name:e.name,summary:e.summary,done:e.done??!1})):[];return T({total:i.data?.total??n.length,startAt:i.data?.startAt??0,boardId:e,epics:n})}catch(e){return T(K(e))}}),C.registerTool("jira_get_epic_issues",{title:"Get Epic Issues",description:"Get all issues belonging to an epic.",inputSchema:r.object({epicIdOrKey:r.string().min(1).describe("Epic ID or key"),jql:r.string().optional().describe("Additional JQL filter"),fields:r.array(r.string()).optional().describe("Fields to return"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(100).optional().default(50)})},async({epicIdOrKey:e,jql:t,fields:s,startAt:a,maxResults:r})=>{try{const i=A(await v()),n=await i.get(`/rest/agile/1.0/epic/${e}/issue`,{params:{jql:t,fields:s?.join(","),startAt:a,maxResults:r??50}}),o=Array.isArray(n.data?.issues)?n.data.issues.map(e=>({key:e.key,summary:e.fields?.summary,status:e.fields?.status?.name,assignee:e.fields?.assignee?.displayName,issueType:e.fields?.issuetype?.name})):[];return T({total:n.data?.total??o.length,startAt:n.data?.startAt??0,epicKey:e,issues:o})}catch(e){return T(K(e))}}),C.registerTool("jira_move_issues_to_epic",{title:"Move Issues to Epic",description:"Move issues to an epic.",inputSchema:r.object({epicIdOrKey:r.string().min(1).describe("Epic ID or key"),issueKeys:r.array(r.string().min(1)).min(1).describe("Issue keys to move")})},async({epicIdOrKey:e,issueKeys:t})=>{try{const s=A(await v());return await s.post(`/rest/agile/1.0/epic/${e}/issue`,{issues:t}),T({success:!0,epicKey:e,issuesMoved:t,message:`${t.length} issue(s) moved to epic ${e}`})}catch(e){return T(K(e))}}),C.registerTool("jira_remove_issues_from_epic",{title:"Remove Issues from Epic",description:"Remove issues from their epic (move to no epic).",inputSchema:r.object({issueKeys:r.array(r.string().min(1)).min(1).describe("Issue keys to remove from epic")})},async({issueKeys:e})=>{try{const t=A(await v());return await t.post("/rest/agile/1.0/epic/none/issue",{issues:e}),T({success:!0,issuesRemoved:e,message:`${e.length} issue(s) removed from epic`})}catch(e){return T(K(e))}}),C.registerTool("jira_get_fields",{title:"Get All Fields",description:"Get all available fields including custom fields.",inputSchema:r.object({})},async()=>{try{const e=A(await v());return T(((await e.get("/rest/api/3/field")).data||[]).map(e=>({id:e.id,key:e.key,name:e.name,custom:e.custom??!1,orderable:e.orderable??!1,navigable:e.navigable??!1,searchable:e.searchable??!1,clauseNames:e.clauseNames||[],schema:e.schema})))}catch(e){return T(K(e))}}),C.registerTool("jira_get_create_metadata",{title:"Get Create Issue Metadata",description:"Get metadata for creating issues in a project, including available issue types and their fields. Uses the modern non-deprecated API endpoints.",inputSchema:r.object({projectIdOrKey:r.string().min(1).describe("Project key or ID (required)"),issueTypeId:r.string().optional().describe("Issue type ID to get field metadata for (optional - if not provided, returns available issue types)"),startAt:r.number().optional().describe("Starting index for pagination"),maxResults:r.number().optional().describe("Maximum results (default 50, max 50)")})},async({projectIdOrKey:e,issueTypeId:t,startAt:s,maxResults:a})=>{try{const r=A(await v());if(t){return T((await r.get(`/rest/api/3/issue/createmeta/${encodeURIComponent(e)}/issuetypes/${encodeURIComponent(t)}`,{params:{startAt:s||0,maxResults:a||50}})).data)}return T((await r.get(`/rest/api/3/issue/createmeta/${encodeURIComponent(e)}/issuetypes`,{params:{startAt:s||0,maxResults:a||50}})).data)}catch(e){return T(K(e))}}),C.registerTool("jira_get_edit_metadata",{title:"Get Edit Issue Metadata",description:"Get metadata for editing a specific issue, including editable fields.",inputSchema:r.object({issueIdOrKey:r.string().min(1).describe("Issue key or ID")})},async({issueIdOrKey:e})=>{try{const t=A(await v());return T((await t.get(`/rest/api/3/issue/${encodeURIComponent(e)}/editmeta`)).data)}catch(e){return T(K(e))}}),C.registerTool("jira_get_filters",{title:"Get Filters",description:"Get saved filters, optionally filtered by name.",inputSchema:r.object({filterName:r.string().optional().describe("Filter by name (contains)"),owner:r.string().optional().describe("Filter by owner account ID"),expand:r.string().optional().describe("Expand options: description, owner, jql, viewUrl, searchUrl, favourite, favouritedCount, sharePermissions"),startAt:r.number().int().nonnegative().optional(),maxResults:r.number().int().positive().max(50).optional().default(50)})},async({filterName:e,owner:t,expand:s,startAt:a,maxResults:r})=>{try{const i=A(await v()),n=await i.get("/rest/api/3/filter/search",{params:{filterName:e,owner:t,expand:s,startAt:a,maxResults:r??50}}),o=Array.isArray(n.data?.values)?n.data.values.map(e=>({id:e.id,name:e.name,description:e.description,owner:e.owner?.displayName,jql:e.jql,favourite:e.favourite??!1,favouritedCount:e.favouritedCount??0})):[];return T({total:n.data?.total??o.length,startAt:n.data?.startAt??0,filters:o})}catch(e){return T(K(e))}}),C.registerTool("jira_get_filter",{title:"Get Filter Details",description:"Get details of a specific filter.",inputSchema:r.object({filterId:r.string().min(1).describe("Filter ID"),expand:r.string().optional().describe("Expand options")})},async({filterId:e,expand:t})=>{try{const s=A(await v()),a=await s.get(`/rest/api/3/filter/${e}`,{params:{expand:t}});return T({id:a.data?.id,name:a.data?.name,description:a.data?.description,owner:a.data?.owner?.displayName,jql:a.data?.jql,favourite:a.data?.favourite??!1,sharePermissions:a.data?.sharePermissions})}catch(e){return T(K(e))}}),C.registerTool("jira_create_filter",{title:"Create Filter",description:"Create a new saved filter.",inputSchema:r.object({name:r.string().min(1).describe("Filter name"),jql:r.string().min(1).describe("JQL query"),description:r.string().optional().describe("Filter description"),favourite:r.boolean().optional().describe("Mark as favourite")})},async({name:e,jql:t,description:s,favourite:a})=>{try{const r=A(await v()),i=await r.post("/rest/api/3/filter",{name:e,jql:t,description:s,favourite:a});return T({success:!0,id:i.data?.id,name:i.data?.name,jql:i.data?.jql,message:`Filter "${e}" created successfully`})}catch(e){return T(K(e))}}),C.registerTool("jira_update_filter",{title:"Update Filter",description:"Update an existing filter.",inputSchema:r.object({filterId:r.string().min(1).describe("Filter ID"),name:r.string().optional().describe("New filter name"),jql:r.string().optional().describe("New JQL query"),description:r.string().optional().describe("New description"),favourite:r.boolean().optional().describe("Favourite status")})},async({filterId:e,name:t,jql:s,description:a,favourite:r})=>{try{const i=A(await v()),n={};if(void 0!==t&&(n.name=t),void 0!==s&&(n.jql=s),void 0!==a&&(n.description=a),void 0!==r&&(n.favourite=r),0===Object.keys(n).length)return T({error:"no_changes",message:"No fields provided to update"});const o=await i.put(`/rest/api/3/filter/${e}`,n);return T({success:!0,id:o.data?.id??e,name:o.data?.name,message:"Filter updated successfully"})}catch(e){return T(K(e))}}),C.registerTool("jira_get_my_filters",{title:"Get My Filters",description:"Get filters owned by the current user.",inputSchema:r.object({expand:r.string().optional().describe("Expand options")})},async({expand:e})=>{try{const t=A(await v());return T(((await t.get("/rest/api/3/filter/my",{params:{expand:e}})).data||[]).map(e=>({id:e.id,name:e.name,description:e.description,jql:e.jql,favourite:e.favourite??!1})))}catch(e){return T(K(e))}}),C.registerTool("jira_get_favourite_filters",{title:"Get Favourite Filters",description:"Get filters marked as favourite by the current user.",inputSchema:r.object({expand:r.string().optional().describe("Expand options")})},async({expand:e})=>{try{const t=A(await v());return T(((await t.get("/rest/api/3/filter/favourite",{params:{expand:e}})).data||[]).map(e=>({id:e.id,name:e.name,description:e.description,owner:e.owner?.displayName,jql:e.jql})))}catch(e){return T(K(e))}}),C.registerTool("jira_bulk_edit_issues",{title:"Bulk Edit Issues",description:"Edit multiple issues at once. Supports bulk editing of labels, assignee, priority, components, and fix versions. Returns a taskId to track progress.",inputSchema:r.object({issueIdsOrKeys:r.array(r.string()).min(1).describe("Array of issue IDs or keys to edit"),editedFieldsInput:r.object({labels:r.object({add:r.array(r.string()).optional().describe("Labels to add"),remove:r.array(r.string()).optional().describe("Labels to remove"),set:r.array(r.string()).optional().describe("Labels to set (replaces all)")}).optional(),assignee:r.object({accountId:r.string().describe("Account ID of the assignee")}).optional(),priority:r.object({id:r.string().describe("Priority ID")}).optional(),components:r.object({add:r.array(r.object({id:r.string()})).optional(),remove:r.array(r.object({id:r.string()})).optional()}).optional(),fixVersions:r.object({add:r.array(r.object({id:r.string()})).optional(),remove:r.array(r.object({id:r.string()})).optional()}).optional()}).describe("Fields to edit with their operations"),sendNotifications:r.boolean().optional().default(!0).describe("Whether to send email notifications")})},async({issueIdsOrKeys:e,editedFieldsInput:t,sendNotifications:s})=>{try{const a=A(await v());return T({success:!0,taskId:(await a.post("/rest/api/3/bulk/issues/fields",{issueIdsOrKeys:e,editedFieldsInput:t,sendNotifications:s})).data.taskId,message:`Bulk edit initiated for ${e.length} issues. Use jira_get_bulk_operation_progress with taskId to track progress.`})}catch(e){return T(K(e))}}),C.registerTool("jira_bulk_watch_issues",{title:"Bulk Watch Issues",description:"Add watchers to multiple issues at once. Returns a taskId to track progress.",inputSchema:r.object({issueIdsOrKeys:r.array(r.string()).min(1).describe("Array of issue IDs or keys to watch"),accountIds:r.array(r.string()).optional().describe("Account IDs to add as watchers (defaults to current user)")})},async({issueIdsOrKeys:e,accountIds:t})=>{try{const s=A(await v());return T({success:!0,taskId:(await s.post("/rest/api/3/bulk/issues/watch",{issueIdsOrKeys:e,...t&&{accountIds:t}})).data.taskId,message:`Bulk watch initiated for ${e.length} issues. Use jira_get_bulk_operation_progress with taskId to track progress.`})}catch(e){return T(K(e))}}),C.registerTool("jira_bulk_unwatch_issues",{title:"Bulk Unwatch Issues",description:"Remove watchers from multiple issues at once. Returns a taskId to track progress.",inputSchema:r.object({issueIdsOrKeys:r.array(r.string()).min(1).describe("Array of issue IDs or keys to unwatch"),accountIds:r.array(r.string()).optional().describe("Account IDs to remove as watchers (defaults to current user)")})},async({issueIdsOrKeys:e,accountIds:t})=>{try{const s=A(await v());return T({success:!0,taskId:(await s.post("/rest/api/3/bulk/issues/unwatch",{issueIdsOrKeys:e,...t&&{accountIds:t}})).data.taskId,message:`Bulk unwatch initiated for ${e.length} issues. Use jira_get_bulk_operation_progress with taskId to track progress.`})}catch(e){return T(K(e))}}),C.registerTool("jira_get_bulk_operation_progress",{title:"Get Bulk Operation Progress",description:"Check the progress of an async bulk operation using its taskId.",inputSchema:r.object({taskId:r.string().describe("The task ID returned from a bulk operation")})},async({taskId:e})=>{try{const t=A(await v()),s=(await t.get(`/rest/api/3/bulk/queue/${e}`)).data;return T({taskId:s.taskId,status:s.status,progress:s.progress,submittedBy:s.submittedBy,created:s.created,started:s.started,finished:s.finished,successfulIssues:s.successfulIssues||[],failedIssues:s.failedIssues||[],totalIssues:(s.successfulIssues?.length||0)+(s.failedIssues?.length||0)})}catch(e){return T(K(e))}}),C.registerTool("jira_get_dashboards",{title:"Get Dashboards",description:"Get a list of dashboards. Can filter by favourite or owned dashboards.",inputSchema:r.object({filter:r.enum(["favourite","my"]).optional().describe("Filter dashboards: 'favourite' for favourited, 'my' for owned"),startAt:r.number().optional().default(0).describe("Index of first result"),maxResults:r.number().optional().default(50).describe("Maximum results to return")})},async({filter:e,startAt:t,maxResults:s})=>{try{const a=A(await v()),r=await a.get("/rest/api/3/dashboard",{params:{filter:e,startAt:t,maxResults:s}});return T({total:r.data.total,startAt:r.data.startAt,maxResults:r.data.maxResults,dashboards:(r.data.dashboards||[]).map(e=>({id:e.id,name:e.name,self:e.self,isFavourite:e.isFavourite,view:e.view}))})}catch(e){return T(K(e))}}),C.registerTool("jira_search_dashboards",{title:"Search Dashboards",description:"Search for dashboards by name, owner, or other criteria.",inputSchema:r.object({dashboardName:r.string().optional().describe("Filter by dashboard name (case insensitive contains)"),accountId:r.string().optional().describe("Filter by owner account ID"),groupname:r.string().optional().describe("Filter by group permission"),orderBy:r.enum(["name","-name","id","-id","owner","-owner","favourite_count","-favourite_count"]).optional().describe("Order results by field (prefix with - for descending)"),startAt:r.number().optional().default(0).describe("Index of first result"),maxResults:r.number().optional().default(50).describe("Maximum results"),expand:r.string().optional().describe("Expand options: description, owner, viewUrl, favourite, favouritedCount, sharePermissions, editPermissions")})},async({dashboardName:e,accountId:t,groupname:s,orderBy:a,startAt:r,maxResults:i,expand:n})=>{try{const o=A(await v()),c=await o.get("/rest/api/3/dashboard/search",{params:{dashboardName:e,accountId:t,groupname:s,orderBy:a,startAt:r,maxResults:i,expand:n}});return T({total:c.data.total,startAt:c.data.startAt,maxResults:c.data.maxResults,dashboards:(c.data.values||[]).map(e=>({id:e.id,name:e.name,description:e.description,owner:e.owner?{accountId:e.owner.accountId,displayName:e.owner.displayName}:void 0,isFavourite:e.isFavourite,popularity:e.popularity,view:e.view}))})}catch(e){return T(K(e))}}),C.registerTool("jira_get_dashboard",{title:"Get Dashboard",description:"Get details of a specific dashboard by ID.",inputSchema:r.object({id:r.string().describe("Dashboard ID")})},async({id:e})=>{try{const t=A(await v()),s=(await t.get(`/rest/api/3/dashboard/${e}`)).data;return T({id:s.id,name:s.name,description:s.description,self:s.self,isFavourite:s.isFavourite,owner:s.owner?{accountId:s.owner.accountId,displayName:s.owner.displayName}:void 0,popularity:s.popularity,view:s.view,editPermissions:s.editPermissions,sharePermissions:s.sharePermissions})}catch(e){return T(K(e))}}),C.registerTool("jira_get_dashboard_gadgets",{title:"Get Dashboard Gadgets",description:"Get all gadgets on a dashboard.",inputSchema:r.object({dashboardId:r.string().describe("Dashboard ID"),moduleKey:r.array(r.string()).optional().describe("Filter by gadget module keys"),uri:r.string().optional().describe("Filter by gadget URI"),gadgetId:r.array(r.string()).optional().describe("Filter by gadget IDs")})},async({dashboardId:e,moduleKey:t,uri:s,gadgetId:a})=>{try{const r=A(await v());return T({gadgets:((await r.get(`/rest/api/3/dashboard/${e}/gadget`,{params:{moduleKey:t?.join(","),uri:s,gadgetId:a?.join(",")}})).data.gadgets||[]).map(e=>({id:e.id,moduleKey:e.moduleKey,uri:e.uri,color:e.color,position:e.position,title:e.title}))})}catch(e){return T(K(e))}}),C.registerTool("jira_add_dashboard_gadget",{title:"Add Dashboard Gadget",description:"Add a gadget to a dashboard. Provide either moduleKey or uri to specify the gadget type.",inputSchema:r.object({dashboardId:r.string().describe("Dashboard ID"),moduleKey:r.string().optional().describe("Module key of the gadget type (e.g., com.atlassian.jira.gadgets:filter-results-gadget)"),uri:r.string().optional().describe("URI of the gadget type"),color:r.enum(["blue","red","yellow","green","cyan","purple","gray","white"]).optional().describe("Gadget colour"),position:r.object({row:r.number().describe("Row position (0-indexed)"),column:r.number().describe("Column position (0-indexed)")}).optional().describe("Position on dashboard grid"),title:r.string().optional().describe("Gadget title"),ignoreUriAndModuleKeyValidation:r.boolean().optional().default(!1).describe("Skip validation of moduleKey/uri")})},async({dashboardId:e,moduleKey:t,uri:s,color:a,position:r,title:i,ignoreUriAndModuleKeyValidation:n})=>{try{if(!t&&!s)return T({error:!0,message:"Either moduleKey or uri must be provided"});const o=A(await v()),c=await o.post(`/rest/api/3/dashboard/${e}/gadget`,{moduleKey:t,uri:s,color:a,position:r,title:i,ignoreUriAndModuleKeyValidation:n});return T({success:!0,gadget:{id:c.data.id,moduleKey:c.data.moduleKey,uri:c.data.uri,color:c.data.color,position:c.data.position,title:c.data.title}})}catch(e){return T(K(e))}}),C.registerTool("jira_upload_attachment",{title:"Upload Attachment",description:"Upload a file attachment to an issue. Requires the file path on the local filesystem.",inputSchema:r.object({issueIdOrKey:r.string().describe("Issue ID or key"),filePath:r.string().describe("Absolute path to the file to upload"),filename:r.string().optional().describe("Override the filename (defaults to original filename)")})},async({issueIdOrKey:e,filePath:t,filename:s})=>{try{const a=await import("fs"),r=await import("path"),i=(await import("form-data")).default;if(!a.existsSync(t))return T({error:!0,message:`File not found: ${t}`});const n=A(await v()),o=new i,c=a.createReadStream(t),u=s||r.basename(t);o.append("file",c,u);const d=await n.post(`/rest/api/3/issue/${e}/attachments`,o,{headers:{...o.getHeaders(),"X-Atlassian-Token":"no-check"}});return T({success:!0,attachments:(d.data||[]).map(e=>({id:e.id,filename:e.filename,size:e.size,mimeType:e.mimeType,created:e.created,author:e.author?.displayName,content:e.content}))})}catch(e){return T(K(e))}}),C.registerTool("jira_get_attachment_metadata",{title:"Get Attachment Metadata",description:"Get metadata for a specific attachment by ID.",inputSchema:r.object({id:r.string().describe("Attachment ID")})},async({id:e})=>{try{const t=A(await v()),s=(await t.get(`/rest/api/3/attachment/${e}`)).data;return T({id:s.id,filename:s.filename,size:s.size,mimeType:s.mimeType,created:s.created,author:s.author?{accountId:s.author.accountId,displayName:s.author.displayName}:void 0,content:s.content,thumbnail:s.thumbnail,self:s.self})}catch(e){return T(K(e))}}),C.registerTool("jira_get_attachment_content",{title:"Get Attachment Content",description:"Get the content/download URL for an attachment. Returns the redirect URL or content depending on redirect setting.",inputSchema:r.object({id:r.string().describe("Attachment ID"),redirect:r.boolean().optional().default(!0).describe("Whether to return redirect URL (true) or follow redirect (false)")})},async({id:e,redirect:t})=>{try{const s=A(await v()),a=await s.get(`/rest/api/3/attachment/content/${e}`,{params:{redirect:t},maxRedirects:t?0:5,validateStatus:e=>e<400||302===e});return 302===a.status||a.headers.location?T({downloadUrl:a.headers.location||a.data,message:"Use this URL to download the attachment content"}):T({contentType:a.headers["content-type"],contentLength:a.headers["content-length"],message:"Content retrieved. For binary files, use the download URL instead."})}catch(e){return T(K(e))}}),C.registerTool("jira_get_all_labels",{title:"Get All Labels",description:"Get all labels used across all issues in the Jira instance.",inputSchema:r.object({startAt:r.number().optional().default(0).describe("Index of first result"),maxResults:r.number().optional().default(1e3).describe("Maximum results to return")})},async({startAt:e,maxResults:t})=>{try{const s=A(await v()),a=await s.get("/rest/api/3/label",{params:{startAt:e,maxResults:t}});return T({total:a.data.total,maxResults:a.data.maxResults,startAt:a.data.startAt,labels:a.data.values||[]})}catch(e){return T(K(e))}}),C.registerTool("jira_add_labels",{title:"Add/Set/Remove Labels",description:"Add, set, or remove labels on an issue. Use 'add' to append, 'set' to replace all, or 'remove' to delete specific labels.",inputSchema:r.object({issueIdOrKey:r.string().describe("Issue ID or key"),labels:r.array(r.string()).min(1).describe("Labels to add/set/remove"),operation:r.enum(["add","set","remove"]).default("add").describe("Operation: 'add' appends, 'set' replaces all, 'remove' deletes specified labels")})},async({issueIdOrKey:e,labels:t,operation:s})=>{try{const a=A(await v());let r;return r="set"===s?{fields:{labels:t}}:{update:{labels:t.map(e=>({[s]:e}))}},await a.put(`/rest/api/3/issue/${e}`,r),T({success:!0,message:`Labels ${"add"===s?"added to":"remove"===s?"removed from":"set on"} issue ${e}`,labels:t,operation:s})}catch(e){return T(K(e))}}),C.registerTool("jira_autocomplete_jql",{title:"Autocomplete JQL",description:"Get autocomplete suggestions for JQL field values. Useful for building JQL queries interactively.",inputSchema:r.object({fieldName:r.string().optional().describe("Field name to get value suggestions for (e.g., status, priority, assignee)"),fieldValue:r.string().optional().describe("Partial value to autocomplete"),predicateName:r.string().optional().describe("Predicate name for function suggestions"),predicateValue:r.string().optional().describe("Partial predicate value to autocomplete")})},async({fieldName:e,fieldValue:t,predicateName:s,predicateValue:a})=>{try{const r=A(await v());return T({results:((await r.get("/rest/api/3/jql/autocompletedata/suggestions",{params:{fieldName:e,fieldValue:t,predicateName:s,predicateValue:a}})).data.results||[]).map(e=>({value:e.value,displayName:e.displayName}))})}catch(e){return T(K(e))}}),C.registerTool("jira_validate_jql",{title:"Validate JQL",description:"Validate one or more JQL queries for syntax and semantic correctness.",inputSchema:r.object({queries:r.array(r.string()).min(1).describe("JQL queries to validate"),validation:r.enum(["strict","warn","none"]).optional().default("strict").describe("Validation level: strict (errors only), warn (errors and warnings), none (no validation)")})},async({queries:e,validation:t})=>{try{const s=A(await v());return T({queries:((await s.post("/rest/api/3/jql/parse",{queries:e,validation:t})).data.queries||[]).map(e=>({query:e.query,errors:e.errors||[],warnings:e.warnings||[],isValid:!e.errors||0===e.errors.length}))})}catch(e){return T(K(e))}}),C.registerTool("jira_parse_jql",{title:"Parse JQL",description:"Parse JQL queries and return their abstract syntax tree (AST) structure. Useful for understanding query structure.",inputSchema:r.object({queries:r.array(r.string()).min(1).describe("JQL queries to parse"),validation:r.enum(["strict","warn","none"]).optional().default("none").describe("Validation level")})},async({queries:e,validation:t})=>{try{const s=A(await v());return T({queries:((await s.post("/rest/api/3/jql/parse",{queries:e,validation:t})).data.queries||[]).map(e=>({query:e.query,structure:e.structure,errors:e.errors||[]}))})}catch(e){return T(K(e))}}),C.registerTool("jira_get_updated_worklog_ids",{title:"Get Updated Worklog IDs",description:"Get IDs of worklogs that were created or updated since a specific date/time. Use this to discover worklogs for reporting purposes.",inputSchema:r.object({since:r.string().describe("Get worklogs updated since this date. Can be ISO 8601 format (e.g., '2026-01-15T00:00:00.000Z') or Unix timestamp in milliseconds."),expand:r.string().optional().describe("Expand options for additional worklog properties")})},async({since:e,expand:t})=>{try{const s=A(await v());let a;a=/^\d+$/.test(e)?parseInt(e,10):new Date(e).getTime();const r={since:a};t&&(r.expand=t);const i=await s.get("/rest/api/3/worklog/updated",{params:r});return T({since:a,sinceDate:new Date(a).toISOString(),until:i.data.until,untilDate:i.data.until?new Date(i.data.until).toISOString():null,lastPage:i.data.lastPage,nextPage:i.data.nextPage,worklogIds:(i.data.values||[]).map(e=>({worklogId:e.worklogId,updatedTime:e.updatedTime,updatedDate:new Date(e.updatedTime).toISOString()})),total:i.data.values?.length||0})}catch(e){return T(K(e))}}),C.registerTool("jira_get_worklogs_by_ids",{title:"Get Worklogs by IDs",description:"Get full worklog details for a list of worklog IDs. Use after getting IDs from jira_get_updated_worklog_ids.",inputSchema:r.object({ids:r.array(r.number().int().positive()).min(1).max(1e3).describe("Array of worklog IDs to fetch (max 1000)"),expand:r.string().optional().describe("Expand options")})},async({ids:e,expand:t})=>{try{const s=A(await v()),a={};t&&(a.expand=t);const r=await s.post("/rest/api/3/worklog/list",{ids:e},{params:a}),i=Array.isArray(r.data)?r.data.map(e=>({id:e.id,issueId:e.issueId,author:{accountId:e.author?.accountId,displayName:e.author?.displayName,emailAddress:e.author?.emailAddress},updateAuthor:{accountId:e.updateAuthor?.accountId,displayName:e.updateAuthor?.displayName},timeSpent:e.timeSpent,timeSpentSeconds:e.timeSpentSeconds,started:e.started,created:e.created,updated:e.updated,comment:D(e.comment)})):[];return T({worklogs:i,total:i.length})}catch(e){return T(K(e))}}),C.registerTool("jira_get_user_worklogs",{title:"Get User Worklogs",description:"Get all worklogs for a specific user within a date range. Combines worklog discovery and filtering to provide a complete time tracking report for a person.",inputSchema:r.object({accountId:r.string().optional().describe("User account ID to filter worklogs for. If not provided, returns worklogs for the current user."),since:r.string().describe("Start date for the report. ISO 8601 format (e.g., '2026-01-15') or relative like '30 days ago' will be parsed."),until:r.string().optional().describe("End date for the report. Defaults to now if not provided."),includeIssueDetails:r.boolean().optional().default(!1).describe("Whether to fetch issue details (key, summary) for each worklog")})},async({accountId:e,since:t,until:s,includeIssueDetails:a})=>{try{const r=A(await v());let i,n=e,o="";if(!n){const e=await r.get("/rest/api/3/myself");n=e.data.accountId,o=e.data.displayName||e.data.emailAddress}const c=t.match(/^(\d+)\s*(days?|weeks?|months?)\s*ago$/i);if(c&&c[1]&&c[2]){const e=parseInt(c[1],10),t=c[2].toLowerCase(),s=new Date;t.startsWith("day")?s.setDate(s.getDate()-e):t.startsWith("week")?s.setDate(s.getDate()-7*e):t.startsWith("month")&&s.setMonth(s.getMonth()-e),i=s.getTime()}else i=new Date(t).getTime();let u;s&&(u=new Date(s).getTime());const d=[];let l=`/rest/api/3/worklog/updated?since=${i}`,p=0;const m=10;for(;l&&p<m;){const e=await r.get(l),t=e.data.values||[];for(const e of t)u&&e.updatedTime>u||d.push(e.worklogId);if(e.data.lastPage)break;l=e.data.nextPage,p++}if(0===d.length)return T({user:o||n,accountId:n,period:{since:new Date(i).toISOString(),until:u?new Date(u).toISOString():(new Date).toISOString()},worklogs:[],summary:{totalWorklogs:0,totalTimeSpentSeconds:0,totalTimeSpent:"0h"}});const y=[];for(let e=0;e<d.length;e+=1e3){const t=d.slice(e,e+1e3),s=await r.post("/rest/api/3/worklog/list",{ids:t});Array.isArray(s.data)&&y.push(...s.data)}const h=y.filter(e=>e.author?.accountId===n),f={};if(a&&h.length>0){const e=[...new Set(h.map(e=>e.issueId))];for(let t=0;t<e.length;t+=50){const s=e.slice(t,t+50);try{const e=await r.get("/rest/api/3/search",{params:{jql:`id in (${s.join(",")})`,fields:"summary",maxResults:50}});for(const t of e.data.issues||[])f[t.id]={key:t.key,summary:t.fields?.summary||""}}catch{}}}const w=h.map(e=>{const t={id:e.id,issueId:e.issueId,timeSpent:e.timeSpent,timeSpentSeconds:e.timeSpentSeconds,started:e.started,created:e.created,comment:D(e.comment)},s=f[e.issueId];return a&&s&&(t.issueKey=s.key,t.issueSummary=s.summary),t});w.sort((e,t)=>new Date(e.started).getTime()-new Date(t.started).getTime());const g=h.reduce((e,t)=>e+(t.timeSpentSeconds||0),0),I=Math.floor(g/3600),k=Math.floor(g%3600/60),_=k>0?`${I}h ${k}m`:`${I}h`;let b;if(a){b={};for(const e of w){const t=e.issueId;b[t]||(b[t]={key:e.issueKey||t,summary:e.issueSummary||"",totalSeconds:0,totalTime:""}),b[t].totalSeconds+=e.timeSpentSeconds||0}for(const e of Object.keys(b)){const t=b[e];if(t){const e=t.totalSeconds,s=Math.floor(e/3600),a=Math.floor(e%3600/60);t.totalTime=a>0?`${s}h ${a}m`:`${s}h`}}}return T({user:o||n,accountId:n,period:{since:new Date(i).toISOString(),until:u?new Date(u).toISOString():(new Date).toISOString()},worklogs:w,summary:{totalWorklogs:w.length,totalTimeSpentSeconds:g,totalTimeSpent:_,...b&&{byIssue:Object.values(b)}}})}catch(e){return T(K(e))}}),C.registerTool("jira_get_deleted_worklog_ids",{title:"Get Deleted Worklog IDs",description:"Get IDs of worklogs that were deleted since a specific date/time. Useful for audit and sync purposes.",inputSchema:r.object({since:r.string().describe("Get worklogs deleted since this date. ISO 8601 format or Unix timestamp in milliseconds.")})},async({since:e})=>{try{const t=A(await v());let s;s=/^\d+$/.test(e)?parseInt(e,10):new Date(e).getTime();const a=await t.get("/rest/api/3/worklog/deleted",{params:{since:s}});return T({since:s,sinceDate:new Date(s).toISOString(),until:a.data.until,lastPage:a.data.lastPage,nextPage:a.data.nextPage,deletedWorklogIds:(a.data.values||[]).map(e=>({worklogId:e.worklogId,updatedTime:e.updatedTime})),total:a.data.values?.length||0})}catch(e){return T(K(e))}});const G=new a;await C.connect(G);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-jira-cloud",
3
- "version": "3.0.0",
4
- "description": "A Model Context Protocol (MCP) server for Jira Cloud integration. Enables AI assistants like GitHub Copilot and Claude to search issues, manage tickets, log work, handle sprints, and interact with your Jira instance seamlessly. Features 73 tools for comprehensive Jira management.",
3
+ "version": "3.1.1",
4
+ "description": "A Model Context Protocol (MCP) server for Jira Cloud integration. Enables AI assistants like GitHub Copilot and Claude to search issues, manage tickets, log work, handle sprints, and interact with your Jira instance seamlessly. Features 74 tools for comprehensive Jira management.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",