punchline-mcp 1.4.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/client.js +14 -1
- package/dist/server.js +5 -1
- package/dist/tools/tickets.js +6 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ claude mcp add punchline --env PUNCHLINE_API_KEY=pnchl_live_... -- node /path/to
|
|
|
49
49
|
| Variable | Required | Default | Notes |
|
|
50
50
|
|---|---|---|---|
|
|
51
51
|
| `PUNCHLINE_API_KEY` | Yes | — | A `pnchl_live_…` project key or a `pnchl_user_…` user provisioning key. Sent as `X-Punchline-Key`. |
|
|
52
|
-
| `PUNCHLINE_BASE_URL` | No | `https://proding.co:8050` | Punchline API
|
|
52
|
+
| `PUNCHLINE_BASE_URL` | No | `https://proding.co:8050` | Punchline API base URL. A path prefix (e.g. `https://host/punchline`) is kept in front of `/api/v1/…` *(1.4.1+)*. |
|
|
53
53
|
|
|
54
54
|
Any other key prefix, or a missing key, fails at startup with a `ConfigError` on stderr.
|
|
55
55
|
|
|
@@ -71,7 +71,7 @@ the server twice under two names with two different keys, e.g. `punchline` (proj
|
|
|
71
71
|
|
|
72
72
|
| Tool | Available with | Description |
|
|
73
73
|
|---|---|---|
|
|
74
|
-
| `create_ticket` | project key | Create a ticket. `reporter_email` is required. Supports idempotent retries via `idempotency_key`, sent to the API as the `idempotencyKey` body field. Optional `custom_fields` (sent as `customFields`) is an array of `{ name, value }` pairs; `name` must match a custom-field definition configured on the project or the request is rejected with 400. |
|
|
74
|
+
| `create_ticket` | project key | Create a ticket. `reporter_email` is required. Supports idempotent retries via `idempotency_key`, sent to the API as the `idempotencyKey` body field. Optional `custom_fields` (sent as `customFields`) is an array of `{ name, value }` pairs; `name` must match a custom-field definition configured on the project or the request is rejected with 400. `description` is at most 16 000 characters, the API's limit *(1.4.1+; was wrongly 50 000)*. |
|
|
75
75
|
| `list_tickets` | project key | List tickets visible to the key (own tickets, or the whole project with `read:all`). Paginated. |
|
|
76
76
|
| `get_ticket` | project key | Fetch one ticket by display id (e.g. `ACM-7`). |
|
|
77
77
|
| `update_ticket` | project key | Change a ticket's workflow status, its priority, its labels, and/or customer-facing public summary. Setting `priority` records a **triage ruling** — confirming the current value counts as one — which a project enforcing triage requires before a ticket can close. Priority is applied before status, so one call can triage and close. A ticket created through this API arrives untriaged: the priority sent at creation is the reporter's proposal, not a ruling. *(1.2.0+)* `labels` is a **replace-set** applied before everything else: omit it to leave the labels alone, send the full list to change them, send `[]` to clear them — unknown names are created, exactly as at `create_ticket`; a blank name is refused by both tools rather than silently dropped, and surrounding whitespace is stripped server-side. *(1.3.0+)* `title` and `description` may also be patched: `title` is trimmed and rejected if blank, `description` **replaces only the reporter-authored body**, preserving the `**Reported by** … via …` provenance header the ticket was created with — send `""` to clear the body and keep just the header. Both are applied before `labels`, `priority` and `status`. *(1.4.0+)* |
|
package/dist/client.js
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
export const DEFAULT_BASE_URL = 'https://proding.co:8050';
|
|
2
|
+
/**
|
|
3
|
+
* Joins an API path onto the base URL keeping any path prefix the base carries. `new URL('/api/v1/x', base)`
|
|
4
|
+
* would resolve the absolute path against the origin and drop a prefix such as `https://host/punchline`.
|
|
5
|
+
*/
|
|
6
|
+
export function joinUrl(baseUrl, path) {
|
|
7
|
+
const base = new URL(baseUrl);
|
|
8
|
+
const prefix = base.pathname.replace(/\/+$/, '');
|
|
9
|
+
const suffix = path.startsWith('/') ? path : `/${path}`;
|
|
10
|
+
base.pathname = `${prefix}${suffix}`;
|
|
11
|
+
base.search = '';
|
|
12
|
+
base.hash = '';
|
|
13
|
+
return base;
|
|
14
|
+
}
|
|
2
15
|
export class ApiError extends Error {
|
|
3
16
|
status;
|
|
4
17
|
title;
|
|
@@ -25,7 +38,7 @@ export class PunchlineClient {
|
|
|
25
38
|
this.baseUrl = baseUrl;
|
|
26
39
|
}
|
|
27
40
|
async request(method, path, opts = {}) {
|
|
28
|
-
const url =
|
|
41
|
+
const url = joinUrl(this.baseUrl, path);
|
|
29
42
|
for (const [k, v] of Object.entries(opts.query ?? {})) {
|
|
30
43
|
if (v !== undefined)
|
|
31
44
|
url.searchParams.set(k, String(v));
|
package/dist/server.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
1
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
3
|
import { DEFAULT_BASE_URL, PunchlineClient } from './client.js';
|
|
3
4
|
import { registerCommunicationTools, COMMUNICATION_TOOL_NAMES } from './tools/communications.js';
|
|
@@ -5,6 +6,9 @@ import { registerLinkTools, LINK_TOOL_NAMES } from './tools/links.js';
|
|
|
5
6
|
import { registerMetaTools, META_TOOL_NAMES } from './tools/meta.js';
|
|
6
7
|
import { registerProvisioningTools, PROVISIONING_TOOL_NAMES } from './tools/provisioning.js';
|
|
7
8
|
import { registerTicketTools, TICKET_TOOL_NAMES } from './tools/tickets.js';
|
|
9
|
+
// package.json sits one level above both src/ (tests) and dist/ (the built and published package; npm always
|
|
10
|
+
// ships package.json), so the same relative path works everywhere.
|
|
11
|
+
export const SERVER_VERSION = createRequire(import.meta.url)('../package.json').version;
|
|
8
12
|
export const PROJECT_KEY_PREFIX = 'pnchl_live_';
|
|
9
13
|
export const USER_KEY_PREFIX = 'pnchl_user_';
|
|
10
14
|
export class ConfigError extends Error {
|
|
@@ -20,7 +24,7 @@ export function parseConfig(env) {
|
|
|
20
24
|
return { apiKey, baseUrl: env.PUNCHLINE_BASE_URL?.trim() || DEFAULT_BASE_URL };
|
|
21
25
|
}
|
|
22
26
|
export function buildServer(config) {
|
|
23
|
-
const server = new McpServer({ name: 'punchline', version:
|
|
27
|
+
const server = new McpServer({ name: 'punchline', version: SERVER_VERSION });
|
|
24
28
|
const client = new PunchlineClient(config.apiKey, config.baseUrl);
|
|
25
29
|
const registeredTools = [];
|
|
26
30
|
if (config.apiKey.startsWith(USER_KEY_PREFIX)) {
|
package/dist/tools/tickets.js
CHANGED
|
@@ -18,6 +18,8 @@ export const CROSS_PROJECT_LINK_NOTE = ' Links to tickets in other projects are
|
|
|
18
18
|
const LABEL_NAME = z.string().max(100)
|
|
19
19
|
.refine((n) => n.trim().length > 0, { message: 'Label names must not be blank' });
|
|
20
20
|
export const TICKETS_PATH = '/api/v1/external/tickets';
|
|
21
|
+
/** ExternalTicketRequest / ExternalTicketPatchRequest: `@Size(max = 16_000) String description`. */
|
|
22
|
+
export const DESCRIPTION_MAX = 16_000;
|
|
21
23
|
export function ticketPath(displayId) {
|
|
22
24
|
return `${TICKETS_PATH}/${encodeURIComponent(displayId)}`;
|
|
23
25
|
}
|
|
@@ -31,7 +33,8 @@ export function registerTicketTools(server, client) {
|
|
|
31
33
|
inputSchema: {
|
|
32
34
|
title: z.string().min(1).max(500).describe('Ticket title'),
|
|
33
35
|
reporter_email: z.string().min(1).email().max(320).describe('Reporter email (required by the API)'),
|
|
34
|
-
description: z.string().max(
|
|
36
|
+
description: z.string().max(DESCRIPTION_MAX).optional()
|
|
37
|
+
.describe('Ticket description (markdown), at most 16 000 characters.'),
|
|
35
38
|
type: z.enum(TICKET_TYPES).optional().describe('Defaults to BUG'),
|
|
36
39
|
priority: z.enum(TICKET_PRIORITIES).optional().describe('Defaults to MEDIUM'),
|
|
37
40
|
labels: z.array(LABEL_NAME).max(100).optional().describe('Label names; unknown labels are created'),
|
|
@@ -107,8 +110,8 @@ export function registerTicketTools(server, client) {
|
|
|
107
110
|
+ ' Omit to leave them untouched, [] to clear them.'),
|
|
108
111
|
title: z.string().min(1).max(500).optional()
|
|
109
112
|
.describe('Replaces the ticket title. Must not be blank.'),
|
|
110
|
-
description: z.string().max(
|
|
111
|
-
.describe('Replaces the reporter-authored body of the description. The'
|
|
113
|
+
description: z.string().max(DESCRIPTION_MAX).optional()
|
|
114
|
+
.describe('Replaces the reporter-authored body of the description (at most 16 000 characters). The'
|
|
112
115
|
+ ' "Reported by … via …" header a ticket was created with is preserved;'
|
|
113
116
|
+ ' pass "" to clear the body and keep only that header.'),
|
|
114
117
|
},
|