@geogenio/mcp-server 1.0.1 → 1.0.2

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/Dockerfile ADDED
@@ -0,0 +1,8 @@
1
+ FROM node:20-slim
2
+ WORKDIR /app
3
+ COPY package.json package-lock.json* ./
4
+ RUN npm install
5
+ COPY . .
6
+ RUN npm run build
7
+ EXPOSE 3100
8
+ CMD ["node", "build/index.js", "--http"]
@@ -54,6 +54,10 @@ export class GeoGenApiClient {
54
54
  body: JSON.stringify(body),
55
55
  });
56
56
  }
57
+ async getTrackingStatus(params) {
58
+ const query = this.buildQuery(params);
59
+ return this.request(`/v1/entities/tracking-status${query}`);
60
+ }
57
61
  async deletePrompt(promptId) {
58
62
  const query = this.buildQuery({ promptId });
59
63
  return this.request(`/v1/entities/deleteprompt${query}`, {
@@ -115,4 +119,21 @@ export class GeoGenApiClient {
115
119
  const query = this.buildQuery(params);
116
120
  return this.request(`/v1/query-fanouts${query}`);
117
121
  }
122
+ // ── Actions (Actionables + Tasks) ─────────────────────────────
123
+ async getEntityActions(params) {
124
+ const query = this.buildQuery(params);
125
+ return this.request(`/v1/entities/actions${query}`);
126
+ }
127
+ async dismissActionable(actionableId) {
128
+ return this.request("/v1/entities/actions/dismiss", {
129
+ method: "POST",
130
+ body: JSON.stringify({ actionableId }),
131
+ });
132
+ }
133
+ async updateTaskStatus(taskId, status) {
134
+ return this.request("/v1/entities/actions/update-status", {
135
+ method: "POST",
136
+ body: JSON.stringify({ taskId, status }),
137
+ });
138
+ }
118
139
  }
package/build/index.js CHANGED
@@ -6,42 +6,138 @@
6
6
  * for AI assistants (Claude Desktop, Cursor, Windsurf, Claude Code, etc.)
7
7
  *
8
8
  * Configuration via environment variables:
9
- * GEOGEN_API_KEY - Your GeoGen workspace API key (required)
10
- * GEOGEN_BASE_URL - GeoGen API URL (required)
9
+ * GEOGEN_API_KEY - Your GeoGen workspace API key (required for stdio mode)
10
+ * GEOGEN_BASE_URL - GeoGen API URL (default: https://api.geogen.io)
11
+ *
12
+ * Transport modes:
13
+ * --http - Start as HTTP server with SSE transport (for OpenAI Agent Builder, remote clients)
14
+ * --port <number> - Port for HTTP mode (default: 3100)
15
+ * (default) - Stdio transport (for Claude Desktop, Cursor, etc.)
16
+ *
17
+ * In HTTP mode, clients pass their API key via the Authorization header on the
18
+ * initial GET /sse request. No server-side API key is needed.
11
19
  */
12
20
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
21
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
14
23
  import { GeoGenApiClient } from "./api-client.js";
15
24
  import { registerTools } from "./tools.js";
16
- function main() {
17
- const apiKey = process.env.GEOGEN_API_KEY;
18
- const baseUrl = process.env.GEOGEN_BASE_URL;
19
- if (!apiKey) {
20
- console.error("Error: GEOGEN_API_KEY environment variable is required.");
21
- console.error("Set it to your GeoGen workspace API key.");
22
- process.exit(1);
23
- }
24
- if (!baseUrl) {
25
- console.error("Error: GEOGEN_BASE_URL environment variable is required.");
26
- console.error("Set it to https://api.geogen.io");
27
- process.exit(1);
28
- }
29
- // Create the API client
25
+ import http from "node:http";
26
+ const DEFAULT_BASE_URL = "https://api.geogen.io";
27
+ function createServer(apiKey, baseUrl) {
30
28
  const client = new GeoGenApiClient({ baseUrl, apiKey });
31
- // Create the MCP server
32
29
  const server = new McpServer({
33
30
  name: "geogen",
34
31
  version: "1.0.0",
35
32
  });
36
- // Register all tools
37
33
  registerTools(server, client);
38
- // Connect via stdio transport and start
34
+ return server;
35
+ }
36
+ function parseArgs() {
37
+ const args = process.argv.slice(2);
38
+ const httpMode = args.includes("--http");
39
+ const portIdx = args.indexOf("--port");
40
+ const port = portIdx !== -1 ? parseInt(args[portIdx + 1], 10) : 3100;
41
+ return { httpMode, port };
42
+ }
43
+ /** Extract Bearer token from Authorization header */
44
+ function extractApiKey(req) {
45
+ const auth = req.headers.authorization;
46
+ if (!auth)
47
+ return null;
48
+ if (auth.startsWith("Bearer "))
49
+ return auth.slice(7);
50
+ return auth;
51
+ }
52
+ async function startStdio(apiKey, baseUrl) {
53
+ const server = createServer(apiKey, baseUrl);
39
54
  const transport = new StdioServerTransport();
40
- server.connect(transport).then(() => {
41
- console.error("GeoGen MCP Server running on stdio");
42
- }).catch((error) => {
43
- console.error("Failed to start GeoGen MCP Server:", error);
44
- process.exit(1);
55
+ await server.connect(transport);
56
+ console.error("GeoGen MCP Server running on stdio");
57
+ }
58
+ async function startHttp(baseUrl, port) {
59
+ // Track active SSE transports by session ID
60
+ const transports = new Map();
61
+ const httpServer = http.createServer(async (req, res) => {
62
+ // CORS headers for remote clients
63
+ res.setHeader("Access-Control-Allow-Origin", "*");
64
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
65
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
66
+ if (req.method === "OPTIONS") {
67
+ res.writeHead(204);
68
+ res.end();
69
+ return;
70
+ }
71
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
72
+ // GET /sse — establish SSE connection (client sends API key here)
73
+ if (req.method === "GET" && url.pathname === "/sse") {
74
+ const apiKey = extractApiKey(req);
75
+ if (!apiKey) {
76
+ res.writeHead(401, { "Content-Type": "application/json" });
77
+ res.end(JSON.stringify({ error: "Missing Authorization header. Pass your GeoGen API key as: Authorization: Bearer <key>" }));
78
+ return;
79
+ }
80
+ const server = createServer(apiKey, baseUrl);
81
+ const transport = new SSEServerTransport("/messages", res);
82
+ transports.set(transport.sessionId, transport);
83
+ transport.onclose = () => {
84
+ transports.delete(transport.sessionId);
85
+ };
86
+ await server.connect(transport);
87
+ return;
88
+ }
89
+ // POST /messages — receive messages from client
90
+ if (req.method === "POST" && url.pathname === "/messages") {
91
+ const sessionId = url.searchParams.get("sessionId");
92
+ if (!sessionId) {
93
+ res.writeHead(400, { "Content-Type": "application/json" });
94
+ res.end(JSON.stringify({ error: "Missing sessionId" }));
95
+ return;
96
+ }
97
+ const transport = transports.get(sessionId);
98
+ if (!transport) {
99
+ res.writeHead(404, { "Content-Type": "application/json" });
100
+ res.end(JSON.stringify({ error: "Session not found" }));
101
+ return;
102
+ }
103
+ await transport.handlePostMessage(req, res);
104
+ return;
105
+ }
106
+ // Health check
107
+ if (req.method === "GET" && url.pathname === "/") {
108
+ res.writeHead(200, { "Content-Type": "application/json" });
109
+ res.end(JSON.stringify({ status: "ok", name: "geogen-mcp-server", transport: "sse" }));
110
+ return;
111
+ }
112
+ // 404 for anything else
113
+ res.writeHead(404, { "Content-Type": "application/json" });
114
+ res.end(JSON.stringify({ error: "Not found" }));
115
+ });
116
+ httpServer.listen(port, () => {
117
+ console.error(`GeoGen MCP Server running on http://localhost:${port}`);
118
+ console.error(` SSE endpoint: http://localhost:${port}/sse`);
119
+ console.error(` Messages: http://localhost:${port}/messages`);
45
120
  });
46
121
  }
47
- main();
122
+ async function main() {
123
+ const { httpMode, port } = parseArgs();
124
+ const baseUrl = process.env.GEOGEN_BASE_URL || DEFAULT_BASE_URL;
125
+ if (httpMode) {
126
+ // HTTP mode: no API key needed on server — clients pass their own
127
+ await startHttp(baseUrl, port);
128
+ }
129
+ else {
130
+ // Stdio mode: API key from env (local usage)
131
+ const apiKey = process.env.GEOGEN_API_KEY;
132
+ if (!apiKey) {
133
+ console.error("Error: GEOGEN_API_KEY environment variable is required.");
134
+ console.error("Set it to your GeoGen workspace API key.");
135
+ process.exit(1);
136
+ }
137
+ await startStdio(apiKey, baseUrl);
138
+ }
139
+ }
140
+ main().catch((error) => {
141
+ console.error("Failed to start GeoGen MCP Server:", error);
142
+ process.exit(1);
143
+ });
package/build/tools.js CHANGED
@@ -190,7 +190,20 @@ export function registerTools(server, client) {
190
190
  }
191
191
  });
192
192
  // ────────────────────────────────────────────────────────────
193
- // 10. GET RESPONSES
193
+ // 10. GET TRACKING STATUS
194
+ // ────────────────────────────────────────────────────────────
195
+ server.tool("get_tracking_status", "Get the mention check status for an entity — when the last check completed, when the next is scheduled, and whether one is currently running", {
196
+ entityId: entityIdSchema,
197
+ }, async (args) => {
198
+ try {
199
+ return jsonContent(await client.getTrackingStatus(args));
200
+ }
201
+ catch (err) {
202
+ return errorContent(err);
203
+ }
204
+ });
205
+ // ────────────────────────────────────────────────────────────
206
+ // 11. GET RESPONSES
194
207
  // ────────────────────────────────────────────────────────────
195
208
  server.tool("get_responses", "Get LLM responses for an entity with mention status. Filter by period, models, tags, or mention status.", {
196
209
  entityId: entityIdSchema,
@@ -399,4 +412,50 @@ export function registerTools(server, client) {
399
412
  return errorContent(err);
400
413
  }
401
414
  });
415
+ // ────────────────────────────────────────────────────────────
416
+ // 19. GET ENTITY ACTIONS (Actionables + Tasks)
417
+ // ────────────────────────────────────────────────────────────
418
+ server.tool("get_entity_actions", "Get AI-generated actionable recommendations and their associated tasks for an entity. Actionables are SEO improvement suggestions; tasks are actionables that have been moved to the kanban board for tracking.", {
419
+ entityId: entityIdSchema,
420
+ status: z
421
+ .enum(["active", "dismissed", "in_task"])
422
+ .optional()
423
+ .describe("Filter actionables by status (default: all)"),
424
+ }, async (args) => {
425
+ try {
426
+ return jsonContent(await client.getEntityActions(args));
427
+ }
428
+ catch (err) {
429
+ return errorContent(err);
430
+ }
431
+ });
432
+ // ────────────────────────────────────────────────────────────
433
+ // 20. DISMISS ACTIONABLE
434
+ // ────────────────────────────────────────────────────────────
435
+ server.tool("dismiss_actionable", "Dismiss an actionable recommendation so it no longer appears in the active feed. Dismissed actionables free up slots for new ones to be generated on the next mention check.", {
436
+ actionableId: z.string().describe("The actionable ID to dismiss"),
437
+ }, async (args) => {
438
+ try {
439
+ return jsonContent(await client.dismissActionable(args.actionableId));
440
+ }
441
+ catch (err) {
442
+ return errorContent(err);
443
+ }
444
+ });
445
+ // ────────────────────────────────────────────────────────────
446
+ // 21. UPDATE TASK STATUS
447
+ // ────────────────────────────────────────────────────────────
448
+ server.tool("update_task_status", "Update the status of a task on the kanban board. Move tasks through the workflow: not_started → in_progress → in_review → done.", {
449
+ taskId: z.string().describe("The task ID to update"),
450
+ status: z
451
+ .enum(["not_started", "in_progress", "in_review", "done"])
452
+ .describe("New status for the task"),
453
+ }, async (args) => {
454
+ try {
455
+ return jsonContent(await client.updateTaskStatus(args.taskId, args.status));
456
+ }
457
+ catch (err) {
458
+ return errorContent(err);
459
+ }
460
+ });
402
461
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geogenio/mcp-server",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "MCP server for GeoGen LLM SEO tracking platform",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,6 +9,7 @@
9
9
  "scripts": {
10
10
  "build": "tsc",
11
11
  "start": "node build/index.js",
12
+ "start:http": "node build/index.js --http",
12
13
  "dev": "tsc --watch"
13
14
  },
14
15
  "dependencies": {
package/src/api-client.ts CHANGED
@@ -96,6 +96,11 @@ export class GeoGenApiClient {
96
96
  });
97
97
  }
98
98
 
99
+ async getTrackingStatus(params: { entityId: string }): Promise<any> {
100
+ const query = this.buildQuery(params);
101
+ return this.request(`/v1/entities/tracking-status${query}`);
102
+ }
103
+
99
104
  async deletePrompt(promptId: string): Promise<any> {
100
105
  const query = this.buildQuery({ promptId });
101
106
  return this.request(`/v1/entities/deleteprompt${query}`, {
@@ -253,4 +258,28 @@ export class GeoGenApiClient {
253
258
  const query = this.buildQuery(params);
254
259
  return this.request(`/v1/query-fanouts${query}`);
255
260
  }
261
+
262
+ // ── Actions (Actionables + Tasks) ─────────────────────────────
263
+
264
+ async getEntityActions(params: {
265
+ entityId: string;
266
+ status?: string;
267
+ }): Promise<any> {
268
+ const query = this.buildQuery(params);
269
+ return this.request(`/v1/entities/actions${query}`);
270
+ }
271
+
272
+ async dismissActionable(actionableId: string): Promise<any> {
273
+ return this.request("/v1/entities/actions/dismiss", {
274
+ method: "POST",
275
+ body: JSON.stringify({ actionableId }),
276
+ });
277
+ }
278
+
279
+ async updateTaskStatus(taskId: string, status: string): Promise<any> {
280
+ return this.request("/v1/entities/actions/update-status", {
281
+ method: "POST",
282
+ body: JSON.stringify({ taskId, status }),
283
+ });
284
+ }
256
285
  }
package/src/index.ts CHANGED
@@ -7,51 +7,158 @@
7
7
  * for AI assistants (Claude Desktop, Cursor, Windsurf, Claude Code, etc.)
8
8
  *
9
9
  * Configuration via environment variables:
10
- * GEOGEN_API_KEY - Your GeoGen workspace API key (required)
11
- * GEOGEN_BASE_URL - GeoGen API URL (required)
10
+ * GEOGEN_API_KEY - Your GeoGen workspace API key (required for stdio mode)
11
+ * GEOGEN_BASE_URL - GeoGen API URL (default: https://api.geogen.io)
12
+ *
13
+ * Transport modes:
14
+ * --http - Start as HTTP server with SSE transport (for OpenAI Agent Builder, remote clients)
15
+ * --port <number> - Port for HTTP mode (default: 3100)
16
+ * (default) - Stdio transport (for Claude Desktop, Cursor, etc.)
17
+ *
18
+ * In HTTP mode, clients pass their API key via the Authorization header on the
19
+ * initial GET /sse request. No server-side API key is needed.
12
20
  */
13
21
 
14
22
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
15
23
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
24
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
16
25
  import { GeoGenApiClient } from "./api-client.js";
17
26
  import { registerTools } from "./tools.js";
27
+ import http from "node:http";
18
28
 
19
- function main() {
20
- const apiKey = process.env.GEOGEN_API_KEY;
21
- const baseUrl = process.env.GEOGEN_BASE_URL;
29
+ const DEFAULT_BASE_URL = "https://api.geogen.io";
22
30
 
23
- if (!apiKey) {
24
- console.error("Error: GEOGEN_API_KEY environment variable is required.");
25
- console.error("Set it to your GeoGen workspace API key.");
26
- process.exit(1);
27
- }
28
-
29
- if (!baseUrl) {
30
- console.error("Error: GEOGEN_BASE_URL environment variable is required.");
31
- console.error("Set it to https://api.geogen.io");
32
- process.exit(1);
33
- }
34
-
35
- // Create the API client
31
+ function createServer(apiKey: string, baseUrl: string): McpServer {
36
32
  const client = new GeoGenApiClient({ baseUrl, apiKey });
37
-
38
- // Create the MCP server
39
33
  const server = new McpServer({
40
34
  name: "geogen",
41
35
  version: "1.0.0",
42
36
  });
43
-
44
- // Register all tools
45
37
  registerTools(server, client);
38
+ return server;
39
+ }
46
40
 
47
- // Connect via stdio transport and start
41
+ function parseArgs() {
42
+ const args = process.argv.slice(2);
43
+ const httpMode = args.includes("--http");
44
+ const portIdx = args.indexOf("--port");
45
+ const port = portIdx !== -1 ? parseInt(args[portIdx + 1], 10) : 3100;
46
+ return { httpMode, port };
47
+ }
48
+
49
+ /** Extract Bearer token from Authorization header */
50
+ function extractApiKey(req: http.IncomingMessage): string | null {
51
+ const auth = req.headers.authorization;
52
+ if (!auth) return null;
53
+ if (auth.startsWith("Bearer ")) return auth.slice(7);
54
+ return auth;
55
+ }
56
+
57
+ async function startStdio(apiKey: string, baseUrl: string) {
58
+ const server = createServer(apiKey, baseUrl);
48
59
  const transport = new StdioServerTransport();
49
- server.connect(transport).then(() => {
50
- console.error("GeoGen MCP Server running on stdio");
51
- }).catch((error) => {
52
- console.error("Failed to start GeoGen MCP Server:", error);
53
- process.exit(1);
60
+ await server.connect(transport);
61
+ console.error("GeoGen MCP Server running on stdio");
62
+ }
63
+
64
+ async function startHttp(baseUrl: string, port: number) {
65
+ // Track active SSE transports by session ID
66
+ const transports = new Map<string, SSEServerTransport>();
67
+
68
+ const httpServer = http.createServer(async (req, res) => {
69
+ // CORS headers for remote clients
70
+ res.setHeader("Access-Control-Allow-Origin", "*");
71
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
72
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
73
+
74
+ if (req.method === "OPTIONS") {
75
+ res.writeHead(204);
76
+ res.end();
77
+ return;
78
+ }
79
+
80
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
81
+
82
+ // GET /sse — establish SSE connection (client sends API key here)
83
+ if (req.method === "GET" && url.pathname === "/sse") {
84
+ const apiKey = extractApiKey(req);
85
+ if (!apiKey) {
86
+ res.writeHead(401, { "Content-Type": "application/json" });
87
+ res.end(JSON.stringify({ error: "Missing Authorization header. Pass your GeoGen API key as: Authorization: Bearer <key>" }));
88
+ return;
89
+ }
90
+
91
+ const server = createServer(apiKey, baseUrl);
92
+ const transport = new SSEServerTransport("/messages", res);
93
+ transports.set(transport.sessionId, transport);
94
+
95
+ transport.onclose = () => {
96
+ transports.delete(transport.sessionId);
97
+ };
98
+
99
+ await server.connect(transport);
100
+ return;
101
+ }
102
+
103
+ // POST /messages — receive messages from client
104
+ if (req.method === "POST" && url.pathname === "/messages") {
105
+ const sessionId = url.searchParams.get("sessionId");
106
+ if (!sessionId) {
107
+ res.writeHead(400, { "Content-Type": "application/json" });
108
+ res.end(JSON.stringify({ error: "Missing sessionId" }));
109
+ return;
110
+ }
111
+
112
+ const transport = transports.get(sessionId);
113
+ if (!transport) {
114
+ res.writeHead(404, { "Content-Type": "application/json" });
115
+ res.end(JSON.stringify({ error: "Session not found" }));
116
+ return;
117
+ }
118
+
119
+ await transport.handlePostMessage(req, res);
120
+ return;
121
+ }
122
+
123
+ // Health check
124
+ if (req.method === "GET" && url.pathname === "/") {
125
+ res.writeHead(200, { "Content-Type": "application/json" });
126
+ res.end(JSON.stringify({ status: "ok", name: "geogen-mcp-server", transport: "sse" }));
127
+ return;
128
+ }
129
+
130
+ // 404 for anything else
131
+ res.writeHead(404, { "Content-Type": "application/json" });
132
+ res.end(JSON.stringify({ error: "Not found" }));
133
+ });
134
+
135
+ httpServer.listen(port, () => {
136
+ console.error(`GeoGen MCP Server running on http://localhost:${port}`);
137
+ console.error(` SSE endpoint: http://localhost:${port}/sse`);
138
+ console.error(` Messages: http://localhost:${port}/messages`);
54
139
  });
55
140
  }
56
141
 
57
- main();
142
+ async function main() {
143
+ const { httpMode, port } = parseArgs();
144
+ const baseUrl = process.env.GEOGEN_BASE_URL || DEFAULT_BASE_URL;
145
+
146
+ if (httpMode) {
147
+ // HTTP mode: no API key needed on server — clients pass their own
148
+ await startHttp(baseUrl, port);
149
+ } else {
150
+ // Stdio mode: API key from env (local usage)
151
+ const apiKey = process.env.GEOGEN_API_KEY;
152
+ if (!apiKey) {
153
+ console.error("Error: GEOGEN_API_KEY environment variable is required.");
154
+ console.error("Set it to your GeoGen workspace API key.");
155
+ process.exit(1);
156
+ }
157
+ await startStdio(apiKey, baseUrl);
158
+ }
159
+ }
160
+
161
+ main().catch((error) => {
162
+ console.error("Failed to start GeoGen MCP Server:", error);
163
+ process.exit(1);
164
+ });
package/src/tools.ts CHANGED
@@ -252,7 +252,25 @@ export function registerTools(server: McpServer, client: GeoGenApiClient) {
252
252
  );
253
253
 
254
254
  // ────────────────────────────────────────────────────────────
255
- // 10. GET RESPONSES
255
+ // 10. GET TRACKING STATUS
256
+ // ────────────────────────────────────────────────────────────
257
+ server.tool(
258
+ "get_tracking_status",
259
+ "Get the mention check status for an entity — when the last check completed, when the next is scheduled, and whether one is currently running",
260
+ {
261
+ entityId: entityIdSchema,
262
+ },
263
+ async (args) => {
264
+ try {
265
+ return jsonContent(await client.getTrackingStatus(args));
266
+ } catch (err) {
267
+ return errorContent(err);
268
+ }
269
+ }
270
+ );
271
+
272
+ // ────────────────────────────────────────────────────────────
273
+ // 11. GET RESPONSES
256
274
  // ────────────────────────────────────────────────────────────
257
275
  server.tool(
258
276
  "get_responses",
@@ -517,4 +535,65 @@ export function registerTools(server: McpServer, client: GeoGenApiClient) {
517
535
  }
518
536
  }
519
537
  );
538
+
539
+ // ────────────────────────────────────────────────────────────
540
+ // 19. GET ENTITY ACTIONS (Actionables + Tasks)
541
+ // ────────────────────────────────────────────────────────────
542
+ server.tool(
543
+ "get_entity_actions",
544
+ "Get AI-generated actionable recommendations and their associated tasks for an entity. Actionables are SEO improvement suggestions; tasks are actionables that have been moved to the kanban board for tracking.",
545
+ {
546
+ entityId: entityIdSchema,
547
+ status: z
548
+ .enum(["active", "dismissed", "in_task"])
549
+ .optional()
550
+ .describe("Filter actionables by status (default: all)"),
551
+ },
552
+ async (args) => {
553
+ try {
554
+ return jsonContent(await client.getEntityActions(args));
555
+ } catch (err) {
556
+ return errorContent(err);
557
+ }
558
+ }
559
+ );
560
+
561
+ // ────────────────────────────────────────────────────────────
562
+ // 20. DISMISS ACTIONABLE
563
+ // ────────────────────────────────────────────────────────────
564
+ server.tool(
565
+ "dismiss_actionable",
566
+ "Dismiss an actionable recommendation so it no longer appears in the active feed. Dismissed actionables free up slots for new ones to be generated on the next mention check.",
567
+ {
568
+ actionableId: z.string().describe("The actionable ID to dismiss"),
569
+ },
570
+ async (args) => {
571
+ try {
572
+ return jsonContent(await client.dismissActionable(args.actionableId));
573
+ } catch (err) {
574
+ return errorContent(err);
575
+ }
576
+ }
577
+ );
578
+
579
+ // ────────────────────────────────────────────────────────────
580
+ // 21. UPDATE TASK STATUS
581
+ // ────────────────────────────────────────────────────────────
582
+ server.tool(
583
+ "update_task_status",
584
+ "Update the status of a task on the kanban board. Move tasks through the workflow: not_started → in_progress → in_review → done.",
585
+ {
586
+ taskId: z.string().describe("The task ID to update"),
587
+ status: z
588
+ .enum(["not_started", "in_progress", "in_review", "done"])
589
+ .describe("New status for the task"),
590
+ },
591
+ async (args) => {
592
+ try {
593
+ return jsonContent(await client.updateTaskStatus(args.taskId, args.status));
594
+ } catch (err) {
595
+ return errorContent(err);
596
+ }
597
+ }
598
+ );
520
599
  }