@mcp-consultant-tools/sharepoint 21.0.0-beta.2 → 21.0.0-beta.4

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 (35) hide show
  1. package/build/SharePointService.d.ts +9 -0
  2. package/build/SharePointService.d.ts.map +1 -1
  3. package/build/SharePointService.js +13 -0
  4. package/build/SharePointService.js.map +1 -1
  5. package/build/http-server.d.ts +3 -0
  6. package/build/http-server.d.ts.map +1 -0
  7. package/build/http-server.js +216 -0
  8. package/build/http-server.js.map +1 -0
  9. package/build/index.d.ts +6 -0
  10. package/build/index.d.ts.map +1 -1
  11. package/build/index.js +42 -1036
  12. package/build/index.js.map +1 -1
  13. package/build/services/FileOperationsService.d.ts +52 -0
  14. package/build/services/FileOperationsService.d.ts.map +1 -0
  15. package/build/services/FileOperationsService.js +451 -0
  16. package/build/services/FileOperationsService.js.map +1 -0
  17. package/build/tools/prompts.d.ts +11 -0
  18. package/build/tools/prompts.d.ts.map +1 -0
  19. package/build/tools/prompts.js +470 -0
  20. package/build/tools/prompts.js.map +1 -0
  21. package/build/tools/read-tools.d.ts +13 -0
  22. package/build/tools/read-tools.d.ts.map +1 -0
  23. package/build/tools/read-tools.js +271 -0
  24. package/build/tools/read-tools.js.map +1 -0
  25. package/build/tools/write-tools.d.ts +14 -0
  26. package/build/tools/write-tools.d.ts.map +1 -0
  27. package/build/tools/write-tools.js +137 -0
  28. package/build/tools/write-tools.js.map +1 -0
  29. package/build/types/sharepoint-types.d.ts +34 -0
  30. package/build/types/sharepoint-types.d.ts.map +1 -1
  31. package/build/utils/tool-examples.d.ts +35 -0
  32. package/build/utils/tool-examples.d.ts.map +1 -0
  33. package/build/utils/tool-examples.js +55 -0
  34. package/build/utils/tool-examples.js.map +1 -0
  35. package/package.json +5 -2
package/build/index.js CHANGED
@@ -1,13 +1,22 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * SharePoint MCP Server - Orchestrator
4
+ *
5
+ * Slim entry point that delegates to extracted tool/prompt modules.
6
+ * File management tools require feature flags (SHAREPOINT_ENABLE_WRITE, SHAREPOINT_ENABLE_DELETE).
7
+ */
2
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
9
  import { pathToFileURL } from "node:url";
4
10
  import { realpathSync } from "node:fs";
5
11
  import { createMcpServer, createEnvLoader } from "@mcp-consultant-tools/core";
6
12
  import { SharePointService } from "./SharePointService.js";
7
- import { z } from 'zod';
8
- import * as spoFormatters from './utils/sharepoint-formatters.js';
13
+ import { FileOperationsService } from "./services/FileOperationsService.js";
14
+ import { registerSharePointPrompts } from "./tools/prompts.js";
15
+ import { registerReadTools } from "./tools/read-tools.js";
16
+ import { registerWriteTools } from "./tools/write-tools.js";
9
17
  export function registerSharePointTools(server, sharepointService) {
10
18
  let service = sharepointService || null;
19
+ let fileOps = null;
11
20
  function getSharePointService() {
12
21
  if (!service) {
13
22
  const missingConfig = [];
@@ -52,1047 +61,44 @@ export function registerSharePointTools(server, sharepointService) {
52
61
  }
53
62
  return service;
54
63
  }
64
+ function getFileOperationsService() {
65
+ if (!fileOps) {
66
+ const spoService = getSharePointService();
67
+ fileOps = new FileOperationsService(spoService, {
68
+ maxDownloadSizeMB: parseInt(process.env.SHAREPOINT_MAX_DOWNLOAD_SIZE_MB || '50', 10),
69
+ maxUploadSizeMB: parseInt(process.env.SHAREPOINT_MAX_UPLOAD_SIZE_MB || '100', 10),
70
+ });
71
+ console.error("FileOperationsService initialized");
72
+ }
73
+ return fileOps;
74
+ }
55
75
  // PowerPlatform integration (requires cross-package dependency)
56
76
  function getPowerPlatformService() {
57
77
  throw new Error('PowerPlatform integration not available in standalone SharePoint package. ' +
58
78
  'Use the complete @mcp-consultant-tools package for cross-service validation.');
59
79
  }
60
- // ========================================
61
- // PROMPTS
62
- // ========================================
63
- server.prompt("spo-site-overview", {
64
- siteId: z.string().describe("Site ID from configuration"),
65
- }, async ({ siteId }) => {
66
- try {
67
- const service = getSharePointService();
68
- // Get site info
69
- const site = await service.getSiteInfo(siteId);
70
- // Get drives (document libraries)
71
- const drives = await service.listDrives(siteId);
72
- // Build report
73
- const sections = [];
74
- sections.push(spoFormatters.formatSiteOverviewAsMarkdown(site));
75
- sections.push('');
76
- sections.push('## Document Libraries');
77
- sections.push(spoFormatters.formatDrivesAsMarkdown(drives));
78
- return {
79
- description: `SharePoint site overview: ${site.displayName}`,
80
- messages: [
81
- {
82
- role: "user",
83
- content: {
84
- type: "text",
85
- text: `Show overview of SharePoint site ${siteId}`,
86
- },
87
- },
88
- {
89
- role: "assistant",
90
- content: {
91
- type: "text",
92
- text: sections.join('\n'),
93
- },
94
- },
95
- ],
96
- };
97
- }
98
- catch (error) {
99
- console.error("Error generating site overview:", error);
100
- throw error;
101
- }
102
- });
103
- server.prompt("spo-library-details", {
104
- siteId: z.string().describe("Site ID"),
105
- driveId: z.string().describe("Drive (library) ID"),
106
- }, async ({ siteId, driveId }) => {
107
- try {
108
- const service = getSharePointService();
109
- // Get drive info
110
- const drive = await service.getDriveInfo(siteId, driveId);
111
- // Get recent items
112
- const recentItems = await service.getRecentItems(siteId, driveId, 10, 30);
113
- // Build report
114
- const sections = [];
115
- sections.push(spoFormatters.formatDriveDetailsAsMarkdown(drive));
116
- sections.push('');
117
- sections.push('## Recent Activity (Last 30 days)');
118
- sections.push(spoFormatters.formatItemsAsMarkdown(recentItems));
119
- return {
120
- description: `Document library details: ${drive.name}`,
121
- messages: [
122
- {
123
- role: "user",
124
- content: {
125
- type: "text",
126
- text: `Show details for document library ${driveId} in site ${siteId}`,
127
- },
128
- },
129
- {
130
- role: "assistant",
131
- content: {
132
- type: "text",
133
- text: sections.join('\n'),
134
- },
135
- },
136
- ],
137
- };
138
- }
139
- catch (error) {
140
- console.error("Error generating library details:", error);
141
- throw error;
142
- }
143
- });
144
- server.prompt("spo-document-search", {
145
- siteId: z.string().describe("Site ID"),
146
- driveId: z.string().describe("Drive ID"),
147
- query: z.string().describe("Search query (filename or keywords)"),
148
- }, async ({ siteId, driveId, query }) => {
149
- try {
150
- const service = getSharePointService();
151
- // Search items
152
- const searchResults = await service.searchItems(siteId, driveId, query);
153
- // Build report
154
- const sections = [];
155
- sections.push(`# 🔍 Search Results: "${query}"`);
156
- sections.push('');
157
- sections.push(`Found ${searchResults.items.length} result(s)`);
158
- sections.push('');
159
- sections.push(spoFormatters.formatItemsAsMarkdown(searchResults.items));
160
- return {
161
- description: `Search results for "${query}"`,
162
- messages: [
163
- {
164
- role: "user",
165
- content: {
166
- type: "text",
167
- text: `Search for "${query}" in drive ${driveId} of site ${siteId}`,
168
- },
169
- },
170
- {
171
- role: "assistant",
172
- content: {
173
- type: "text",
174
- text: sections.join('\n'),
175
- },
176
- },
177
- ],
178
- };
179
- }
180
- catch (error) {
181
- console.error("Error generating search results:", error);
182
- throw error;
183
- }
184
- });
185
- server.prompt("spo-recent-activity", {
186
- siteId: z.string().describe("Site ID"),
187
- driveId: z.string().describe("Drive ID"),
188
- days: z.string().optional().describe("Number of days to look back (default: 7)"),
189
- }, async ({ siteId, driveId, days }) => {
190
- try {
191
- const service = getSharePointService();
192
- const daysBack = days ? parseInt(days) : 7;
193
- const recentItems = await service.getRecentItems(siteId, driveId, 50, daysBack);
194
- // Build report
195
- const sections = [];
196
- sections.push(`# 📅 Recent Activity (Last ${daysBack} days)`);
197
- sections.push('');
198
- sections.push(`**Document Library:** ${driveId}`);
199
- sections.push(`**Total Changes:** ${recentItems.length}`);
200
- sections.push('');
201
- sections.push(spoFormatters.formatItemsAsMarkdown(recentItems));
202
- return {
203
- description: `Recent activity for last ${daysBack} days`,
204
- messages: [
205
- {
206
- role: "user",
207
- content: {
208
- type: "text",
209
- text: `Show recent activity in drive ${driveId} for last ${daysBack} days`,
210
- },
211
- },
212
- {
213
- role: "assistant",
214
- content: {
215
- type: "text",
216
- text: sections.join('\n'),
217
- },
218
- },
219
- ],
220
- };
80
+ // Feature flag guards
81
+ function checkWriteEnabled() {
82
+ if (process.env.SHAREPOINT_ENABLE_WRITE !== 'true') {
83
+ throw new Error('Write operations are disabled. Set SHAREPOINT_ENABLE_WRITE=true to enable.');
221
84
  }
222
- catch (error) {
223
- console.error("Error generating recent activity report:", error);
224
- throw error;
225
- }
226
- });
227
- server.prompt("spo-validate-crm-integration", {
228
- documentLocationId: z.string().describe("Document location ID from PowerPlatform"),
229
- }, async ({ documentLocationId }) => {
230
- try {
231
- const spoService = getSharePointService();
232
- const ppService = getPowerPlatformService();
233
- // Validate document location
234
- const result = await spoService.validateDocumentLocation(ppService, documentLocationId);
235
- // Generate analysis
236
- const sections = [];
237
- sections.push(spoFormatters.formatValidationResultAsMarkdown(result));
238
- return {
239
- description: `Validation result for document location ${documentLocationId}`,
240
- messages: [
241
- {
242
- role: "user",
243
- content: {
244
- type: "text",
245
- text: `Validate PowerPlatform document location ${documentLocationId}`,
246
- },
247
- },
248
- {
249
- role: "assistant",
250
- content: {
251
- type: "text",
252
- text: sections.join('\n'),
253
- },
254
- },
255
- ],
256
- };
257
- }
258
- catch (error) {
259
- console.error("Error validating CRM integration:", error);
260
- throw error;
261
- }
262
- });
263
- server.prompt("spo-document-location-audit", {
264
- entityName: z.string().optional().describe("Entity logical name (e.g., 'account')"),
265
- recordId: z.string().optional().describe("Record ID (GUID)"),
266
- }, async ({ entityName, recordId }) => {
267
- try {
268
- const spoService = getSharePointService();
269
- const ppService = getPowerPlatformService();
270
- // Get document locations
271
- const locations = await spoService.getCrmDocumentLocations(ppService, entityName, recordId);
272
- // Analyze
273
- const analysis = spoFormatters.analyzeCrmDocumentLocations(locations);
274
- // Build report
275
- const sections = [];
276
- sections.push('# 📋 Document Location Audit');
277
- sections.push('');
278
- if (entityName) {
279
- sections.push(`**Entity:** ${entityName}`);
280
- }
281
- if (recordId) {
282
- sections.push(`**Record ID:** ${recordId}`);
283
- }
284
- sections.push('');
285
- sections.push('## Insights');
286
- analysis.insights.forEach(insight => {
287
- sections.push(insight);
288
- });
289
- sections.push('');
290
- sections.push('## Document Locations');
291
- sections.push(spoFormatters.formatCrmDocumentLocationsAsMarkdown(locations));
292
- if (analysis.recommendations.length > 0) {
293
- sections.push('');
294
- sections.push('## Recommendations');
295
- analysis.recommendations.forEach(rec => {
296
- sections.push(`- ${rec}`);
297
- });
298
- }
299
- return {
300
- description: `Document location audit${entityName ? ` for ${entityName}` : ''}`,
301
- messages: [
302
- {
303
- role: "user",
304
- content: {
305
- type: "text",
306
- text: `Audit document locations${entityName ? ` for entity ${entityName}` : ''}${recordId ? ` record ${recordId}` : ''}`,
307
- },
308
- },
309
- {
310
- role: "assistant",
311
- content: {
312
- type: "text",
313
- text: sections.join('\n'),
314
- },
315
- },
316
- ],
317
- };
318
- }
319
- catch (error) {
320
- console.error("Error generating document location audit:", error);
321
- throw error;
322
- }
323
- });
324
- server.prompt("spo-migration-verification-report", {
325
- sourceSiteId: z.string().describe("Source site ID"),
326
- sourcePath: z.string().describe("Source folder path"),
327
- targetSiteId: z.string().describe("Target site ID"),
328
- targetPath: z.string().describe("Target folder path"),
329
- }, async ({ sourceSiteId, sourcePath, targetSiteId, targetPath }) => {
330
- try {
331
- const spoService = getSharePointService();
332
- const ppService = getPowerPlatformService();
333
- // Verify migration
334
- const result = await spoService.verifyDocumentMigration(ppService, sourceSiteId, sourcePath, targetSiteId, targetPath);
335
- // Analyze
336
- const analysis = spoFormatters.analyzeMigrationVerification(result);
337
- // Build report
338
- const sections = [];
339
- sections.push(spoFormatters.formatMigrationReportAsMarkdown(result));
340
- sections.push('');
341
- sections.push('## Analysis');
342
- analysis.insights.forEach(insight => {
343
- sections.push(`- ${insight}`);
344
- });
345
- sections.push('');
346
- sections.push('## Recommendations');
347
- analysis.recommendations.forEach(rec => {
348
- sections.push(`- ${rec}`);
349
- });
350
- return {
351
- description: `Migration verification: ${result.status} (${result.successRate}% success)`,
352
- messages: [
353
- {
354
- role: "user",
355
- content: {
356
- type: "text",
357
- text: `Verify document migration from ${sourcePath} to ${targetPath}`,
358
- },
359
- },
360
- {
361
- role: "assistant",
362
- content: {
363
- type: "text",
364
- text: sections.join('\n'),
365
- },
366
- },
367
- ],
368
- };
369
- }
370
- catch (error) {
371
- console.error("Error generating migration verification report:", error);
372
- throw error;
373
- }
374
- });
375
- server.prompt("spo-setup-validation-guide", {}, async () => {
376
- const guide = `# SharePoint Integration Setup Validation Guide
377
-
378
- ## Prerequisites Checklist
379
-
380
- ### 1. Azure AD App Registration
381
- - ✅ App registered in Azure Active Directory
382
- - ✅ Client ID and Client Secret generated
383
- - ✅ Tenant ID noted
384
-
385
- ### 2. API Permissions
386
- Required Microsoft Graph API permissions (Application permissions):
387
- - ✅ Sites.Read.All or Sites.ReadWrite.All
388
- - ✅ Files.Read.All or Files.ReadWrite.All
389
- - ✅ Admin consent granted
390
-
391
- ### 3. SharePoint Site Access
392
- - ✅ Service principal added to site(s) as Site Collection Admin
393
- - ✅ Site URLs accessible and correct
394
-
395
- ### 4. Configuration
396
- Environment variables configured:
397
- - ✅ SHAREPOINT_TENANT_ID
398
- - ✅ SHAREPOINT_CLIENT_ID
399
- - ✅ SHAREPOINT_CLIENT_SECRET
400
- - ✅ SHAREPOINT_SITES (JSON array) or SHAREPOINT_SITE_URL
401
-
402
- ## Testing Steps
403
-
404
- ### Step 1: Test Connection
405
- \`\`\`
406
- Use tool: spo-test-connection
407
- Parameters: { siteId: "your-site-id" }
408
- Expected: Site information returned with no errors
409
- \`\`\`
410
-
411
- ### Step 2: List Document Libraries
412
- \`\`\`
413
- Use tool: spo-list-drives
414
- Parameters: { siteId: "your-site-id" }
415
- Expected: List of document libraries with quota info
416
- \`\`\`
417
-
418
- ### Step 3: List Files
419
- \`\`\`
420
- Use tool: spo-list-items
421
- Parameters: { siteId: "your-site-id", driveId: "library-id" }
422
- Expected: List of files and folders
423
- \`\`\`
424
-
425
- ### Step 4: Test PowerPlatform Integration (Optional)
426
- \`\`\`
427
- Use tool: spo-get-crm-doc-locs
428
- Expected: List of document locations from Dataverse
429
- \`\`\`
430
-
431
- ## Common Issues
432
-
433
- ### Issue: "Access denied" error
434
- **Solution:**
435
- 1. Verify API permissions are granted
436
- 2. Ensure admin consent is granted
437
- 3. Check service principal is Site Collection Admin
438
-
439
- ### Issue: "Site not found"
440
- **Solution:**
441
- 1. Verify site URL is correct (use full URL)
442
- 2. Check site exists and is accessible
443
- 3. Ensure site is in SHAREPOINT_SITES configuration
444
-
445
- ### Issue: "Authentication failed"
446
- **Solution:**
447
- 1. Verify tenant ID, client ID, and client secret
448
- 2. Check client secret hasn't expired
449
- 3. Ensure app registration is active
450
-
451
- ## Next Steps
452
-
453
- Once setup is validated:
454
- 1. Configure additional sites in SHAREPOINT_SITES
455
- 2. Set up PowerPlatform integration for document location validation
456
- 3. Use validation tools to audit document locations
457
- 4. Set up migration verification workflows
458
-
459
- For more help, refer to SETUP.md documentation.
460
- `;
461
- return {
462
- description: "SharePoint integration setup validation guide",
463
- messages: [
464
- {
465
- role: "user",
466
- content: {
467
- type: "text",
468
- text: "Show SharePoint integration setup validation guide",
469
- },
470
- },
471
- {
472
- role: "assistant",
473
- content: {
474
- type: "text",
475
- text: guide,
476
- },
477
- },
478
- ],
479
- };
480
- });
481
- server.prompt("spo-troubleshooting-guide", {
482
- errorType: z.string().optional().describe("Type of error (e.g., 'access-denied', 'site-not-found')"),
483
- }, async ({ errorType }) => {
484
- const guide = `# SharePoint Integration Troubleshooting Guide
485
-
486
- ## Common Error Scenarios
487
-
488
- ### 1. Access Denied (403 Forbidden)
489
-
490
- **Symptoms:**
491
- - "Access denied" errors when accessing sites or files
492
- - "Insufficient permissions" messages
493
-
494
- **Causes:**
495
- - Missing API permissions
496
- - Admin consent not granted
497
- - Service principal not added to site
498
-
499
- **Solutions:**
500
- 1. Verify Microsoft Graph API permissions:
501
- - Sites.Read.All (or Sites.ReadWrite.All)
502
- - Files.Read.All (or Files.ReadWrite.All)
503
- 2. Grant admin consent in Azure AD
504
- 3. Add service principal as Site Collection Admin:
505
- - Go to site settings → Site permissions
506
- - Add app with client ID
507
- - Grant Full Control or Read permissions
508
-
509
- ### 2. Site Not Found (404 Not Found)
510
-
511
- **Symptoms:**
512
- - "Site not found" errors
513
- - "Resource does not exist" messages
514
-
515
- **Causes:**
516
- - Incorrect site URL
517
- - Site not in SHAREPOINT_SITES configuration
518
- - Site deleted or moved
519
-
520
- **Solutions:**
521
- 1. Verify site URL format: https://tenant.sharepoint.com/sites/sitename
522
- 2. Check site exists by visiting in browser
523
- 3. Add site to SHAREPOINT_SITES configuration
524
- 4. Ensure site is not archived or deleted
525
-
526
- ### 3. Authentication Failed (401 Unauthorized)
527
-
528
- **Symptoms:**
529
- - "Authentication failed" errors
530
- - "Invalid credentials" messages
531
-
532
- **Causes:**
533
- - Incorrect tenant ID, client ID, or client secret
534
- - Client secret expired
535
- - App registration disabled
536
-
537
- **Solutions:**
538
- 1. Verify credentials in environment variables
539
- 2. Check client secret expiration in Azure AD
540
- 3. Generate new client secret if expired
541
- 4. Ensure app registration is active
542
-
543
- ### 4. Token Acquisition Failed
544
-
545
- **Symptoms:**
546
- - "Failed to acquire access token" errors
547
- - MSAL errors
548
-
549
- **Causes:**
550
- - Network connectivity issues
551
- - Firewall blocking Azure AD
552
- - Incorrect tenant ID
553
-
554
- **Solutions:**
555
- 1. Verify network connectivity to login.microsoftonline.com
556
- 2. Check firewall rules
557
- 3. Verify tenant ID is correct
558
- 4. Test authentication manually
559
-
560
- ### 5. Folder Not Found
561
-
562
- **Symptoms:**
563
- - "Folder not accessible" in validation results
564
- - "Item not found" errors
565
-
566
- **Causes:**
567
- - Incorrect folder path
568
- - Folder deleted or moved
569
- - Permissions issue
570
-
571
- **Solutions:**
572
- 1. Verify folder path format: /LibraryName/Folder1/Folder2
573
- 2. Check folder exists in SharePoint
574
- 3. Ensure service principal has access
575
- 4. Use spo-list-items to browse folder structure
576
-
577
- ### 6. Document Location Validation Fails
578
-
579
- **Symptoms:**
580
- - Validation status: "error" or "warning"
581
- - Missing or inaccessible folders
582
-
583
- **Causes:**
584
- - CRM absolute URL incorrect
585
- - Site not configured
586
- - Folder path mismatch
587
-
588
- **Solutions:**
589
- 1. Verify absolute URL in PowerPlatform
590
- 2. Add site to SHAREPOINT_SITES configuration
591
- 3. Check folder path matches SharePoint structure
592
- 4. Use spo-validate-doc-loc tool
593
-
594
- ## Diagnostic Tools
595
-
596
- ### Test Connection
597
- \`\`\`
598
- Use: spo-test-connection
599
- Purpose: Verify site accessibility and permissions
600
- \`\`\`
601
-
602
- ### List Sites
603
- \`\`\`
604
- Use: spo-list-sites
605
- Purpose: Verify configured sites and status
606
- \`\`\`
607
-
608
- ### Validate Document Location
609
- \`\`\`
610
- Use: spo-validate-doc-loc
611
- Purpose: Check PowerPlatform integration
612
- \`\`\`
613
-
614
- ## Getting Help
615
-
616
- If issues persist:
617
- 1. Check application logs for detailed error messages
618
- 2. Review audit logs in Azure AD
619
- 3. Test permissions using Microsoft Graph Explorer
620
- 4. Refer to SETUP.md for detailed configuration steps
621
-
622
- For API-specific errors, refer to Microsoft Graph API documentation.
623
- `;
624
- return {
625
- description: `SharePoint troubleshooting guide${errorType ? ` for ${errorType}` : ''}`,
626
- messages: [
627
- {
628
- role: "user",
629
- content: {
630
- type: "text",
631
- text: `Show SharePoint troubleshooting guide${errorType ? ` for ${errorType}` : ''}`,
632
- },
633
- },
634
- {
635
- role: "assistant",
636
- content: {
637
- type: "text",
638
- text: guide,
639
- },
640
- },
641
- ],
642
- };
643
- });
644
- server.prompt("spo-powerplatform-integration-health", {
645
- entityName: z.string().optional().describe("Entity to check (e.g., 'account')"),
646
- }, async ({ entityName }) => {
647
- try {
648
- const spoService = getSharePointService();
649
- const ppService = getPowerPlatformService();
650
- // Get all document locations for entity
651
- const locations = await spoService.getCrmDocumentLocations(ppService, entityName);
652
- // Analyze
653
- const analysis = spoFormatters.analyzeCrmDocumentLocations(locations);
654
- // Build health report
655
- const sections = [];
656
- sections.push('# 🏥 PowerPlatform-SharePoint Integration Health Check');
657
- sections.push('');
658
- if (entityName) {
659
- sections.push(`**Entity:** ${entityName}`);
660
- sections.push('');
661
- }
662
- sections.push('## Health Summary');
663
- sections.push('');
664
- analysis.insights.forEach(insight => {
665
- sections.push(insight);
666
- });
667
- sections.push('');
668
- sections.push('## Configured Document Locations');
669
- sections.push(spoFormatters.formatCrmDocumentLocationsAsMarkdown(locations));
670
- if (analysis.recommendations.length > 0) {
671
- sections.push('');
672
- sections.push('## Recommendations');
673
- analysis.recommendations.forEach(rec => {
674
- sections.push(`- 💡 ${rec}`);
675
- });
676
- }
677
- sections.push('');
678
- sections.push('## Next Steps');
679
- sections.push('');
680
- sections.push('1. Use `spo-validate-doc-loc` to validate individual locations');
681
- sections.push('2. Check for missing or inaccessible folders');
682
- sections.push('3. Verify service principal has access to all sites');
683
- sections.push('4. Review empty folders and upload documents');
684
- return {
685
- description: `Integration health check${entityName ? ` for ${entityName}` : ''}`,
686
- messages: [
687
- {
688
- role: "user",
689
- content: {
690
- type: "text",
691
- text: `Check PowerPlatform-SharePoint integration health${entityName ? ` for ${entityName}` : ''}`,
692
- },
693
- },
694
- {
695
- role: "assistant",
696
- content: {
697
- type: "text",
698
- text: sections.join('\n'),
699
- },
700
- },
701
- ],
702
- };
703
- }
704
- catch (error) {
705
- console.error("Error checking integration health:", error);
706
- throw error;
707
- }
708
- });
709
- // ========================================
710
- // TOOLS
711
- // ========================================
712
- server.tool("spo-list-sites", "List all configured SharePoint sites (active and inactive)", {}, async () => {
713
- try {
714
- const service = getSharePointService();
715
- const sites = service.getAllSites();
716
- return {
717
- content: [{
718
- type: "text",
719
- text: JSON.stringify(sites, null, 2),
720
- }],
721
- };
722
- }
723
- catch (error) {
724
- console.error("Error listing SharePoint sites:", error);
725
- return {
726
- content: [{
727
- type: "text",
728
- text: `Failed to list sites: ${error.message}`,
729
- }],
730
- isError: true,
731
- };
732
- }
733
- });
734
- server.tool("spo-get-site-info", "Get detailed site information including metadata, created/modified dates, and owner info", {
735
- siteId: z.string().describe("Site ID from configuration (use spo-list-sites to find IDs)"),
736
- }, async ({ siteId }) => {
737
- try {
738
- const service = getSharePointService();
739
- const siteInfo = await service.getSiteInfo(siteId);
740
- return {
741
- content: [{
742
- type: "text",
743
- text: JSON.stringify(siteInfo, null, 2),
744
- }],
745
- };
746
- }
747
- catch (error) {
748
- console.error("Error getting SharePoint site info:", error);
749
- return {
750
- content: [{
751
- type: "text",
752
- text: `Failed to get site info: ${error.message}`,
753
- }],
754
- isError: true,
755
- };
756
- }
757
- });
758
- server.tool("spo-test-connection", "Test connectivity to a SharePoint site and verify permissions (Sites.Read.All and Files.Read.All required)", {
759
- siteId: z.string().describe("Site ID from configuration"),
760
- }, async ({ siteId }) => {
761
- try {
762
- const service = getSharePointService();
763
- const result = await service.testConnection(siteId);
764
- return {
765
- content: [{
766
- type: "text",
767
- text: JSON.stringify(result, null, 2),
768
- }],
769
- };
770
- }
771
- catch (error) {
772
- console.error("Error testing SharePoint connection:", error);
773
- return {
774
- content: [{
775
- type: "text",
776
- text: `Failed to test connection: ${error.message}`,
777
- }],
778
- isError: true,
779
- };
780
- }
781
- });
782
- server.tool("spo-list-drives", "List all document libraries (drives) in a SharePoint site with metadata", {
783
- siteId: z.string().describe("Site ID from configuration"),
784
- }, async ({ siteId }) => {
785
- try {
786
- const service = getSharePointService();
787
- const drives = await service.listDrives(siteId);
788
- return {
789
- content: [{
790
- type: "text",
791
- text: JSON.stringify(drives, null, 2),
792
- }],
793
- };
794
- }
795
- catch (error) {
796
- console.error("Error listing SharePoint drives:", error);
797
- return {
798
- content: [{
799
- type: "text",
800
- text: `Failed to list drives: ${error.message}`,
801
- }],
802
- isError: true,
803
- };
804
- }
805
- });
806
- server.tool("spo-get-drive-info", "Get detailed document library information including quota, owner, and created/modified dates", {
807
- siteId: z.string().describe("Site ID from configuration"),
808
- driveId: z.string().describe("Drive ID (use spo-list-drives to find IDs)"),
809
- }, async ({ siteId, driveId }) => {
810
- try {
811
- const service = getSharePointService();
812
- const driveInfo = await service.getDriveInfo(siteId, driveId);
813
- return {
814
- content: [{
815
- type: "text",
816
- text: JSON.stringify(driveInfo, null, 2),
817
- }],
818
- };
819
- }
820
- catch (error) {
821
- console.error("Error getting SharePoint drive info:", error);
822
- return {
823
- content: [{
824
- type: "text",
825
- text: `Failed to get drive info: ${error.message}`,
826
- }],
827
- isError: true,
828
- };
829
- }
830
- });
831
- server.tool("spo-clear-cache", "Clear cached SharePoint responses (useful after site changes or for troubleshooting)", {
832
- siteId: z.string().optional().describe("Clear cache for specific site only (optional)"),
833
- pattern: z.string().optional().describe("Clear only cache entries matching this pattern (optional)"),
834
- }, async ({ siteId, pattern }) => {
835
- try {
836
- const service = getSharePointService();
837
- const clearedCount = service.clearCache(pattern, siteId);
838
- return {
839
- content: [{
840
- type: "text",
841
- text: JSON.stringify({ clearedCount, message: `Cleared ${clearedCount} cache entries` }, null, 2),
842
- }],
843
- };
844
- }
845
- catch (error) {
846
- console.error("Error clearing SharePoint cache:", error);
847
- return {
848
- content: [{
849
- type: "text",
850
- text: `Failed to clear cache: ${error.message}`,
851
- }],
852
- isError: true,
853
- };
854
- }
855
- });
856
- server.tool("spo-list-items", "List all files and folders in a document library or folder", {
857
- siteId: z.string().describe("Site ID from configuration"),
858
- driveId: z.string().describe("Drive ID"),
859
- folderId: z.string().optional().describe("Folder ID (optional, defaults to root)"),
860
- }, async ({ siteId, driveId, folderId }) => {
861
- try {
862
- const service = getSharePointService();
863
- const items = await service.listItems(siteId, driveId, folderId);
864
- return {
865
- content: [{
866
- type: "text",
867
- text: JSON.stringify(items, null, 2),
868
- }],
869
- };
870
- }
871
- catch (error) {
872
- console.error("Error listing SharePoint items:", error);
873
- return {
874
- content: [{
875
- type: "text",
876
- text: `Failed to list items: ${error.message}`,
877
- }],
878
- isError: true,
879
- };
880
- }
881
- });
882
- server.tool("spo-get-item", "Get detailed file or folder metadata by ID", {
883
- siteId: z.string().describe("Site ID from configuration"),
884
- driveId: z.string().describe("Drive ID"),
885
- itemId: z.string().describe("Item ID"),
886
- }, async ({ siteId, driveId, itemId }) => {
887
- try {
888
- const service = getSharePointService();
889
- const item = await service.getItem(siteId, driveId, itemId);
890
- return {
891
- content: [{
892
- type: "text",
893
- text: JSON.stringify(item, null, 2),
894
- }],
895
- };
896
- }
897
- catch (error) {
898
- console.error("Error getting SharePoint item:", error);
899
- return {
900
- content: [{
901
- type: "text",
902
- text: `Failed to get item: ${error.message}`,
903
- }],
904
- isError: true,
905
- };
906
- }
907
- });
908
- server.tool("spo-get-item-by-path", "Get file or folder metadata by path (relative to drive root)", {
909
- siteId: z.string().describe("Site ID from configuration"),
910
- driveId: z.string().describe("Drive ID"),
911
- path: z.string().describe("Item path (e.g., '/folder/file.docx' or 'folder/subfolder')"),
912
- }, async ({ siteId, driveId, path }) => {
913
- try {
914
- const service = getSharePointService();
915
- const item = await service.getItemByPath(siteId, driveId, path);
916
- return {
917
- content: [{
918
- type: "text",
919
- text: JSON.stringify(item, null, 2),
920
- }],
921
- };
922
- }
923
- catch (error) {
924
- console.error("Error getting SharePoint item by path:", error);
925
- return {
926
- content: [{
927
- type: "text",
928
- text: `Failed to get item by path: ${error.message}`,
929
- }],
930
- isError: true,
931
- };
932
- }
933
- });
934
- server.tool("spo-search-items", "Search for files by filename or metadata (filename and metadata search only, not full-text)", {
935
- siteId: z.string().describe("Site ID from configuration"),
936
- query: z.string().describe("Search query"),
937
- driveId: z.string().optional().describe("Limit search to specific drive (optional)"),
938
- limit: z.number().optional().describe("Maximum results (default: 100, max configured in SHAREPOINT_MAX_SEARCH_RESULTS)"),
939
- }, async ({ siteId, query, driveId, limit }) => {
940
- try {
941
- const service = getSharePointService();
942
- const result = await service.searchItems(siteId, query, driveId, limit);
943
- return {
944
- content: [{
945
- type: "text",
946
- text: JSON.stringify(result, null, 2),
947
- }],
948
- };
949
- }
950
- catch (error) {
951
- console.error("Error searching SharePoint items:", error);
952
- return {
953
- content: [{
954
- type: "text",
955
- text: `Failed to search items: ${error.message}`,
956
- }],
957
- isError: true,
958
- };
959
- }
960
- });
961
- server.tool("spo-get-recent-items", "Get recently modified items in a document library", {
962
- siteId: z.string().describe("Site ID from configuration"),
963
- driveId: z.string().describe("Drive ID"),
964
- limit: z.number().optional().describe("Maximum results (default: 20, max: 100)"),
965
- days: z.number().optional().describe("Days back to search (default: 30)"),
966
- }, async ({ siteId, driveId, limit, days }) => {
967
- try {
968
- const service = getSharePointService();
969
- const items = await service.getRecentItems(siteId, driveId, limit, days);
970
- return {
971
- content: [{
972
- type: "text",
973
- text: JSON.stringify(items, null, 2),
974
- }],
975
- };
976
- }
977
- catch (error) {
978
- console.error("Error getting recent SharePoint items:", error);
979
- return {
980
- content: [{
981
- type: "text",
982
- text: `Failed to get recent items: ${error.message}`,
983
- }],
984
- isError: true,
985
- };
986
- }
987
- });
988
- server.tool("spo-get-folder-structure", "Get recursive folder tree structure (useful for understanding site organization)", {
989
- siteId: z.string().describe("Site ID from configuration"),
990
- driveId: z.string().describe("Drive ID"),
991
- folderId: z.string().optional().describe("Root folder ID (optional, defaults to drive root)"),
992
- depth: z.number().optional().describe("Recursion depth (default: 3, max: 10)"),
993
- }, async ({ siteId, driveId, folderId, depth }) => {
994
- try {
995
- const service = getSharePointService();
996
- const tree = await service.getFolderStructure(siteId, driveId, folderId, depth);
997
- return {
998
- content: [{
999
- type: "text",
1000
- text: JSON.stringify(tree, null, 2),
1001
- }],
1002
- };
1003
- }
1004
- catch (error) {
1005
- console.error("Error getting SharePoint folder structure:", error);
1006
- return {
1007
- content: [{
1008
- type: "text",
1009
- text: `Failed to get folder structure: ${error.message}`,
1010
- }],
1011
- isError: true,
1012
- };
1013
- }
1014
- });
1015
- server.tool("spo-get-crm-doc-locs", "Get SharePoint document locations configured in PowerPlatform Dataverse (sharepointdocumentlocation entity)", {
1016
- entityName: z.string().optional().describe("Filter by entity logical name (e.g., 'account', 'contact')"),
1017
- recordId: z.string().optional().describe("Filter by specific record ID (GUID)"),
1018
- }, async ({ entityName, recordId }) => {
1019
- try {
1020
- const spoService = getSharePointService();
1021
- const ppService = getPowerPlatformService();
1022
- const locations = await spoService.getCrmDocumentLocations(ppService, entityName, recordId);
1023
- return {
1024
- content: [{
1025
- type: "text",
1026
- text: JSON.stringify(locations, null, 2),
1027
- }],
1028
- };
1029
- }
1030
- catch (error) {
1031
- console.error("Error getting CRM document locations:", error);
1032
- return {
1033
- content: [{
1034
- type: "text",
1035
- text: `Failed to get CRM document locations: ${error.message}`,
1036
- }],
1037
- isError: true,
1038
- };
1039
- }
1040
- });
1041
- server.tool("spo-validate-doc-loc", "Validate that a PowerPlatform document location configuration matches the actual SharePoint site structure. Checks site accessibility, folder existence, and file counts. Returns validation status (valid/warning/error) with issues and recommendations.", {
1042
- documentLocationId: z.string().describe("GUID of the sharepointdocumentlocation record in PowerPlatform"),
1043
- }, async ({ documentLocationId }) => {
1044
- try {
1045
- const spoService = getSharePointService();
1046
- const ppService = getPowerPlatformService();
1047
- const result = await spoService.validateDocumentLocation(ppService, documentLocationId);
1048
- return {
1049
- content: [{
1050
- type: "text",
1051
- text: JSON.stringify(result, null, 2),
1052
- }],
1053
- };
1054
- }
1055
- catch (error) {
1056
- console.error("Error validating document location:", error);
1057
- return {
1058
- content: [{
1059
- type: "text",
1060
- text: `Failed to validate document location: ${error.message}`,
1061
- }],
1062
- isError: true,
1063
- };
1064
- }
1065
- });
1066
- server.tool("spo-verify-doc-mig", "Verify that documents were successfully migrated from source to target SharePoint folder. Compares file counts, sizes, names, and modified dates. Returns migration status (complete/incomplete/failed) with success rate and detailed comparison.", {
1067
- sourceSiteId: z.string().describe("Source SharePoint site ID"),
1068
- sourcePath: z.string().describe("Source folder path (e.g., '/Documents/Archive')"),
1069
- targetSiteId: z.string().describe("Target SharePoint site ID"),
1070
- targetPath: z.string().describe("Target folder path (e.g., '/NewLibrary/Archive')"),
1071
- }, async ({ sourceSiteId, sourcePath, targetSiteId, targetPath }) => {
1072
- try {
1073
- const spoService = getSharePointService();
1074
- const ppService = getPowerPlatformService();
1075
- const result = await spoService.verifyDocumentMigration(ppService, sourceSiteId, sourcePath, targetSiteId, targetPath);
1076
- return {
1077
- content: [{
1078
- type: "text",
1079
- text: JSON.stringify(result, null, 2),
1080
- }],
1081
- };
1082
- }
1083
- catch (error) {
1084
- console.error("Error verifying document migration:", error);
1085
- return {
1086
- content: [{
1087
- type: "text",
1088
- text: `Failed to verify document migration: ${error.message}`,
1089
- }],
1090
- isError: true,
1091
- };
85
+ }
86
+ function checkDeleteEnabled() {
87
+ if (process.env.SHAREPOINT_ENABLE_DELETE !== 'true') {
88
+ throw new Error('Delete operations are disabled. Set SHAREPOINT_ENABLE_DELETE=true to enable.');
1092
89
  }
1093
- });
1094
- console.error("sharepoint tools registered: 15 tools, 10 prompts");
1095
- console.error("SharePoint tools registered: 15 tools, 10 prompts");
90
+ }
91
+ // Register all prompts and tools
92
+ registerSharePointPrompts(server, getSharePointService, getPowerPlatformService);
93
+ registerReadTools(server, getSharePointService, getFileOperationsService, getPowerPlatformService);
94
+ registerWriteTools(server, getSharePointService, getFileOperationsService, checkWriteEnabled, checkDeleteEnabled);
95
+ // Log tool counts
96
+ const writeEnabled = process.env.SHAREPOINT_ENABLE_WRITE === 'true';
97
+ const deleteEnabled = process.env.SHAREPOINT_ENABLE_DELETE === 'true';
98
+ const writeToolCount = writeEnabled ? 5 : 0;
99
+ const deleteToolCount = deleteEnabled ? 1 : 0;
100
+ const totalTools = 16 + writeToolCount + deleteToolCount; // 15 existing + 1 download + write + delete
101
+ console.error(`SharePoint tools registered: ${totalTools} tools (${writeToolCount + deleteToolCount} write), 10 prompts`);
1096
102
  }
1097
103
  // CLI entry point (standalone execution)
1098
104
  // Uses realpathSync to resolve symlinks created by npx