@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
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { access, chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { constants } from "node:fs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { dirname, resolve } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { createInterface } from "node:readline/promises";
|
|
10
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
11
|
+
import { bootstrapBoard } from "./bootstrap-board.mjs";
|
|
12
|
+
import { PlankaApi, PlankaApiError, boardEntities } from "./lib/planka-api.mjs";
|
|
13
|
+
|
|
14
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
15
|
+
const serverEntry = resolve(root, "dist/index.js");
|
|
16
|
+
const credentials = {
|
|
17
|
+
baseUrl: process.env.PLANKA_BASE_URL,
|
|
18
|
+
email: process.env.PLANKA_AGENT_EMAIL,
|
|
19
|
+
password: process.env.PLANKA_AGENT_PASSWORD,
|
|
20
|
+
};
|
|
21
|
+
const rl = createInterface({ input, output });
|
|
22
|
+
|
|
23
|
+
async function ask(question, fallback = "") {
|
|
24
|
+
const suffix = fallback ? ` [${fallback}]` : "";
|
|
25
|
+
const answer = (await rl.question(`${question}${suffix}: `)).trim();
|
|
26
|
+
return answer || fallback;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function choose(question, items) {
|
|
30
|
+
console.log(`\n${question}`);
|
|
31
|
+
items.forEach((item, index) => console.log(` ${index + 1}) ${item.label}`));
|
|
32
|
+
while (true) {
|
|
33
|
+
const raw = await ask("Choose", "1");
|
|
34
|
+
const index = Number(raw) - 1;
|
|
35
|
+
if (Number.isInteger(index) && items[index]) return items[index].value;
|
|
36
|
+
console.log(`Enter a number from 1 to ${items.length}.`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function writePrivateLauncher() {
|
|
41
|
+
const configDir = resolve(
|
|
42
|
+
process.env.XDG_CONFIG_HOME || resolve(homedir(), ".config"),
|
|
43
|
+
"planka-mcp"
|
|
44
|
+
);
|
|
45
|
+
const configPath = resolve(configDir, "config.json");
|
|
46
|
+
const binDir = resolve(homedir(), ".local", "bin");
|
|
47
|
+
const launcherPath = resolve(binDir, "planka-mcp");
|
|
48
|
+
await mkdir(configDir, { recursive: true, mode: 0o700 });
|
|
49
|
+
await mkdir(binDir, { recursive: true, mode: 0o755 });
|
|
50
|
+
await writeFile(
|
|
51
|
+
configPath,
|
|
52
|
+
`${JSON.stringify(
|
|
53
|
+
{
|
|
54
|
+
baseUrl: credentials.baseUrl.replace(/\/+$/, ""),
|
|
55
|
+
email: credentials.email,
|
|
56
|
+
password: credentials.password,
|
|
57
|
+
serverEntry,
|
|
58
|
+
},
|
|
59
|
+
null,
|
|
60
|
+
2
|
|
61
|
+
)}\n`,
|
|
62
|
+
{ mode: 0o600 }
|
|
63
|
+
);
|
|
64
|
+
await chmod(configPath, 0o600);
|
|
65
|
+
|
|
66
|
+
const launcher = `#!/usr/bin/env node
|
|
67
|
+
import { readFile } from "node:fs/promises";
|
|
68
|
+
import { pathToFileURL } from "node:url";
|
|
69
|
+
const config = JSON.parse(await readFile(${JSON.stringify(configPath)}, "utf8"));
|
|
70
|
+
process.env.PLANKA_BASE_URL = config.baseUrl;
|
|
71
|
+
process.env.PLANKA_AGENT_EMAIL = config.email;
|
|
72
|
+
process.env.PLANKA_AGENT_PASSWORD = config.password;
|
|
73
|
+
await import(pathToFileURL(config.serverEntry).href);
|
|
74
|
+
`;
|
|
75
|
+
await writeFile(launcherPath, launcher, { mode: 0o700 });
|
|
76
|
+
await chmod(launcherPath, 0o700);
|
|
77
|
+
return { configPath, launcherPath };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function configureClaude(launcherPath) {
|
|
81
|
+
console.log("\nChoose how Claude Code should load the server:");
|
|
82
|
+
console.log(" CLI/user scope: private to this machine and available in every project.");
|
|
83
|
+
console.log(" .mcp.json: committed with a project so every teammate gets the same server entry; credentials stay outside Git.");
|
|
84
|
+
const mode = await choose("Configuration method", [
|
|
85
|
+
{ label: "claude mcp add (easiest)", value: "cli" },
|
|
86
|
+
{ label: ".mcp.json in a project (team-friendly)", value: "project" },
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
if (mode === "cli") {
|
|
90
|
+
const probe = spawnSync("claude", ["--version"], { encoding: "utf8" });
|
|
91
|
+
if (probe.error?.code === "ENOENT") {
|
|
92
|
+
throw new Error(
|
|
93
|
+
"Claude Code is not installed or is not on PATH. Install it, then rerun setup."
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
spawnSync("claude", ["mcp", "remove", "planka", "--scope", "user"], {
|
|
97
|
+
stdio: "ignore",
|
|
98
|
+
});
|
|
99
|
+
const added = spawnSync(
|
|
100
|
+
"claude",
|
|
101
|
+
[
|
|
102
|
+
"mcp",
|
|
103
|
+
"add",
|
|
104
|
+
"--scope",
|
|
105
|
+
"user",
|
|
106
|
+
"--transport",
|
|
107
|
+
"stdio",
|
|
108
|
+
"planka",
|
|
109
|
+
"--",
|
|
110
|
+
launcherPath,
|
|
111
|
+
],
|
|
112
|
+
{ encoding: "utf8" }
|
|
113
|
+
);
|
|
114
|
+
if (added.status !== 0) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`claude mcp add failed: ${(added.stderr || added.stdout || "unknown error").trim()}`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
console.log("Claude Code user configuration updated.");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const projectDir = resolve(await ask("Project directory", process.cwd()));
|
|
124
|
+
await access(projectDir, constants.R_OK | constants.W_OK);
|
|
125
|
+
const mcpPath = resolve(projectDir, ".mcp.json");
|
|
126
|
+
let document = { mcpServers: {} };
|
|
127
|
+
try {
|
|
128
|
+
document = JSON.parse(await readFile(mcpPath, "utf8"));
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (error.code !== "ENOENT") {
|
|
131
|
+
throw new Error(`Cannot parse ${mcpPath}: ${error.message}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!document.mcpServers || typeof document.mcpServers !== "object") {
|
|
135
|
+
document.mcpServers = {};
|
|
136
|
+
}
|
|
137
|
+
document.mcpServers.planka = {
|
|
138
|
+
type: "stdio",
|
|
139
|
+
command: "${HOME}/.local/bin/planka-mcp",
|
|
140
|
+
args: [],
|
|
141
|
+
};
|
|
142
|
+
await writeFile(mcpPath, `${JSON.stringify(document, null, 2)}\n`);
|
|
143
|
+
console.log(`Wrote ${mcpPath}`);
|
|
144
|
+
console.log("Claude Code will ask you to trust this project-scoped server on first use.");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function selectBoard(api) {
|
|
148
|
+
const projectsResponse = await api.getProjects();
|
|
149
|
+
const projects = Array.isArray(projectsResponse?.items)
|
|
150
|
+
? projectsResponse.items
|
|
151
|
+
: [];
|
|
152
|
+
const boards = Array.isArray(projectsResponse?.included?.boards)
|
|
153
|
+
? projectsResponse.included.boards
|
|
154
|
+
: [];
|
|
155
|
+
if (!projects.length) {
|
|
156
|
+
throw new Error("The agent user cannot see any Planka projects.");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const boardOptions = boards.map((board) => {
|
|
160
|
+
const project = projects.find((item) => item.id === board.projectId);
|
|
161
|
+
return {
|
|
162
|
+
label: `${project?.name || "Unknown project"} / ${board.name}`,
|
|
163
|
+
value: { kind: "existing", board },
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
boardOptions.push({
|
|
167
|
+
label: "Create a new board with the optional workflow template",
|
|
168
|
+
value: { kind: "create" },
|
|
169
|
+
});
|
|
170
|
+
const selection = await choose("Boards visible to the agent user", boardOptions);
|
|
171
|
+
|
|
172
|
+
if (selection.kind === "create") {
|
|
173
|
+
const projectId = await choose(
|
|
174
|
+
"Project for the new board (the agent user must be a project manager)",
|
|
175
|
+
projects.map((project) => ({ label: project.name, value: project.id }))
|
|
176
|
+
);
|
|
177
|
+
const boardName = await ask("New board name", "Agent Work");
|
|
178
|
+
const result = await bootstrapBoard(api, { projectId, boardName });
|
|
179
|
+
console.log(
|
|
180
|
+
`Created ${result.boardName} with ${result.listCount} lists and ${result.labelCount} labels.`
|
|
181
|
+
);
|
|
182
|
+
return result.boardId;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const boardId = selection.board.id;
|
|
186
|
+
const state = boardEntities(await api.getBoard(boardId));
|
|
187
|
+
const usableLists = state.lists.filter((item) => item.name && item.type !== "closed");
|
|
188
|
+
if (!usableLists.length) {
|
|
189
|
+
console.log("The selected board has no active list, so setup will install the workflow template.");
|
|
190
|
+
await bootstrapBoard(api, { boardId });
|
|
191
|
+
}
|
|
192
|
+
return boardId;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function runSmoke(boardId) {
|
|
196
|
+
console.log("\nRunning the real MCP smoke test (the temporary card is deleted)...");
|
|
197
|
+
const result = spawnSync(
|
|
198
|
+
process.execPath,
|
|
199
|
+
[resolve(root, "tests/smoke/planka-smoke.mjs")],
|
|
200
|
+
{
|
|
201
|
+
cwd: root,
|
|
202
|
+
stdio: "inherit",
|
|
203
|
+
env: {
|
|
204
|
+
...process.env,
|
|
205
|
+
PLANKA_SMOKE_BOARD_ID: boardId,
|
|
206
|
+
PLANKA_MCP_ENTRY: serverEntry,
|
|
207
|
+
},
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
if (result.status !== 0) {
|
|
211
|
+
throw new Error("The real smoke test failed. The MCP configuration was written, but setup is not verified.");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function main() {
|
|
216
|
+
try {
|
|
217
|
+
console.log("\nValidating credentials against POST /api/access-tokens...");
|
|
218
|
+
const api = await PlankaApi.fromCredentials(credentials);
|
|
219
|
+
console.log("Credentials valid. JSON API response received.");
|
|
220
|
+
const boardId = await selectBoard(api);
|
|
221
|
+
const { launcherPath } = await writePrivateLauncher();
|
|
222
|
+
await configureClaude(launcherPath);
|
|
223
|
+
await runSmoke(boardId);
|
|
224
|
+
console.log("\nSetup complete.");
|
|
225
|
+
console.log('Reiniciá Claude Code y pedile “mostrame mi board de Planka”.');
|
|
226
|
+
console.log('Restart Claude Code, approve the server if prompted, and ask: “show me my Planka board”.');
|
|
227
|
+
} finally {
|
|
228
|
+
rl.close();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
main().catch((error) => {
|
|
233
|
+
const prefix = error instanceof PlankaApiError ? "Planka API error" : "Setup error";
|
|
234
|
+
console.error(`\n${prefix}: ${error.message}`);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
});
|
package/scripts/setup.sh
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
|
|
7
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
8
|
+
echo "setup: Node.js 18 or newer is required." >&2
|
|
9
|
+
exit 1
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
NODE_MAJOR="$(node -p 'Number(process.versions.node.split(".")[0])')"
|
|
13
|
+
if [[ "$NODE_MAJOR" -lt 18 ]]; then
|
|
14
|
+
echo "setup: Node.js 18 or newer is required (found $(node --version))." >&2
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
echo "Planka MCP setup"
|
|
19
|
+
echo
|
|
20
|
+
|
|
21
|
+
DEFAULT_URL="${PLANKA_BASE_URL:-https://planka.example.com}"
|
|
22
|
+
read -r -p "Planka base URL [$DEFAULT_URL]: " INPUT_URL
|
|
23
|
+
export PLANKA_BASE_URL="${INPUT_URL:-$DEFAULT_URL}"
|
|
24
|
+
|
|
25
|
+
DEFAULT_EMAIL="${PLANKA_AGENT_EMAIL:-}"
|
|
26
|
+
if [[ -n "$DEFAULT_EMAIL" ]]; then
|
|
27
|
+
read -r -p "Agent email or username [$DEFAULT_EMAIL]: " INPUT_EMAIL
|
|
28
|
+
export PLANKA_AGENT_EMAIL="${INPUT_EMAIL:-$DEFAULT_EMAIL}"
|
|
29
|
+
else
|
|
30
|
+
read -r -p "Agent email or username: " PLANKA_AGENT_EMAIL
|
|
31
|
+
export PLANKA_AGENT_EMAIL
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
if [[ -z "${PLANKA_AGENT_PASSWORD:-}" ]]; then
|
|
35
|
+
read -r -s -p "Agent password: " PLANKA_AGENT_PASSWORD
|
|
36
|
+
echo
|
|
37
|
+
export PLANKA_AGENT_PASSWORD
|
|
38
|
+
else
|
|
39
|
+
echo "Using PLANKA_AGENT_PASSWORD from the environment."
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
if [[ ! -f "$ROOT_DIR/dist/index.js" ]]; then
|
|
43
|
+
echo
|
|
44
|
+
echo "Building the MCP server..."
|
|
45
|
+
(cd "$ROOT_DIR" && npm ci && npm run build)
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
exec node "$ROOT_DIR/scripts/setup.mjs"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
const checkOnly = process.argv.includes("--check");
|
|
9
|
+
const readJson = async (name) =>
|
|
10
|
+
JSON.parse(await readFile(resolve(root, name), "utf8"));
|
|
11
|
+
|
|
12
|
+
const identity = await readJson("project.identity.json");
|
|
13
|
+
const packageJson = await readJson("package.json");
|
|
14
|
+
const serverJson = await readJson("server.json");
|
|
15
|
+
|
|
16
|
+
packageJson.name = identity.packageName;
|
|
17
|
+
packageJson.mcpName = identity.mcpServerName;
|
|
18
|
+
packageJson.bin = { [identity.binName]: "dist/index.js" };
|
|
19
|
+
packageJson.repository.url = `${identity.repositoryUrl}.git`;
|
|
20
|
+
packageJson.bugs.url = `${identity.repositoryUrl}/issues`;
|
|
21
|
+
packageJson.homepage = `${identity.repositoryUrl}#readme`;
|
|
22
|
+
|
|
23
|
+
serverJson.name = identity.mcpServerName;
|
|
24
|
+
serverJson.repository.url = identity.repositoryUrl;
|
|
25
|
+
serverJson.version = packageJson.version;
|
|
26
|
+
serverJson.packages[0].identifier = identity.packageName;
|
|
27
|
+
serverJson.packages[0].version = packageJson.version;
|
|
28
|
+
|
|
29
|
+
const generatedIdentity = `// Generated by scripts/sync-identity.mjs. Edit project.identity.json instead.\nexport const SERVER_NAME = ${JSON.stringify(identity.mcpServerName)};\nexport const SERVER_VERSION = ${JSON.stringify(packageJson.version)};\n`;
|
|
30
|
+
|
|
31
|
+
const outputs = new Map([
|
|
32
|
+
["package.json", `${JSON.stringify(packageJson, null, 2)}\n`],
|
|
33
|
+
["server.json", `${JSON.stringify(serverJson, null, 2)}\n`],
|
|
34
|
+
["src/identity.generated.ts", generatedIdentity],
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
let stale = false;
|
|
38
|
+
for (const [name, expected] of outputs) {
|
|
39
|
+
const path = resolve(root, name);
|
|
40
|
+
let current = "";
|
|
41
|
+
try {
|
|
42
|
+
current = await readFile(path, "utf8");
|
|
43
|
+
} catch {
|
|
44
|
+
// A missing generated file is stale and will be created below.
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (current === expected) continue;
|
|
48
|
+
stale = true;
|
|
49
|
+
if (!checkOnly) await writeFile(path, expected);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (checkOnly && stale) {
|
|
53
|
+
console.error("Identity files are stale. Run: npm run sync:identity");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!checkOnly) console.log("Identity files synchronized.");
|
package/server.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.omnicoreos/planka-mcp",
|
|
4
|
+
"description": "Planka 2.x MCP server with verified card, task, comment, label, and list operations.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/omnicoreos/planka-mcp",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "0.2.0",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "npm",
|
|
13
|
+
"identifier": "@omnicoreos/planka-mcp",
|
|
14
|
+
"version": "0.2.0",
|
|
15
|
+
"transport": {
|
|
16
|
+
"type": "stdio"
|
|
17
|
+
},
|
|
18
|
+
"environmentVariables": [
|
|
19
|
+
{
|
|
20
|
+
"name": "PLANKA_BASE_URL",
|
|
21
|
+
"description": "Planka base URL, for example https://planka.example.com",
|
|
22
|
+
"isRequired": true,
|
|
23
|
+
"format": "string",
|
|
24
|
+
"isSecret": false
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "PLANKA_AGENT_EMAIL",
|
|
28
|
+
"description": "Email address or username for the Planka agent user",
|
|
29
|
+
"isRequired": true,
|
|
30
|
+
"format": "string",
|
|
31
|
+
"isSecret": false
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "PLANKA_AGENT_PASSWORD",
|
|
35
|
+
"description": "Password for the Planka agent user",
|
|
36
|
+
"isRequired": true,
|
|
37
|
+
"format": "string",
|
|
38
|
+
"isSecret": true
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|