@unboundai/tb-ppt-mgagent 0.1.1 → 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/README.md CHANGED
@@ -38,6 +38,30 @@ The one thing still **not** confirmed against a real response is the exact `file
38
38
  - `ANTHROPIC_API_BASE` — optional, overrides the SDK's default API base URL.
39
39
  - `TB_PPT_OUTPUT_DIR` — required for `get_tb_powerpoint_result`. Local folder that's synced to GCS by something outside this server; finished `.pptx` files are written here.
40
40
  - `TB_PPT_PUBLIC_URL_BASE` — required for `get_tb_powerpoint_result`. Base URL the synced folder is served from (e.g. `https://storage.googleapis.com/bucket/prefix`), used to build the returned download link.
41
+ - `TB_PPT_LOG_FILE` — optional. Path to the task log file. Defaults to `logs/tasks.log` next to this package.
42
+
43
+ ## Task logging
44
+
45
+ Every tool call writes one JSON-lines entry to `TB_PPT_LOG_FILE` (default `logs/tasks.log`) and mirrors it to stderr (which the MCP host's own log viewer already shows, as seen in the `[powerpoint-creator]` logs). Each line has a `timestamp`, `tool`, and `status`, plus whatever's relevant to that outcome:
46
+
47
+ | status | tool | meaning |
48
+ |---|---|---|
49
+ | `started` | either | call received, with its key arguments (outline preview/length, or sessionId) |
50
+ | `success` | `generate_tb_powerpoint` | session created; includes `sessionId` |
51
+ | `not_ready` | `get_tb_powerpoint_result` | session isn't `idle` yet; includes `sessionStatus` |
52
+ | `not_end_turn` | `get_tb_powerpoint_result` | idle, but `stop_reason` wasn't `end_turn`; includes `stopReason` |
53
+ | `no_output_file` | `get_tb_powerpoint_result` | finished cleanly but no `.pptx` found; includes `fileCount` |
54
+ | `success` | `get_tb_powerpoint_result` | file downloaded; includes `filename`, `downloadUrl` |
55
+ | `invalid_arguments` | either | zod validation failed; includes `details` |
56
+ | `error` | either | anything else threw; includes `message` |
57
+
58
+ All entries include `durationMs` except the initial `started` line. To see what's been run:
59
+
60
+ ```bash
61
+ tail -f logs/tasks.log
62
+ # or, for just failures:
63
+ grep -E '"status":"(error|not_end_turn|no_output_file)"' logs/tasks.log
64
+ ```
41
65
 
42
66
  4. **Register with your MCP client** (Claude Desktop / TypingMind's local MCP connector — same pattern as this repo's siblings, e.g. `tb-gcs-assembly-mcp`). Published to npm as [`@unboundai/tb-ppt-mgagent`](https://www.npmjs.com/package/@unboundai/tb-ppt-mgagent), so `npx` can run it without a local checkout:
43
67
 
package/build/index.js CHANGED
@@ -37,6 +37,11 @@
37
37
  * - TB_PPT_PUBLIC_URL_BASE: required for retrieval. Base URL the synced
38
38
  * folder is served from (e.g. "https://storage.googleapis.com/bucket/prefix"),
39
39
  * used to build the download link returned to the caller.
40
+ * - TB_PPT_LOG_FILE: optional. Path to a JSON-lines task log (one call per
41
+ * line: tool, status, timing, and relevant ids). Defaults to logs/tasks.log
42
+ * next to this package. Every entry is also mirrored to stderr, which the
43
+ * MCP host's own log viewer already picks up - the file exists so history
44
+ * survives a host restart and can be grepped/tailed directly.
40
45
  */
41
46
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
42
47
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -45,7 +50,27 @@ import { z } from "zod";
45
50
  import Anthropic from "@anthropic-ai/sdk";
46
51
  import fs from "fs";
47
52
  import path from "path";
53
+ import { fileURLToPath } from "url";
48
54
  const MANAGED_AGENTS_BETA = "managed-agents-2026-04-01";
55
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
56
+ const LOG_FILE = process.env.TB_PPT_LOG_FILE || path.join(__dirname, "..", "logs", "tasks.log");
57
+ /**
58
+ * Records one line of task history: a JSON object to LOG_FILE, and a short
59
+ * human-readable echo to stderr (stdout is reserved for MCP's own JSON-RPC
60
+ * traffic in stdio mode - never write task logs there).
61
+ */
62
+ function logTask(event) {
63
+ const record = { timestamp: new Date().toISOString(), ...event };
64
+ const line = JSON.stringify(record);
65
+ try {
66
+ fs.mkdirSync(path.dirname(LOG_FILE), { recursive: true });
67
+ fs.appendFileSync(LOG_FILE, line + "\n");
68
+ }
69
+ catch (err) {
70
+ console.error(`Failed to write task log to ${LOG_FILE}: ${err.message}`);
71
+ }
72
+ console.error(`[task] ${line}`);
73
+ }
49
74
  const API_KEY = process.env.ANTHROPIC_API_KEY;
50
75
  const AGENT_ID = process.env.ANTHROPIC_AGENT_ID;
51
76
  const ENVIRONMENT_ID = process.env.ANTHROPIC_ENVIRONMENT_ID;
@@ -168,9 +193,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
168
193
  });
169
194
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
170
195
  const { name, arguments: args } = request.params;
196
+ const startedAt = Date.now();
171
197
  try {
172
198
  if (name === "generate_tb_powerpoint") {
173
199
  const { outline, deckType, clientName, filename } = GenerateTbPowerpointSchema.parse(args);
200
+ logTask({
201
+ tool: name,
202
+ status: "started",
203
+ outlinePreview: outline.slice(0, 120),
204
+ outlineLength: outline.length,
205
+ deckType: deckType ?? null,
206
+ clientName: clientName ?? null,
207
+ filename: filename ?? null
208
+ });
174
209
  const { apiKey, agentId, environmentId } = requireSessionConfig();
175
210
  const client = getClient(apiKey);
176
211
  const session = await client.beta.sessions.create({
@@ -191,6 +226,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
191
226
  { type: "user.message", content: [{ type: "text", text: messageText }] }
192
227
  ]
193
228
  });
229
+ logTask({
230
+ tool: name,
231
+ status: "success",
232
+ sessionId: session.id,
233
+ durationMs: Date.now() - startedAt
234
+ });
194
235
  return {
195
236
  content: [{
196
237
  type: "text",
@@ -204,10 +245,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
204
245
  }
205
246
  if (name === "get_tb_powerpoint_result") {
206
247
  const { sessionId } = GetTbPowerpointResultSchema.parse(args);
248
+ logTask({ tool: name, status: "started", sessionId });
207
249
  const apiKey = requireApiKey();
208
250
  const client = getClient(apiKey);
209
251
  const session = await client.beta.sessions.retrieve(sessionId);
210
252
  if (session.status !== "idle") {
253
+ logTask({
254
+ tool: name,
255
+ status: "not_ready",
256
+ sessionId,
257
+ sessionStatus: session.status,
258
+ durationMs: Date.now() - startedAt
259
+ });
211
260
  return {
212
261
  content: [{
213
262
  type: "text",
@@ -230,6 +279,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
230
279
  }
231
280
  const stopReasonType = latestIdleEvent?.stop_reason.type;
232
281
  if (stopReasonType !== "end_turn") {
282
+ logTask({
283
+ tool: name,
284
+ status: "not_end_turn",
285
+ sessionId,
286
+ stopReason: stopReasonType ?? null,
287
+ durationMs: Date.now() - startedAt
288
+ });
233
289
  return {
234
290
  content: [{
235
291
  type: "text",
@@ -252,6 +308,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
252
308
  const outputPptxFiles = pptxFiles.filter((f) => /outputs?[\\/]/i.test(f.filename));
253
309
  const chosen = outputPptxFiles[0] ?? pptxFiles[0];
254
310
  if (!chosen) {
311
+ logTask({
312
+ tool: name,
313
+ status: "no_output_file",
314
+ sessionId,
315
+ fileCount: fileList.length,
316
+ durationMs: Date.now() - startedAt
317
+ });
255
318
  return {
256
319
  content: [{
257
320
  type: "text",
@@ -277,6 +340,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
277
340
  fs.mkdirSync(outputDir, { recursive: true });
278
341
  fs.writeFileSync(destPath, Buffer.from(arrayBuffer));
279
342
  const downloadUrl = `${publicUrlBase.replace(/\/$/, "")}/${encodeURIComponent(safeName)}`;
343
+ logTask({
344
+ tool: name,
345
+ status: "success",
346
+ sessionId,
347
+ filename: safeName,
348
+ downloadUrl,
349
+ durationMs: Date.now() - startedAt
350
+ });
280
351
  return {
281
352
  content: [{
282
353
  type: "text",
@@ -294,17 +365,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
294
365
  throw new Error(`Unknown tool: ${name}`);
295
366
  }
296
367
  catch (error) {
368
+ const durationMs = Date.now() - startedAt;
297
369
  if (error instanceof z.ZodError) {
370
+ const details = error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
371
+ logTask({ tool: name, status: "invalid_arguments", details, durationMs });
298
372
  return {
299
373
  content: [{
300
374
  type: "text",
301
375
  text: JSON.stringify({
302
376
  error: "Invalid arguments",
303
- details: error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ")
377
+ details
304
378
  }, null, 2)
305
379
  }]
306
380
  };
307
381
  }
382
+ logTask({ tool: name, status: "error", message: error.message, durationMs });
308
383
  return {
309
384
  content: [{
310
385
  type: "text",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unboundai/tb-ppt-mgagent",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server that hands a presentation outline to the Traffic Builders PowerPoint Managed Agent, then retrieves the finished .pptx",
5
5
  "main": "build/index.js",
6
6
  "bin": {