@k-system/tickr-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +87 -0
- package/dist/api-client.d.ts +29 -0
- package/dist/api-client.js +65 -0
- package/dist/auth.d.ts +18 -0
- package/dist/auth.js +60 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +33 -0
- package/dist/formatters.d.ts +10 -0
- package/dist/formatters.js +58 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +67 -0
- package/dist/resources/project-resource.d.ts +4 -0
- package/dist/resources/project-resource.js +18 -0
- package/dist/resources/ticket-resource.d.ts +4 -0
- package/dist/resources/ticket-resource.js +17 -0
- package/dist/tools/add-comment.d.ts +4 -0
- package/dist/tools/add-comment.js +25 -0
- package/dist/tools/add-label.d.ts +4 -0
- package/dist/tools/add-label.js +21 -0
- package/dist/tools/assign-cycle.d.ts +4 -0
- package/dist/tools/assign-cycle.js +22 -0
- package/dist/tools/assign-epic.d.ts +4 -0
- package/dist/tools/assign-epic.js +22 -0
- package/dist/tools/complete-dev-task.d.ts +4 -0
- package/dist/tools/complete-dev-task.js +37 -0
- package/dist/tools/create-ticket.d.ts +4 -0
- package/dist/tools/create-ticket.js +38 -0
- package/dist/tools/get-ticket.d.ts +4 -0
- package/dist/tools/get-ticket.js +20 -0
- package/dist/tools/list-cycles.d.ts +4 -0
- package/dist/tools/list-cycles.js +23 -0
- package/dist/tools/list-epics.d.ts +4 -0
- package/dist/tools/list-epics.js +23 -0
- package/dist/tools/list-labels.d.ts +4 -0
- package/dist/tools/list-labels.js +23 -0
- package/dist/tools/list-tickets.d.ts +4 -0
- package/dist/tools/list-tickets.js +39 -0
- package/dist/tools/poll-dev-queue.d.ts +4 -0
- package/dist/tools/poll-dev-queue.js +37 -0
- package/dist/tools/remove-label.d.ts +4 -0
- package/dist/tools/remove-label.js +21 -0
- package/dist/tools/search-tickets.d.ts +4 -0
- package/dist/tools/search-tickets.js +30 -0
- package/dist/tools/triage-accept.d.ts +4 -0
- package/dist/tools/triage-accept.js +30 -0
- package/dist/tools/triage-list.d.ts +4 -0
- package/dist/tools/triage-list.js +23 -0
- package/dist/tools/triage-reject.d.ts +4 -0
- package/dist/tools/triage-reject.js +28 -0
- package/dist/tools/update-implementation-item.d.ts +4 -0
- package/dist/tools/update-implementation-item.js +33 -0
- package/dist/tools/update-ticket.d.ts +4 -0
- package/dist/tools/update-ticket.js +32 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.js +3 -0
- package/package.json +55 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@tickr/mcp-server` will be documented here.
|
|
4
|
+
|
|
5
|
+
## [0.1.0] — 2026-03-12
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Initial release
|
|
9
|
+
- 17 MCP tools (ticket CRUD, triage, labels, cycles, epics)
|
|
10
|
+
- 2 MCP resources (ticket detail, project overview)
|
|
11
|
+
- API key and JWT authentication
|
|
12
|
+
- Config via environment variables or `~/.tickr/config.json`
|
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# @tickr/mcp-server
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for [Tickr](https://tickr.io) project management.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### Claude Desktop
|
|
8
|
+
|
|
9
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
|
|
10
|
+
or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"mcpServers": {
|
|
15
|
+
"tickr": {
|
|
16
|
+
"command": "npx",
|
|
17
|
+
"args": ["-y", "@tickr/mcp-server"],
|
|
18
|
+
"env": {
|
|
19
|
+
"TICKR_API_URL": "https://your-tickr-instance.com",
|
|
20
|
+
"TICKR_API_KEY": "tk_your_api_key"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Claude Code
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
claude mcp add tickr -- npx -y @tickr/mcp-server
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Set environment variables:
|
|
34
|
+
```bash
|
|
35
|
+
export TICKR_API_URL=https://your-tickr-instance.com
|
|
36
|
+
export TICKR_API_KEY=tk_your_api_key
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Configuration
|
|
40
|
+
|
|
41
|
+
| Variable | Required | Default | Description |
|
|
42
|
+
|----------|----------|---------|-------------|
|
|
43
|
+
| `TICKR_API_URL` | Yes | `https://localhost:6001` | Tickr API base URL |
|
|
44
|
+
| `TICKR_API_KEY` | Yes | — | API key (`tk_...`) from Tickr admin |
|
|
45
|
+
| `TICKR_DEFAULT_PROJECT` | No | — | Default project slug |
|
|
46
|
+
|
|
47
|
+
Or use config file `~/.tickr/config.json`:
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"apiUrl": "https://your-tickr-instance.com",
|
|
51
|
+
"apiKey": "tk_your_api_key",
|
|
52
|
+
"defaultProject": "my-project"
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Available Tools (17)
|
|
57
|
+
|
|
58
|
+
| Tool | Description |
|
|
59
|
+
|------|-------------|
|
|
60
|
+
| `create_ticket` | Create a new ticket |
|
|
61
|
+
| `list_tickets` | List tickets with filters |
|
|
62
|
+
| `get_ticket` | Get ticket detail by number |
|
|
63
|
+
| `update_ticket` | Update ticket fields |
|
|
64
|
+
| `search_tickets` | Fulltext search |
|
|
65
|
+
| `add_comment` | Add comment to ticket |
|
|
66
|
+
| `update_implementation_item` | Update implementation table row |
|
|
67
|
+
| `triage_list` | List untriaged tickets |
|
|
68
|
+
| `triage_accept` | Accept triaged ticket |
|
|
69
|
+
| `triage_reject` | Reject triaged ticket |
|
|
70
|
+
| `list_labels` | List project labels |
|
|
71
|
+
| `add_label` | Add label to ticket |
|
|
72
|
+
| `remove_label` | Remove label from ticket |
|
|
73
|
+
| `list_cycles` | List project cycles |
|
|
74
|
+
| `assign_cycle` | Assign ticket to cycle |
|
|
75
|
+
| `list_epics` | List project epics |
|
|
76
|
+
| `assign_epic` | Assign ticket to epic |
|
|
77
|
+
|
|
78
|
+
## Resources (2)
|
|
79
|
+
|
|
80
|
+
| URI | Description |
|
|
81
|
+
|-----|-------------|
|
|
82
|
+
| `tickr://ticket/{number}` | Ticket detail as markdown |
|
|
83
|
+
| `tickr://project/{slug}` | Project overview + recent tickets |
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { TickrConfig } from "./config.js";
|
|
2
|
+
/** Unified API response wrapper */
|
|
3
|
+
export interface ApiResponse<T> {
|
|
4
|
+
success: boolean;
|
|
5
|
+
data: T | null;
|
|
6
|
+
error: {
|
|
7
|
+
code: string;
|
|
8
|
+
message: string;
|
|
9
|
+
traceId?: string;
|
|
10
|
+
} | null;
|
|
11
|
+
meta: {
|
|
12
|
+
totalCount?: number;
|
|
13
|
+
page?: number;
|
|
14
|
+
pageSize?: number;
|
|
15
|
+
totalPages?: number;
|
|
16
|
+
} | null;
|
|
17
|
+
}
|
|
18
|
+
/** Fetch wrapper pro Tickr REST API s automatickou autentizací */
|
|
19
|
+
export declare class ApiClient {
|
|
20
|
+
private config;
|
|
21
|
+
constructor(config: TickrConfig);
|
|
22
|
+
get<T = unknown>(path: string): Promise<T>;
|
|
23
|
+
post<T = unknown>(path: string, body?: unknown): Promise<T>;
|
|
24
|
+
patch<T = unknown>(path: string, body?: unknown): Promise<T>;
|
|
25
|
+
put<T = unknown>(path: string, body?: unknown): Promise<T>;
|
|
26
|
+
delete<T = unknown>(path: string): Promise<T>;
|
|
27
|
+
private request;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=api-client.d.ts.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { getAuthHeader } from "./auth.js";
|
|
2
|
+
/** Fetch wrapper pro Tickr REST API s automatickou autentizací */
|
|
3
|
+
export class ApiClient {
|
|
4
|
+
config;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
}
|
|
8
|
+
async get(path) {
|
|
9
|
+
return this.request("GET", path);
|
|
10
|
+
}
|
|
11
|
+
async post(path, body) {
|
|
12
|
+
return this.request("POST", path, body);
|
|
13
|
+
}
|
|
14
|
+
async patch(path, body) {
|
|
15
|
+
return this.request("PATCH", path, body);
|
|
16
|
+
}
|
|
17
|
+
async put(path, body) {
|
|
18
|
+
return this.request("PUT", path, body);
|
|
19
|
+
}
|
|
20
|
+
async delete(path) {
|
|
21
|
+
return this.request("DELETE", path);
|
|
22
|
+
}
|
|
23
|
+
async request(method, path, body) {
|
|
24
|
+
const auth = await getAuthHeader(this.config);
|
|
25
|
+
const url = `${this.config.apiUrl}${path}`;
|
|
26
|
+
const headers = {
|
|
27
|
+
Authorization: auth,
|
|
28
|
+
Accept: "application/json",
|
|
29
|
+
};
|
|
30
|
+
if (body !== undefined) {
|
|
31
|
+
headers["Content-Type"] = "application/json";
|
|
32
|
+
}
|
|
33
|
+
const res = await fetch(url, {
|
|
34
|
+
method,
|
|
35
|
+
headers,
|
|
36
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
37
|
+
});
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
// Pokus o parsování unified error response
|
|
40
|
+
const text = await res.text().catch(() => "");
|
|
41
|
+
let errorMessage = `API ${method} ${path} failed: ${res.status} ${res.statusText}`;
|
|
42
|
+
try {
|
|
43
|
+
const json = JSON.parse(text);
|
|
44
|
+
if (json.error?.message) {
|
|
45
|
+
errorMessage = json.error.message;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
if (text)
|
|
50
|
+
errorMessage += ` — ${text}`;
|
|
51
|
+
}
|
|
52
|
+
throw new Error(errorMessage);
|
|
53
|
+
}
|
|
54
|
+
// 204 No Content
|
|
55
|
+
if (res.status === 204) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
const json = await res.json();
|
|
59
|
+
if (!json.success) {
|
|
60
|
+
throw new Error(json.error?.message ?? `API ${method} ${path} failed`);
|
|
61
|
+
}
|
|
62
|
+
return json.data;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=api-client.js.map
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TickrConfig } from "./config.js";
|
|
2
|
+
interface TokenPair {
|
|
3
|
+
accessToken: string;
|
|
4
|
+
refreshToken: string;
|
|
5
|
+
expiresAt: number;
|
|
6
|
+
}
|
|
7
|
+
/** Přihlášení přes username/password — vrátí JWT token pair */
|
|
8
|
+
export declare function login(config: TickrConfig, username: string, password: string): Promise<TokenPair>;
|
|
9
|
+
/** Refresh JWT tokenu */
|
|
10
|
+
export declare function refreshToken(config: TickrConfig): Promise<TokenPair>;
|
|
11
|
+
/**
|
|
12
|
+
* Vrátí Authorization header value.
|
|
13
|
+
* Pokud config má apiKey (tk_...), použije ho přímo.
|
|
14
|
+
* Jinak použije JWT token (auto-refresh pokud expiruje).
|
|
15
|
+
*/
|
|
16
|
+
export declare function getAuthHeader(config: TickrConfig): Promise<string>;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=auth.d.ts.map
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
let currentTokens = null;
|
|
2
|
+
/** Přihlášení přes username/password — vrátí JWT token pair */
|
|
3
|
+
export async function login(config, username, password) {
|
|
4
|
+
const res = await fetch(`${config.apiUrl}/api/auth/login`, {
|
|
5
|
+
method: "POST",
|
|
6
|
+
headers: { "Content-Type": "application/json" },
|
|
7
|
+
body: JSON.stringify({ username, password }),
|
|
8
|
+
});
|
|
9
|
+
if (!res.ok) {
|
|
10
|
+
throw new Error(`Login failed: ${res.status} ${res.statusText}`);
|
|
11
|
+
}
|
|
12
|
+
const data = (await res.json());
|
|
13
|
+
currentTokens = {
|
|
14
|
+
accessToken: data.accessToken,
|
|
15
|
+
refreshToken: data.refreshToken,
|
|
16
|
+
expiresAt: Date.now() + data.expiresIn * 1000,
|
|
17
|
+
};
|
|
18
|
+
return currentTokens;
|
|
19
|
+
}
|
|
20
|
+
/** Refresh JWT tokenu */
|
|
21
|
+
export async function refreshToken(config) {
|
|
22
|
+
if (!currentTokens) {
|
|
23
|
+
throw new Error("No tokens to refresh — login first");
|
|
24
|
+
}
|
|
25
|
+
const res = await fetch(`${config.apiUrl}/api/auth/refresh`, {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: { "Content-Type": "application/json" },
|
|
28
|
+
body: JSON.stringify({ refreshToken: currentTokens.refreshToken }),
|
|
29
|
+
});
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
throw new Error(`Token refresh failed: ${res.status}`);
|
|
32
|
+
}
|
|
33
|
+
const data = (await res.json());
|
|
34
|
+
currentTokens = {
|
|
35
|
+
accessToken: data.accessToken,
|
|
36
|
+
refreshToken: data.refreshToken,
|
|
37
|
+
expiresAt: Date.now() + data.expiresIn * 1000,
|
|
38
|
+
};
|
|
39
|
+
return currentTokens;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Vrátí Authorization header value.
|
|
43
|
+
* Pokud config má apiKey (tk_...), použije ho přímo.
|
|
44
|
+
* Jinak použije JWT token (auto-refresh pokud expiruje).
|
|
45
|
+
*/
|
|
46
|
+
export async function getAuthHeader(config) {
|
|
47
|
+
// API key auth — jednoduché, bez expiry
|
|
48
|
+
if (config.apiKey) {
|
|
49
|
+
return config.apiKey;
|
|
50
|
+
}
|
|
51
|
+
// JWT auth — refresh pokud token brzy vyprší (30s buffer)
|
|
52
|
+
if (currentTokens && currentTokens.expiresAt - Date.now() < 30_000) {
|
|
53
|
+
await refreshToken(config);
|
|
54
|
+
}
|
|
55
|
+
if (!currentTokens) {
|
|
56
|
+
throw new Error("Not authenticated — set TICKR_API_KEY or call login()");
|
|
57
|
+
}
|
|
58
|
+
return `Bearer ${currentTokens.accessToken}`;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
/** Načte konfiguraci z env vars nebo ~/.tickr/config.json */
|
|
5
|
+
export function loadConfig() {
|
|
6
|
+
const apiUrl = process.env.TICKR_API_URL;
|
|
7
|
+
const apiKey = process.env.TICKR_API_KEY;
|
|
8
|
+
const defaultProject = process.env.TICKR_DEFAULT_PROJECT;
|
|
9
|
+
// Env vars mají prioritu
|
|
10
|
+
if (apiUrl && apiKey) {
|
|
11
|
+
return { apiUrl, apiKey, defaultProject };
|
|
12
|
+
}
|
|
13
|
+
// Fallback na config soubor
|
|
14
|
+
const configPath = join(homedir(), ".tickr", "config.json");
|
|
15
|
+
try {
|
|
16
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
17
|
+
const file = JSON.parse(raw);
|
|
18
|
+
return {
|
|
19
|
+
apiUrl: apiUrl || file.apiUrl || "https://localhost:6001",
|
|
20
|
+
apiKey: apiKey || file.apiKey || "",
|
|
21
|
+
defaultProject: defaultProject || file.defaultProject,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Config soubor neexistuje — použijeme defaults
|
|
26
|
+
return {
|
|
27
|
+
apiUrl: apiUrl || "https://localhost:6001",
|
|
28
|
+
apiKey: apiKey || "",
|
|
29
|
+
defaultProject,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Ticket, Project } from "./types.js";
|
|
2
|
+
/** Formátuje ticket do markdown pro resource/tool output */
|
|
3
|
+
export declare function formatTicketMarkdown(t: Ticket): string;
|
|
4
|
+
/** Formátuje projekt do markdown pro resource output */
|
|
5
|
+
export declare function formatProjectMarkdown(p: Project, recentTickets?: Array<{
|
|
6
|
+
displayNumber: string;
|
|
7
|
+
title: string;
|
|
8
|
+
status: string;
|
|
9
|
+
}>): string;
|
|
10
|
+
//# sourceMappingURL=formatters.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** Formátuje ticket do markdown pro resource/tool output */
|
|
2
|
+
export function formatTicketMarkdown(t) {
|
|
3
|
+
const lines = [
|
|
4
|
+
`# ${t.displayNumber}: ${t.title}`,
|
|
5
|
+
"",
|
|
6
|
+
`| Field | Value |`,
|
|
7
|
+
`|-------|-------|`,
|
|
8
|
+
`| Type | ${t.type} |`,
|
|
9
|
+
`| Status | ${t.status} |`,
|
|
10
|
+
`| Priority | ${t.priority} |`,
|
|
11
|
+
`| Scope | ${t.scope || "-"} |`,
|
|
12
|
+
`| Assignee | ${t.assigneeName || "Unassigned"} |`,
|
|
13
|
+
`| Estimate | ${t.estimate || "-"} |`,
|
|
14
|
+
`| Created | ${t.createdAt} |`,
|
|
15
|
+
`| Updated | ${t.updatedAt} |`,
|
|
16
|
+
];
|
|
17
|
+
if (t.affectedParts.length > 0) {
|
|
18
|
+
lines.push("", `**Affected parts:** ${t.affectedParts.join(", ")}`);
|
|
19
|
+
}
|
|
20
|
+
if (t.content) {
|
|
21
|
+
lines.push("", "---", "", t.content);
|
|
22
|
+
}
|
|
23
|
+
if (t.implementationItems.length > 0) {
|
|
24
|
+
lines.push("", "## Implementation Items", "", "| # | Area | Detail | Status | Note |", "|---|------|--------|--------|------|");
|
|
25
|
+
t.implementationItems.forEach((item, i) => {
|
|
26
|
+
lines.push(`| ${i} | ${item.area} | ${item.fileOrDetail} | ${item.status} | ${item.note || "-"} |`);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (t.comments.length > 0) {
|
|
30
|
+
lines.push("", "## Comments", "");
|
|
31
|
+
for (const c of t.comments) {
|
|
32
|
+
lines.push(`**${c.authorName}** (${c.createdAt}):`);
|
|
33
|
+
lines.push(c.text, "");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return lines.join("\n");
|
|
37
|
+
}
|
|
38
|
+
/** Formátuje projekt do markdown pro resource output */
|
|
39
|
+
export function formatProjectMarkdown(p, recentTickets) {
|
|
40
|
+
const lines = [
|
|
41
|
+
`# Project: ${p.name} (${p.slug})`,
|
|
42
|
+
"",
|
|
43
|
+
`**Prefix:** ${p.prefix}`,
|
|
44
|
+
`**Scope tags:** ${p.scopeTags.join(", ") || "-"}`,
|
|
45
|
+
`**Ticket types:** ${p.ticketTypes.join(", ") || "-"}`,
|
|
46
|
+
];
|
|
47
|
+
if (p.description) {
|
|
48
|
+
lines.push("", p.description);
|
|
49
|
+
}
|
|
50
|
+
if (recentTickets && recentTickets.length > 0) {
|
|
51
|
+
lines.push("", "## Recent Tickets", "");
|
|
52
|
+
for (const t of recentTickets) {
|
|
53
|
+
lines.push(`- ${t.displayNumber} [${t.status}] ${t.title}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return lines.join("\n");
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=formatters.js.map
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { loadConfig } from "./config.js";
|
|
5
|
+
import { ApiClient } from "./api-client.js";
|
|
6
|
+
// Tools
|
|
7
|
+
import { registerCreateTicket } from "./tools/create-ticket.js";
|
|
8
|
+
import { registerListTickets } from "./tools/list-tickets.js";
|
|
9
|
+
import { registerGetTicket } from "./tools/get-ticket.js";
|
|
10
|
+
import { registerUpdateTicket } from "./tools/update-ticket.js";
|
|
11
|
+
import { registerAddComment } from "./tools/add-comment.js";
|
|
12
|
+
import { registerUpdateImplementationItem } from "./tools/update-implementation-item.js";
|
|
13
|
+
import { registerSearchTickets } from "./tools/search-tickets.js";
|
|
14
|
+
import { registerTriageList } from "./tools/triage-list.js";
|
|
15
|
+
import { registerTriageAccept } from "./tools/triage-accept.js";
|
|
16
|
+
import { registerTriageReject } from "./tools/triage-reject.js";
|
|
17
|
+
import { registerListLabels } from "./tools/list-labels.js";
|
|
18
|
+
import { registerAddLabel } from "./tools/add-label.js";
|
|
19
|
+
import { registerRemoveLabel } from "./tools/remove-label.js";
|
|
20
|
+
import { registerListCycles } from "./tools/list-cycles.js";
|
|
21
|
+
import { registerAssignCycle } from "./tools/assign-cycle.js";
|
|
22
|
+
import { registerListEpics } from "./tools/list-epics.js";
|
|
23
|
+
import { registerAssignEpic } from "./tools/assign-epic.js";
|
|
24
|
+
import { registerPollDevQueue } from "./tools/poll-dev-queue.js";
|
|
25
|
+
import { registerCompleteDevTask } from "./tools/complete-dev-task.js";
|
|
26
|
+
// Resources
|
|
27
|
+
import { registerTicketResource } from "./resources/ticket-resource.js";
|
|
28
|
+
import { registerProjectResource } from "./resources/project-resource.js";
|
|
29
|
+
async function main() {
|
|
30
|
+
const config = loadConfig();
|
|
31
|
+
const api = new ApiClient(config);
|
|
32
|
+
const server = new McpServer({
|
|
33
|
+
name: "tickr",
|
|
34
|
+
version: "0.1.0",
|
|
35
|
+
});
|
|
36
|
+
// Registrace tools
|
|
37
|
+
registerCreateTicket(server, api);
|
|
38
|
+
registerListTickets(server, api);
|
|
39
|
+
registerGetTicket(server, api);
|
|
40
|
+
registerUpdateTicket(server, api);
|
|
41
|
+
registerAddComment(server, api);
|
|
42
|
+
registerUpdateImplementationItem(server, api);
|
|
43
|
+
registerSearchTickets(server, api);
|
|
44
|
+
registerTriageList(server, api);
|
|
45
|
+
registerTriageAccept(server, api);
|
|
46
|
+
registerTriageReject(server, api);
|
|
47
|
+
registerListLabels(server, api);
|
|
48
|
+
registerAddLabel(server, api);
|
|
49
|
+
registerRemoveLabel(server, api);
|
|
50
|
+
registerListCycles(server, api);
|
|
51
|
+
registerAssignCycle(server, api);
|
|
52
|
+
registerListEpics(server, api);
|
|
53
|
+
registerAssignEpic(server, api);
|
|
54
|
+
registerPollDevQueue(server, api);
|
|
55
|
+
registerCompleteDevTask(server, api);
|
|
56
|
+
// Registrace resources
|
|
57
|
+
registerTicketResource(server, api);
|
|
58
|
+
registerProjectResource(server, api);
|
|
59
|
+
// Spuštění na stdio transportu
|
|
60
|
+
const transport = new StdioServerTransport();
|
|
61
|
+
await server.connect(transport);
|
|
62
|
+
}
|
|
63
|
+
main().catch((err) => {
|
|
64
|
+
console.error("MCP server failed to start:", err);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { formatProjectMarkdown } from "../formatters.js";
|
|
2
|
+
export function registerProjectResource(server, api) {
|
|
3
|
+
server.resource("project", "tickr://project/{slug}", { description: "Get project overview with recent tickets" }, async (uri) => {
|
|
4
|
+
const slug = uri.pathname.split("/").pop();
|
|
5
|
+
const project = await api.get(`/api/projects/${slug}`);
|
|
6
|
+
const tickets = await api.get(`/api/projects/${slug}/tickets?limit=20`);
|
|
7
|
+
return {
|
|
8
|
+
contents: [
|
|
9
|
+
{
|
|
10
|
+
uri: uri.href,
|
|
11
|
+
mimeType: "text/markdown",
|
|
12
|
+
text: formatProjectMarkdown(project, tickets),
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=project-resource.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { formatTicketMarkdown } from "../formatters.js";
|
|
2
|
+
export function registerTicketResource(server, api) {
|
|
3
|
+
server.resource("ticket", "tickr://ticket/{number}", { description: "Get full ticket detail as markdown" }, async (uri) => {
|
|
4
|
+
const number = uri.pathname.split("/").pop();
|
|
5
|
+
const ticket = await api.get(`/api/tickets/${number}`);
|
|
6
|
+
return {
|
|
7
|
+
contents: [
|
|
8
|
+
{
|
|
9
|
+
uri: uri.href,
|
|
10
|
+
mimeType: "text/markdown",
|
|
11
|
+
text: formatTicketMarkdown(ticket),
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=ticket-resource.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerAddComment(server, api) {
|
|
3
|
+
server.tool("add_comment", "Add a comment to a ticket", {
|
|
4
|
+
number: z.string().describe("Ticket display number, e.g. 'TKR-42' (legacy) or 'TKR-BUG-0042' (typed)"),
|
|
5
|
+
text: z.string().describe("Comment text (markdown)"),
|
|
6
|
+
}, async (params) => {
|
|
7
|
+
try {
|
|
8
|
+
await api.post(`/api/tickets/${params.number}/comments`, {
|
|
9
|
+
text: params.text,
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
content: [
|
|
13
|
+
{ type: "text", text: `Comment added to ${params.number}` },
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
20
|
+
isError: true,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=add-comment.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerAddLabel(server, api) {
|
|
3
|
+
server.tool("add_label", "Add a label to a ticket", {
|
|
4
|
+
number: z.string().describe("Ticket display number, e.g. 'TKR-42' (legacy) or 'TKR-BUG-0042' (typed)"),
|
|
5
|
+
label_id: z.string().describe("UUID of the label to add"),
|
|
6
|
+
}, async (params) => {
|
|
7
|
+
try {
|
|
8
|
+
await api.post(`/api/tickets/${params.number}/labels/${params.label_id}`);
|
|
9
|
+
return {
|
|
10
|
+
content: [{ type: "text", text: `Label added to ${params.number}` }],
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
return {
|
|
15
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
16
|
+
isError: true,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=add-label.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerAssignCycle(server, api) {
|
|
3
|
+
server.tool("assign_cycle", "Assign a ticket to a cycle (sprint)", {
|
|
4
|
+
project: z.string().describe("Project slug"),
|
|
5
|
+
cycle_id: z.string().describe("UUID of the cycle"),
|
|
6
|
+
number: z.string().describe("Ticket display number, e.g. 'TKR-42' (legacy) or 'TKR-BUG-0042' (typed)"),
|
|
7
|
+
}, async (params) => {
|
|
8
|
+
try {
|
|
9
|
+
await api.post(`/api/projects/${params.project}/cycles/${params.cycle_id}/tickets/${params.number}`);
|
|
10
|
+
return {
|
|
11
|
+
content: [{ type: "text", text: `${params.number} assigned to cycle ${params.cycle_id}` }],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
return {
|
|
16
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
17
|
+
isError: true,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=assign-cycle.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerAssignEpic(server, api) {
|
|
3
|
+
server.tool("assign_epic", "Assign a ticket to an epic", {
|
|
4
|
+
project: z.string().describe("Project slug"),
|
|
5
|
+
epic_id: z.string().describe("UUID of the epic"),
|
|
6
|
+
number: z.string().describe("Ticket display number, e.g. 'TKR-42' (legacy) or 'TKR-BUG-0042' (typed)"),
|
|
7
|
+
}, async (params) => {
|
|
8
|
+
try {
|
|
9
|
+
await api.post(`/api/projects/${params.project}/epics/${params.epic_id}/tickets/${params.number}`);
|
|
10
|
+
return {
|
|
11
|
+
content: [{ type: "text", text: `${params.number} assigned to epic ${params.epic_id}` }],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
return {
|
|
16
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
17
|
+
isError: true,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=assign-epic.js.map
|