tavant-docs-mcp 1.0.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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/assets/bg-agenda-data.jpeg +0 -0
  3. package/assets/bg-breaker-brain.jpeg +0 -0
  4. package/assets/bg-breaker-cloud.jpeg +0 -0
  5. package/assets/bg-breaker-lines.jpeg +0 -0
  6. package/assets/bg-thankyou.jpeg +0 -0
  7. package/assets/bg-title-tech.jpeg +0 -0
  8. package/assets/cr-image1.png +0 -0
  9. package/assets/decor-cubes.png +0 -0
  10. package/assets/footer-bar.png +0 -0
  11. package/assets/tavant-logo-orange.png +0 -0
  12. package/assets/tavant-logo-small.png +0 -0
  13. package/assets/tavant-logo-white-sm.png +0 -0
  14. package/assets/tavant-logo-white.png +0 -0
  15. package/assets/tavant-template.potx +0 -0
  16. package/brand.js +21 -0
  17. package/index.js +172 -0
  18. package/knowledge/tavant-company.md +181 -0
  19. package/knowledge/tavant-template.md +61 -0
  20. package/package.json +32 -0
  21. package/templates/contract/builders.js +317 -0
  22. package/templates/contract/register.js +213 -0
  23. package/templates/contract/sections.js +73 -0
  24. package/templates/cr/builders.js +286 -0
  25. package/templates/cr/register.js +189 -0
  26. package/templates/cr/sections.js +55 -0
  27. package/templates/msa/builders.js +480 -0
  28. package/templates/msa/register.js +185 -0
  29. package/templates/msa/sections.js +86 -0
  30. package/templates/nda/builders.js +277 -0
  31. package/templates/nda/register.js +185 -0
  32. package/templates/nda/sections.js +73 -0
  33. package/templates/pptx/builders.js +712 -0
  34. package/templates/pptx/layouts.js +168 -0
  35. package/templates/pptx/register.js +363 -0
  36. package/templates/sow/builders.js +294 -0
  37. package/templates/sow/register.js +183 -0
  38. package/templates/sow/sections.js +76 -0
  39. package/test-custom-slide.js +79 -0
  40. package/test-e2e.js +190 -0
  41. package/test-msa.js +48 -0
  42. package/test-nda-cr.js +88 -0
  43. package/test-pptx.js +93 -0
@@ -0,0 +1,294 @@
1
+ const { Paragraph, TextRun, HeadingLevel, AlignmentType } = require("docx");
2
+ const BRAND = require("../../brand");
3
+
4
+ const FONT = BRAND.font;
5
+ const ORANGE = BRAND.colors.orange;
6
+
7
+ function heading(text, level = HeadingLevel.HEADING_1) {
8
+ return new Paragraph({
9
+ heading: level,
10
+ spacing: { before: 300, after: 150 },
11
+ children: [
12
+ new TextRun({ text, bold: true, font: FONT, size: level === HeadingLevel.HEADING_1 ? 28 : 24, color: ORANGE }),
13
+ ],
14
+ });
15
+ }
16
+
17
+ function subheading(text) {
18
+ return heading(text, HeadingLevel.HEADING_2);
19
+ }
20
+
21
+ function bodyText(text, options = {}) {
22
+ return new Paragraph({
23
+ spacing: { after: 120 },
24
+ children: [new TextRun({ text, font: FONT, size: 22, color: "333333", ...options })],
25
+ });
26
+ }
27
+
28
+ function bulletItem(text) {
29
+ return new Paragraph({
30
+ bullet: { level: 0 },
31
+ spacing: { after: 80 },
32
+ children: [new TextRun({ text, font: FONT, size: 22, color: "333333" })],
33
+ });
34
+ }
35
+
36
+ function emptyLine() {
37
+ return new Paragraph({ spacing: { after: 100 }, children: [] });
38
+ }
39
+
40
+ const sectionBuilders = {
41
+ cover_page(data) {
42
+ return [
43
+ emptyLine(), emptyLine(), emptyLine(),
44
+ new Paragraph({
45
+ alignment: AlignmentType.CENTER,
46
+ spacing: { after: 200 },
47
+ children: [new TextRun({ text: BRAND.company.toUpperCase(), bold: true, font: FONT, size: 48, color: ORANGE })],
48
+ }),
49
+ emptyLine(),
50
+ new Paragraph({
51
+ alignment: AlignmentType.CENTER,
52
+ spacing: { after: 100 },
53
+ children: [new TextRun({ text: "Statement of Work", bold: true, font: FONT, size: 36, color: "333333" })],
54
+ }),
55
+ new Paragraph({
56
+ alignment: AlignmentType.CENTER,
57
+ spacing: { after: 80 },
58
+ children: [new TextRun({ text: data.sow_title || data.project_name || "[Project Name]", font: FONT, size: 28, color: "666666" })],
59
+ }),
60
+ emptyLine(),
61
+ new Paragraph({
62
+ alignment: AlignmentType.CENTER,
63
+ children: [new TextRun({ text: `Prepared for: ${data.client_name || "[Client]"}`, font: FONT, size: 22, color: "666666" })],
64
+ }),
65
+ data.sow_number ? new Paragraph({
66
+ alignment: AlignmentType.CENTER,
67
+ children: [new TextRun({ text: `SOW #: ${data.sow_number}`, font: FONT, size: 22, color: "666666" })],
68
+ }) : emptyLine(),
69
+ new Paragraph({
70
+ alignment: AlignmentType.CENTER,
71
+ children: [new TextRun({ text: `Date: ${data.effective_date || "[Date]"}`, font: FONT, size: 22, color: "666666" })],
72
+ }),
73
+ emptyLine(),
74
+ new Paragraph({
75
+ alignment: AlignmentType.CENTER,
76
+ children: [new TextRun({ text: BRAND.footer, font: FONT, size: 18, color: "999999", italics: true })],
77
+ }),
78
+ ];
79
+ },
80
+
81
+ overview(data) {
82
+ const paragraphs = [heading("1. PROJECT OVERVIEW")];
83
+ if (data.background) {
84
+ paragraphs.push(subheading("1.1 Background"));
85
+ paragraphs.push(bodyText(data.background));
86
+ }
87
+ if (data.objectives) {
88
+ paragraphs.push(subheading("1.2 Objectives"));
89
+ if (Array.isArray(data.objectives)) {
90
+ data.objectives.forEach((o) => paragraphs.push(bulletItem(o)));
91
+ } else {
92
+ paragraphs.push(bodyText(data.objectives));
93
+ }
94
+ }
95
+ if (data.success_criteria) {
96
+ paragraphs.push(subheading("1.3 Success Criteria"));
97
+ if (Array.isArray(data.success_criteria)) {
98
+ data.success_criteria.forEach((c) => paragraphs.push(bulletItem(c)));
99
+ } else {
100
+ paragraphs.push(bodyText(data.success_criteria));
101
+ }
102
+ }
103
+ return paragraphs;
104
+ },
105
+
106
+ scope(data) {
107
+ const paragraphs = [heading("2. SCOPE OF WORK")];
108
+ if (data.work_packages) {
109
+ paragraphs.push(subheading("2.1 Work Packages"));
110
+ if (Array.isArray(data.work_packages)) {
111
+ data.work_packages.forEach((wp) => {
112
+ if (typeof wp === "string") {
113
+ paragraphs.push(bulletItem(wp));
114
+ } else {
115
+ paragraphs.push(bodyText(wp.name || "", { bold: true }));
116
+ paragraphs.push(bodyText(wp.description || ""));
117
+ }
118
+ });
119
+ } else {
120
+ paragraphs.push(bodyText(data.work_packages));
121
+ }
122
+ }
123
+ if (data.in_scope) {
124
+ paragraphs.push(subheading("2.2 In Scope"));
125
+ (Array.isArray(data.in_scope) ? data.in_scope : [data.in_scope]).forEach((s) => paragraphs.push(bulletItem(s)));
126
+ }
127
+ if (data.out_of_scope) {
128
+ paragraphs.push(subheading("2.3 Out of Scope"));
129
+ (Array.isArray(data.out_of_scope) ? data.out_of_scope : [data.out_of_scope]).forEach((s) => paragraphs.push(bulletItem(s)));
130
+ }
131
+ return paragraphs;
132
+ },
133
+
134
+ approach(data) {
135
+ const paragraphs = [heading("3. APPROACH & METHODOLOGY")];
136
+ if (data.methodology) paragraphs.push(bodyText(`Methodology: ${data.methodology}`));
137
+ if (data.technologies) {
138
+ paragraphs.push(subheading("3.1 Technologies"));
139
+ (Array.isArray(data.technologies) ? data.technologies : [data.technologies]).forEach((t) => paragraphs.push(bulletItem(t)));
140
+ }
141
+ if (data.tools) {
142
+ paragraphs.push(subheading("3.2 Tools"));
143
+ (Array.isArray(data.tools) ? data.tools : [data.tools]).forEach((t) => paragraphs.push(bulletItem(t)));
144
+ }
145
+ if (data.team_structure) paragraphs.push(emptyLine(), bodyText(data.team_structure));
146
+ return paragraphs;
147
+ },
148
+
149
+ deliverables(data) {
150
+ const paragraphs = [heading("4. DELIVERABLES")];
151
+ if (Array.isArray(data.deliverables)) {
152
+ data.deliverables.forEach((d, i) => {
153
+ if (typeof d === "string") {
154
+ paragraphs.push(bulletItem(d));
155
+ } else {
156
+ paragraphs.push(bodyText(`${i + 1}. ${d.name || d.title || ""}`, { bold: true }));
157
+ if (d.description) paragraphs.push(bodyText(d.description));
158
+ if (d.acceptance_criteria) paragraphs.push(bodyText(`Acceptance: ${d.acceptance_criteria}`, { italics: true, color: "666666" }));
159
+ paragraphs.push(emptyLine());
160
+ }
161
+ });
162
+ }
163
+ return paragraphs;
164
+ },
165
+
166
+ timeline(data) {
167
+ const paragraphs = [heading("5. TIMELINE & PHASES")];
168
+ if (Array.isArray(data.phases)) {
169
+ data.phases.forEach((phase, i) => {
170
+ if (typeof phase === "string") {
171
+ paragraphs.push(bulletItem(phase));
172
+ } else {
173
+ paragraphs.push(bodyText(`Phase ${i + 1}: ${phase.name || ""}`, { bold: true }));
174
+ if (phase.duration) paragraphs.push(bodyText(`Duration: ${phase.duration}`));
175
+ if (phase.description) paragraphs.push(bodyText(phase.description));
176
+ if (phase.deliverables) {
177
+ (Array.isArray(phase.deliverables) ? phase.deliverables : [phase.deliverables])
178
+ .forEach((d) => paragraphs.push(bulletItem(d)));
179
+ }
180
+ paragraphs.push(emptyLine());
181
+ }
182
+ });
183
+ }
184
+ return paragraphs;
185
+ },
186
+
187
+ team(data) {
188
+ const paragraphs = [heading("6. TEAM & RESOURCES")];
189
+ if (Array.isArray(data.roles)) {
190
+ data.roles.forEach((role) => {
191
+ if (typeof role === "string") {
192
+ paragraphs.push(bulletItem(role));
193
+ } else {
194
+ paragraphs.push(bodyText(`${role.title || role.role || ""}`, { bold: true }));
195
+ if (role.count) paragraphs.push(bodyText(`Count: ${role.count}`));
196
+ if (role.responsibilities) paragraphs.push(bodyText(`Responsibilities: ${role.responsibilities}`));
197
+ paragraphs.push(emptyLine());
198
+ }
199
+ });
200
+ }
201
+ return paragraphs;
202
+ },
203
+
204
+ pricing(data) {
205
+ const paragraphs = [
206
+ heading("7. PRICING & ESTIMATES"),
207
+ bodyText(`Pricing Model: ${data.pricing_model || "Time & Materials"}`),
208
+ bodyText(`Currency: ${data.currency || "USD"}`),
209
+ ];
210
+ if (data.rate_card && Array.isArray(data.rate_card)) {
211
+ paragraphs.push(subheading("7.1 Rate Card"));
212
+ data.rate_card.forEach((r) => {
213
+ paragraphs.push(bulletItem(`${r.role || ""}: ${r.rate || ""}/hr`));
214
+ });
215
+ }
216
+ if (data.total_estimate) {
217
+ paragraphs.push(emptyLine());
218
+ paragraphs.push(bodyText(`Total Estimate: ${data.currency || "USD"} ${data.total_estimate}`, { bold: true }));
219
+ }
220
+ return paragraphs;
221
+ },
222
+
223
+ assumptions(data) {
224
+ const paragraphs = [heading("8. ASSUMPTIONS & DEPENDENCIES")];
225
+ if (data.assumptions) {
226
+ paragraphs.push(subheading("8.1 Assumptions"));
227
+ (Array.isArray(data.assumptions) ? data.assumptions : [data.assumptions]).forEach((a) => paragraphs.push(bulletItem(a)));
228
+ }
229
+ if (data.dependencies) {
230
+ paragraphs.push(subheading("8.2 Dependencies"));
231
+ (Array.isArray(data.dependencies) ? data.dependencies : [data.dependencies]).forEach((d) => paragraphs.push(bulletItem(d)));
232
+ }
233
+ if (data.risks) {
234
+ paragraphs.push(subheading("8.3 Risks"));
235
+ (Array.isArray(data.risks) ? data.risks : [data.risks]).forEach((r) => {
236
+ if (typeof r === "string") {
237
+ paragraphs.push(bulletItem(r));
238
+ } else {
239
+ paragraphs.push(bulletItem(`${r.risk || ""} — Mitigation: ${r.mitigation || ""}`));
240
+ }
241
+ });
242
+ }
243
+ return paragraphs;
244
+ },
245
+
246
+ governance(data) {
247
+ const paragraphs = [heading("9. GOVERNANCE & COMMUNICATION")];
248
+ if (data.meetings && Array.isArray(data.meetings)) {
249
+ paragraphs.push(subheading("9.1 Meeting Cadence"));
250
+ data.meetings.forEach((m) => {
251
+ if (typeof m === "string") {
252
+ paragraphs.push(bulletItem(m));
253
+ } else {
254
+ paragraphs.push(bulletItem(`${m.type || ""}: ${m.frequency || ""} — ${m.participants || ""}`));
255
+ }
256
+ });
257
+ }
258
+ if (data.reporting) paragraphs.push(subheading("9.2 Reporting"), bodyText(data.reporting));
259
+ if (data.escalation_path) paragraphs.push(subheading("9.3 Escalation"), bodyText(data.escalation_path));
260
+ return paragraphs;
261
+ },
262
+
263
+ acceptance(data) {
264
+ return [
265
+ heading("10. ACCEPTANCE CRITERIA"),
266
+ bodyText(`Acceptance Process: ${data.acceptance_process || "Client review and written sign-off within the review period."}`),
267
+ bodyText(`Review Period: ${data.review_period || "5 business days from deliverable submission."}`),
268
+ ];
269
+ },
270
+
271
+ signatures(data) {
272
+ return [
273
+ heading("11. SIGNATURES"),
274
+ emptyLine(),
275
+ bodyText("IN WITNESS WHEREOF, the parties agree to the scope and terms described in this Statement of Work."),
276
+ emptyLine(), emptyLine(),
277
+ bodyText("For and on behalf of Client:", { bold: true }),
278
+ emptyLine(),
279
+ bodyText("_________________________________"),
280
+ bodyText(`Name: ${data.client_signatory || "[Authorized Signatory]"}`),
281
+ bodyText(`Title: ${data.client_title || "[Title]"}`),
282
+ bodyText("Date: _______________"),
283
+ emptyLine(), emptyLine(),
284
+ bodyText("For and on behalf of Tavant:", { bold: true }),
285
+ emptyLine(),
286
+ bodyText("_________________________________"),
287
+ bodyText(`Name: ${data.tavant_signatory || "[Authorized Signatory]"}`),
288
+ bodyText(`Title: ${data.tavant_title || "[Title]"}`),
289
+ bodyText("Date: _______________"),
290
+ ];
291
+ },
292
+ };
293
+
294
+ module.exports = sectionBuilders;
@@ -0,0 +1,183 @@
1
+ const { Document, Packer, Header, Footer, Paragraph, TextRun, AlignmentType } = require("docx");
2
+ const { z } = require("zod");
3
+ const { v4: uuidv4 } = require("uuid");
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+ const SECTIONS = require("./sections");
7
+ const sectionBuilders = require("./builders");
8
+ const BRAND = require("../../brand");
9
+
10
+ const sows = new Map();
11
+
12
+ function register(server) {
13
+ server.tool(
14
+ "sow_list_sections",
15
+ "List all available sections for Tavant Statement of Work documents",
16
+ {},
17
+ async () => ({
18
+ content: [{
19
+ type: "text",
20
+ text: JSON.stringify(Object.values(SECTIONS).map((s) => ({
21
+ id: s.id, name: s.name, description: s.description, fields: s.fields,
22
+ })), null, 2),
23
+ }],
24
+ })
25
+ );
26
+
27
+ server.tool(
28
+ "sow_create",
29
+ "Create a new Tavant Statement of Work document. Returns a sow_id.",
30
+ {
31
+ project_name: z.string().optional().describe("Project name"),
32
+ client_name: z.string().optional().describe("Client company name"),
33
+ effective_date: z.string().optional().describe("SOW date"),
34
+ },
35
+ async ({ project_name, client_name, effective_date }) => {
36
+ const id = uuidv4();
37
+ sows.set(id, {
38
+ project_name: project_name || "[Project]",
39
+ client_name: client_name || "[Client]",
40
+ effective_date: effective_date || "[Date]",
41
+ sections: [],
42
+ });
43
+ return {
44
+ content: [{
45
+ type: "text",
46
+ text: JSON.stringify({
47
+ sow_id: id,
48
+ project_name: sows.get(id).project_name,
49
+ message: "SOW created. Use sow_add_section to build it, then sow_export to save.",
50
+ }),
51
+ }],
52
+ };
53
+ }
54
+ );
55
+
56
+ server.tool(
57
+ "sow_add_section",
58
+ "Add a section to a Statement of Work document",
59
+ {
60
+ sow_id: z.string().describe("The SOW ID from sow_create"),
61
+ section: z.string().describe(
62
+ "Section ID: cover_page, overview, scope, approach, deliverables, timeline, team, pricing, assumptions, governance, acceptance, signatures"
63
+ ),
64
+ data: z.record(z.any()).describe("Section content data. Use sow_list_sections to see fields."),
65
+ },
66
+ async ({ sow_id, section, data }) => {
67
+ const sow = sows.get(sow_id);
68
+ if (!sow) {
69
+ return { content: [{ type: "text", text: "Error: SOW not found." }], isError: true };
70
+ }
71
+ const builder = sectionBuilders[section];
72
+ if (!builder) {
73
+ return {
74
+ content: [{ type: "text", text: `Error: Unknown section "${section}". Available: ${Object.keys(SECTIONS).join(", ")}` }],
75
+ isError: true,
76
+ };
77
+ }
78
+ sow.sections.push({ section, data: data || {} });
79
+ return {
80
+ content: [{
81
+ type: "text",
82
+ text: JSON.stringify({ message: `Section added: ${section}`, total_sections: sow.sections.length }),
83
+ }],
84
+ };
85
+ }
86
+ );
87
+
88
+ server.tool(
89
+ "sow_export",
90
+ "Export Statement of Work as a .docx Word document",
91
+ {
92
+ sow_id: z.string().describe("The SOW ID"),
93
+ output_path: z.string().optional().describe("Output path. Defaults to ./output/<project>.docx"),
94
+ },
95
+ async ({ sow_id, output_path }) => {
96
+ const sow = sows.get(sow_id);
97
+ if (!sow) {
98
+ return { content: [{ type: "text", text: "Error: SOW not found." }], isError: true };
99
+ }
100
+
101
+ const children = [];
102
+ for (const { section, data } of sow.sections) {
103
+ const builder = sectionBuilders[section];
104
+ if (builder) {
105
+ children.push(...builder({
106
+ ...data,
107
+ client_name: data.client_name || sow.client_name,
108
+ effective_date: data.effective_date || sow.effective_date,
109
+ project_name: data.project_name || sow.project_name,
110
+ }));
111
+ }
112
+ }
113
+
114
+ const doc = new Document({
115
+ creator: "Tavant",
116
+ title: `SOW — ${sow.project_name}`,
117
+ styles: {
118
+ default: {
119
+ document: {
120
+ run: { font: BRAND.font, size: 22, color: "333333" },
121
+ },
122
+ },
123
+ },
124
+ sections: [{
125
+ properties: {
126
+ page: { margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } },
127
+ },
128
+ headers: {
129
+ default: new Header({
130
+ children: [
131
+ new Paragraph({
132
+ alignment: AlignmentType.RIGHT,
133
+ children: [new TextRun({ text: BRAND.footer, font: BRAND.font, size: 16, color: "999999", italics: true })],
134
+ }),
135
+ ],
136
+ }),
137
+ },
138
+ footers: {
139
+ default: new Footer({
140
+ children: [
141
+ new Paragraph({
142
+ alignment: AlignmentType.CENTER,
143
+ children: [new TextRun({ text: `SOW: ${sow.project_name} | ${sow.client_name} | ${BRAND.company}`, font: BRAND.font, size: 16, color: "999999" })],
144
+ }),
145
+ ],
146
+ }),
147
+ },
148
+ children,
149
+ }],
150
+ });
151
+
152
+ const buffer = await Packer.toBuffer(doc);
153
+ const sanitized = (sow.project_name || "sow").replace(/[^a-zA-Z0-9_-]/g, "_").substring(0, 50);
154
+ const defaultDir = path.join(process.cwd(), "output");
155
+ if (!fs.existsSync(defaultDir)) fs.mkdirSync(defaultDir, { recursive: true });
156
+ const filePath = output_path || path.join(defaultDir, `SOW_${sanitized}.docx`);
157
+ const dir = path.dirname(filePath);
158
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
159
+ fs.writeFileSync(filePath, buffer);
160
+
161
+ return {
162
+ content: [{
163
+ type: "text",
164
+ text: JSON.stringify({ message: "SOW exported", file_path: filePath, total_sections: sow.sections.length }),
165
+ }],
166
+ };
167
+ }
168
+ );
169
+
170
+ server.tool(
171
+ "sow_delete",
172
+ "Delete a SOW from memory",
173
+ { sow_id: z.string().describe("The SOW ID") },
174
+ async ({ sow_id }) => {
175
+ if (sows.delete(sow_id)) {
176
+ return { content: [{ type: "text", text: "SOW deleted." }] };
177
+ }
178
+ return { content: [{ type: "text", text: "Not found." }], isError: true };
179
+ }
180
+ );
181
+ }
182
+
183
+ module.exports = { register };
@@ -0,0 +1,76 @@
1
+ const SECTIONS = {
2
+ cover_page: {
3
+ id: "cover_page",
4
+ name: "Cover Page",
5
+ description: "SOW title, project name, parties, and dates",
6
+ fields: ["sow_title", "project_name", "client_name", "sow_number", "effective_date"],
7
+ },
8
+ overview: {
9
+ id: "overview",
10
+ name: "Project Overview",
11
+ description: "High-level summary of the project, objectives, and business context",
12
+ fields: ["background", "objectives", "success_criteria"],
13
+ },
14
+ scope: {
15
+ id: "scope",
16
+ name: "Scope of Work",
17
+ description: "Detailed work packages, in-scope items, and out-of-scope items",
18
+ fields: ["work_packages", "in_scope", "out_of_scope"],
19
+ },
20
+ approach: {
21
+ id: "approach",
22
+ name: "Approach & Methodology",
23
+ description: "Development methodology, tools, technologies, and team structure",
24
+ fields: ["methodology", "technologies", "tools", "team_structure"],
25
+ },
26
+ deliverables: {
27
+ id: "deliverables",
28
+ name: "Deliverables",
29
+ description: "List of deliverables with acceptance criteria",
30
+ fields: ["deliverables"],
31
+ },
32
+ timeline: {
33
+ id: "timeline",
34
+ name: "Timeline & Phases",
35
+ description: "Project phases with durations and milestones",
36
+ fields: ["phases"],
37
+ },
38
+ team: {
39
+ id: "team",
40
+ name: "Team & Resources",
41
+ description: "Team composition, roles, and resource allocation",
42
+ fields: ["roles"],
43
+ },
44
+ pricing: {
45
+ id: "pricing",
46
+ name: "Pricing & Estimates",
47
+ description: "Cost breakdown, rate card, and total estimate",
48
+ fields: ["pricing_model", "rate_card", "total_estimate", "currency"],
49
+ },
50
+ assumptions: {
51
+ id: "assumptions",
52
+ name: "Assumptions & Dependencies",
53
+ description: "Project assumptions, dependencies, and risks",
54
+ fields: ["assumptions", "dependencies", "risks"],
55
+ },
56
+ governance: {
57
+ id: "governance",
58
+ name: "Governance & Communication",
59
+ description: "Reporting structure, meeting cadence, escalation path",
60
+ fields: ["meetings", "reporting", "escalation_path"],
61
+ },
62
+ acceptance: {
63
+ id: "acceptance",
64
+ name: "Acceptance Criteria",
65
+ description: "Definition of done, acceptance process, and sign-off",
66
+ fields: ["acceptance_process", "review_period"],
67
+ },
68
+ signatures: {
69
+ id: "signatures",
70
+ name: "Signatures",
71
+ description: "Sign-off blocks for both parties",
72
+ fields: ["client_signatory", "client_title", "tavant_signatory", "tavant_title"],
73
+ },
74
+ };
75
+
76
+ module.exports = SECTIONS;
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Test: pptx_add_custom_slide + pptx_add_element (creative freedom tools)
4
+ */
5
+ const { Client } = require("@modelcontextprotocol/sdk/client/index.js");
6
+ const { StdioClientTransport } = require("@modelcontextprotocol/sdk/client/stdio.js");
7
+ const path = require("path");
8
+
9
+ (async () => {
10
+ console.log("=== Custom Slide + Element Test ===\n");
11
+ const transport = new StdioClientTransport({ command: "node", args: [path.join(__dirname, "index.js")] });
12
+ const client = new Client({ name: "test", version: "1.0.0" });
13
+ await client.connect(transport);
14
+
15
+ // Create presentation
16
+ const pres = await client.callTool({ name: "pptx_create", arguments: { title: "Custom Slide Test", author: "Test" } });
17
+ const pid = JSON.parse(pres.content[0].text).presentation_id;
18
+ console.log("Created presentation:", pid);
19
+
20
+ // Add a custom white slide
21
+ const cs1 = await client.callTool({ name: "pptx_add_custom_slide", arguments: { presentation_id: pid, background: "FFFFFF" } });
22
+ const cs1Info = JSON.parse(cs1.content[0].text);
23
+ console.log("Custom slide (white):", cs1Info);
24
+
25
+ // Add elements to it
26
+ const el1 = await client.callTool({ name: "pptx_add_element", arguments: {
27
+ presentation_id: pid,
28
+ slide_index: cs1Info.slide_index,
29
+ element_type: "text",
30
+ props: { text: "CREATIVE TITLE", x: 0.5, y: 0.5, w: 12, h: 0.8, fontSize: 36, bold: true, color: "F36E26", fontFace: "Aptos" }
31
+ }});
32
+ console.log("Added title:", JSON.parse(el1.content[0].text));
33
+
34
+ const el2 = await client.callTool({ name: "pptx_add_element", arguments: {
35
+ presentation_id: pid,
36
+ slide_index: cs1Info.slide_index,
37
+ element_type: "shape",
38
+ props: { shape: "rect", x: 0.5, y: 1.5, w: 5.5, h: 4.5, fill: "F5F5F5", rectRadius: 0.1 }
39
+ }});
40
+ console.log("Added shape:", JSON.parse(el2.content[0].text));
41
+
42
+ const el3 = await client.callTool({ name: "pptx_add_element", arguments: {
43
+ presentation_id: pid,
44
+ slide_index: cs1Info.slide_index,
45
+ element_type: "text",
46
+ props: { text: "Key Insights:\n• AI adoption grew 270% YoY\n• 75+ data scientists on staff\n• Multi-LLM architecture deployed", x: 0.8, y: 1.8, w: 4.9, h: 3.8, fontSize: 16, color: "333333", fontFace: "Aptos", valign: "top" }
47
+ }});
48
+ console.log("Added body text:", JSON.parse(el3.content[0].text));
49
+
50
+ // Add a custom dark slide
51
+ const cs2 = await client.callTool({ name: "pptx_add_custom_slide", arguments: { presentation_id: pid, background: "222222" } });
52
+ const cs2Info = JSON.parse(cs2.content[0].text);
53
+ console.log("\nCustom slide (dark):", cs2Info);
54
+
55
+ const el4 = await client.callTool({ name: "pptx_add_element", arguments: {
56
+ presentation_id: pid,
57
+ slide_index: cs2Info.slide_index,
58
+ element_type: "text",
59
+ props: { text: "DARK SLIDE WITH FULL CREATIVE CONTROL", x: 0.5, y: 2, w: 12, h: 1, fontSize: 32, bold: true, color: "FF8909", fontFace: "Aptos", align: "center" }
60
+ }});
61
+ console.log("Added dark text:", JSON.parse(el4.content[0].text));
62
+
63
+ // Add a chart element
64
+ const el5 = await client.callTool({ name: "pptx_add_element", arguments: {
65
+ presentation_id: pid,
66
+ slide_index: cs2Info.slide_index,
67
+ element_type: "chart",
68
+ props: { x: 2, y: 3.2, w: 9, h: 3, chartType: "bar", data: [{ name: "Revenue", labels: ["Q1", "Q2", "Q3", "Q4"], values: [2.1, 3.4, 5.2, 7.8] }] }
69
+ }});
70
+ console.log("Added chart:", JSON.parse(el5.content[0].text));
71
+
72
+ // Export
73
+ const outPath = path.join(__dirname, "output", "Custom_Slide_Test.pptx");
74
+ await client.callTool({ name: "pptx_export", arguments: { presentation_id: pid, output_path: outPath } });
75
+ console.log(`\nExported: ${outPath}`);
76
+
77
+ await client.close();
78
+ process.exit(0);
79
+ })().catch(e => { console.error("FAILED:", e); process.exit(1); });