issues-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/dist/chunk-Y53ZARWV.js +1671 -0
- package/dist/chunk-Y53ZARWV.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +37 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/server.ts","../../core/src/errors.ts","../../jira/src/client.ts","../../jira/src/adf.ts","../../jira/src/mappers.ts","../../jira/src/jql.ts","../../jira/src/issuesTracker.ts","../../jira/src/timeTracker.ts","../../jira/src/session.ts","../../redmine/src/client.ts","../../redmine/src/mappers.ts","../../redmine/src/issuesTracker.ts","../../redmine/src/timeTracker.ts","../../redmine/src/session.ts","../src/result.ts","../src/schemas.ts","../src/tools.ts"],"sourcesContent":["import type { ConnectionConfig } from '@issues-mcp/core';\n\n// Which backend a configured tracker targets. Tool names are prefixed with this.\nexport type TrackerKind = 'jira' | 'redmine';\n\n// A tracker that has a complete env configuration and is ready to authenticate.\nexport type TrackerConfig = {\n kind: TrackerKind;\n connection: ConnectionConfig;\n};\n\n// The result of reading configuration from the environment.\nexport type LoadedConfig = {\n // Trackers whose full env var set was present.\n trackers: TrackerConfig[];\n // Secret values (tokens/keys) discovered, for redaction. Never logged.\n secrets: string[];\n // Non-secret, human-readable notes for stderr (e.g. partial config skipped).\n warnings: string[];\n};\n\n// Declares the env vars each tracker needs. Order matters only for messages.\ntype EnvSpec = {\n kind: TrackerKind;\n // Required vars; a tracker is enabled only when all are present.\n required: string[];\n // Of the required vars, which carry secrets (redacted, never logged).\n secretVars: string[];\n build: (env: NodeJS.ProcessEnv) => ConnectionConfig;\n};\n\nconst ENV_SPECS: EnvSpec[] = [\n {\n kind: 'jira',\n required: ['JIRA_BASE_URL', 'JIRA_API_TOKEN', 'JIRA_ACCOUNT_EMAIL'],\n secretVars: ['JIRA_API_TOKEN'],\n build: (env) => ({\n baseUrl: env.JIRA_BASE_URL ?? '',\n credentials: {\n type: 'apiKey',\n apiKey: env.JIRA_API_TOKEN ?? '',\n accountId: env.JIRA_ACCOUNT_EMAIL ?? '',\n },\n }),\n },\n {\n kind: 'redmine',\n required: ['REDMINE_BASE_URL', 'REDMINE_API_KEY'],\n secretVars: ['REDMINE_API_KEY'],\n build: (env) => ({\n baseUrl: env.REDMINE_BASE_URL ?? '',\n credentials: { type: 'apiKey', apiKey: env.REDMINE_API_KEY ?? '' },\n }),\n },\n];\n\n// Reads tracker configuration from the environment. A tracker is enabled only\n// when ALL of its required vars are set; a partially-configured tracker is\n// skipped with a warning that names the missing vars (never their values). A\n// tracker with none of its vars set is silently ignored.\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): LoadedConfig {\n const trackers: TrackerConfig[] = [];\n const secrets: string[] = [];\n const warnings: string[] = [];\n\n for (const spec of ENV_SPECS) {\n const present = spec.required.filter((name) => hasValue(env[name]));\n if (present.length === 0) continue; // tracker not configured at all\n if (present.length < spec.required.length) {\n const missing = spec.required.filter((name) => !hasValue(env[name]));\n warnings.push(`${spec.kind}: skipped — missing required env var(s): ${missing.join(', ')}`);\n continue;\n }\n\n trackers.push({ kind: spec.kind, connection: spec.build(env) });\n for (const name of spec.secretVars) {\n const value = env[name];\n if (hasValue(value)) secrets.push(value);\n }\n }\n\n return { trackers, secrets, warnings };\n}\n\nfunction hasValue(value: string | undefined): value is string {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\n// Builds a function that masks every known secret substring in a string. Used\n// to scrub any text returned to the model or written to logs, so credentials\n// can never leak through error messages.\nexport function makeRedactor(secrets: string[]): (text: string) => string {\n const real = [...new Set(secrets)].filter((s) => s.length > 0);\n // Mask longer secrets first so a secret that contains another is fully hidden.\n real.sort((a, b) => b.length - a.length);\n return (text: string) => real.reduce((acc, s) => acc.split(s).join('***'), text);\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { Authenticator, Session } from '@issues-mcp/core';\nimport { JiraAuthenticator } from '@issues-mcp/jira';\nimport { RedmineAuthenticator } from '@issues-mcp/redmine';\nimport { loadConfig, makeRedactor, type TrackerKind } from './config.js';\nimport { registerTrackerTools } from './tools.js';\n\n// Factory per tracker kind. Kept lazy so only configured trackers are built.\nconst AUTHENTICATORS: Record<TrackerKind, () => Authenticator> = {\n jira: () => new JiraAuthenticator(),\n redmine: () => new RedmineAuthenticator(),\n};\n\n// A server ready to connect to a transport, plus the live sessions it owns and\n// any non-secret warnings worth surfacing to the operator.\nexport type BuiltServer = {\n server: McpServer;\n sessions: Session[];\n warnings: string[];\n};\n\n// Builds the MCP server from the environment: reads per-tracker config,\n// authenticates each configured tracker (validating credentials up front), and\n// registers its tools. A tracker that fails to authenticate is skipped with a\n// redacted warning so the remaining trackers still serve. Credentials are read\n// only from the environment and never appear in tool schemas or outputs.\nexport async function createServer(env: NodeJS.ProcessEnv = process.env): Promise<BuiltServer> {\n const { trackers, secrets, warnings: configWarnings } = loadConfig(env);\n const redact = makeRedactor(secrets);\n\n const server = new McpServer({ name: 'issues-mcp', version: '1.0.0' });\n const sessions: Session[] = [];\n const warnings = [...configWarnings];\n\n for (const { kind, connection } of trackers) {\n try {\n const session = await AUTHENTICATORS[kind]().authenticate(connection);\n sessions.push(session);\n registerTrackerTools(server, kind, session, redact);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n warnings.push(`${kind}: authentication failed — ${redact(message)}`);\n }\n }\n\n return { server, sessions, warnings };\n}\n","// A typed error hierarchy shared by all tracker adapters. The MCP layer maps\n// these onto tool responses, so they are runtime classes (not types) and can be\n// distinguished with `instanceof`.\n\n// Base class for every error a tracker adapter raises.\nexport class IssueTrackerError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n }\n}\n\n// A requested resource (issue, time entry, transition) does not exist.\nexport class NotFoundError extends IssueTrackerError {}\n\n// Authentication or authorization failed (bad credentials, insufficient\n// permission).\nexport class AuthError extends IssueTrackerError {}\n\n// The request was rejected as invalid. `fields` carries per-field messages\n// where the tracker reports them.\nexport class ValidationError extends IssueTrackerError {\n readonly fields?: Record<string, string>;\n\n constructor(message: string, options?: ErrorOptions & { fields?: Record<string, string> }) {\n super(message, options);\n this.fields = options?.fields;\n }\n}\n\n// The tracker is throttling requests. `retryAfterSeconds` is set when the\n// tracker advertises a retry delay.\nexport class RateLimitError extends IssueTrackerError {\n readonly retryAfterSeconds?: number;\n\n constructor(message: string, options?: ErrorOptions & { retryAfterSeconds?: number }) {\n super(message, options);\n this.retryAfterSeconds = options?.retryAfterSeconds;\n }\n}\n\n// A capability the tracker does not support was invoked. Should be avoidable by\n// consulting the tracker's capabilities first; raised as a backstop.\nexport class NotSupportedError extends IssueTrackerError {}\n","import {\n AuthError,\n IssueTrackerError,\n NotFoundError,\n RateLimitError,\n ValidationError,\n} from '@issues-mcp/core';\nimport type { ConnectionConfig } from '@issues-mcp/core';\n\n// Options for a single request. `query` entries with `undefined` values are\n// dropped before building the URL.\nexport interface RequestOptions {\n method?: string;\n body?: unknown;\n query?: Record<string, string | number | undefined>;\n signal?: AbortSignal;\n}\n\n// Shape of a Jira error response body. Both fields are optional; Jira populates\n// `errorMessages` for global errors and `errors` for per-field validation.\ninterface JiraErrorBody {\n errorMessages?: string[];\n errors?: Record<string, string>;\n}\n\n// A thin HTTP client around the Jira Cloud REST v3 API. Holds the base URL and\n// pre-computed Basic auth header, performs fetch, maps non-2xx responses onto\n// the typed core error hierarchy, and parses JSON bodies.\nexport class JiraClient {\n private readonly baseUrl: string;\n private readonly authHeader: string;\n\n constructor(config: ConnectionConfig) {\n const { credentials } = config;\n // Jira Cloud requires HTTP Basic auth of the form base64(email:apiToken).\n // The account email is carried in `credentials.accountId`; without it we\n // cannot authenticate.\n if (credentials.accountId === undefined || credentials.accountId === '') {\n throw new AuthError(\n 'Jira requires credentials.accountId (the account email) for Basic auth.'\n );\n }\n const token = `${credentials.accountId}:${credentials.apiKey}`;\n this.authHeader = `Basic ${Buffer.from(token).toString('base64')}`;\n this.baseUrl = config.baseUrl.replace(/\\/+$/, '');\n }\n\n // Perform a request against `/rest/api/3{path}` and return the parsed JSON\n // body, or undefined for empty (204) responses.\n async request<T>(path: string, options: RequestOptions = {}): Promise<T> {\n const url = new URL(`${this.baseUrl}/rest/api/3${path}`);\n if (options.query !== undefined) {\n for (const [key, value] of Object.entries(options.query)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n const headers: Record<string, string> = {\n Authorization: this.authHeader,\n Accept: 'application/json',\n };\n const init: RequestInit = {\n method: options.method ?? 'GET',\n headers,\n signal: options.signal,\n };\n if (options.body !== undefined) {\n headers['Content-Type'] = 'application/json';\n init.body = JSON.stringify(options.body);\n }\n\n let response: Response;\n try {\n response = await fetch(url, init);\n } catch (cause) {\n throw new IssueTrackerError(`Network error calling Jira: ${String(cause)}`, { cause });\n }\n\n if (!response.ok) {\n await this.throwForStatus(response);\n }\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n const text = await response.text();\n if (text === '') {\n return undefined as T;\n }\n return JSON.parse(text) as T;\n }\n\n // Map a non-2xx response onto the typed error hierarchy. Always throws.\n private async throwForStatus(response: Response): Promise<never> {\n const status = response.status;\n const raw = await response.text().catch(() => '');\n let parsed: JiraErrorBody | undefined;\n try {\n parsed = raw === '' ? undefined : (JSON.parse(raw) as JiraErrorBody);\n } catch {\n parsed = undefined;\n }\n const messages = parsed?.errorMessages ?? [];\n const fields = parsed?.errors;\n const summary =\n messages.length > 0\n ? messages.join('; ')\n : fields !== undefined\n ? Object.entries(fields)\n .map(([k, v]) => `${k}: ${v}`)\n .join('; ')\n : raw.slice(0, 500);\n\n switch (status) {\n case 401:\n throw new AuthError(`Jira authentication failed (401): ${summary}`);\n case 403:\n throw new AuthError(`Jira authorization failed (403): ${summary}`);\n case 404:\n throw new NotFoundError(`Jira resource not found (404): ${summary}`);\n case 400:\n case 422:\n throw new ValidationError(`Jira rejected the request (${status}): ${summary}`, {\n fields,\n });\n case 429: {\n const retryHeader = response.headers.get('Retry-After');\n const retryAfterSeconds =\n retryHeader !== null && retryHeader !== '' ? Number(retryHeader) : undefined;\n throw new RateLimitError(`Jira is throttling requests (429): ${summary}`, {\n retryAfterSeconds: Number.isFinite(retryAfterSeconds) ? retryAfterSeconds : undefined,\n });\n }\n default:\n throw new IssueTrackerError(`Jira request failed (${status}): ${summary}`);\n }\n }\n}\n","// Helpers for Atlassian Document Format (ADF), the rich-text shape Jira Cloud\n// uses for issue descriptions and comment bodies. The core contracts model\n// these as plain strings, so we flatten ADF on read and wrap text on write.\n\n// A minimal structural view of an ADF node. ADF is a tree of typed nodes; for\n// flattening we only care about `text` leaves and `content` children, plus the\n// `hardBreak` node which represents a line break.\nexport interface AdfNode {\n type?: string;\n text?: string;\n content?: AdfNode[];\n}\n\n// Recursively flatten an ADF document (or any node) to plain text. Text nodes\n// contribute their text; block-level nodes (paragraph, heading, list item, …)\n// are separated by newlines so structure survives reasonably. Returns null for\n// nullish input so a missing description stays null.\nexport function adfToText(node: AdfNode | null | undefined): string | null {\n if (node === null || node === undefined) {\n return null;\n }\n return flatten(node)\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n\nconst BLOCK_TYPES = new Set([\n 'paragraph',\n 'heading',\n 'blockquote',\n 'listItem',\n 'bulletList',\n 'orderedList',\n 'codeBlock',\n 'rule',\n]);\n\nfunction flatten(node: AdfNode): string {\n if (node.type === 'text') {\n return node.text ?? '';\n }\n if (node.type === 'hardBreak') {\n return '\\n';\n }\n\n const children = node.content ?? [];\n const inner = children.map(flatten).join('');\n\n if (node.type !== undefined && BLOCK_TYPES.has(node.type)) {\n return `${inner}\\n`;\n }\n return inner;\n}\n\n// Wrap plain text into a minimal valid ADF document for write operations.\nexport function textToAdf(text: string): AdfNode {\n return {\n type: 'doc',\n version: 1,\n content: [\n {\n type: 'paragraph',\n content: [{ type: 'text', text }],\n },\n ],\n } as AdfNode & { version: number };\n}\n","import type {\n AttributeValue,\n Issue,\n IssueItem,\n Status,\n StatusCategory,\n TimeEntry,\n Transition,\n User,\n} from '@issues-mcp/core';\nimport { adfToText, type AdfNode } from './adf.js';\n\n// --- Narrow views of the Jira JSON shapes we consume ---------------------\n\nexport interface JiraUser {\n accountId: string;\n displayName?: string;\n emailAddress?: string;\n}\n\nexport interface JiraStatusCategory {\n key?: string;\n}\n\nexport interface JiraStatus {\n id: string;\n name: string;\n statusCategory?: JiraStatusCategory;\n}\n\nexport interface JiraComment {\n id: string;\n author?: JiraUser;\n body?: AdfNode | null;\n created: string;\n updated?: string;\n}\n\nexport interface JiraChangelogItem {\n field?: string;\n fromString?: string | null;\n toString?: string | null;\n}\n\nexport interface JiraChangelogHistory {\n id: string;\n author?: JiraUser;\n created: string;\n items?: JiraChangelogItem[];\n}\n\nexport interface JiraIssueFields {\n summary?: string;\n description?: AdfNode | null;\n status?: JiraStatus;\n reporter?: JiraUser | null;\n assignee?: JiraUser | null;\n project?: { id: string; key?: string };\n created: string;\n updated: string;\n comment?: { comments?: JiraComment[] };\n}\n\nexport interface JiraIssue {\n id: string;\n key: string;\n fields: JiraIssueFields;\n changelog?: { histories?: JiraChangelogHistory[] };\n}\n\nexport interface JiraWorklog {\n id: string;\n author?: JiraUser;\n started: string;\n timeSpentSeconds: number;\n comment?: AdfNode | null;\n created: string;\n updated: string;\n}\n\n// --- Mapping functions ---------------------------------------------------\n\nexport function mapUser(raw: JiraUser): User {\n const user: User = {\n id: raw.accountId,\n name: raw.displayName ?? raw.accountId,\n };\n if (raw.emailAddress !== undefined) {\n user.email = raw.emailAddress;\n }\n return user;\n}\n\nexport function mapStatusCategory(key: string | undefined): StatusCategory {\n switch (key) {\n case 'new':\n return 'open';\n case 'indeterminate':\n return 'in_progress';\n case 'done':\n return 'done';\n default:\n return 'unknown';\n }\n}\n\nexport function mapStatus(raw: JiraStatus): Status {\n return {\n id: raw.id,\n name: raw.name,\n category: mapStatusCategory(raw.statusCategory?.key),\n };\n}\n\nexport function mapComment(raw: JiraComment): IssueItem {\n const item: IssueItem = {\n kind: 'comment',\n id: raw.id,\n author: raw.author !== undefined ? mapUser(raw.author) : { id: 'unknown', name: 'Unknown' },\n body: adfToText(raw.body) ?? '',\n createdAt: raw.created,\n };\n if (raw.updated !== undefined) {\n item.updatedAt = raw.updated;\n }\n return item;\n}\n\nexport function mapChangelogHistory(raw: JiraChangelogHistory): IssueItem {\n return {\n kind: 'change',\n id: raw.id,\n author: raw.author !== undefined ? mapUser(raw.author) : { id: 'unknown', name: 'Unknown' },\n createdAt: raw.created,\n changes: (raw.items ?? []).map((item) => ({\n field: item.field ?? '',\n from: item.fromString ?? null,\n to: item.toString ?? null,\n })),\n };\n}\n\nexport function mapIssue(raw: JiraIssue): Issue {\n const fields = raw.fields;\n const items: IssueItem[] = [];\n\n for (const history of raw.changelog?.histories ?? []) {\n items.push(mapChangelogHistory(history));\n }\n for (const comment of fields.comment?.comments ?? []) {\n items.push(mapComment(comment));\n }\n\n const status: Status =\n fields.status !== undefined\n ? mapStatus(fields.status)\n : { id: 'unknown', name: 'Unknown', category: 'unknown' };\n\n const issue: Issue = {\n id: raw.id,\n key: raw.key,\n title: fields.summary ?? '',\n description: adfToText(fields.description),\n status,\n author:\n fields.reporter !== undefined && fields.reporter !== null ? mapUser(fields.reporter) : null,\n assignee:\n fields.assignee !== undefined && fields.assignee !== null ? mapUser(fields.assignee) : null,\n attributes: {},\n items,\n createdAt: fields.created,\n updatedAt: fields.updated,\n };\n if (fields.project !== undefined) {\n issue.projectId = fields.project.id;\n }\n return issue;\n}\n\n// Map a Jira transition to the core Transition shape.\nexport interface JiraTransition {\n id: string;\n name: string;\n to?: JiraStatus;\n}\n\nexport function mapTransition(raw: JiraTransition): Transition {\n return {\n id: raw.id,\n name: raw.name,\n to:\n raw.to !== undefined\n ? mapStatus(raw.to)\n : { id: 'unknown', name: 'Unknown', category: 'unknown' },\n };\n}\n\n// Map a Jira worklog to a core TimeEntry. The issue id is supplied from the\n// request context because worklog payloads do not echo it back reliably.\nexport function mapWorklog(raw: JiraWorklog, issueId: string): TimeEntry {\n const entry: TimeEntry = {\n id: `${issueId}/${raw.id}`,\n issueId,\n user: raw.author !== undefined ? mapUser(raw.author) : { id: 'unknown', name: 'Unknown' },\n date: raw.started,\n durationMinutes: Math.round(raw.timeSpentSeconds / 60),\n createdAt: raw.created,\n updatedAt: raw.updated,\n };\n const description = adfToText(raw.comment);\n if (description !== null) {\n entry.description = description;\n }\n return entry;\n}\n\n// Convert a core AttributeValue into the Jira field value shape expected by the\n// create/update field payload.\nexport function attributeToJiraValue(attr: AttributeValue): unknown {\n switch (attr.type) {\n case 'string':\n case 'text':\n case 'number':\n case 'date':\n case 'boolean':\n return attr.value;\n case 'enum':\n return { id: attr.value.id };\n case 'multi_enum':\n return attr.value.map((option) => ({ id: option.id }));\n case 'user':\n return { accountId: attr.value.id };\n }\n}\n","import type { IssueFilter, StatusCategory } from '@issues-mcp/core';\n\nfunction jqlString(value: string): string {\n // Escape backslashes and double quotes for safe embedding in a JQL string.\n return `\"${value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\"`;\n}\n\nfunction statusCategoryToJql(category: StatusCategory): string | undefined {\n switch (category) {\n case 'open':\n return 'To Do';\n case 'in_progress':\n return 'In Progress';\n case 'done':\n return 'Done';\n default:\n return undefined;\n }\n}\n\n// Build a JQL query string from a normalized IssueFilter. Clauses are ANDed; an\n// optional ORDER BY is appended from `filter.sort`.\nexport function buildJql(filter: IssueFilter): string {\n const clauses: string[] = [];\n\n if (filter.text !== undefined && filter.text !== '') {\n clauses.push(`text ~ ${jqlString(filter.text)}`);\n }\n if (filter.projectId !== undefined) {\n clauses.push(`project = ${jqlString(filter.projectId)}`);\n }\n if (filter.assigneeId !== undefined) {\n clauses.push(`assignee = ${jqlString(filter.assigneeId)}`);\n }\n if (filter.authorId !== undefined) {\n clauses.push(`reporter = ${jqlString(filter.authorId)}`);\n }\n if (filter.statusCategory !== undefined) {\n const mapped = statusCategoryToJql(filter.statusCategory);\n if (mapped !== undefined) {\n clauses.push(`statusCategory = ${jqlString(mapped)}`);\n }\n }\n if (filter.createdAfter !== undefined) {\n clauses.push(`created >= ${jqlString(filter.createdAfter)}`);\n }\n if (filter.createdBefore !== undefined) {\n clauses.push(`created <= ${jqlString(filter.createdBefore)}`);\n }\n if (filter.updatedAfter !== undefined) {\n clauses.push(`updated >= ${jqlString(filter.updatedAfter)}`);\n }\n if (filter.updatedBefore !== undefined) {\n clauses.push(`updated <= ${jqlString(filter.updatedBefore)}`);\n }\n\n let jql = clauses.join(' AND ');\n if (filter.sort !== undefined) {\n const dir = filter.sort.dir === 'desc' ? 'DESC' : 'ASC';\n jql = `${jql}${jql === '' ? '' : ' '}ORDER BY ${filter.sort.field} ${dir}`;\n }\n return jql;\n}\n","import { ValidationError } from '@issues-mcp/core';\nimport type {\n AttributeValue,\n FieldOption,\n FieldSchema,\n Issue,\n IssueFilter,\n IssueInput,\n IssueItem,\n IssuesTracker,\n IssuesTrackerCapabilities,\n Page,\n Transition,\n} from '@issues-mcp/core';\nimport type { JiraClient } from './client.js';\nimport { textToAdf } from './adf.js';\nimport {\n attributeToJiraValue,\n mapComment,\n mapIssue,\n mapTransition,\n type JiraComment,\n type JiraIssue,\n type JiraTransition,\n} from './mappers.js';\nimport { buildJql } from './jql.js';\n\n// Fields requested from search/get so mappers have what they need.\nconst ISSUE_FIELDS = [\n 'summary',\n 'description',\n 'status',\n 'reporter',\n 'assignee',\n 'project',\n 'created',\n 'updated',\n];\n\ninterface SearchResponse {\n issues?: JiraIssue[];\n nextPageToken?: string;\n isLast?: boolean;\n}\n\ninterface TransitionsResponse {\n transitions?: JiraTransition[];\n}\n\ninterface ProjectSearchResponse {\n values?: { id: string; key?: string; name?: string }[];\n}\n\ninterface IssueTypeResponse {\n id: string;\n name: string;\n}\n\n// Build the Jira `fields` payload from an IssueInput (or partial). Shared by\n// create and update. `issuetype` is read from attributes (Jira requires it on\n// create) and other attribute entries become custom fields keyed by their id.\nfunction buildFieldsPayload(input: Partial<IssueInput>): Record<string, unknown> {\n const fields: Record<string, unknown> = {};\n\n if (input.title !== undefined) {\n fields.summary = input.title;\n }\n if (input.description !== undefined) {\n fields.description = input.description === null ? null : textToAdf(input.description);\n }\n if (input.projectId !== undefined) {\n fields.project = { key: input.projectId };\n }\n if (input.assigneeId !== undefined) {\n fields.assignee = input.assigneeId === null ? null : { accountId: input.assigneeId };\n }\n\n if (input.attributes !== undefined) {\n for (const [key, attr] of Object.entries(input.attributes)) {\n const value: AttributeValue = attr;\n if (key === 'issuetype') {\n // issuetype is a first-class Jira field, expected as an enum option.\n fields.issuetype =\n value.type === 'enum' ? { id: value.value.id } : attributeToJiraValue(value);\n } else {\n fields[key] = attributeToJiraValue(value);\n }\n }\n }\n\n return fields;\n}\n\n// IssuesTracker backed by the Jira Cloud REST v3 issue endpoints.\nexport class JiraIssuesTracker implements IssuesTracker {\n private readonly client: JiraClient;\n\n constructor(client: JiraClient) {\n this.client = client;\n }\n\n async capabilities(): Promise<IssuesTrackerCapabilities> {\n const fields: FieldSchema[] = [\n {\n id: 'project',\n name: 'Project',\n type: 'enum',\n required: true,\n readOnly: false,\n allowedValues: await this.fetchProjects(),\n },\n {\n id: 'issuetype',\n name: 'Issue Type',\n type: 'enum',\n required: true,\n readOnly: false,\n allowedValues: await this.fetchIssueTypes(),\n },\n { id: 'summary', name: 'Summary', type: 'string', required: true, readOnly: false },\n { id: 'description', name: 'Description', type: 'text', required: false, readOnly: false },\n ];\n\n return {\n canCreate: true,\n canUpdate: true,\n canDelete: true,\n canComment: true,\n canTransition: true,\n fields,\n };\n }\n\n private async fetchProjects(): Promise<FieldOption[]> {\n const res = await this.client.request<ProjectSearchResponse>('/project/search', {\n query: { maxResults: 100 },\n });\n return (res.values ?? []).map((project) => ({\n id: project.id,\n name: project.name ?? project.key ?? project.id,\n }));\n }\n\n private async fetchIssueTypes(): Promise<FieldOption[]> {\n const res = await this.client.request<IssueTypeResponse[]>('/issuetype');\n return res.map((type) => ({ id: type.id, name: type.name }));\n }\n\n async search(filter: IssueFilter): Promise<Page<Issue>> {\n const body: Record<string, unknown> = {\n jql: buildJql(filter),\n fields: ISSUE_FIELDS,\n };\n if (filter.limit !== undefined) {\n body.maxResults = filter.limit;\n }\n if (filter.cursor !== undefined) {\n body.nextPageToken = filter.cursor;\n }\n\n const res = await this.client.request<SearchResponse>('/search/jql', {\n method: 'POST',\n body,\n });\n\n const page: Page<Issue> = {\n items: (res.issues ?? []).map(mapIssue),\n };\n // The /search/jql endpoint paginates by token and does not return a total.\n if (res.nextPageToken !== undefined && res.isLast !== true) {\n page.nextCursor = res.nextPageToken;\n }\n return page;\n }\n\n async get(id: string): Promise<Issue> {\n const raw = await this.client.request<JiraIssue>(`/issue/${encodeURIComponent(id)}`, {\n query: { expand: 'changelog' },\n });\n const issue = mapIssue(raw);\n\n // Comments are paginated separately; fetch and merge them in.\n const comments = await this.client.request<{ comments?: JiraComment[] }>(\n `/issue/${encodeURIComponent(id)}/comment`\n );\n const commentItems: IssueItem[] = (comments.comments ?? []).map(mapComment);\n issue.items = [...issue.items, ...commentItems];\n return issue;\n }\n\n async create(input: IssueInput): Promise<Issue> {\n const fields = buildFieldsPayload(input);\n const created = await this.client.request<{ id?: string; key?: string }>('/issue', {\n method: 'POST',\n body: { fields },\n });\n const idOrKey = created.key ?? created.id;\n if (idOrKey === undefined) {\n throw new ValidationError('Jira did not return an id or key for the created issue.');\n }\n return this.get(idOrKey);\n }\n\n async update(id: string, input: Partial<IssueInput>): Promise<Issue> {\n const fields = buildFieldsPayload(input);\n await this.client.request<void>(`/issue/${encodeURIComponent(id)}`, {\n method: 'PUT',\n body: { fields },\n });\n return this.get(id);\n }\n\n async delete(id: string): Promise<void> {\n await this.client.request<void>(`/issue/${encodeURIComponent(id)}`, {\n method: 'DELETE',\n });\n }\n\n async addComment(issueId: string, body: string): Promise<IssueItem> {\n const created = await this.client.request<JiraComment>(\n `/issue/${encodeURIComponent(issueId)}/comment`,\n {\n method: 'POST',\n body: { body: textToAdf(body) },\n }\n );\n return mapComment(created);\n }\n\n async getTransitions(issueId: string): Promise<Transition[]> {\n const res = await this.client.request<TransitionsResponse>(\n `/issue/${encodeURIComponent(issueId)}/transitions`\n );\n return (res.transitions ?? []).map(mapTransition);\n }\n\n async transition(issueId: string, transitionId: string): Promise<Issue> {\n await this.client.request<void>(`/issue/${encodeURIComponent(issueId)}/transitions`, {\n method: 'POST',\n body: { transition: { id: transitionId } },\n });\n return this.get(issueId);\n }\n}\n","import { NotSupportedError, ValidationError } from '@issues-mcp/core';\nimport type {\n Page,\n TimeEntry,\n TimeEntryFilter,\n TimeEntryInput,\n TimeTracker,\n TimeTrackerCapabilities,\n} from '@issues-mcp/core';\nimport type { JiraClient } from './client.js';\nimport { textToAdf } from './adf.js';\nimport { mapWorklog, type JiraWorklog } from './mappers.js';\n\ninterface WorklogListResponse {\n worklogs?: JiraWorklog[];\n}\n\n// Parse a composite worklog id of the form \"<issueId>/<worklogId>\". Jira\n// worklog ids are only unique within an issue, so the TimeEntry id encodes\n// both; callers must pass that composite id back to get/update/delete.\nfunction parseWorklogId(id: string): { issueId: string; worklogId: string } {\n const slash = id.indexOf('/');\n if (slash === -1) {\n throw new ValidationError(\n `Invalid worklog id \"${id}\". Expected the format \"<issueId>/<worklogId>\".`\n );\n }\n return { issueId: id.slice(0, slash), worklogId: id.slice(slash + 1) };\n}\n\n// Convert an ISO date (e.g. \"2026-06-07\" or full ISO) into Jira's required\n// \"started\" format: yyyy-MM-ddTHH:mm:ss.SSS+ZZZZ (milliseconds + numeric offset\n// without a colon). We normalize through Date and emit a +0000 (UTC) offset.\nfunction toJiraStarted(isoDate: string): string {\n const date = new Date(isoDate);\n if (Number.isNaN(date.getTime())) {\n throw new ValidationError(`Invalid date \"${isoDate}\" for worklog.`);\n }\n // Render UTC components so the offset is always +0000.\n const iso = date.toISOString(); // e.g. 2026-06-07T00:00:00.000Z\n return iso.replace('Z', '+0000');\n}\n\n// TimeTracker backed by Jira core worklogs. Worklogs are scoped per issue, so\n// cross-issue search is unsupported and ids are composite (see parseWorklogId).\nexport class JiraTimeTracker implements TimeTracker {\n private readonly client: JiraClient;\n\n constructor(client: JiraClient) {\n this.client = client;\n }\n\n capabilities(): Promise<TimeTrackerCapabilities> {\n return Promise.resolve({\n canCreate: true,\n canUpdate: true,\n canDelete: true,\n requiresIssue: true,\n activities: [],\n });\n }\n\n async search(filter: TimeEntryFilter): Promise<Page<TimeEntry>> {\n if (filter.issueId === undefined) {\n throw new NotSupportedError(\n 'Jira core worklogs are scoped per issue; set filter.issueId to search worklogs.'\n );\n }\n const res = await this.client.request<WorklogListResponse>(\n `/issue/${encodeURIComponent(filter.issueId)}/worklog`\n );\n let entries = (res.worklogs ?? []).map((worklog) =>\n mapWorklog(worklog, filter.issueId as string)\n );\n\n if (filter.userId !== undefined) {\n entries = entries.filter((entry) => entry.user.id === filter.userId);\n }\n if (filter.from !== undefined) {\n const from = new Date(filter.from).getTime();\n entries = entries.filter((entry) => new Date(entry.date).getTime() >= from);\n }\n if (filter.to !== undefined) {\n const to = new Date(filter.to).getTime();\n entries = entries.filter((entry) => new Date(entry.date).getTime() <= to);\n }\n\n return { items: entries };\n }\n\n async get(id: string): Promise<TimeEntry> {\n const { issueId, worklogId } = parseWorklogId(id);\n const raw = await this.client.request<JiraWorklog>(\n `/issue/${encodeURIComponent(issueId)}/worklog/${encodeURIComponent(worklogId)}`\n );\n return mapWorklog(raw, issueId);\n }\n\n async create(input: TimeEntryInput): Promise<TimeEntry> {\n const body: Record<string, unknown> = {\n started: toJiraStarted(input.date),\n timeSpentSeconds: input.durationMinutes * 60,\n };\n if (input.description !== undefined) {\n body.comment = textToAdf(input.description);\n }\n const raw = await this.client.request<JiraWorklog>(\n `/issue/${encodeURIComponent(input.issueId)}/worklog`,\n { method: 'POST', body }\n );\n return mapWorklog(raw, input.issueId);\n }\n\n async update(id: string, input: Partial<TimeEntryInput>): Promise<TimeEntry> {\n const { issueId, worklogId } = parseWorklogId(id);\n const body: Record<string, unknown> = {};\n if (input.date !== undefined) {\n body.started = toJiraStarted(input.date);\n }\n if (input.durationMinutes !== undefined) {\n body.timeSpentSeconds = input.durationMinutes * 60;\n }\n if (input.description !== undefined) {\n body.comment = textToAdf(input.description);\n }\n const raw = await this.client.request<JiraWorklog>(\n `/issue/${encodeURIComponent(issueId)}/worklog/${encodeURIComponent(worklogId)}`,\n { method: 'PUT', body }\n );\n return mapWorklog(raw, issueId);\n }\n\n async delete(id: string): Promise<void> {\n const { issueId, worklogId } = parseWorklogId(id);\n await this.client.request<void>(\n `/issue/${encodeURIComponent(issueId)}/worklog/${encodeURIComponent(worklogId)}`,\n { method: 'DELETE' }\n );\n }\n}\n","import { AuthError } from '@issues-mcp/core';\nimport type { Authenticator, ConnectionConfig, Session, Tracker, User } from '@issues-mcp/core';\nimport { JiraClient } from './client.js';\nimport { JiraIssuesTracker } from './issuesTracker.js';\nimport { JiraTimeTracker } from './timeTracker.js';\nimport { mapUser, type JiraUser } from './mappers.js';\n\n// An authenticated Jira session. Holds the client and the identity resolved at\n// authentication time via GET /myself.\nclass JiraSession implements Session {\n readonly tracker: Tracker;\n private readonly user: User;\n\n constructor(tracker: Tracker, user: User) {\n this.tracker = tracker;\n this.user = user;\n }\n\n currentUser(): Promise<User> {\n return Promise.resolve(this.user);\n }\n\n close(): Promise<void> {\n // No persistent resources are held (fetch manages its own sockets).\n return Promise.resolve();\n }\n}\n\n// The Jira entry point: turns a ConnectionConfig into an authenticated Session,\n// validating the credentials with a GET /myself call.\nexport class JiraAuthenticator implements Authenticator {\n async authenticate(config: ConnectionConfig): Promise<Session> {\n // Constructing the client validates that accountId is present (throws\n // AuthError otherwise).\n const client = new JiraClient(config);\n\n let me: JiraUser;\n try {\n me = await client.request<JiraUser>('/myself');\n } catch (cause) {\n if (cause instanceof AuthError) {\n throw cause;\n }\n throw new AuthError(`Failed to validate Jira credentials: ${String(cause)}`, { cause });\n }\n\n const tracker: Tracker = {\n issues: new JiraIssuesTracker(client),\n time: new JiraTimeTracker(client),\n };\n return new JiraSession(tracker, mapUser(me));\n }\n}\n\n// Convenience factory mirroring the package's documented entry point.\nexport function createJiraAuthenticator(): JiraAuthenticator {\n return new JiraAuthenticator();\n}\n","import {\n AuthError,\n IssueTrackerError,\n NotFoundError,\n RateLimitError,\n ValidationError,\n} from '@issues-mcp/core';\nimport type { ConnectionConfig } from '@issues-mcp/core';\n\n// Options for a single request. `query` entries with `undefined` values are\n// dropped before building the URL. `query` values may be arrays, which are\n// serialised as repeated params (e.g. `f[]=subject&f[]=status`).\nexport interface RequestOptions {\n method?: string;\n body?: unknown;\n query?: Record<string, string | number | string[] | undefined>;\n signal?: AbortSignal;\n}\n\n// Shape of a Redmine error response body. Redmine returns `{ errors: [...] }`\n// for 422 validation failures.\ninterface RedmineErrorBody {\n errors?: string[];\n}\n\n// A thin HTTP client around the Redmine REST API. Holds the base URL and the\n// API key, performs fetch, maps non-2xx responses onto the typed core error\n// hierarchy, and parses JSON bodies (treating 204 as an empty success).\nexport class RedmineClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n\n constructor(config: ConnectionConfig) {\n // Redmine uses a bare API key in the `X-Redmine-API-Key` header. The\n // `accountId` field is irrelevant to Redmine and intentionally ignored.\n this.apiKey = config.credentials.apiKey;\n this.baseUrl = config.baseUrl.replace(/\\/+$/, '');\n }\n\n // Perform a request against `{baseUrl}{path}` and return the parsed JSON body,\n // or undefined for empty (204) responses.\n async request<T>(path: string, options: RequestOptions = {}): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n if (options.query !== undefined) {\n for (const [key, value] of Object.entries(options.query)) {\n if (value === undefined) {\n continue;\n }\n if (Array.isArray(value)) {\n for (const entry of value) {\n url.searchParams.append(key, entry);\n }\n } else {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n const headers: Record<string, string> = {\n 'X-Redmine-API-Key': this.apiKey,\n Accept: 'application/json',\n };\n const init: RequestInit = {\n method: options.method ?? 'GET',\n headers,\n signal: options.signal,\n };\n if (options.body !== undefined) {\n headers['Content-Type'] = 'application/json';\n init.body = JSON.stringify(options.body);\n }\n\n let response: Response;\n try {\n response = await fetch(url, init);\n } catch (cause) {\n throw new IssueTrackerError(`Network error calling Redmine: ${String(cause)}`, { cause });\n }\n\n if (!response.ok) {\n await this.throwForStatus(response);\n }\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n const text = await response.text();\n if (text === '') {\n return undefined as T;\n }\n return JSON.parse(text) as T;\n }\n\n // Map a non-2xx response onto the typed error hierarchy. Always throws.\n private async throwForStatus(response: Response): Promise<never> {\n const status = response.status;\n const raw = await response.text().catch(() => '');\n let parsed: RedmineErrorBody | undefined;\n try {\n parsed = raw === '' ? undefined : (JSON.parse(raw) as RedmineErrorBody);\n } catch {\n parsed = undefined;\n }\n const messages = parsed?.errors ?? [];\n const summary = messages.length > 0 ? messages.join('; ') : raw.slice(0, 500);\n\n switch (status) {\n case 401:\n throw new AuthError(`Redmine authentication failed (401): ${summary}`);\n case 403:\n throw new AuthError(`Redmine authorization failed (403): ${summary}`);\n case 404:\n throw new NotFoundError(`Redmine resource not found (404): ${summary}`);\n case 422:\n throw new ValidationError(`Redmine rejected the request (422): ${summary}`);\n case 429: {\n const retryHeader = response.headers.get('Retry-After');\n const retryAfterSeconds =\n retryHeader !== null && retryHeader !== '' ? Number(retryHeader) : undefined;\n throw new RateLimitError(`Redmine is throttling requests (429): ${summary}`, {\n retryAfterSeconds:\n retryAfterSeconds !== undefined && Number.isFinite(retryAfterSeconds)\n ? retryAfterSeconds\n : undefined,\n });\n }\n default:\n throw new IssueTrackerError(`Redmine request failed (${status}): ${summary}`);\n }\n }\n}\n","import type {\n AttributeValue,\n Issue,\n IssueItem,\n Status,\n StatusCategory,\n TimeEntry,\n User,\n} from '@issues-mcp/core';\nimport type {\n RedmineCustomField,\n RedmineIssue,\n RedmineIssueStatus,\n RedmineJournal,\n RedmineNamedRef,\n RedmineTimeEntry,\n RedmineUser,\n} from './types.js';\n\n// Map a Redmine `/users/current.json` user (firstname/lastname/mail) to a\n// normalized User.\nexport function mapCurrentUser(user: RedmineUser): User {\n const name = `${user.firstname ?? ''} ${user.lastname ?? ''}`.trim();\n const mapped: User = {\n id: String(user.id),\n name: name !== '' ? name : (user.name ?? String(user.id)),\n };\n if (user.mail !== undefined && user.mail !== '') {\n mapped.email = user.mail;\n }\n return mapped;\n}\n\n// Map a Redmine `{ id, name }` reference (author, assignee, journal user) to a\n// normalized User.\nexport function mapNamedUser(ref: RedmineNamedRef): User {\n return { id: String(ref.id), name: ref.name };\n}\n\n// Derive a normalized StatusCategory from a Redmine issue status definition.\nexport function categorizeStatus(status: RedmineIssueStatus): StatusCategory {\n if (status.is_closed === true) {\n return 'done';\n }\n if (/progress|doing|in work/i.test(status.name)) {\n return 'in_progress';\n }\n return 'open';\n}\n\n// Build a Status from an issue's `{ id, name }` status reference and the cached\n// status-category lookup. Falls back to `open` when the status is unknown to\n// the lookup, or `unknown` if the reference is missing entirely.\nexport function mapStatus(\n ref: RedmineNamedRef | undefined,\n categories: Map<number, StatusCategory>\n): Status {\n if (ref === undefined) {\n return { id: '', name: '', category: 'unknown' };\n }\n return {\n id: String(ref.id),\n name: ref.name,\n category: categories.get(ref.id) ?? 'open',\n };\n}\n\n// Map a Redmine custom field to a normalized AttributeValue. Redmine reports\n// neither a rich type nor option ids on the issue payload, so we infer a\n// shape: multi-valued fields become `multi_enum`, others `string`. Option ids\n// are synthesised from the value text.\nfunction mapCustomField(field: RedmineCustomField): AttributeValue | undefined {\n if (field.multiple === true || Array.isArray(field.value)) {\n const values = Array.isArray(field.value)\n ? field.value\n : field.value === null\n ? []\n : [field.value];\n return { type: 'multi_enum', value: values.map((v) => ({ id: v, name: v })) };\n }\n if (field.value === null) {\n return undefined;\n }\n return { type: 'string', value: field.value };\n}\n\n// Map a Redmine journal to zero, one, or two IssueItems: a `comment` when it\n// carries notes, and a `change` when it carries property-change details.\nexport function mapJournal(journal: RedmineJournal): IssueItem[] {\n const items: IssueItem[] = [];\n const author: User =\n journal.user !== undefined ? mapNamedUser(journal.user) : { id: '', name: '' };\n\n if (journal.notes !== undefined && journal.notes !== '') {\n items.push({\n kind: 'comment',\n id: `${journal.id}-note`,\n author,\n body: journal.notes,\n createdAt: journal.created_on,\n });\n }\n\n const details = journal.details ?? [];\n if (details.length > 0) {\n items.push({\n kind: 'change',\n id: `${journal.id}-change`,\n author,\n createdAt: journal.created_on,\n changes: details.map((d) => ({\n field: d.name,\n from: d.old_value,\n to: d.new_value,\n })),\n });\n }\n\n return items;\n}\n\n// Map a Redmine issue (optionally including journals) to a normalized Issue.\nexport function mapIssue(issue: RedmineIssue, categories: Map<number, StatusCategory>): Issue {\n const attributes: Record<string, AttributeValue> = {};\n for (const field of issue.custom_fields ?? []) {\n const value = mapCustomField(field);\n if (value !== undefined) {\n attributes[field.name] = value;\n }\n }\n\n const items: IssueItem[] = [];\n for (const journal of issue.journals ?? []) {\n items.push(...mapJournal(journal));\n }\n\n const mapped: Issue = {\n id: String(issue.id),\n title: issue.subject,\n description: issue.description ?? null,\n status: mapStatus(issue.status, categories),\n author: issue.author !== undefined ? mapNamedUser(issue.author) : null,\n assignee: issue.assigned_to !== undefined ? mapNamedUser(issue.assigned_to) : null,\n attributes,\n items,\n createdAt: issue.created_on,\n updatedAt: issue.updated_on,\n };\n if (issue.project !== undefined) {\n mapped.projectId = String(issue.project.id);\n }\n return mapped;\n}\n\n// Map a Redmine time entry to a normalized TimeEntry.\nexport function mapTimeEntry(entry: RedmineTimeEntry): TimeEntry {\n const mapped: TimeEntry = {\n id: String(entry.id),\n issueId: entry.issue !== undefined ? String(entry.issue.id) : '',\n user: mapNamedUser(entry.user),\n date: entry.spent_on,\n durationMinutes: Math.round(entry.hours * 60),\n createdAt: entry.created_on,\n updatedAt: entry.updated_on,\n };\n if (entry.comments !== undefined && entry.comments !== '') {\n mapped.description = entry.comments;\n }\n if (entry.activity !== undefined) {\n mapped.activity = { id: String(entry.activity.id), name: entry.activity.name };\n }\n return mapped;\n}\n\n// Convert an AttributeValue into the scalar/array Redmine expects for a custom\n// field value.\nexport function attributeToRedmineValue(attr: AttributeValue): string | string[] {\n switch (attr.type) {\n case 'string':\n case 'text':\n return attr.value;\n case 'number':\n return String(attr.value);\n case 'date':\n return attr.value;\n case 'boolean':\n return attr.value ? '1' : '0';\n case 'enum':\n return attr.value.id;\n case 'multi_enum':\n return attr.value.map((o) => o.id);\n case 'user':\n return attr.value.id;\n }\n}\n","import { NotFoundError, ValidationError } from '@issues-mcp/core';\nimport type {\n AttributeValue,\n FieldOption,\n FieldSchema,\n Issue,\n IssueFilter,\n IssueInput,\n IssueItem,\n IssuesTracker,\n IssuesTrackerCapabilities,\n Page,\n Status,\n StatusCategory,\n Transition,\n} from '@issues-mcp/core';\nimport type { RedmineClient } from './client.js';\nimport {\n attributeToRedmineValue,\n categorizeStatus,\n mapIssue,\n mapJournal,\n mapStatus,\n} from './mappers.js';\nimport type {\n RedmineCustomFieldsResponse,\n RedmineIssueResponse,\n RedmineIssuesListResponse,\n RedmineIssueStatus,\n RedmineIssueStatusesResponse,\n RedmineProjectsResponse,\n RedmineTrackersResponse,\n} from './types.js';\n\nconst MAX_LIMIT = 100;\nconst DEFAULT_LIMIT = 25;\n\n// Body shape for create/update issue payloads.\ninterface RedmineIssueWrite {\n project_id?: number;\n subject?: string;\n description?: string | null;\n assigned_to_id?: number | null;\n tracker_id?: number;\n status_id?: number;\n notes?: string;\n custom_fields?: { id: number; value: string | string[] }[];\n}\n\n// Redmine issue-tracker adapter. Redmine has no concept of human-facing issue\n// keys distinct from numeric ids, and no workflow-transition API (status is set\n// directly), so transitions are surfaced as the full set of issue statuses.\nexport class RedmineIssuesTracker implements IssuesTracker {\n private readonly client: RedmineClient;\n private statusCache: RedmineIssueStatus[] | undefined;\n private categoryCache: Map<number, StatusCategory> | undefined;\n\n constructor(client: RedmineClient) {\n this.client = client;\n }\n\n // Fetch and cache the issue-status definitions. Used both to derive status\n // categories and to expose transitions.\n private async loadStatuses(): Promise<RedmineIssueStatus[]> {\n if (this.statusCache === undefined) {\n const res = await this.client.request<RedmineIssueStatusesResponse>('/issue_statuses.json');\n this.statusCache = res.issue_statuses;\n }\n return this.statusCache;\n }\n\n // Build (and cache) the status-id to category lookup.\n private async categories(): Promise<Map<number, StatusCategory>> {\n if (this.categoryCache === undefined) {\n const statuses = await this.loadStatuses();\n const map = new Map<number, StatusCategory>();\n for (const status of statuses) {\n map.set(status.id, categorizeStatus(status));\n }\n this.categoryCache = map;\n }\n return this.categoryCache;\n }\n\n async capabilities(): Promise<IssuesTrackerCapabilities> {\n const fields: FieldSchema[] = [];\n\n const projectOptions = await this.fetchOptions(\n () =>\n this.client.request<RedmineProjectsResponse>('/projects.json', {\n query: { limit: MAX_LIMIT },\n }),\n (res) => res.projects\n );\n fields.push({\n id: 'project',\n name: 'Project',\n type: 'enum',\n required: true,\n readOnly: false,\n allowedValues: projectOptions,\n });\n\n const trackerOptions = await this.fetchOptions(\n () => this.client.request<RedmineTrackersResponse>('/trackers.json'),\n (res) => res.trackers\n );\n fields.push({\n id: 'tracker',\n name: 'Tracker',\n type: 'enum',\n required: true,\n readOnly: false,\n allowedValues: trackerOptions,\n });\n\n fields.push({\n id: 'subject',\n name: 'Subject',\n type: 'string',\n required: true,\n readOnly: false,\n });\n fields.push({\n id: 'description',\n name: 'Description',\n type: 'text',\n required: false,\n readOnly: false,\n });\n\n const statuses = await this.loadStatuses();\n fields.push({\n id: 'status',\n name: 'Status',\n type: 'enum',\n required: false,\n readOnly: false,\n allowedValues: statuses.map((s) => ({ id: String(s.id), name: s.name })),\n });\n\n // Custom fields require admin and may return 403; best-effort only.\n try {\n const res = await this.client.request<RedmineCustomFieldsResponse>('/custom_fields.json');\n for (const cf of res.custom_fields) {\n fields.push({\n id: `cf_${cf.id}`,\n name: cf.name,\n type: 'string',\n required: cf.is_required ?? false,\n readOnly: false,\n });\n }\n } catch {\n // Ignore - custom-field introspection is not available to this account.\n }\n\n return {\n canCreate: true,\n canUpdate: true,\n canDelete: true,\n canComment: true,\n canTransition: true,\n fields,\n };\n }\n\n // Helper to fetch a reference list and map it to FieldOption[], swallowing\n // failures (returns []) so capabilities() degrades gracefully.\n private async fetchOptions<T>(\n fetcher: () => Promise<T>,\n extract: (res: T) => { id: number; name: string }[]\n ): Promise<FieldOption[]> {\n try {\n const res = await fetcher();\n return extract(res).map((o) => ({ id: String(o.id), name: o.name }));\n } catch {\n return [];\n }\n }\n\n async search(filter: IssueFilter): Promise<Page<Issue>> {\n const categories = await this.categories();\n const limit = Math.min(filter.limit ?? DEFAULT_LIMIT, MAX_LIMIT);\n const offset = filter.cursor !== undefined ? Number(filter.cursor) : 0;\n const startOffset = Number.isFinite(offset) && offset > 0 ? offset : 0;\n\n const query: Record<string, string | number | string[] | undefined> = {\n offset: startOffset,\n limit,\n };\n if (filter.projectId !== undefined) {\n query['project_id'] = filter.projectId;\n }\n if (filter.assigneeId !== undefined) {\n query['assigned_to_id'] = filter.assigneeId;\n }\n if (filter.authorId !== undefined) {\n query['author_id'] = filter.authorId;\n }\n\n // statusCategory to Redmine status_id. open/done map directly; in_progress\n // has no native filter, so we fetch open issues and filter client-side.\n let clientSideInProgress = false;\n if (filter.statusCategory === 'open') {\n query['status_id'] = 'open';\n } else if (filter.statusCategory === 'done') {\n query['status_id'] = 'closed';\n } else if (filter.statusCategory === 'in_progress') {\n query['status_id'] = 'open';\n clientSideInProgress = true;\n }\n\n const created = buildDateRange(filter.createdAfter, filter.createdBefore);\n if (created !== undefined) {\n query['created_on'] = created;\n }\n const updated = buildDateRange(filter.updatedAfter, filter.updatedBefore);\n if (updated !== undefined) {\n query['updated_on'] = updated;\n }\n\n if (filter.sort !== undefined) {\n query['sort'] = `${filter.sort.field}:${filter.sort.dir}`;\n }\n\n // Redmine issues.json has no full-text param; map `text` to a \"subject\n // contains\" filter using the f[]/op[]/v[] operator syntax.\n if (filter.text !== undefined && filter.text !== '') {\n query['f[]'] = ['subject'];\n query['op[subject]'] = '~';\n query['v[subject][]'] = filter.text;\n }\n\n const res = await this.client.request<RedmineIssuesListResponse>('/issues.json', { query });\n\n let items = res.issues.map((i) => mapIssue(i, categories));\n if (clientSideInProgress) {\n items = items.filter((i) => i.status.category === 'in_progress');\n }\n\n const page: Page<Issue> = { items, total: res.total_count };\n const nextOffset = res.offset + res.limit;\n if (nextOffset < res.total_count) {\n page.nextCursor = String(nextOffset);\n }\n return page;\n }\n\n async get(id: string): Promise<Issue> {\n const categories = await this.categories();\n const res = await this.client.request<RedmineIssueResponse>(`/issues/${id}.json`, {\n query: { include: 'journals' },\n });\n return mapIssue(res.issue, categories);\n }\n\n async create(input: IssueInput): Promise<Issue> {\n const categories = await this.categories();\n const attributes = input.attributes ?? {};\n\n const trackerId = readEnumAttribute(attributes, 'tracker');\n if (trackerId === undefined) {\n throw new ValidationError(\n 'Redmine requires a `tracker` attribute (enum) to create an issue.'\n );\n }\n\n const body: RedmineIssueWrite = {\n subject: input.title,\n tracker_id: Number(trackerId),\n };\n if (input.projectId !== undefined) {\n body.project_id = Number(input.projectId);\n }\n if (input.description !== undefined) {\n body.description = input.description;\n }\n if (input.assigneeId !== undefined && input.assigneeId !== null) {\n body.assigned_to_id = Number(input.assigneeId);\n }\n const statusId = readEnumAttribute(attributes, 'status');\n if (statusId !== undefined) {\n body.status_id = Number(statusId);\n }\n const customFields = buildCustomFields(attributes);\n if (customFields.length > 0) {\n body.custom_fields = customFields;\n }\n\n const res = await this.client.request<RedmineIssueResponse>('/issues.json', {\n method: 'POST',\n body: { issue: body },\n });\n return mapIssue(res.issue, categories);\n }\n\n async update(id: string, input: Partial<IssueInput>): Promise<Issue> {\n const body: RedmineIssueWrite = {};\n if (input.title !== undefined) {\n body.subject = input.title;\n }\n if (input.description !== undefined) {\n body.description = input.description;\n }\n if (input.projectId !== undefined) {\n body.project_id = Number(input.projectId);\n }\n if (input.assigneeId !== undefined) {\n body.assigned_to_id = input.assigneeId === null ? null : Number(input.assigneeId);\n }\n if (input.attributes !== undefined) {\n const statusId = readEnumAttribute(input.attributes, 'status');\n if (statusId !== undefined) {\n body.status_id = Number(statusId);\n }\n const customFields = buildCustomFields(input.attributes);\n if (customFields.length > 0) {\n body.custom_fields = customFields;\n }\n }\n\n // PUT returns 204 No Content; refetch to return the updated issue.\n await this.client.request<void>(`/issues/${id}.json`, {\n method: 'PUT',\n body: { issue: body },\n });\n return this.get(id);\n }\n\n async delete(id: string): Promise<void> {\n await this.client.request<void>(`/issues/${id}.json`, { method: 'DELETE' });\n }\n\n async addComment(issueId: string, body: string): Promise<IssueItem> {\n // Redmine adds a comment via a notes update (204), then we refetch and\n // return the most recent journal that carries notes.\n await this.client.request<void>(`/issues/${issueId}.json`, {\n method: 'PUT',\n body: { issue: { notes: body } satisfies RedmineIssueWrite },\n });\n\n const res = await this.client.request<RedmineIssueResponse>(`/issues/${issueId}.json`, {\n query: { include: 'journals' },\n });\n const journals = res.issue.journals ?? [];\n for (let i = journals.length - 1; i >= 0; i--) {\n const journal = journals[i];\n if (journal !== undefined && journal.notes !== undefined && journal.notes !== '') {\n const comment = mapJournal(journal).find((item) => item.kind === 'comment');\n if (comment !== undefined) {\n return comment;\n }\n }\n }\n throw new NotFoundError(`Comment was added to issue ${issueId} but could not be retrieved.`);\n }\n\n // Redmine does not enforce workflow transitions through the REST API: status\n // is set directly. We therefore offer every issue status as a transition.\n async getTransitions(): Promise<Transition[]> {\n const categories = await this.categories();\n const statuses = await this.loadStatuses();\n return statuses.map((s) => {\n const to: Status = mapStatus({ id: s.id, name: s.name }, categories);\n return { id: String(s.id), name: s.name, to };\n });\n }\n\n async transition(issueId: string, transitionId: string): Promise<Issue> {\n await this.client.request<void>(`/issues/${issueId}.json`, {\n method: 'PUT',\n body: { issue: { status_id: Number(transitionId) } satisfies RedmineIssueWrite },\n });\n return this.get(issueId);\n }\n}\n\n// Build a Redmine date-filter operator expression from optional bounds. Returns\n// `><start|end` for a range, `>=start` / `<=end` for a single bound, or\n// undefined when neither is set.\nfunction buildDateRange(after?: string, before?: string): string | undefined {\n if (after !== undefined && before !== undefined) {\n return `><${after}|${before}`;\n }\n if (after !== undefined) {\n return `>=${after}`;\n }\n if (before !== undefined) {\n return `<=${before}`;\n }\n return undefined;\n}\n\n// Read an enum-typed attribute's option id by key (used for `tracker` and\n// `status`, which Redmine takes as numeric ids).\nfunction readEnumAttribute(\n attributes: Record<string, AttributeValue>,\n key: string\n): string | undefined {\n const attr = attributes[key];\n if (attr === undefined) {\n return undefined;\n }\n if (attr.type === 'enum') {\n return attr.value.id;\n }\n if (attr.type === 'string' || attr.type === 'number') {\n return String(attr.value);\n }\n return undefined;\n}\n\n// Map `cf_<id>` keyed attributes to Redmine custom_fields[] entries. Attributes\n// not matching that convention (e.g. `tracker`, `status`) are skipped.\nfunction buildCustomFields(\n attributes: Record<string, AttributeValue>\n): { id: number; value: string | string[] }[] {\n const result: { id: number; value: string | string[] }[] = [];\n for (const [key, attr] of Object.entries(attributes)) {\n const match = /^cf_(\\d+)$/.exec(key);\n if (match === null || match[1] === undefined) {\n continue;\n }\n result.push({ id: Number(match[1]), value: attributeToRedmineValue(attr) });\n }\n return result;\n}\n","import type {\n FieldOption,\n Page,\n TimeEntry,\n TimeEntryFilter,\n TimeEntryInput,\n TimeTracker,\n TimeTrackerCapabilities,\n} from '@issues-mcp/core';\nimport type { RedmineClient } from './client.js';\nimport { mapTimeEntry } from './mappers.js';\nimport type {\n RedmineActivitiesResponse,\n RedmineTimeEntriesListResponse,\n RedmineTimeEntryResponse,\n} from './types.js';\n\nconst MAX_LIMIT = 100;\nconst DEFAULT_LIMIT = 25;\n\n// Body shape for create/update time-entry payloads.\ninterface RedmineTimeEntryWrite {\n issue_id?: number;\n hours?: number;\n spent_on?: string;\n activity_id?: number;\n comments?: string;\n}\n\n// Redmine time-tracking adapter over `/time_entries.json`. Redmine can log time\n// against either an issue or a project, so `requiresIssue` is false.\nexport class RedmineTimeTracker implements TimeTracker {\n private readonly client: RedmineClient;\n\n constructor(client: RedmineClient) {\n this.client = client;\n }\n\n async capabilities(): Promise<TimeTrackerCapabilities> {\n let activities: FieldOption[] = [];\n try {\n const res = await this.client.request<RedmineActivitiesResponse>(\n '/enumerations/time_entry_activities.json'\n );\n activities = res.time_entry_activities.map((a) => ({ id: String(a.id), name: a.name }));\n } catch {\n // Best-effort: leave activities empty if the enumeration is unavailable.\n }\n\n return {\n canCreate: true,\n canUpdate: true,\n canDelete: true,\n requiresIssue: false,\n activities,\n };\n }\n\n async search(filter: TimeEntryFilter): Promise<Page<TimeEntry>> {\n const limit = Math.min(filter.limit ?? DEFAULT_LIMIT, MAX_LIMIT);\n const offset = filter.cursor !== undefined ? Number(filter.cursor) : 0;\n const startOffset = Number.isFinite(offset) && offset > 0 ? offset : 0;\n\n const query: Record<string, string | number | undefined> = {\n offset: startOffset,\n limit,\n };\n if (filter.issueId !== undefined) {\n query['issue_id'] = filter.issueId;\n }\n if (filter.userId !== undefined) {\n query['user_id'] = filter.userId;\n }\n if (filter.projectId !== undefined) {\n query['project_id'] = filter.projectId;\n }\n if (filter.from !== undefined) {\n query['from'] = filter.from;\n }\n if (filter.to !== undefined) {\n query['to'] = filter.to;\n }\n if (filter.sort !== undefined) {\n query['sort'] = `${filter.sort.field}:${filter.sort.dir}`;\n }\n\n const res = await this.client.request<RedmineTimeEntriesListResponse>('/time_entries.json', {\n query,\n });\n\n const page: Page<TimeEntry> = {\n items: res.time_entries.map(mapTimeEntry),\n total: res.total_count,\n };\n const nextOffset = res.offset + res.limit;\n if (nextOffset < res.total_count) {\n page.nextCursor = String(nextOffset);\n }\n return page;\n }\n\n async get(id: string): Promise<TimeEntry> {\n const res = await this.client.request<RedmineTimeEntryResponse>(`/time_entries/${id}.json`);\n return mapTimeEntry(res.time_entry);\n }\n\n async create(input: TimeEntryInput): Promise<TimeEntry> {\n const body: RedmineTimeEntryWrite = {\n issue_id: Number(input.issueId),\n hours: input.durationMinutes / 60,\n spent_on: input.date,\n };\n if (input.activityId !== undefined) {\n body.activity_id = Number(input.activityId);\n }\n if (input.description !== undefined) {\n body.comments = input.description;\n }\n\n const res = await this.client.request<RedmineTimeEntryResponse>('/time_entries.json', {\n method: 'POST',\n body: { time_entry: body },\n });\n return mapTimeEntry(res.time_entry);\n }\n\n async update(id: string, input: Partial<TimeEntryInput>): Promise<TimeEntry> {\n const body: RedmineTimeEntryWrite = {};\n if (input.issueId !== undefined) {\n body.issue_id = Number(input.issueId);\n }\n if (input.durationMinutes !== undefined) {\n body.hours = input.durationMinutes / 60;\n }\n if (input.date !== undefined) {\n body.spent_on = input.date;\n }\n if (input.activityId !== undefined) {\n body.activity_id = Number(input.activityId);\n }\n if (input.description !== undefined) {\n body.comments = input.description;\n }\n\n // PUT returns 204 No Content; refetch to return the updated entry.\n await this.client.request<void>(`/time_entries/${id}.json`, {\n method: 'PUT',\n body: { time_entry: body },\n });\n return this.get(id);\n }\n\n async delete(id: string): Promise<void> {\n await this.client.request<void>(`/time_entries/${id}.json`, { method: 'DELETE' });\n }\n}\n","import { AuthError } from '@issues-mcp/core';\nimport type { Authenticator, ConnectionConfig, Session, Tracker, User } from '@issues-mcp/core';\nimport { RedmineClient } from './client.js';\nimport { RedmineIssuesTracker } from './issuesTracker.js';\nimport { RedmineTimeTracker } from './timeTracker.js';\nimport { mapCurrentUser } from './mappers.js';\nimport type { RedmineUserResponse } from './types.js';\n\n// An authenticated Redmine session. Bundles the bound issue/time trackers with\n// the authenticated identity. `close()` is a no-op as the client holds no\n// long-lived resources.\nclass RedmineSession implements Session {\n readonly tracker: Tracker;\n private readonly user: User;\n\n constructor(tracker: Tracker, user: User) {\n this.tracker = tracker;\n this.user = user;\n }\n\n async currentUser(): Promise<User> {\n return this.user;\n }\n\n async close(): Promise<void> {\n // No persistent resources to release.\n }\n}\n\n// Entry point for the Redmine adapter: validates credentials against\n// `/users/current.json` and returns a ready-to-use Session.\nexport class RedmineAuthenticator implements Authenticator {\n async authenticate(config: ConnectionConfig): Promise<Session> {\n const client = new RedmineClient(config);\n\n let user: User;\n try {\n const res = await client.request<RedmineUserResponse>('/users/current.json');\n user = mapCurrentUser(res.user);\n } catch (cause) {\n if (cause instanceof AuthError) {\n throw cause;\n }\n throw new AuthError(`Redmine credential validation failed: ${String(cause)}`, { cause });\n }\n\n const tracker: Tracker = {\n issues: new RedmineIssuesTracker(client),\n time: new RedmineTimeTracker(client),\n };\n\n return new RedmineSession(tracker, user);\n }\n}\n\n// Convenience factory mirroring the Authenticator construction.\nexport function createRedmineAuthenticator(): RedmineAuthenticator {\n return new RedmineAuthenticator();\n}\n","import { IssueTrackerError } from '@issues-mcp/core';\n\n// A minimal CallToolResult shape (text content only). Structurally compatible\n// with the MCP SDK's expected tool return type.\nexport type ToolResult = {\n content: { type: 'text'; text: string }[];\n isError?: boolean;\n};\n\n// Wraps a successful result as pretty-printed JSON text content.\nfunction ok(data: unknown): ToolResult {\n return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };\n}\n\n// Wraps an error message as an error result.\nfunction fail(text: string): ToolResult {\n return { content: [{ type: 'text', text }], isError: true };\n}\n\n// Runs a tracker operation and converts the outcome into a tool result. Known\n// IssueTrackerError types are surfaced by name + message; anything else is\n// reported generically. All returned text is passed through `redact` so secrets\n// can never leak to the model via an error message.\nexport async function runTool(\n redact: (text: string) => string,\n fn: () => Promise<unknown>\n): Promise<ToolResult> {\n try {\n return ok(await fn());\n } catch (err) {\n let message: string;\n if (err instanceof IssueTrackerError) {\n message = `${err.name}: ${err.message}`;\n } else if (err instanceof Error) {\n message = err.message;\n } else {\n message = String(err);\n }\n return fail(redact(message));\n }\n}\n","import { z } from 'zod';\n\n// Zod shapes mirroring the @issues-mcp/core input contracts. These describe the\n// tool argument surface; secrets are deliberately absent — credentials come\n// only from the environment, never from tool arguments.\n\nconst fieldOption = z.object({ id: z.string(), name: z.string() });\n\nconst user = z.object({\n id: z.string(),\n name: z.string(),\n email: z.string().optional(),\n});\n\n// A typed custom-field value, mirroring core's AttributeValue discriminated\n// union. 'string' and 'text' are separate literals so the discriminator stays a\n// single literal per branch (Zod requirement).\nconst attributeValue = z.discriminatedUnion('type', [\n z.object({ type: z.literal('string'), value: z.string() }),\n z.object({ type: z.literal('text'), value: z.string() }),\n z.object({ type: z.literal('number'), value: z.number() }),\n z.object({ type: z.literal('date'), value: z.string() }),\n z.object({ type: z.literal('boolean'), value: z.boolean() }),\n z.object({ type: z.literal('enum'), value: fieldOption }),\n z.object({ type: z.literal('multi_enum'), value: z.array(fieldOption) }),\n z.object({ type: z.literal('user'), value: user }),\n]);\n\nconst attributes = z.record(z.string(), attributeValue);\nconst statusCategory = z.enum(['open', 'in_progress', 'done', 'unknown']);\nconst sort = z.object({ field: z.string(), dir: z.enum(['asc', 'desc']) });\n\n// --- Issue tool shapes (raw shapes, suitable for `inputSchema`) ---\n\nexport const issueFilterShape = {\n text: z.string().optional(),\n projectId: z.string().optional(),\n statusCategory: statusCategory.optional(),\n assigneeId: z.string().optional(),\n authorId: z.string().optional(),\n updatedAfter: z.string().optional(),\n updatedBefore: z.string().optional(),\n createdAfter: z.string().optional(),\n createdBefore: z.string().optional(),\n sort: sort.optional(),\n limit: z.number().int().positive().optional(),\n cursor: z.string().optional(),\n};\n\nexport const getIssueShape = { id: z.string() };\n\nexport const createIssueShape = {\n title: z.string(),\n description: z.string().nullable().optional(),\n projectId: z.string().optional(),\n assigneeId: z.string().nullable().optional(),\n attributes: attributes.optional(),\n};\n\nexport const updateIssueShape = {\n id: z.string(),\n title: z.string().optional(),\n description: z.string().nullable().optional(),\n projectId: z.string().optional(),\n assigneeId: z.string().nullable().optional(),\n attributes: attributes.optional(),\n};\n\nexport const deleteIssueShape = { id: z.string() };\nexport const addCommentShape = { issueId: z.string(), body: z.string() };\nexport const getTransitionsShape = { issueId: z.string() };\nexport const transitionShape = { issueId: z.string(), transitionId: z.string() };\n\n// --- Time entry tool shapes ---\n\nexport const timeEntryFilterShape = {\n issueId: z.string().optional(),\n userId: z.string().optional(),\n projectId: z.string().optional(),\n from: z.string().optional(),\n to: z.string().optional(),\n sort: sort.optional(),\n limit: z.number().int().positive().optional(),\n cursor: z.string().optional(),\n};\n\nexport const getTimeEntryShape = { id: z.string() };\n\nexport const createTimeEntryShape = {\n issueId: z.string(),\n date: z.string(),\n durationMinutes: z.number().positive(),\n description: z.string().optional(),\n activityId: z.string().optional(),\n attributes: attributes.optional(),\n};\n\nexport const updateTimeEntryShape = {\n id: z.string(),\n issueId: z.string().optional(),\n date: z.string().optional(),\n durationMinutes: z.number().positive().optional(),\n description: z.string().optional(),\n activityId: z.string().optional(),\n attributes: attributes.optional(),\n};\n\nexport const deleteTimeEntryShape = { id: z.string() };\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { Session } from '@issues-mcp/core';\nimport type { TrackerKind } from './config.js';\nimport { runTool } from './result.js';\nimport {\n addCommentShape,\n createIssueShape,\n createTimeEntryShape,\n deleteIssueShape,\n deleteTimeEntryShape,\n getIssueShape,\n getTimeEntryShape,\n getTransitionsShape,\n issueFilterShape,\n timeEntryFilterShape,\n transitionShape,\n updateIssueShape,\n updateTimeEntryShape,\n} from './schemas.js';\n\nconst READ_ONLY = { readOnlyHint: true } as const;\nconst DESTRUCTIVE = { destructiveHint: true } as const;\n\n// Registers all tools for a single authenticated tracker. Tool names are\n// prefixed with the tracker kind (e.g. `jira_search_issues`) so multiple\n// trackers can coexist in one server. `redact` scrubs secrets from any text\n// returned to the model.\nexport function registerTrackerTools(\n server: McpServer,\n kind: TrackerKind,\n session: Session,\n redact: (text: string) => string\n): void {\n const { issues, time } = session.tracker;\n const name = (op: string): string => `${kind}_${op}`;\n const run = (fn: () => Promise<unknown>) => runTool(redact, fn);\n\n server.registerTool(\n name('whoami'),\n {\n title: `${kind}: current user`,\n description: `Return the authenticated ${kind} account.`,\n annotations: READ_ONLY,\n },\n () => run(() => session.currentUser())\n );\n\n server.registerTool(\n name('capabilities'),\n {\n title: `${kind}: capabilities`,\n description: `Describe what the ${kind} tracker supports, including the fields accepted on create/update. Consult this before constructing a create_issue payload.`,\n annotations: READ_ONLY,\n },\n () =>\n run(async () => ({\n issues: await issues.capabilities(),\n time: time ? await time.capabilities() : null,\n }))\n );\n\n server.registerTool(\n name('search_issues'),\n {\n title: `${kind}: search issues`,\n description: `Search ${kind} issues with a normalized filter. Returns a page of issues with an optional nextCursor.`,\n inputSchema: issueFilterShape,\n annotations: READ_ONLY,\n },\n (args) => run(() => issues.search(args))\n );\n\n server.registerTool(\n name('get_issue'),\n {\n title: `${kind}: get issue`,\n description: `Fetch a single ${kind} issue by id (or key), including its timeline items.`,\n inputSchema: getIssueShape,\n annotations: READ_ONLY,\n },\n ({ id }) => run(() => issues.get(id))\n );\n\n server.registerTool(\n name('create_issue'),\n {\n title: `${kind}: create issue`,\n description: `Create a ${kind} issue. Tracker-specific required fields go in \\`attributes\\`; check \\`${kind}_capabilities\\` first.`,\n inputSchema: createIssueShape,\n },\n (args) => run(() => issues.create(args))\n );\n\n server.registerTool(\n name('update_issue'),\n {\n title: `${kind}: update issue`,\n description: `Update fields on a ${kind} issue. Status is changed via transitions, not here.`,\n inputSchema: updateIssueShape,\n annotations: { idempotentHint: true },\n },\n ({ id, ...input }) => run(() => issues.update(id, input))\n );\n\n server.registerTool(\n name('delete_issue'),\n {\n title: `${kind}: delete issue`,\n description: `Permanently delete a ${kind} issue.`,\n inputSchema: deleteIssueShape,\n annotations: DESTRUCTIVE,\n },\n ({ id }) => run(() => issues.delete(id).then(() => ({ deleted: id })))\n );\n\n server.registerTool(\n name('add_comment'),\n {\n title: `${kind}: add comment`,\n description: `Add a comment to a ${kind} issue.`,\n inputSchema: addCommentShape,\n },\n ({ issueId, body }) => run(() => issues.addComment(issueId, body))\n );\n\n server.registerTool(\n name('get_transitions'),\n {\n title: `${kind}: get transitions`,\n description: `List the status transitions currently available for a ${kind} issue.`,\n inputSchema: getTransitionsShape,\n annotations: READ_ONLY,\n },\n ({ issueId }) => run(() => issues.getTransitions(issueId))\n );\n\n server.registerTool(\n name('transition_issue'),\n {\n title: `${kind}: transition issue`,\n description: `Move a ${kind} issue through a transition (use get_transitions for valid ids).`,\n inputSchema: transitionShape,\n },\n ({ issueId, transitionId }) => run(() => issues.transition(issueId, transitionId))\n );\n\n if (!time) return;\n\n server.registerTool(\n name('search_time_entries'),\n {\n title: `${kind}: search time entries`,\n description: `Search ${kind} time entries with a normalized filter.`,\n inputSchema: timeEntryFilterShape,\n annotations: READ_ONLY,\n },\n (args) => run(() => time.search(args))\n );\n\n server.registerTool(\n name('get_time_entry'),\n {\n title: `${kind}: get time entry`,\n description: `Fetch a single ${kind} time entry by id.`,\n inputSchema: getTimeEntryShape,\n annotations: READ_ONLY,\n },\n ({ id }) => run(() => time.get(id))\n );\n\n server.registerTool(\n name('create_time_entry'),\n {\n title: `${kind}: log time`,\n description: `Log a ${kind} time entry against an issue. durationMinutes is in minutes.`,\n inputSchema: createTimeEntryShape,\n },\n (args) => run(() => time.create(args))\n );\n\n server.registerTool(\n name('update_time_entry'),\n {\n title: `${kind}: update time entry`,\n description: `Update a ${kind} time entry.`,\n inputSchema: updateTimeEntryShape,\n annotations: { idempotentHint: true },\n },\n ({ id, ...input }) => run(() => time.update(id, input))\n );\n\n server.registerTool(\n name('delete_time_entry'),\n {\n title: `${kind}: delete time entry`,\n description: `Permanently delete a ${kind} time entry.`,\n inputSchema: deleteTimeEntryShape,\n annotations: DESTRUCTIVE,\n },\n ({ id }) => run(() => time.delete(id).then(() => ({ deleted: id })))\n );\n}\n"],"mappings":"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","names":["user","user","user","mapStatus","mapIssue","attributes","mapIssue","attributes","mapStatus","MAX_LIMIT","DEFAULT_LIMIT","user"]}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createServer
|
|
4
|
+
} from "./chunk-Y53ZARWV.js";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
function note(message) {
|
|
9
|
+
process.stderr.write(`[issues-mcp] ${message}
|
|
10
|
+
`);
|
|
11
|
+
}
|
|
12
|
+
async function main() {
|
|
13
|
+
const { server, sessions, warnings } = await createServer(process.env);
|
|
14
|
+
for (const warning of warnings) note(warning);
|
|
15
|
+
if (sessions.length === 0) {
|
|
16
|
+
note(
|
|
17
|
+
"No trackers configured. Set JIRA_BASE_URL/JIRA_API_TOKEN/JIRA_ACCOUNT_EMAIL and/or REDMINE_BASE_URL/REDMINE_API_KEY, then restart."
|
|
18
|
+
);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const shutdown = () => {
|
|
22
|
+
void Promise.allSettled(sessions.map((s) => s.close())).then(() => {
|
|
23
|
+
process.exit(0);
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
process.on("SIGINT", shutdown);
|
|
27
|
+
process.on("SIGTERM", shutdown);
|
|
28
|
+
const transport = new StdioServerTransport();
|
|
29
|
+
await server.connect(transport);
|
|
30
|
+
note(`ready \u2014 ${sessions.length} tracker(s) connected`);
|
|
31
|
+
}
|
|
32
|
+
main().catch((err) => {
|
|
33
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
34
|
+
note(`fatal: ${message}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { createServer } from './server.js';\n\n// Writes a diagnostic line to stderr. stdout is reserved for the MCP protocol,\n// so all human-facing output goes to stderr.\nfunction note(message: string): void {\n process.stderr.write(`[issues-mcp] ${message}\\n`);\n}\n\nasync function main(): Promise<void> {\n const { server, sessions, warnings } = await createServer(process.env);\n for (const warning of warnings) note(warning);\n\n if (sessions.length === 0) {\n note(\n 'No trackers configured. Set JIRA_BASE_URL/JIRA_API_TOKEN/JIRA_ACCOUNT_EMAIL ' +\n 'and/or REDMINE_BASE_URL/REDMINE_API_KEY, then restart.'\n );\n process.exit(1);\n }\n\n const shutdown = (): void => {\n void Promise.allSettled(sessions.map((s) => s.close())).then(() => {\n process.exit(0);\n });\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n note(`ready — ${sessions.length} tracker(s) connected`);\n}\n\nmain().catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n note(`fatal: ${message}`);\n process.exit(1);\n});\n"],"mappings":";;;;;;AACA,SAAS,4BAA4B;AAKrC,SAAS,KAAK,SAAuB;AACnC,UAAQ,OAAO,MAAM,gBAAgB,OAAO;AAAA,CAAI;AAClD;AAEA,eAAe,OAAsB;AACnC,QAAM,EAAE,QAAQ,UAAU,SAAS,IAAI,MAAM,aAAa,QAAQ,GAAG;AACrE,aAAW,WAAW,SAAU,MAAK,OAAO;AAE5C,MAAI,SAAS,WAAW,GAAG;AACzB;AAAA,MACE;AAAA,IAEF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,WAAW,MAAY;AAC3B,SAAK,QAAQ,WAAW,SAAS,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,KAAK,MAAM;AACjE,cAAQ,KAAK,CAAC;AAAA,IAChB,CAAC;AAAA,EACH;AACA,UAAQ,GAAG,UAAU,QAAQ;AAC7B,UAAQ,GAAG,WAAW,QAAQ;AAE9B,QAAM,YAAY,IAAI,qBAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAC9B,OAAK,gBAAW,SAAS,MAAM,uBAAuB;AACxD;AAEA,KAAK,EAAE,MAAM,CAAC,QAAiB;AAC7B,QAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,OAAK,UAAU,OAAO,EAAE;AACxB,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { Session, ConnectionConfig } from '@issues-mcp/core';
|
|
3
|
+
|
|
4
|
+
type BuiltServer = {
|
|
5
|
+
server: McpServer;
|
|
6
|
+
sessions: Session[];
|
|
7
|
+
warnings: string[];
|
|
8
|
+
};
|
|
9
|
+
declare function createServer(env?: NodeJS.ProcessEnv): Promise<BuiltServer>;
|
|
10
|
+
|
|
11
|
+
type TrackerKind = 'jira' | 'redmine';
|
|
12
|
+
type TrackerConfig = {
|
|
13
|
+
kind: TrackerKind;
|
|
14
|
+
connection: ConnectionConfig;
|
|
15
|
+
};
|
|
16
|
+
type LoadedConfig = {
|
|
17
|
+
trackers: TrackerConfig[];
|
|
18
|
+
secrets: string[];
|
|
19
|
+
warnings: string[];
|
|
20
|
+
};
|
|
21
|
+
declare function loadConfig(env?: NodeJS.ProcessEnv): LoadedConfig;
|
|
22
|
+
declare function makeRedactor(secrets: string[]): (text: string) => string;
|
|
23
|
+
|
|
24
|
+
export { type BuiltServer, type LoadedConfig, type TrackerConfig, type TrackerKind, createServer, loadConfig, makeRedactor };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "issues-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "One MCP server for every issue tracker — normalized issue & time-tracking tools for Jira and Redmine.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Daniel Drobot",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"bin": {
|
|
11
|
+
"issues-mcp": "dist/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"keywords": [
|
|
24
|
+
"mcp",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"jira",
|
|
27
|
+
"redmine",
|
|
28
|
+
"issue-tracker",
|
|
29
|
+
"time-tracking",
|
|
30
|
+
"ai",
|
|
31
|
+
"llm",
|
|
32
|
+
"claude"
|
|
33
|
+
],
|
|
34
|
+
"homepage": "https://github.com/daniel-drobot/issues-mcp#readme",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/daniel-drobot/issues-mcp.git",
|
|
38
|
+
"directory": "packages/mcp"
|
|
39
|
+
},
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/daniel-drobot/issues-mcp/issues"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=20"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsup",
|
|
51
|
+
"dev": "tsx watch src/cli.ts",
|
|
52
|
+
"start": "node dist/cli.js",
|
|
53
|
+
"lint": "eslint src",
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
55
|
+
"clean": "rimraf dist .turbo",
|
|
56
|
+
"prepack": "tsup"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
60
|
+
"zod": "^4.4.3"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@issues-mcp/core": "*",
|
|
64
|
+
"@issues-mcp/jira": "*",
|
|
65
|
+
"@issues-mcp/redmine": "*",
|
|
66
|
+
"rimraf": "^6.0.1",
|
|
67
|
+
"tsup": "^8.3.5",
|
|
68
|
+
"tsx": "^4.19.2",
|
|
69
|
+
"typescript": "^5.7.2"
|
|
70
|
+
}
|
|
71
|
+
}
|