@omnicoreos/planka-mcp 0.2.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 +86 -0
- package/CONTRIBUTING.md +37 -0
- package/CREDITS.md +39 -0
- package/LICENSE +22 -0
- package/README.es.md +213 -0
- package/README.md +213 -0
- package/dist/client.d.ts +96 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +281 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +85 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +226 -0
- package/dist/errors.js.map +1 -0
- package/dist/identity.generated.d.ts +3 -0
- package/dist/identity.generated.d.ts.map +1 -0
- package/dist/identity.generated.js +4 -0
- package/dist/identity.generated.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/dist/operations/attachments.d.ts +34 -0
- package/dist/operations/attachments.d.ts.map +1 -0
- package/dist/operations/attachments.js +89 -0
- package/dist/operations/attachments.js.map +1 -0
- package/dist/operations/board-id.d.ts +9 -0
- package/dist/operations/board-id.d.ts.map +1 -0
- package/dist/operations/board-id.js +86 -0
- package/dist/operations/board-id.js.map +1 -0
- package/dist/operations/boards.d.ts +143 -0
- package/dist/operations/boards.d.ts.map +1 -0
- package/dist/operations/boards.js +391 -0
- package/dist/operations/boards.js.map +1 -0
- package/dist/operations/cards.d.ts +36 -0
- package/dist/operations/cards.d.ts.map +1 -0
- package/dist/operations/cards.js +81 -0
- package/dist/operations/cards.js.map +1 -0
- package/dist/operations/comments.d.ts +28 -0
- package/dist/operations/comments.d.ts.map +1 -0
- package/dist/operations/comments.js +53 -0
- package/dist/operations/comments.js.map +1 -0
- package/dist/operations/labels.d.ts +60 -0
- package/dist/operations/labels.d.ts.map +1 -0
- package/dist/operations/labels.js +146 -0
- package/dist/operations/labels.js.map +1 -0
- package/dist/operations/lists.d.ts +15 -0
- package/dist/operations/lists.d.ts.map +1 -0
- package/dist/operations/lists.js +40 -0
- package/dist/operations/lists.js.map +1 -0
- package/dist/operations/projects.d.ts +24 -0
- package/dist/operations/projects.d.ts.map +1 -0
- package/dist/operations/projects.js +61 -0
- package/dist/operations/projects.js.map +1 -0
- package/dist/operations/tasks.d.ts +30 -0
- package/dist/operations/tasks.d.ts.map +1 -0
- package/dist/operations/tasks.js +111 -0
- package/dist/operations/tasks.js.map +1 -0
- package/dist/schemas/entities.d.ts +389 -0
- package/dist/schemas/entities.d.ts.map +1 -0
- package/dist/schemas/entities.js +202 -0
- package/dist/schemas/entities.js.map +1 -0
- package/dist/schemas/requests.d.ts +539 -0
- package/dist/schemas/requests.d.ts.map +1 -0
- package/dist/schemas/requests.js +187 -0
- package/dist/schemas/requests.js.map +1 -0
- package/dist/schemas/responses.d.ts +2626 -0
- package/dist/schemas/responses.d.ts.map +1 -0
- package/dist/schemas/responses.js +75 -0
- package/dist/schemas/responses.js.map +1 -0
- package/dist/tools/attachments.d.ts +270 -0
- package/dist/tools/attachments.d.ts.map +1 -0
- package/dist/tools/attachments.js +247 -0
- package/dist/tools/attachments.js.map +1 -0
- package/dist/tools/cards.d.ts +401 -0
- package/dist/tools/cards.d.ts.map +1 -0
- package/dist/tools/cards.js +368 -0
- package/dist/tools/cards.js.map +1 -0
- package/dist/tools/comments.d.ts +217 -0
- package/dist/tools/comments.d.ts.map +1 -0
- package/dist/tools/comments.js +208 -0
- package/dist/tools/comments.js.map +1 -0
- package/dist/tools/index.d.ts +1293 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +62 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/labels.d.ts +214 -0
- package/dist/tools/labels.d.ts.map +1 -0
- package/dist/tools/labels.js +302 -0
- package/dist/tools/labels.js.map +1 -0
- package/dist/tools/lists.d.ts +117 -0
- package/dist/tools/lists.d.ts.map +1 -0
- package/dist/tools/lists.js +178 -0
- package/dist/tools/lists.js.map +1 -0
- package/dist/tools/navigation.d.ts +106 -0
- package/dist/tools/navigation.d.ts.map +1 -0
- package/dist/tools/navigation.js +151 -0
- package/dist/tools/navigation.js.map +1 -0
- package/dist/tools/queries.d.ts +380 -0
- package/dist/tools/queries.d.ts.map +1 -0
- package/dist/tools/queries.js +256 -0
- package/dist/tools/queries.js.map +1 -0
- package/dist/tools/tasks.d.ts +223 -0
- package/dist/tools/tasks.d.ts.map +1 -0
- package/dist/tools/tasks.js +169 -0
- package/dist/tools/tasks.js.map +1 -0
- package/docs/planka-2x-gotchas.md +192 -0
- package/docs/tools.md +659 -0
- package/docs/troubleshooting.md +138 -0
- package/package.json +71 -0
- package/project.identity.json +7 -0
- package/scripts/bootstrap-board.mjs +160 -0
- package/scripts/lib/planka-api.mjs +244 -0
- package/scripts/lib/workflow-template.mjs +26 -0
- package/scripts/setup.mjs +236 -0
- package/scripts/setup.sh +48 -0
- package/scripts/sync-identity.mjs +57 -0
- package/server.json +43 -0
- package/tests/smoke/planka-smoke.mjs +920 -0
- package/workflow/README.md +232 -0
- package/workflow/board-template.md +115 -0
- package/workflow/skills/planka-close-card/SKILL.md +81 -0
- package/workflow/skills/planka-orchestrator/SKILL.md +126 -0
- package/workflow/worktrees/README.md +104 -0
- package/workflow/worktrees/wt.conf.example +24 -0
- package/workflow/worktrees/wt.sh +345 -0
package/dist/errors.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed error classes for PLANKA API operations.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
export class PlankaError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
status;
|
|
8
|
+
details;
|
|
9
|
+
constructor(message, code, status, details) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.details = details;
|
|
14
|
+
this.name = "PlankaError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class PlankaAuthError extends PlankaError {
|
|
18
|
+
constructor(message = "Authentication failed") {
|
|
19
|
+
super(message, "AUTH_FAILED", 401);
|
|
20
|
+
this.name = "PlankaAuthError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class PlankaNotFoundError extends PlankaError {
|
|
24
|
+
constructor(resource, id) {
|
|
25
|
+
super(`${resource} not found: ${id}`, "NOT_FOUND", 404);
|
|
26
|
+
this.name = "PlankaNotFoundError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class PlankaValidationError extends PlankaError {
|
|
30
|
+
constructor(message, details) {
|
|
31
|
+
super(message, "VALIDATION_ERROR", 422, details);
|
|
32
|
+
this.name = "PlankaValidationError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export class PlankaPermissionError extends PlankaError {
|
|
36
|
+
constructor(message = "Insufficient permissions") {
|
|
37
|
+
super(message, "PERMISSION_DENIED", 403);
|
|
38
|
+
this.name = "PlankaPermissionError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export class PlankaConfigError extends PlankaError {
|
|
42
|
+
constructor(message) {
|
|
43
|
+
super(message, "CONFIG_ERROR", 0);
|
|
44
|
+
this.name = "PlankaConfigError";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export class PlankaNetworkError extends PlankaError {
|
|
48
|
+
constructor(message, details) {
|
|
49
|
+
super(message, "NETWORK_ERROR", 0, details);
|
|
50
|
+
this.name = "PlankaNetworkError";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Factory function to create typed errors from API responses.
|
|
55
|
+
* @param status HTTP status code
|
|
56
|
+
* @param body Response body (parsed JSON or null)
|
|
57
|
+
* @param context Optional context (e.g., "GET /api/cards/123")
|
|
58
|
+
*/
|
|
59
|
+
export function createPlankaError(status, body, context) {
|
|
60
|
+
const message = typeof body === "object" && body !== null && "message" in body
|
|
61
|
+
? String(body.message)
|
|
62
|
+
: "Unknown error";
|
|
63
|
+
const contextSuffix = context ? ` (${context})` : "";
|
|
64
|
+
switch (status) {
|
|
65
|
+
case 401:
|
|
66
|
+
return new PlankaAuthError(message + contextSuffix);
|
|
67
|
+
case 403:
|
|
68
|
+
return new PlankaPermissionError(message + contextSuffix);
|
|
69
|
+
case 404:
|
|
70
|
+
return new PlankaNotFoundError("Resource", context || "unknown");
|
|
71
|
+
case 422:
|
|
72
|
+
return new PlankaValidationError(message + contextSuffix, body);
|
|
73
|
+
default:
|
|
74
|
+
return new PlankaError(message + contextSuffix, "API_ERROR", status, body);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Type guard for PlankaError instances.
|
|
79
|
+
*/
|
|
80
|
+
export function isPlankaError(error) {
|
|
81
|
+
return error instanceof PlankaError;
|
|
82
|
+
}
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Input validation — errors an agent can act on
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
/**
|
|
87
|
+
* An operation's INPUT failed validation.
|
|
88
|
+
*
|
|
89
|
+
* Deliberately NOT a subclass of PlankaError: every tool catches PlankaError
|
|
90
|
+
* to report Planka API failures, and this error has to travel untouched to the
|
|
91
|
+
* dispatcher in `src/index.ts` — the only place that knows the tool name and
|
|
92
|
+
* its inputSchema, and therefore the only place that can build the actionable
|
|
93
|
+
* message.
|
|
94
|
+
*/
|
|
95
|
+
export class PlankaInputError extends Error {
|
|
96
|
+
issues;
|
|
97
|
+
received;
|
|
98
|
+
constructor(issues, received) {
|
|
99
|
+
super(`Invalid input: ${issues
|
|
100
|
+
.map((issue) => `${fieldLabel(issue.path)}: ${issue.message}`)
|
|
101
|
+
.join("; ")}`);
|
|
102
|
+
this.issues = issues;
|
|
103
|
+
this.received = received;
|
|
104
|
+
this.name = "PlankaInputError";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Validates an operation's input against its schema.
|
|
109
|
+
* Replaces `Schema.parse(input)`: instead of a bare ZodError it throws a
|
|
110
|
+
* PlankaInputError that keeps what was received, so it can be shown back.
|
|
111
|
+
*/
|
|
112
|
+
export function parseInput(schema, input) {
|
|
113
|
+
const result = schema.safeParse(input);
|
|
114
|
+
if (!result.success) {
|
|
115
|
+
throw new PlankaInputError(result.error.issues, input);
|
|
116
|
+
}
|
|
117
|
+
return result.data;
|
|
118
|
+
}
|
|
119
|
+
/** Type guard for Zod validation errors (response-shape failures). */
|
|
120
|
+
export function isZodError(error) {
|
|
121
|
+
return error instanceof z.ZodError;
|
|
122
|
+
}
|
|
123
|
+
const MAX_ARGS_PREVIEW = 400;
|
|
124
|
+
const MAX_VALUE_PREVIEW = 120;
|
|
125
|
+
/** Serializes a value for display inside an error, clipped. */
|
|
126
|
+
function preview(value, max = MAX_ARGS_PREVIEW) {
|
|
127
|
+
if (value === undefined)
|
|
128
|
+
return "undefined";
|
|
129
|
+
let text;
|
|
130
|
+
try {
|
|
131
|
+
text = JSON.stringify(value) ?? String(value);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
text = String(value);
|
|
135
|
+
}
|
|
136
|
+
return text.length > max ? `${text.slice(0, max)}… (truncated)` : text;
|
|
137
|
+
}
|
|
138
|
+
/** Zod's `path: []` orients nobody; name it explicitly. */
|
|
139
|
+
function fieldLabel(path) {
|
|
140
|
+
return path.length > 0 ? path.join(".") : "(whole arguments object)";
|
|
141
|
+
}
|
|
142
|
+
function valueAtPath(root, path) {
|
|
143
|
+
let current = root;
|
|
144
|
+
for (const key of path) {
|
|
145
|
+
if (current === null || typeof current !== "object")
|
|
146
|
+
return undefined;
|
|
147
|
+
current = current[key];
|
|
148
|
+
}
|
|
149
|
+
return current;
|
|
150
|
+
}
|
|
151
|
+
/** What the tool's JSON inputSchema declares for a field, when it declares it. */
|
|
152
|
+
function propertyInfo(properties, field) {
|
|
153
|
+
const property = properties?.[field];
|
|
154
|
+
if (!property || typeof property !== "object") {
|
|
155
|
+
return { type: "", description: "" };
|
|
156
|
+
}
|
|
157
|
+
const { type, description } = property;
|
|
158
|
+
return {
|
|
159
|
+
type: typeof type === "string" ? ` (${type})` : "",
|
|
160
|
+
description: typeof description === "string" && description.length > 0
|
|
161
|
+
? ` — ${description}`
|
|
162
|
+
: "",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The validation message the model sees: which tool failed, which field, what
|
|
167
|
+
* was expected and what arrived.
|
|
168
|
+
*/
|
|
169
|
+
export function formatInputError(toolName, error, schema) {
|
|
170
|
+
const lines = error.issues.map((issue) => {
|
|
171
|
+
const label = fieldLabel(issue.path);
|
|
172
|
+
const value = valueAtPath(error.received, issue.path);
|
|
173
|
+
const suffix = value === undefined
|
|
174
|
+
? issue.path.length > 0
|
|
175
|
+
? " (field missing)"
|
|
176
|
+
: " (nothing received)"
|
|
177
|
+
: ` (received: ${preview(value, MAX_VALUE_PREVIEW)})`;
|
|
178
|
+
const info = issue.path.length === 1
|
|
179
|
+
? propertyInfo(schema?.properties, String(issue.path[0]))
|
|
180
|
+
: { type: "", description: "" };
|
|
181
|
+
return ` - ${label}${info.type}: ${issue.message}${suffix}${info.description}`;
|
|
182
|
+
});
|
|
183
|
+
const out = [`Invalid arguments for ${toolName}:`, ...lines];
|
|
184
|
+
if (schema?.required && schema.required.length > 0) {
|
|
185
|
+
out.push(`Required fields for ${toolName}: ${schema.required.join(", ")}`);
|
|
186
|
+
}
|
|
187
|
+
out.push(`Arguments received: ${preview(error.received)}`);
|
|
188
|
+
return out.join("\n");
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Required fields that never arrived, detected before entering the handler.
|
|
192
|
+
* Without this a missing `tasks` dies as "Cannot read properties of undefined
|
|
193
|
+
* (reading 'map')", which names neither the tool nor the field.
|
|
194
|
+
*/
|
|
195
|
+
export function formatMissingArgsError(toolName, missing, args, schema) {
|
|
196
|
+
const out = [
|
|
197
|
+
`Invalid arguments for ${toolName}: missing required field(s): ${missing.join(", ")}.`,
|
|
198
|
+
...missing.map((field) => {
|
|
199
|
+
const info = propertyInfo(schema?.properties, field);
|
|
200
|
+
return ` - ${field}${info.type}${info.description}`;
|
|
201
|
+
}),
|
|
202
|
+
];
|
|
203
|
+
if (schema?.required && schema.required.length > 0) {
|
|
204
|
+
out.push(`Required fields for ${toolName}: ${schema.required.join(", ")}`);
|
|
205
|
+
}
|
|
206
|
+
out.push(`Arguments received: ${preview(args)}`);
|
|
207
|
+
return out.join("\n");
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* A ZodError that did NOT come from the input: Planka's response did not match
|
|
211
|
+
* the schema. Saying so explicitly keeps the model from assuming it got the
|
|
212
|
+
* arguments wrong.
|
|
213
|
+
*/
|
|
214
|
+
export function formatResponseShapeError(toolName, error) {
|
|
215
|
+
const lines = error.issues
|
|
216
|
+
.slice(0, 5)
|
|
217
|
+
.map((issue) => ` - ${fieldLabel(issue.path)}: ${issue.message}`);
|
|
218
|
+
const extra = error.issues.length > 5 ? [` … and ${error.issues.length - 5} more`] : [];
|
|
219
|
+
return [
|
|
220
|
+
`PLANKA returned an unexpected response shape while running ${toolName}. ` +
|
|
221
|
+
`This is an API/server mismatch, not a problem with the arguments:`,
|
|
222
|
+
...lines,
|
|
223
|
+
...extra,
|
|
224
|
+
].join("\n");
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.generated.d.ts","sourceRoot":"","sources":["../src/identity.generated.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,WAAW,oCAAoC,CAAC;AAC7D,eAAO,MAAM,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.generated.js","sourceRoot":"","sources":["../src/identity.generated.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,MAAM,CAAC,MAAM,WAAW,GAAG,iCAAiC,CAAC;AAC7D,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PLANKA MCP Server
|
|
4
|
+
*
|
|
5
|
+
* A Model Context Protocol server for PLANKA kanban boards.
|
|
6
|
+
* Provides tools for managing projects, boards, cards, tasks, labels, and comments.
|
|
7
|
+
*/
|
|
8
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
11
|
+
import { getToolDefinitions, getTool, allTools, findMissingRequiredArgs, } from "./tools/index.js";
|
|
12
|
+
import { PlankaError, PlankaConfigError, PlankaInputError, formatInputError, formatMissingArgsError, formatResponseShapeError, isZodError, } from "./errors.js";
|
|
13
|
+
import { SERVER_NAME, SERVER_VERSION } from "./identity.generated.js";
|
|
14
|
+
/** Standard error response back to the model. */
|
|
15
|
+
function errorResult(text) {
|
|
16
|
+
return {
|
|
17
|
+
content: [{ type: "text", text }],
|
|
18
|
+
isError: true,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Main entry point.
|
|
23
|
+
*/
|
|
24
|
+
async function main() {
|
|
25
|
+
// Create the MCP server
|
|
26
|
+
const server = new Server({
|
|
27
|
+
name: SERVER_NAME,
|
|
28
|
+
version: SERVER_VERSION,
|
|
29
|
+
}, {
|
|
30
|
+
capabilities: {
|
|
31
|
+
tools: {},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
// Handler for listing available tools
|
|
35
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
36
|
+
return {
|
|
37
|
+
tools: getToolDefinitions(),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
// Handler for tool calls
|
|
41
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
42
|
+
const { name, arguments: args } = request.params;
|
|
43
|
+
const tool = getTool(name);
|
|
44
|
+
if (!tool) {
|
|
45
|
+
return errorResult(`Unknown tool: ${name}. Available tools: ${allTools
|
|
46
|
+
.map((t) => t.name)
|
|
47
|
+
.join(", ")}`);
|
|
48
|
+
}
|
|
49
|
+
// Presence check before entering the handler: a missing required field has
|
|
50
|
+
// to name the tool and the field, not die somewhere inside.
|
|
51
|
+
const missing = findMissingRequiredArgs(tool, args);
|
|
52
|
+
if (missing.length > 0) {
|
|
53
|
+
return errorResult(formatMissingArgsError(name, missing, args, tool.inputSchema));
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const result = await tool.handler(args || {});
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
// INPUT validation: name the tool, the field and what arrived.
|
|
61
|
+
if (error instanceof PlankaInputError) {
|
|
62
|
+
return errorResult(formatInputError(name, error, tool.inputSchema));
|
|
63
|
+
}
|
|
64
|
+
// A loose ZodError means Planka's RESPONSE did not match the schema.
|
|
65
|
+
// Name it separately so the model does not assume it got the args wrong.
|
|
66
|
+
if (isZodError(error)) {
|
|
67
|
+
return errorResult(formatResponseShapeError(name, error));
|
|
68
|
+
}
|
|
69
|
+
// Handle configuration errors specifically
|
|
70
|
+
if (error instanceof PlankaConfigError) {
|
|
71
|
+
return errorResult(`Configuration error: ${error.message}\n\nRequired environment variables:\n- PLANKA_BASE_URL\n- PLANKA_AGENT_EMAIL\n- PLANKA_AGENT_PASSWORD`);
|
|
72
|
+
}
|
|
73
|
+
// Handle other PLANKA errors
|
|
74
|
+
if (error instanceof PlankaError) {
|
|
75
|
+
return errorResult(`PLANKA error: ${error.message}`);
|
|
76
|
+
}
|
|
77
|
+
// Log unexpected errors and return generic message
|
|
78
|
+
console.error(`Error in tool ${name}:`, error);
|
|
79
|
+
return errorResult(`Unexpected error while running ${name}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
// Connect to stdio transport
|
|
83
|
+
const transport = new StdioServerTransport();
|
|
84
|
+
await server.connect(transport);
|
|
85
|
+
// Log startup (to stderr so it doesn't interfere with MCP protocol)
|
|
86
|
+
console.error("PLANKA MCP server started");
|
|
87
|
+
}
|
|
88
|
+
// Run the server
|
|
89
|
+
main().catch((error) => {
|
|
90
|
+
console.error("Fatal error:", error);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Attachment } from "../schemas/entities.js";
|
|
2
|
+
import { AttachmentIdInput, CardIdInput, UploadAttachmentInput } from "../schemas/requests.js";
|
|
3
|
+
export declare function guessMimeType(input: {
|
|
4
|
+
filePath: string;
|
|
5
|
+
}): string;
|
|
6
|
+
/**
|
|
7
|
+
* Get all attachments on a card (from the card endpoint's `included`).
|
|
8
|
+
*/
|
|
9
|
+
export declare function getAttachmentsForCard(input: CardIdInput): Promise<Attachment[]>;
|
|
10
|
+
/**
|
|
11
|
+
* Upload a local file as a card attachment.
|
|
12
|
+
*/
|
|
13
|
+
export declare function uploadAttachment(input: UploadAttachmentInput): Promise<Attachment>;
|
|
14
|
+
/**
|
|
15
|
+
* Delete an attachment.
|
|
16
|
+
*/
|
|
17
|
+
export declare function deleteAttachment(input: AttachmentIdInput): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* An attachment's download URL. 2.0 exposes it as data.url; when it is
|
|
20
|
+
* missing, fall back to the /attachments/:id/download/:name pattern.
|
|
21
|
+
*/
|
|
22
|
+
export declare function attachmentDownloadUrl(input: {
|
|
23
|
+
attachment: Attachment;
|
|
24
|
+
}): string;
|
|
25
|
+
/**
|
|
26
|
+
* Download an attachment's binary content.
|
|
27
|
+
*/
|
|
28
|
+
export declare function downloadAttachment(input: {
|
|
29
|
+
attachment: Attachment;
|
|
30
|
+
}): Promise<{
|
|
31
|
+
bytes: Uint8Array;
|
|
32
|
+
mimeType: string;
|
|
33
|
+
}>;
|
|
34
|
+
//# sourceMappingURL=attachments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../../src/operations/attachments.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAMpD,OAAO,EAEL,iBAAiB,EAEjB,WAAW,EAEX,qBAAqB,EACtB,MAAM,wBAAwB,CAAC;AAmBhC,wBAAgB,aAAa,CAAC,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAKjE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,UAAU,EAAE,CAAC,CAQvB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,UAAU,CAAC,CAqBrB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,UAAU,EAAE,UAAU,CAAC;CACxB,GAAG,MAAM,CAOT;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,UAAU,EAAE,UAAU,CAAC;CACxB,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAYnD"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attachment operations for PLANKA API.
|
|
3
|
+
*
|
|
4
|
+
* Upload: multipart POST /api/cards/:id/attachments — both `type=file` and
|
|
5
|
+
* `name` are REQUIRED in PLANKA 2.0 (without `name` it answers 400
|
|
6
|
+
* E_MISSING_OR_INVALID_PARAMS, which reads like a permission problem).
|
|
7
|
+
*/
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
import { basename, extname } from "node:path";
|
|
10
|
+
import { plankaClient } from "../client.js";
|
|
11
|
+
import { parseInput } from "../errors.js";
|
|
12
|
+
import { AttachmentResponse, CardIncludedSchema, CardResponse, } from "../schemas/responses.js";
|
|
13
|
+
import { AttachmentIdSchema, CardIdSchema, UploadAttachmentSchema, } from "../schemas/requests.js";
|
|
14
|
+
/** MIME by extension for the usual uploads (screenshots, logs, docs). */
|
|
15
|
+
const MIME_BY_EXT = {
|
|
16
|
+
".png": "image/png",
|
|
17
|
+
".jpg": "image/jpeg",
|
|
18
|
+
".jpeg": "image/jpeg",
|
|
19
|
+
".gif": "image/gif",
|
|
20
|
+
".webp": "image/webp",
|
|
21
|
+
".svg": "image/svg+xml",
|
|
22
|
+
".pdf": "application/pdf",
|
|
23
|
+
".txt": "text/plain",
|
|
24
|
+
".md": "text/markdown",
|
|
25
|
+
".log": "text/plain",
|
|
26
|
+
".json": "application/json",
|
|
27
|
+
".csv": "text/csv",
|
|
28
|
+
".html": "text/html",
|
|
29
|
+
};
|
|
30
|
+
export function guessMimeType(input) {
|
|
31
|
+
return (MIME_BY_EXT[extname(input.filePath).toLowerCase()] ??
|
|
32
|
+
"application/octet-stream");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get all attachments on a card (from the card endpoint's `included`).
|
|
36
|
+
*/
|
|
37
|
+
export async function getAttachmentsForCard(input) {
|
|
38
|
+
const { cardId } = parseInput(CardIdSchema, input);
|
|
39
|
+
const response = await plankaClient.get(`/api/cards/${cardId}`);
|
|
40
|
+
CardResponse.parse(response); // validates the overall shape
|
|
41
|
+
const included = CardIncludedSchema.parse(response.included || {});
|
|
42
|
+
return included.attachments || [];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Upload a local file as a card attachment.
|
|
46
|
+
*/
|
|
47
|
+
export async function uploadAttachment(input) {
|
|
48
|
+
const { cardId, filePath, name } = parseInput(UploadAttachmentSchema, input);
|
|
49
|
+
const bytes = await readFile(filePath);
|
|
50
|
+
const fileName = name ?? basename(filePath);
|
|
51
|
+
const mimeType = guessMimeType({ filePath });
|
|
52
|
+
const form = new FormData();
|
|
53
|
+
form.append("type", "file"); // required in 2.0
|
|
54
|
+
form.append("name", fileName); // required in 2.0
|
|
55
|
+
form.append("file", new Blob([new Uint8Array(bytes)], { type: mimeType }), fileName);
|
|
56
|
+
const response = await plankaClient.postMultipart(`/api/cards/${cardId}/attachments`, form);
|
|
57
|
+
return AttachmentResponse.parse(response).item;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Delete an attachment.
|
|
61
|
+
*/
|
|
62
|
+
export async function deleteAttachment(input) {
|
|
63
|
+
const { attachmentId } = parseInput(AttachmentIdSchema, input);
|
|
64
|
+
await plankaClient.delete(`/api/attachments/${attachmentId}`);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* An attachment's download URL. 2.0 exposes it as data.url; when it is
|
|
68
|
+
* missing, fall back to the /attachments/:id/download/:name pattern.
|
|
69
|
+
*/
|
|
70
|
+
export function attachmentDownloadUrl(input) {
|
|
71
|
+
const { attachment } = input;
|
|
72
|
+
return (attachment.data?.url ??
|
|
73
|
+
attachment.url ??
|
|
74
|
+
`/attachments/${attachment.id}/download/${encodeURIComponent(attachment.name)}`);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Download an attachment's binary content.
|
|
78
|
+
*/
|
|
79
|
+
export async function downloadAttachment(input) {
|
|
80
|
+
const { attachment } = input;
|
|
81
|
+
const { bytes, mimeType } = await plankaClient.getBinary(attachmentDownloadUrl({ attachment }));
|
|
82
|
+
return {
|
|
83
|
+
bytes,
|
|
84
|
+
mimeType: attachment.data?.mimeType ??
|
|
85
|
+
mimeType ??
|
|
86
|
+
guessMimeType({ filePath: attachment.name }),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=attachments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachments.js","sourceRoot":"","sources":["../../src/operations/attachments.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs an operation that needs a boardId and, on a 404, tries to explain why
|
|
3
|
+
* instead of propagating the mute 404.
|
|
4
|
+
*
|
|
5
|
+
* `run` receives the id to use: it is invoked again with the resolved board
|
|
6
|
+
* when the id turned out to be a single-board project.
|
|
7
|
+
*/
|
|
8
|
+
export declare function withBoardId<T>(boardId: string, run: (id: string) => Promise<T>): Promise<T>;
|
|
9
|
+
//# sourceMappingURL=board-id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"board-id.d.ts","sourceRoot":"","sources":["../../src/operations/board-id.ts"],"names":[],"mappings":"AAyFA;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,CAAC,EACjC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAC9B,OAAO,CAAC,CAAC,CAAC,CAUZ"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Board id resolution: turn a bare 404 into something a model can act on.
|
|
3
|
+
*
|
|
4
|
+
* Why it exists: in Planka, project ids and board ids are both strings of
|
|
5
|
+
* digits, indistinguishable at a glance. Handing a projectId to a tool that
|
|
6
|
+
* expects a boardId gets `GET /api/boards/<projectId>` → a naked 404, and an
|
|
7
|
+
* agent burns a whole cycle discovering it picked the wrong entity.
|
|
8
|
+
*
|
|
9
|
+
* With this, that 404 becomes either:
|
|
10
|
+
* - an automatic resolution, when the project has exactly one board; or
|
|
11
|
+
* - an error saying "that is a projectId, and these are its boards".
|
|
12
|
+
*/
|
|
13
|
+
import { PlankaError, PlankaNotFoundError } from "../errors.js";
|
|
14
|
+
import { getProjects } from "./projects.js";
|
|
15
|
+
/** A 404 can arrive as PlankaNotFoundError or as PlankaError with status 404. */
|
|
16
|
+
function isNotFound(error) {
|
|
17
|
+
if (error instanceof PlankaNotFoundError)
|
|
18
|
+
return true;
|
|
19
|
+
return error instanceof PlankaError && error.status === 404;
|
|
20
|
+
}
|
|
21
|
+
/** Formats `id — name` for the listings inside error messages. */
|
|
22
|
+
function describe(id, name) {
|
|
23
|
+
return name ? `${id} — "${name}"` : id;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Diagnoses an id that failed as a board: was it actually a projectId?
|
|
27
|
+
*
|
|
28
|
+
* Returns the boardId to retry when the resolution is unambiguous (a project
|
|
29
|
+
* with exactly one board). In every other case it throws an actionable error
|
|
30
|
+
* and never returns.
|
|
31
|
+
*/
|
|
32
|
+
async function diagnoseBoardId(boardId) {
|
|
33
|
+
let projects;
|
|
34
|
+
let boards;
|
|
35
|
+
try {
|
|
36
|
+
({ projects, boards } = await getProjects());
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// If we cannot even list projects, the original 404 is the best
|
|
40
|
+
// information available: let the caller re-throw it.
|
|
41
|
+
throw new PlankaNotFoundError("Board", boardId);
|
|
42
|
+
}
|
|
43
|
+
const asProject = projects.find((p) => p.id === boardId);
|
|
44
|
+
if (asProject) {
|
|
45
|
+
const projectBoards = boards.filter((b) => b.projectId === asProject.id);
|
|
46
|
+
if (projectBoards.length === 1) {
|
|
47
|
+
return projectBoards[0].id;
|
|
48
|
+
}
|
|
49
|
+
if (projectBoards.length === 0) {
|
|
50
|
+
throw new PlankaError(`${boardId} is a projectId (project "${asProject.name}"), not a boardId — ` +
|
|
51
|
+
`and that project has no boards yet. Create one, or call planka_get_structure to see what exists.`, "WRONG_ENTITY_TYPE", 404);
|
|
52
|
+
}
|
|
53
|
+
const list = projectBoards
|
|
54
|
+
.map((b) => ` - ${describe(b.id, b.name)}`)
|
|
55
|
+
.join("\n");
|
|
56
|
+
throw new PlankaError(`${boardId} is a projectId (project "${asProject.name}"), not a boardId. ` +
|
|
57
|
+
`That project has ${projectBoards.length} boards — pass one of these instead:\n${list}`, "WRONG_ENTITY_TYPE", 404);
|
|
58
|
+
}
|
|
59
|
+
// Neither project nor board: show the boards that do exist, which is what
|
|
60
|
+
// the caller needs to correct itself on the next attempt.
|
|
61
|
+
const known = boards.map((b) => ` - ${describe(b.id, b.name)}`).join("\n");
|
|
62
|
+
throw new PlankaError(`No board with id ${boardId}, and it is not a projectId either. ` +
|
|
63
|
+
(boards.length
|
|
64
|
+
? `Known boards:\n${known}\nUse planka_get_structure for the full project/board tree.`
|
|
65
|
+
: `No boards are visible to this account. Check credentials or call planka_get_structure.`), "NOT_FOUND", 404);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Runs an operation that needs a boardId and, on a 404, tries to explain why
|
|
69
|
+
* instead of propagating the mute 404.
|
|
70
|
+
*
|
|
71
|
+
* `run` receives the id to use: it is invoked again with the resolved board
|
|
72
|
+
* when the id turned out to be a single-board project.
|
|
73
|
+
*/
|
|
74
|
+
export async function withBoardId(boardId, run) {
|
|
75
|
+
try {
|
|
76
|
+
return await run(boardId);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (!isNotFound(error))
|
|
80
|
+
throw error;
|
|
81
|
+
// diagnoseBoardId throws unless the resolution is unambiguous.
|
|
82
|
+
const resolved = await diagnoseBoardId(boardId);
|
|
83
|
+
return await run(resolved);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=board-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"board-id.js","sourceRoot":"","sources":["../../src/operations/board-id.ts"],"names":[],"mappings":"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"}
|