@unboundai/tb-ppt-mgagent 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.
- package/LICENSE +21 -0
- package/README.md +69 -0
- package/build/index.js +327 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Traffic Builders
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Traffic Builders PowerPoint MCP Server
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server that hands a presentation outline to Anthropic's hosted **Managed Agent** for building Traffic Builders-branded PowerPoints, then retrieves the finished file once it's done. Brand assets (logos, Outfit font) and the design brief are already provisioned in that agent's environment — this server does not upload anything, it just starts the run and later collects the result.
|
|
4
|
+
|
|
5
|
+
Built against the official Managed Agents docs (session creation, session operations, events & streaming, files, event/type reference) using the official `@anthropic-ai/sdk` client, which sets the `managed-agents-2026-04-01` beta header automatically for session endpoints.
|
|
6
|
+
|
|
7
|
+
## Available tools
|
|
8
|
+
|
|
9
|
+
- **`generate_tb_powerpoint(outline, deckType?, clientName?, filename?)`** — creates a session against the configured agent + environment, sends the outline as the first message, returns `{ success, sessionId, message }`. Does not wait for the deck to finish.
|
|
10
|
+
|
|
11
|
+
- **`get_tb_powerpoint_result(sessionId)`** — checks on a generation started above:
|
|
12
|
+
1. Retrieves the session. If its `status` isn't `idle`, returns `{ ready: false, status }` — still running, try again later.
|
|
13
|
+
2. If idle, finds the most recent `session.status_idle` event and checks its `stop_reason.type`. Only proceeds if it's `end_turn` (a clean finish) — a different stop reason (e.g. a budget cap) also returns `{ ready: false, ... }` rather than treating the session as done.
|
|
14
|
+
3. Lists files scoped to the session, filters for a `.pptx` filename (preferring one whose path looks like it's under an outputs directory), downloads it via the Files API into `TB_PPT_OUTPUT_DIR` (the local folder synced to GCS outside this server), and returns a download link built from `TB_PPT_PUBLIC_URL_BASE`.
|
|
15
|
+
|
|
16
|
+
## What's still an assumption
|
|
17
|
+
|
|
18
|
+
Everything above matches the documented API and SDK method signatures verbatim (verified against platform.claude.com/docs/en/managed-agents/\* while building this). The one thing **not** confirmed against a real response is the exact `filename`/path convention the Files API returns for a file the agent wrote to its outputs directory — the code guesses by matching `/outputs?[\/]/i` against the filename and falling back to any `.pptx` file on the session if nothing matches that pattern. If a real run's file list looks different, adjust the filter in `get_tb_powerpoint_result` in `src/index.ts`.
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
1. **Install dependencies**:
|
|
23
|
+
```
|
|
24
|
+
npm install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
2. **Build the project**:
|
|
28
|
+
```
|
|
29
|
+
npm run build
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
3. **Configure environment variables**:
|
|
33
|
+
- `ANTHROPIC_API_KEY` — Anthropic API key with Managed Agents access.
|
|
34
|
+
- `ANTHROPIC_AGENT_ID` — e.g. `agent_01H8osDSMeEtGcEHT1ajzuin`.
|
|
35
|
+
- `ANTHROPIC_ENVIRONMENT_ID` — e.g. `env_01VHzZmqBababsghFjQoAGFx`.
|
|
36
|
+
- `ANTHROPIC_API_BASE` — optional, overrides the SDK's default API base URL.
|
|
37
|
+
- `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.
|
|
38
|
+
- `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.
|
|
39
|
+
|
|
40
|
+
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`):
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
"tb-ppt-mgagent": {
|
|
44
|
+
"command": "node",
|
|
45
|
+
"args": [
|
|
46
|
+
"/path/to/tb-ppt-mgAgent/build/index.js"
|
|
47
|
+
],
|
|
48
|
+
"env": {
|
|
49
|
+
"ANTHROPIC_API_KEY": "sk-ant-...",
|
|
50
|
+
"ANTHROPIC_AGENT_ID": "agent_...",
|
|
51
|
+
"ANTHROPIC_ENVIRONMENT_ID": "env_...",
|
|
52
|
+
"TB_PPT_OUTPUT_DIR": "/path/to/gcs-synced-folder",
|
|
53
|
+
"TB_PPT_PUBLIC_URL_BASE": "https://storage.googleapis.com/your-bucket/your-prefix"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Development
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Watch mode
|
|
62
|
+
npm run dev
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Both tools were smoke-tested end-to-end over the real MCP protocol (`tools/list`, and calling each with a dummy API key to confirm the SDK calls actually reach `api.anthropic.com` and get a real `401 authentication_error` back rather than failing on a wrong method/shape before the network call).
|
|
66
|
+
|
|
67
|
+
## Design brief reference
|
|
68
|
+
|
|
69
|
+
`TrafficBuilders-PPT-Handoff.md`, `logos/`, and `fonts/` in this repo are the source material that was provisioned into the Managed Agent's environment (colors, type scale, the six slide archetypes, brand logo PNGs, Outfit variable font). Kept here as the local record of what the environment should contain — not read or uploaded by this server at runtime.
|
package/build/index.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Traffic Builders PowerPoint MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Hands a presentation outline to Anthropic's hosted Managed Agent that
|
|
6
|
+
* builds Traffic Builders-branded PowerPoints (brand assets and design
|
|
7
|
+
* brief are already provisioned in that agent's environment - this server
|
|
8
|
+
* does not upload any brand files itself), then later retrieves the
|
|
9
|
+
* finished .pptx once the agent's turn has ended.
|
|
10
|
+
*
|
|
11
|
+
* Built against the documented Managed Agents API:
|
|
12
|
+
* https://platform.claude.com/docs/en/managed-agents/sessions
|
|
13
|
+
* https://platform.claude.com/docs/en/managed-agents/session-operations
|
|
14
|
+
* https://platform.claude.com/docs/en/managed-agents/events-and-streaming
|
|
15
|
+
* https://platform.claude.com/docs/en/managed-agents/files
|
|
16
|
+
* https://platform.claude.com/docs/en/managed-agents/reference
|
|
17
|
+
* using the official @anthropic-ai/sdk client (client.beta.sessions.*,
|
|
18
|
+
* client.beta.files.*), which sets the managed-agents beta header
|
|
19
|
+
* automatically for session endpoints. The Files API additionally takes an
|
|
20
|
+
* explicit `betas` array on list/download, per the docs' own example.
|
|
21
|
+
*
|
|
22
|
+
* A couple of the beta SDK's exact TypeScript types for newer event/file
|
|
23
|
+
* fields (stop_reason, processed_at, filename) aren't fully pinned down
|
|
24
|
+
* here - those spots are narrowed with `as any` and a comment rather than
|
|
25
|
+
* guessed field names baked into strict types, so a real API response can't
|
|
26
|
+
* silently mismatch a wrong compile-time shape.
|
|
27
|
+
*
|
|
28
|
+
* Environment variables:
|
|
29
|
+
* - ANTHROPIC_API_KEY: required. Anthropic API key with Managed Agents access.
|
|
30
|
+
* - ANTHROPIC_AGENT_ID: required. e.g. "agent_01H8osDSMeEtGcEHT1ajzuin".
|
|
31
|
+
* - ANTHROPIC_ENVIRONMENT_ID: required. e.g. "env_01VHzZmqBababsghFjQoAGFx".
|
|
32
|
+
* - ANTHROPIC_API_BASE: optional. Overrides the SDK's default API base URL.
|
|
33
|
+
* - TB_PPT_OUTPUT_DIR: required for retrieval. Local folder (synced to GCS
|
|
34
|
+
* by something outside this server) that finished .pptx files are saved into.
|
|
35
|
+
* - TB_PPT_PUBLIC_URL_BASE: required for retrieval. Base URL the synced
|
|
36
|
+
* folder is served from (e.g. "https://storage.googleapis.com/bucket/prefix"),
|
|
37
|
+
* used to build the download link returned to the caller.
|
|
38
|
+
*/
|
|
39
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
40
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
41
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
42
|
+
import { z } from "zod";
|
|
43
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
44
|
+
import fs from "fs";
|
|
45
|
+
import path from "path";
|
|
46
|
+
const MANAGED_AGENTS_BETA = "managed-agents-2026-04-01";
|
|
47
|
+
const API_KEY = process.env.ANTHROPIC_API_KEY;
|
|
48
|
+
const AGENT_ID = process.env.ANTHROPIC_AGENT_ID;
|
|
49
|
+
const ENVIRONMENT_ID = process.env.ANTHROPIC_ENVIRONMENT_ID;
|
|
50
|
+
const API_BASE = process.env.ANTHROPIC_API_BASE;
|
|
51
|
+
const OUTPUT_DIR = process.env.TB_PPT_OUTPUT_DIR;
|
|
52
|
+
const PUBLIC_URL_BASE = process.env.TB_PPT_PUBLIC_URL_BASE;
|
|
53
|
+
if (!API_KEY)
|
|
54
|
+
console.error("Warning: ANTHROPIC_API_KEY environment variable is not set");
|
|
55
|
+
if (!AGENT_ID)
|
|
56
|
+
console.error("Warning: ANTHROPIC_AGENT_ID environment variable is not set");
|
|
57
|
+
if (!ENVIRONMENT_ID)
|
|
58
|
+
console.error("Warning: ANTHROPIC_ENVIRONMENT_ID environment variable is not set");
|
|
59
|
+
if (!OUTPUT_DIR)
|
|
60
|
+
console.error("Warning: TB_PPT_OUTPUT_DIR environment variable is not set (needed for get_tb_powerpoint_result)");
|
|
61
|
+
if (!PUBLIC_URL_BASE)
|
|
62
|
+
console.error("Warning: TB_PPT_PUBLIC_URL_BASE environment variable is not set (needed for get_tb_powerpoint_result)");
|
|
63
|
+
function requireSessionConfig() {
|
|
64
|
+
if (!API_KEY)
|
|
65
|
+
throw new Error("ANTHROPIC_API_KEY is not configured on this MCP server.");
|
|
66
|
+
if (!AGENT_ID)
|
|
67
|
+
throw new Error("ANTHROPIC_AGENT_ID is not configured on this MCP server.");
|
|
68
|
+
if (!ENVIRONMENT_ID)
|
|
69
|
+
throw new Error("ANTHROPIC_ENVIRONMENT_ID is not configured on this MCP server.");
|
|
70
|
+
return { apiKey: API_KEY, agentId: AGENT_ID, environmentId: ENVIRONMENT_ID };
|
|
71
|
+
}
|
|
72
|
+
function requireApiKey() {
|
|
73
|
+
if (!API_KEY)
|
|
74
|
+
throw new Error("ANTHROPIC_API_KEY is not configured on this MCP server.");
|
|
75
|
+
return API_KEY;
|
|
76
|
+
}
|
|
77
|
+
function requireOutputConfig() {
|
|
78
|
+
if (!OUTPUT_DIR)
|
|
79
|
+
throw new Error("TB_PPT_OUTPUT_DIR is not configured on this MCP server.");
|
|
80
|
+
if (!PUBLIC_URL_BASE)
|
|
81
|
+
throw new Error("TB_PPT_PUBLIC_URL_BASE is not configured on this MCP server.");
|
|
82
|
+
return { outputDir: OUTPUT_DIR, publicUrlBase: PUBLIC_URL_BASE };
|
|
83
|
+
}
|
|
84
|
+
function getClient(apiKey) {
|
|
85
|
+
return new Anthropic({
|
|
86
|
+
apiKey,
|
|
87
|
+
...(API_BASE ? { baseURL: API_BASE } : {})
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const GenerateTbPowerpointSchema = z.object({
|
|
91
|
+
outline: z.string().min(1, "outline is required"),
|
|
92
|
+
deckType: z.string().optional(),
|
|
93
|
+
clientName: z.string().optional(),
|
|
94
|
+
filename: z.string().optional()
|
|
95
|
+
});
|
|
96
|
+
const GetTbPowerpointResultSchema = z.object({
|
|
97
|
+
sessionId: z.string().min(1, "sessionId is required")
|
|
98
|
+
});
|
|
99
|
+
// Create MCP server
|
|
100
|
+
const server = new Server({
|
|
101
|
+
name: "tb-ppt-mgagent",
|
|
102
|
+
version: "1.0.0"
|
|
103
|
+
}, {
|
|
104
|
+
capabilities: {
|
|
105
|
+
tools: {
|
|
106
|
+
listChanged: false
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
111
|
+
return {
|
|
112
|
+
tools: [
|
|
113
|
+
{
|
|
114
|
+
name: "generate_tb_powerpoint",
|
|
115
|
+
description: "Start generating a Traffic Builders-branded PowerPoint from a presentation outline, " +
|
|
116
|
+
"via Anthropic's hosted Managed Agent (brand assets and design brief already provisioned " +
|
|
117
|
+
"in that agent's environment - nothing is uploaded here). Creates a new session, sends " +
|
|
118
|
+
"the outline as the first message, and returns the session id right away - it does NOT " +
|
|
119
|
+
"wait for the deck to finish building. Use this whenever the user has typed or drafted " +
|
|
120
|
+
"an outline/agenda and wants it turned into a Traffic Builders PowerPoint. Follow up later " +
|
|
121
|
+
"with get_tb_powerpoint_result using the returned session id to fetch the finished file.",
|
|
122
|
+
inputSchema: {
|
|
123
|
+
type: "object",
|
|
124
|
+
properties: {
|
|
125
|
+
outline: {
|
|
126
|
+
type: "string",
|
|
127
|
+
description: "The presentation outline: section/slide topics in order, with any sub-points, KPIs, or copy the user already wrote."
|
|
128
|
+
},
|
|
129
|
+
deckType: {
|
|
130
|
+
type: "string",
|
|
131
|
+
description: "Optional short label for the deck (e.g. 'SEA Audit', 'Kwartaalupdate')."
|
|
132
|
+
},
|
|
133
|
+
clientName: {
|
|
134
|
+
type: "string",
|
|
135
|
+
description: "Optional client/company name for title-slide and footer placeholders."
|
|
136
|
+
},
|
|
137
|
+
filename: {
|
|
138
|
+
type: "string",
|
|
139
|
+
description: "Optional filename without extension for the resulting .pptx."
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
required: ["outline"]
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "get_tb_powerpoint_result",
|
|
147
|
+
description: "Check on a Traffic Builders PowerPoint generation started with generate_tb_powerpoint, and " +
|
|
148
|
+
"fetch the finished file once ready. Retrieves the session, and ONLY looks for output files " +
|
|
149
|
+
"if the session is idle AND its most recent status_idle event has stop_reason 'end_turn' " +
|
|
150
|
+
"(a clean finish - not idle for another reason like a budget cap). When ready, finds a " +
|
|
151
|
+
".pptx file under the session's outputs, downloads it into the local GCS-synced output " +
|
|
152
|
+
"folder, and returns a download link. If not ready yet, reports the current status instead.",
|
|
153
|
+
inputSchema: {
|
|
154
|
+
type: "object",
|
|
155
|
+
properties: {
|
|
156
|
+
sessionId: {
|
|
157
|
+
type: "string",
|
|
158
|
+
description: "The session id returned by generate_tb_powerpoint."
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
required: ["sessionId"]
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
168
|
+
const { name, arguments: args } = request.params;
|
|
169
|
+
try {
|
|
170
|
+
if (name === "generate_tb_powerpoint") {
|
|
171
|
+
const { outline, deckType, clientName, filename } = GenerateTbPowerpointSchema.parse(args);
|
|
172
|
+
const { apiKey, agentId, environmentId } = requireSessionConfig();
|
|
173
|
+
const client = getClient(apiKey);
|
|
174
|
+
const session = await client.beta.sessions.create({
|
|
175
|
+
agent: agentId,
|
|
176
|
+
environment_id: environmentId
|
|
177
|
+
});
|
|
178
|
+
const lines = [];
|
|
179
|
+
if (deckType)
|
|
180
|
+
lines.push(`Deck type: ${deckType}`);
|
|
181
|
+
if (clientName)
|
|
182
|
+
lines.push(`Client name: ${clientName}`);
|
|
183
|
+
if (filename)
|
|
184
|
+
lines.push(`Save the resulting file as "${filename}.pptx".`);
|
|
185
|
+
const meta = lines.length ? `${lines.join("\n")}\n\n` : "";
|
|
186
|
+
const messageText = `Build a Traffic Builders PowerPoint from the following outline.\n\n${meta}## Outline\n${outline}`;
|
|
187
|
+
await client.beta.sessions.events.send(session.id, {
|
|
188
|
+
events: [
|
|
189
|
+
{ type: "user.message", content: [{ type: "text", text: messageText }] }
|
|
190
|
+
]
|
|
191
|
+
});
|
|
192
|
+
return {
|
|
193
|
+
content: [{
|
|
194
|
+
type: "text",
|
|
195
|
+
text: JSON.stringify({
|
|
196
|
+
success: true,
|
|
197
|
+
sessionId: session.id,
|
|
198
|
+
message: "Generation started. Call get_tb_powerpoint_result with this session id later to check on it and fetch the finished file."
|
|
199
|
+
}, null, 2)
|
|
200
|
+
}]
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (name === "get_tb_powerpoint_result") {
|
|
204
|
+
const { sessionId } = GetTbPowerpointResultSchema.parse(args);
|
|
205
|
+
const apiKey = requireApiKey();
|
|
206
|
+
const client = getClient(apiKey);
|
|
207
|
+
const session = await client.beta.sessions.retrieve(sessionId);
|
|
208
|
+
if (session.status !== "idle") {
|
|
209
|
+
return {
|
|
210
|
+
content: [{
|
|
211
|
+
type: "text",
|
|
212
|
+
text: JSON.stringify({
|
|
213
|
+
ready: false,
|
|
214
|
+
status: session.status,
|
|
215
|
+
message: `Session is "${session.status}", not ready yet. Try again shortly.`
|
|
216
|
+
}, null, 2)
|
|
217
|
+
}]
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
// Find the most recent session.status_idle event to read its stop_reason.
|
|
221
|
+
// (Its exact field shape isn't pinned down in our TS types yet - see file header.)
|
|
222
|
+
let latestIdleEvent = null;
|
|
223
|
+
for await (const event of client.beta.sessions.events.list(sessionId)) {
|
|
224
|
+
if (event.type === "session.status_idle") {
|
|
225
|
+
if (!latestIdleEvent || (event.processed_at ?? "") > (latestIdleEvent.processed_at ?? "")) {
|
|
226
|
+
latestIdleEvent = event;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const stopReasonType = latestIdleEvent?.stop_reason?.type;
|
|
231
|
+
if (stopReasonType !== "end_turn") {
|
|
232
|
+
return {
|
|
233
|
+
content: [{
|
|
234
|
+
type: "text",
|
|
235
|
+
text: JSON.stringify({
|
|
236
|
+
ready: false,
|
|
237
|
+
status: session.status,
|
|
238
|
+
stopReason: stopReasonType ?? null,
|
|
239
|
+
message: `Session is idle but its stop reason is "${stopReasonType ?? "unknown"}", not "end_turn" - not fetching files yet.`
|
|
240
|
+
}, null, 2)
|
|
241
|
+
}]
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
// Session finished cleanly - look for a .pptx output file.
|
|
245
|
+
const files = await client.beta.files.list({
|
|
246
|
+
scope_id: sessionId,
|
|
247
|
+
betas: [MANAGED_AGENTS_BETA]
|
|
248
|
+
});
|
|
249
|
+
const fileList = files.data ?? [];
|
|
250
|
+
const pptxFiles = fileList.filter((f) => /\.pptx$/i.test(f.filename ?? ""));
|
|
251
|
+
const outputPptxFiles = pptxFiles.filter((f) => /outputs?[\\/]/i.test(f.filename ?? ""));
|
|
252
|
+
const chosen = outputPptxFiles[0] ?? pptxFiles[0];
|
|
253
|
+
if (!chosen) {
|
|
254
|
+
return {
|
|
255
|
+
content: [{
|
|
256
|
+
type: "text",
|
|
257
|
+
text: JSON.stringify({
|
|
258
|
+
ready: true,
|
|
259
|
+
status: "idle",
|
|
260
|
+
stopReason: "end_turn",
|
|
261
|
+
message: "Session finished but no .pptx output file was found.",
|
|
262
|
+
sessionFiles: fileList.map((f) => ({ id: f.id, filename: f.filename }))
|
|
263
|
+
}, null, 2)
|
|
264
|
+
}]
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const { outputDir, publicUrlBase } = requireOutputConfig();
|
|
268
|
+
const safeName = path.basename(chosen.filename || `${sessionId}.pptx`);
|
|
269
|
+
const destPath = path.join(outputDir, safeName);
|
|
270
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
271
|
+
const content = await client.beta.files.download(chosen.id);
|
|
272
|
+
await content.writeToFile(destPath);
|
|
273
|
+
const downloadUrl = `${publicUrlBase.replace(/\/$/, "")}/${encodeURIComponent(safeName)}`;
|
|
274
|
+
return {
|
|
275
|
+
content: [{
|
|
276
|
+
type: "text",
|
|
277
|
+
text: JSON.stringify({
|
|
278
|
+
ready: true,
|
|
279
|
+
status: "idle",
|
|
280
|
+
stopReason: "end_turn",
|
|
281
|
+
filename: safeName,
|
|
282
|
+
savedTo: destPath,
|
|
283
|
+
downloadUrl
|
|
284
|
+
}, null, 2)
|
|
285
|
+
}]
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
if (error instanceof z.ZodError) {
|
|
292
|
+
return {
|
|
293
|
+
content: [{
|
|
294
|
+
type: "text",
|
|
295
|
+
text: JSON.stringify({
|
|
296
|
+
error: "Invalid arguments",
|
|
297
|
+
details: error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ")
|
|
298
|
+
}, null, 2)
|
|
299
|
+
}]
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
content: [{
|
|
304
|
+
type: "text",
|
|
305
|
+
text: JSON.stringify({
|
|
306
|
+
error: "Tool call failed",
|
|
307
|
+
message: error.message
|
|
308
|
+
}, null, 2)
|
|
309
|
+
}]
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
async function main() {
|
|
314
|
+
try {
|
|
315
|
+
const transport = new StdioServerTransport();
|
|
316
|
+
await server.connect(transport);
|
|
317
|
+
console.error("Traffic Builders PowerPoint MCP Server running on stdio");
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
console.error("Error during startup:", error);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
main().catch((error) => {
|
|
325
|
+
console.error("Fatal error in main():", error);
|
|
326
|
+
process.exit(1);
|
|
327
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unboundai/tb-ppt-mgagent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server that hands a presentation outline to the Traffic Builders PowerPoint Managed Agent, then retrieves the finished .pptx",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"tb-ppt-mgagent": "build/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"build",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"start": "node build/index.js",
|
|
17
|
+
"dev": "tsc -w",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"modelcontextprotocol",
|
|
23
|
+
"powerpoint",
|
|
24
|
+
"managed-agents",
|
|
25
|
+
"anthropic",
|
|
26
|
+
"claude",
|
|
27
|
+
"unboundai"
|
|
28
|
+
],
|
|
29
|
+
"type": "module",
|
|
30
|
+
"author": "Traffic Builders",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@anthropic-ai/sdk": "^0.120.0",
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
38
|
+
"@types/node": "^18.19.84",
|
|
39
|
+
"typescript": "^5.8.2",
|
|
40
|
+
"zod": "^3.24.2"
|
|
41
|
+
}
|
|
42
|
+
}
|