@annexshow/mcp-server 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.
Files changed (3) hide show
  1. package/README.md +100 -0
  2. package/index.js +374 -0
  3. package/package.json +22 -0
package/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # AnnexShow — MCP-Server
2
+
3
+ MCP-Server (Model Context Protocol) für die [AnnexShow](https://annexshow.com)
4
+ Agenten-API. Damit kann ein KI-Agent (Claude Desktop, Claude Code u. a.) direkt
5
+ Diagramme, Präsentationen und Brandkits erzeugen — und dem Nutzer sofort einen
6
+ teilbaren Link präsentieren. Leitmotiv: **Idee rein → Link raus.**
7
+
8
+ Der Server läuft **client-seitig** (lokal beim Agenten) und ruft die AnnexShow
9
+ REST-API auf. Er läuft **nicht** auf dem AnnexShow-Server.
10
+
11
+ ## Voraussetzungen
12
+
13
+ 1. **API-Token erzeugen:** im AnnexShow-Profil unter
14
+ *Einstellungen → API-Tokens* (Pro-Plan nötig). Der Token beginnt mit `ask_`.
15
+ 2. **Projekt anlegen:** Jede Visualisierung gehört zu einem Projekt. Lege es
16
+ einmalig in der App an und merke dir die `project_id`.
17
+ 3. **Node.js >= 18** (für das eingebaute `fetch`).
18
+
19
+ ## Verfügbare Tools
20
+
21
+ | Tool | Zweck |
22
+ |------|-------|
23
+ | `annexshow_visualize` | Brief oder Knoten/Kanten → auto-layoutetes Canvas (+ optional Share-URL) |
24
+ | `annexshow_create_synergy_diagram` | Radiales Synergie-Diagramm: Zentrum + N Items |
25
+ | `annexshow_create_presentation` | Präsentation aus Stichpunkt-Folien |
26
+ | `annexshow_create_brandkit` | Brandkit anlegen/füllen |
27
+ | `annexshow_apply_brandkit` | Brandkit auf ein Canvas anwenden |
28
+ | `annexshow_share_canvas` | Bestehendes Canvas teilen → Share-URL |
29
+
30
+ ## Setup für Claude Desktop
31
+
32
+ In `claude_desktop_config.json` (macOS:
33
+ `~/Library/Application Support/Claude/claude_desktop_config.json`):
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "annexshow": {
39
+ "command": "npx",
40
+ "args": ["-y", "@annexshow/mcp-server"],
41
+ "env": {
42
+ "ANNEXSHOW_API_KEY": "ask_dein_token"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## Setup für Claude Code
50
+
51
+ ```bash
52
+ claude mcp add annexshow \
53
+ --env ANNEXSHOW_API_KEY=ask_dein_token \
54
+ -- npx -y @annexshow/mcp-server
55
+ ```
56
+
57
+ Oder als JSON-Snippet in der MCP-Konfiguration:
58
+
59
+ ```json
60
+ {
61
+ "mcpServers": {
62
+ "annexshow": {
63
+ "command": "npx",
64
+ "args": ["-y", "@annexshow/mcp-server"],
65
+ "env": {
66
+ "ANNEXSHOW_API_KEY": "ask_dein_token"
67
+ }
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ ## Umgebungsvariablen
74
+
75
+ | Variable | Pflicht | Standard |
76
+ |----------|---------|----------|
77
+ | `ANNEXSHOW_API_KEY` | ja | — |
78
+ | `ANNEXSHOW_BASE_URL` | nein | `https://annexshow.com/api/v1` |
79
+
80
+ ## Lokal testen
81
+
82
+ ```bash
83
+ cd mcp
84
+ npm install
85
+ ANNEXSHOW_API_KEY=ask_dein_token node index.js
86
+ ```
87
+
88
+ Der Server kommuniziert über stdio und gibt Statusmeldungen auf stderr aus.
89
+
90
+ ## Beispiel-Dialog
91
+
92
+ > „Erstelle ein Synergie-Diagramm in Projekt 1 mit dem Zentrum *AnnexShow* und
93
+ > den Punkten Python-SDK, PHP-SDK, MCP-Server. Gib mir den Link."
94
+
95
+ Der Agent ruft `annexshow_create_synergy_diagram` auf und antwortet mit der
96
+ fertigen Share-URL.
97
+
98
+ ## Lizenz
99
+
100
+ MIT — ma-kom agentur UG (haftungsbeschränkt).
package/index.js ADDED
@@ -0,0 +1,374 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AnnexShow MCP-Server (stdio).
4
+ *
5
+ * Client-seitiger MCP-Server, der die AnnexShow Agenten-REST-API aufruft.
6
+ * Läuft NICHT auf dem Plesk-Server, sondern lokal beim Agenten/Client.
7
+ *
8
+ * Konfiguration über Umgebungsvariablen:
9
+ * ANNEXSHOW_API_KEY (erforderlich) Agenten-Token (ask_...).
10
+ * ANNEXSHOW_BASE_URL (optional) Standard: https://annexshow.com/api/v1
11
+ *
12
+ * Den Token erzeugst du im AnnexShow-Profil unter Einstellungen -> API-Tokens
13
+ * (Pro-Plan nötig).
14
+ */
15
+
16
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
17
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18
+ import {
19
+ CallToolRequestSchema,
20
+ ListToolsRequestSchema,
21
+ } from "@modelcontextprotocol/sdk/types.js";
22
+
23
+ const API_KEY = process.env.ANNEXSHOW_API_KEY;
24
+ const BASE_URL = (
25
+ process.env.ANNEXSHOW_BASE_URL || "https://annexshow.com/api/v1"
26
+ ).replace(/\/+$/, "");
27
+
28
+ if (!API_KEY) {
29
+ console.error(
30
+ "Fehler: ANNEXSHOW_API_KEY ist nicht gesetzt. Token im AnnexShow-Profil unter Einstellungen -> API-Tokens erzeugen (Pro-Plan nötig)."
31
+ );
32
+ process.exit(1);
33
+ }
34
+
35
+ /**
36
+ * Ruft einen Agenten-Endpunkt per POST auf und liefert den geparsten Body.
37
+ * Wirft bei einem Status außerhalb 2xx mit Statuscode + Message.
38
+ */
39
+ async function apiPost(path, payload) {
40
+ // null/undefined-Felder herausfiltern, damit serverseitige Defaults greifen.
41
+ const clean = {};
42
+ for (const [key, value] of Object.entries(payload || {})) {
43
+ if (value !== null && value !== undefined) {
44
+ clean[key] = value;
45
+ }
46
+ }
47
+
48
+ let response;
49
+ try {
50
+ response = await fetch(`${BASE_URL}${path}`, {
51
+ method: "POST",
52
+ headers: {
53
+ "X-AnnexShow-Api-Key": API_KEY,
54
+ Accept: "application/json",
55
+ "Content-Type": "application/json",
56
+ },
57
+ body: JSON.stringify(clean),
58
+ });
59
+ } catch (err) {
60
+ throw new Error(`Verbindungsfehler: ${err.message}`);
61
+ }
62
+
63
+ const raw = await response.text();
64
+ let body;
65
+ try {
66
+ body = raw ? JSON.parse(raw) : {};
67
+ } catch {
68
+ body = raw;
69
+ }
70
+
71
+ if (!response.ok) {
72
+ const message =
73
+ (body && typeof body === "object" && (body.message || body.error)) ||
74
+ (typeof body === "string" ? body : JSON.stringify(body));
75
+ throw new Error(`AnnexShow-API-Fehler ${response.status}: ${message}`);
76
+ }
77
+
78
+ return body;
79
+ }
80
+
81
+ /** Textantwort für ein MCP-Tool zusammenbauen. */
82
+ function textResult(lines) {
83
+ return {
84
+ content: [{ type: "text", text: lines.filter(Boolean).join("\n") }],
85
+ };
86
+ }
87
+
88
+ /** Fehler als MCP-Tool-Fehlerantwort. */
89
+ function errorResult(message) {
90
+ return {
91
+ isError: true,
92
+ content: [{ type: "text", text: message }],
93
+ };
94
+ }
95
+
96
+ // --------------------------------------------------------------------------
97
+ // Tool-Definitionen
98
+ // --------------------------------------------------------------------------
99
+ const TOOLS = [
100
+ {
101
+ name: "annexshow_visualize",
102
+ description:
103
+ "Erzeugt aus einem Brief ODER aus Knoten/Kanten ein fertig auto-layoutetes Canvas und optional eine Share-URL. Entweder 'nodes' ODER 'brief' angeben (brief braucht serverseitig aktivierten KI-Pfad).",
104
+ inputSchema: {
105
+ type: "object",
106
+ properties: {
107
+ project_id: { type: "integer", description: "ID des Projekts (muss zuerst existieren)." },
108
+ title: { type: "string", description: "Optionaler Titel des Canvas." },
109
+ brief: {
110
+ type: "string",
111
+ description: "Textuelle Beschreibung (alternativ zu nodes; braucht KI-Pfad).",
112
+ },
113
+ nodes: {
114
+ type: "array",
115
+ description: "Knoten-Liste.",
116
+ items: {
117
+ type: "object",
118
+ properties: {
119
+ id: { type: "string" },
120
+ label: { type: "string" },
121
+ color: { type: "string" },
122
+ },
123
+ required: ["id", "label"],
124
+ },
125
+ },
126
+ edges: {
127
+ type: "array",
128
+ description: "Kanten-Liste.",
129
+ items: {
130
+ type: "object",
131
+ properties: {
132
+ from: { type: "string" },
133
+ to: { type: "string" },
134
+ label: { type: "string" },
135
+ },
136
+ required: ["from", "to"],
137
+ },
138
+ },
139
+ layout: {
140
+ type: "string",
141
+ enum: ["tree", "radial", "grid"],
142
+ description: "Layout-Algorithmus.",
143
+ },
144
+ brandkit_id: { type: "integer", description: "Optionales Brandkit zum Anwenden." },
145
+ share: { type: "boolean", description: "Wenn true, wird eine Share-URL erzeugt." },
146
+ width: { type: "integer" },
147
+ height: { type: "integer" },
148
+ },
149
+ required: ["project_id"],
150
+ },
151
+ async run(args) {
152
+ const result = await apiPost("/agent/visualize", args);
153
+ return textResult([
154
+ `Canvas erstellt (${result.node_count} Knoten, ${result.edge_count} Kanten, Layout: ${result.layout}).`,
155
+ result.canvas?.id ? `Canvas-ID: ${result.canvas.id}` : null,
156
+ result.share_url ? `Share-URL: ${result.share_url}` : null,
157
+ result.quality?.message ? `Qualität: ${result.quality.message}` : null,
158
+ ]);
159
+ },
160
+ },
161
+ {
162
+ name: "annexshow_create_synergy_diagram",
163
+ description:
164
+ "Baut ein radiales Synergie-/Netzwerk-Diagramm: ein Zentrum + ein Knoten je Item, jeweils mit dem Zentrum verbunden. Idee rein -> Link raus.",
165
+ inputSchema: {
166
+ type: "object",
167
+ properties: {
168
+ project_id: { type: "integer", description: "ID des Projekts." },
169
+ center: { type: "string", description: "Beschriftung des Zentrumsknotens." },
170
+ items: {
171
+ type: "array",
172
+ description: "Beschriftungen der äußeren Knoten.",
173
+ items: { type: "string" },
174
+ },
175
+ share: { type: "boolean", description: "Wenn true (Standard), wird eine Share-URL erzeugt." },
176
+ title: { type: "string" },
177
+ brandkit_id: { type: "integer" },
178
+ },
179
+ required: ["project_id", "center", "items"],
180
+ },
181
+ async run(args) {
182
+ const { project_id, center, items, share = true, title, brandkit_id } = args;
183
+ const nodes = [{ id: "center", label: center }];
184
+ const edges = [];
185
+ items.forEach((item, i) => {
186
+ const id = `item${i + 1}`;
187
+ nodes.push({ id, label: item });
188
+ edges.push({ from: "center", to: id });
189
+ });
190
+ const result = await apiPost("/agent/visualize", {
191
+ project_id,
192
+ title: title || center,
193
+ nodes,
194
+ edges,
195
+ layout: "radial",
196
+ brandkit_id,
197
+ share,
198
+ });
199
+ return textResult([
200
+ `Synergie-Diagramm erstellt (${result.node_count} Knoten, Layout: ${result.layout}).`,
201
+ result.canvas?.id ? `Canvas-ID: ${result.canvas.id}` : null,
202
+ result.share_url
203
+ ? `Dein Link: ${result.share_url}`
204
+ : "Hinweis: Kein Share-Link (share=false).",
205
+ ]);
206
+ },
207
+ },
208
+ {
209
+ name: "annexshow_create_presentation",
210
+ description:
211
+ "Erzeugt aus mehreren Stichpunkt-Folien eine Präsentation (ein Canvas je Folie).",
212
+ inputSchema: {
213
+ type: "object",
214
+ properties: {
215
+ project_id: { type: "integer", description: "ID des Projekts." },
216
+ title: { type: "string", description: "Titel der Präsentation." },
217
+ slides: {
218
+ type: "array",
219
+ description: "Folien-Liste.",
220
+ items: {
221
+ type: "object",
222
+ properties: {
223
+ title: { type: "string" },
224
+ document: { type: "object", description: "Folien-Inhalt." },
225
+ width: { type: "integer" },
226
+ height: { type: "integer" },
227
+ },
228
+ required: ["title", "document"],
229
+ },
230
+ },
231
+ template_id: { type: "integer" },
232
+ },
233
+ required: ["project_id", "title", "slides"],
234
+ },
235
+ async run(args) {
236
+ const result = await apiPost("/agent/presentations/generate", args);
237
+ return textResult([
238
+ `Präsentation "${result.presentation_title}" erstellt (${result.count} Folien).`,
239
+ Array.isArray(result.canvases) && result.canvases.length
240
+ ? `Canvas-IDs: ${result.canvases.map((c) => c.id ?? c).join(", ")}`
241
+ : null,
242
+ ]);
243
+ },
244
+ },
245
+ {
246
+ name: "annexshow_create_brandkit",
247
+ description:
248
+ "Legt ein Brandkit an oder füllt es (Name, Firma, Slogan, Farben, Fonts, Logos). Liefert Vollständigkeit (0-100) und fehlende Felder zurück.",
249
+ inputSchema: {
250
+ type: "object",
251
+ properties: {
252
+ name: { type: "string", description: "Name des Brandkits." },
253
+ company_name: { type: "string" },
254
+ slogan: { type: "string" },
255
+ offer_description: { type: "string" },
256
+ colors: {
257
+ type: "object",
258
+ properties: {
259
+ primary: { type: "string" },
260
+ secondary: { type: "string" },
261
+ signal: { type: "string" },
262
+ },
263
+ },
264
+ fonts: {
265
+ type: "object",
266
+ properties: {
267
+ heading: { type: "string" },
268
+ body: { type: "string" },
269
+ },
270
+ },
271
+ logos: { type: "array", items: { type: "string" } },
272
+ },
273
+ required: ["name"],
274
+ },
275
+ async run(args) {
276
+ const result = await apiPost("/agent/brandkits", args);
277
+ const missing = Array.isArray(result.missing) ? result.missing : [];
278
+ return textResult([
279
+ `Brandkit angelegt (Vollständigkeit: ${result.completion}%).`,
280
+ result.brandkit?.id ? `Brandkit-ID: ${result.brandkit.id}` : null,
281
+ missing.length ? `Es fehlt noch: ${missing.join(", ")}` : "Alle Felder vorhanden.",
282
+ result.message || null,
283
+ ]);
284
+ },
285
+ },
286
+ {
287
+ name: "annexshow_apply_brandkit",
288
+ description: "Wendet ein Brandkit auf ein bestehendes Canvas an.",
289
+ inputSchema: {
290
+ type: "object",
291
+ properties: {
292
+ canvas_id: { type: "integer", description: "ID des Canvas." },
293
+ brandkit_id: { type: "integer", description: "ID des Brandkits." },
294
+ },
295
+ required: ["canvas_id", "brandkit_id"],
296
+ },
297
+ async run(args) {
298
+ const result = await apiPost("/agent/brandkits/apply", args);
299
+ return textResult([
300
+ "Brandkit auf Canvas angewendet.",
301
+ result.canvas?.id ? `Canvas-ID: ${result.canvas.id}` : null,
302
+ ]);
303
+ },
304
+ },
305
+ {
306
+ name: "annexshow_share_canvas",
307
+ description:
308
+ "Teilt ein bestehendes Canvas und liefert eine öffentliche Share-URL (Formate: html, svg, pdf, png).",
309
+ inputSchema: {
310
+ type: "object",
311
+ properties: {
312
+ canvas_id: { type: "integer", description: "ID des zu teilenden Canvas." },
313
+ title: { type: "string" },
314
+ description: { type: "string" },
315
+ allowed_formats: {
316
+ type: "array",
317
+ items: { type: "string", enum: ["html", "svg", "pdf", "png"] },
318
+ },
319
+ expires_at: { type: "string", description: "Ablaufdatum (ISO-8601), optional." },
320
+ },
321
+ required: ["canvas_id"],
322
+ },
323
+ async run(args) {
324
+ const { canvas_id, ...body } = args;
325
+ const result = await apiPost(`/agent/canvases/${canvas_id}/share`, body);
326
+ return textResult([
327
+ "Canvas geteilt.",
328
+ result.url ? `Dein Link: ${result.url}` : null,
329
+ Array.isArray(result.formats) ? `Formate: ${result.formats.join(", ")}` : null,
330
+ ]);
331
+ },
332
+ },
333
+ ];
334
+
335
+ const TOOL_MAP = new Map(TOOLS.map((tool) => [tool.name, tool]));
336
+
337
+ // --------------------------------------------------------------------------
338
+ // Server-Setup
339
+ // --------------------------------------------------------------------------
340
+ const server = new Server(
341
+ { name: "annexshow-mcp-server", version: "0.1.0" },
342
+ { capabilities: { tools: {} } }
343
+ );
344
+
345
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
346
+ tools: TOOLS.map(({ name, description, inputSchema }) => ({
347
+ name,
348
+ description,
349
+ inputSchema,
350
+ })),
351
+ }));
352
+
353
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
354
+ const tool = TOOL_MAP.get(request.params.name);
355
+ if (!tool) {
356
+ return errorResult(`Unbekanntes Tool: ${request.params.name}`);
357
+ }
358
+ try {
359
+ return await tool.run(request.params.arguments || {});
360
+ } catch (err) {
361
+ return errorResult(err.message);
362
+ }
363
+ });
364
+
365
+ async function main() {
366
+ const transport = new StdioServerTransport();
367
+ await server.connect(transport);
368
+ console.error("AnnexShow MCP-Server läuft (stdio).");
369
+ }
370
+
371
+ main().catch((err) => {
372
+ console.error("AnnexShow MCP-Server konnte nicht starten:", err);
373
+ process.exit(1);
374
+ });
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@annexshow/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP-Server für die AnnexShow Agenten-API — Idee rein, Link raus.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "ma-kom agentur UG <mail@ma-kom.de>",
8
+ "homepage": "https://annexshow.com",
9
+ "bin": {
10
+ "annexshow-mcp": "index.js"
11
+ },
12
+ "files": [
13
+ "index.js",
14
+ "README.md"
15
+ ],
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "dependencies": {
20
+ "@modelcontextprotocol/sdk": "^1.0.0"
21
+ }
22
+ }