mcp-consultant-tools 0.4.5

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/build/index.js ADDED
@@ -0,0 +1,2010 @@
1
+ #!/usr/bin/env node
2
+ import { config } from "dotenv";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+ import { PowerPlatformService } from "./PowerPlatformService.js";
7
+ import { AzureDevOpsService } from "./AzureDevOpsService.js";
8
+ // Load environment variables from .env file (silent mode to not interfere with MCP)
9
+ // Temporarily suppress stdout to prevent dotenv from corrupting the JSON protocol
10
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
11
+ process.stdout.write = (() => true);
12
+ config({ debug: false });
13
+ process.stdout.write = originalStdoutWrite;
14
+ // Environment configuration
15
+ // These values can be set in environment variables or loaded from a configuration file
16
+ const POWERPLATFORM_CONFIG = {
17
+ organizationUrl: process.env.POWERPLATFORM_URL || "",
18
+ clientId: process.env.POWERPLATFORM_CLIENT_ID || "",
19
+ clientSecret: process.env.POWERPLATFORM_CLIENT_SECRET || "",
20
+ tenantId: process.env.POWERPLATFORM_TENANT_ID || "",
21
+ };
22
+ // Azure DevOps configuration
23
+ const AZUREDEVOPS_CONFIG = {
24
+ organization: process.env.AZUREDEVOPS_ORGANIZATION || "",
25
+ pat: process.env.AZUREDEVOPS_PAT || "",
26
+ projects: (process.env.AZUREDEVOPS_PROJECTS || "").split(",").map(p => p.trim()).filter(p => p),
27
+ apiVersion: process.env.AZUREDEVOPS_API_VERSION || "7.1",
28
+ enableWorkItemWrite: process.env.AZUREDEVOPS_ENABLE_WORK_ITEM_WRITE === "true",
29
+ enableWorkItemDelete: process.env.AZUREDEVOPS_ENABLE_WORK_ITEM_DELETE === "true",
30
+ enableWikiWrite: process.env.AZUREDEVOPS_ENABLE_WIKI_WRITE === "true",
31
+ };
32
+ // Create server instance
33
+ const server = new McpServer({
34
+ name: "mcp-consultant-tools",
35
+ version: "1.0.0",
36
+ });
37
+ let powerPlatformService = null;
38
+ // Function to initialize PowerPlatformService on demand
39
+ function getPowerPlatformService() {
40
+ if (!powerPlatformService) {
41
+ // Check if configuration is complete
42
+ const missingConfig = [];
43
+ if (!POWERPLATFORM_CONFIG.organizationUrl)
44
+ missingConfig.push("organizationUrl");
45
+ if (!POWERPLATFORM_CONFIG.clientId)
46
+ missingConfig.push("clientId");
47
+ if (!POWERPLATFORM_CONFIG.clientSecret)
48
+ missingConfig.push("clientSecret");
49
+ if (!POWERPLATFORM_CONFIG.tenantId)
50
+ missingConfig.push("tenantId");
51
+ if (missingConfig.length > 0) {
52
+ throw new Error(`Missing PowerPlatform configuration: ${missingConfig.join(", ")}. Set these in environment variables.`);
53
+ }
54
+ // Initialize service
55
+ powerPlatformService = new PowerPlatformService(POWERPLATFORM_CONFIG);
56
+ console.error("PowerPlatform service initialized");
57
+ }
58
+ return powerPlatformService;
59
+ }
60
+ let azureDevOpsService = null;
61
+ // Function to initialize AzureDevOpsService on demand
62
+ function getAzureDevOpsService() {
63
+ if (!azureDevOpsService) {
64
+ // Check if configuration is complete
65
+ const missingConfig = [];
66
+ if (!AZUREDEVOPS_CONFIG.organization)
67
+ missingConfig.push("organization");
68
+ if (!AZUREDEVOPS_CONFIG.pat)
69
+ missingConfig.push("pat");
70
+ if (!AZUREDEVOPS_CONFIG.projects || AZUREDEVOPS_CONFIG.projects.length === 0) {
71
+ missingConfig.push("projects");
72
+ }
73
+ if (missingConfig.length > 0) {
74
+ throw new Error(`Missing Azure DevOps configuration: ${missingConfig.join(", ")}. Set these in environment variables (AZUREDEVOPS_*).`);
75
+ }
76
+ // Initialize service
77
+ azureDevOpsService = new AzureDevOpsService(AZUREDEVOPS_CONFIG);
78
+ console.error("Azure DevOps service initialized");
79
+ }
80
+ return azureDevOpsService;
81
+ }
82
+ // Pre-defined PowerPlatform Prompts
83
+ const powerPlatformPrompts = {
84
+ // Entity exploration prompts
85
+ ENTITY_OVERVIEW: (entityName) => `## Power Platform Entity: ${entityName}\n\n` +
86
+ `This is an overview of the '${entityName}' entity in Microsoft Power Platform/Dataverse:\n\n` +
87
+ `### Entity Details\n{{entity_details}}\n\n` +
88
+ `### Attributes\n{{key_attributes}}\n\n` +
89
+ `### Relationships\n{{relationships}}\n\n` +
90
+ `You can query this entity using OData filters against the plural name.`,
91
+ ATTRIBUTE_DETAILS: (entityName, attributeName) => `## Attribute: ${attributeName}\n\n` +
92
+ `Details for the '${attributeName}' attribute of the '${entityName}' entity:\n\n` +
93
+ `{{attribute_details}}\n\n` +
94
+ `### Usage Notes\n` +
95
+ `- Data Type: {{data_type}}\n` +
96
+ `- Required: {{required}}\n` +
97
+ `- Max Length: {{max_length}}`,
98
+ // Query builder prompts
99
+ QUERY_TEMPLATE: (entityNamePlural) => `## OData Query Template for ${entityNamePlural}\n\n` +
100
+ `Use this template to build queries against the ${entityNamePlural} entity:\n\n` +
101
+ `\`\`\`\n${entityNamePlural}?$select={{selected_fields}}&$filter={{filter_conditions}}&$orderby={{order_by}}&$top={{max_records}}\n\`\`\`\n\n` +
102
+ `### Common Filter Examples\n` +
103
+ `- Equals: \`name eq 'Contoso'\`\n` +
104
+ `- Contains: \`contains(name, 'Contoso')\`\n` +
105
+ `- Greater than date: \`createdon gt 2023-01-01T00:00:00Z\`\n` +
106
+ `- Multiple conditions: \`name eq 'Contoso' and statecode eq 0\``,
107
+ // Relationship exploration prompts
108
+ RELATIONSHIP_MAP: (entityName) => `## Relationship Map for ${entityName}\n\n` +
109
+ `This shows all relationships for the '${entityName}' entity:\n\n` +
110
+ `### One-to-Many Relationships (${entityName} as Primary)\n{{one_to_many_primary}}\n\n` +
111
+ `### One-to-Many Relationships (${entityName} as Related)\n{{one_to_many_related}}\n\n` +
112
+ `### Many-to-Many Relationships\n{{many_to_many}}\n\n`
113
+ };
114
+ // Register prompts with the server using the correct method signature
115
+ // Entity Overview Prompt
116
+ server.prompt("entity-overview", "Get an overview of a Power Platform entity", {
117
+ entityName: z.string().describe("The logical name of the entity")
118
+ }, async (args) => {
119
+ try {
120
+ const service = getPowerPlatformService();
121
+ const entityName = args.entityName;
122
+ // Get entity metadata and key attributes
123
+ const [metadata, attributes] = await Promise.all([
124
+ service.getEntityMetadata(entityName),
125
+ service.getEntityAttributes(entityName)
126
+ ]);
127
+ // Format entity details
128
+ const entityDetails = `- Display Name: ${metadata.DisplayName?.UserLocalizedLabel?.Label || entityName}\n` +
129
+ `- Schema Name: ${metadata.SchemaName}\n` +
130
+ `- Description: ${metadata.Description?.UserLocalizedLabel?.Label || 'No description'}\n` +
131
+ `- Primary Key: ${metadata.PrimaryIdAttribute}\n` +
132
+ `- Primary Name: ${metadata.PrimaryNameAttribute}`;
133
+ // Get key attributes
134
+ const keyAttributes = attributes.value
135
+ .map((attr) => {
136
+ const attrType = attr["@odata.type"] || attr.odata?.type || "Unknown type";
137
+ return `- ${attr.LogicalName}: ${attrType}`;
138
+ })
139
+ .join('\n');
140
+ // Get relationships summary
141
+ const relationships = await service.getEntityRelationships(entityName);
142
+ const oneToManyCount = relationships.oneToMany.value.length;
143
+ const manyToManyCount = relationships.manyToMany.value.length;
144
+ const relationshipsSummary = `- One-to-Many Relationships: ${oneToManyCount}\n` +
145
+ `- Many-to-Many Relationships: ${manyToManyCount}`;
146
+ let promptContent = powerPlatformPrompts.ENTITY_OVERVIEW(entityName);
147
+ promptContent = promptContent
148
+ .replace('{{entity_details}}', entityDetails)
149
+ .replace('{{key_attributes}}', keyAttributes)
150
+ .replace('{{relationships}}', relationshipsSummary);
151
+ return {
152
+ messages: [
153
+ {
154
+ role: "assistant",
155
+ content: {
156
+ type: "text",
157
+ text: promptContent
158
+ }
159
+ }
160
+ ]
161
+ };
162
+ }
163
+ catch (error) {
164
+ console.error(`Error handling entity-overview prompt:`, error);
165
+ return {
166
+ messages: [
167
+ {
168
+ role: "assistant",
169
+ content: {
170
+ type: "text",
171
+ text: `Error: ${error.message}`
172
+ }
173
+ }
174
+ ]
175
+ };
176
+ }
177
+ });
178
+ // Attribute Details Prompt
179
+ server.prompt("attribute-details", "Get detailed information about a specific entity attribute/field", {
180
+ entityName: z.string().describe("The logical name of the entity"),
181
+ attributeName: z.string().describe("The logical name of the attribute"),
182
+ }, async (args) => {
183
+ try {
184
+ const service = getPowerPlatformService();
185
+ const { entityName, attributeName } = args;
186
+ // Get attribute details
187
+ const attribute = await service.getEntityAttribute(entityName, attributeName);
188
+ // Format attribute details
189
+ const attrDetails = `- Display Name: ${attribute.DisplayName?.UserLocalizedLabel?.Label || attributeName}\n` +
190
+ `- Description: ${attribute.Description?.UserLocalizedLabel?.Label || 'No description'}\n` +
191
+ `- Type: ${attribute.AttributeType}\n` +
192
+ `- Format: ${attribute.Format || 'N/A'}\n` +
193
+ `- Is Required: ${attribute.RequiredLevel?.Value || 'No'}\n` +
194
+ `- Is Searchable: ${attribute.IsValidForAdvancedFind || false}`;
195
+ let promptContent = powerPlatformPrompts.ATTRIBUTE_DETAILS(entityName, attributeName);
196
+ promptContent = promptContent
197
+ .replace('{{attribute_details}}', attrDetails)
198
+ .replace('{{data_type}}', attribute.AttributeType)
199
+ .replace('{{required}}', attribute.RequiredLevel?.Value || 'No')
200
+ .replace('{{max_length}}', attribute.MaxLength || 'N/A');
201
+ return {
202
+ messages: [
203
+ {
204
+ role: "assistant",
205
+ content: {
206
+ type: "text",
207
+ text: promptContent
208
+ }
209
+ }
210
+ ]
211
+ };
212
+ }
213
+ catch (error) {
214
+ console.error(`Error handling attribute-details prompt:`, error);
215
+ return {
216
+ messages: [
217
+ {
218
+ role: "assistant",
219
+ content: {
220
+ type: "text",
221
+ text: `Error: ${error.message}`
222
+ }
223
+ }
224
+ ]
225
+ };
226
+ }
227
+ });
228
+ // Query Template Prompt
229
+ server.prompt("query-template", "Get a template for querying a Power Platform entity", {
230
+ entityName: z.string().describe("The logical name of the entity"),
231
+ }, async (args) => {
232
+ try {
233
+ const service = getPowerPlatformService();
234
+ const entityName = args.entityName;
235
+ // Get entity metadata to determine plural name
236
+ const metadata = await service.getEntityMetadata(entityName);
237
+ const entityNamePlural = metadata.EntitySetName;
238
+ // Get a few important fields for the select example
239
+ const attributes = await service.getEntityAttributes(entityName);
240
+ const selectFields = attributes.value
241
+ .filter((attr) => attr.IsValidForRead === true && !attr.AttributeOf)
242
+ .slice(0, 5) // Just take first 5 for example
243
+ .map((attr) => attr.LogicalName)
244
+ .join(',');
245
+ let promptContent = powerPlatformPrompts.QUERY_TEMPLATE(entityNamePlural);
246
+ promptContent = promptContent
247
+ .replace('{{selected_fields}}', selectFields)
248
+ .replace('{{filter_conditions}}', `${metadata.PrimaryNameAttribute} eq 'Example'`)
249
+ .replace('{{order_by}}', `${metadata.PrimaryNameAttribute} asc`)
250
+ .replace('{{max_records}}', '50');
251
+ return {
252
+ messages: [
253
+ {
254
+ role: "assistant",
255
+ content: {
256
+ type: "text",
257
+ text: promptContent
258
+ }
259
+ }
260
+ ]
261
+ };
262
+ }
263
+ catch (error) {
264
+ console.error(`Error handling query-template prompt:`, error);
265
+ return {
266
+ messages: [
267
+ {
268
+ role: "assistant",
269
+ content: {
270
+ type: "text",
271
+ text: `Error: ${error.message}`
272
+ }
273
+ }
274
+ ]
275
+ };
276
+ }
277
+ });
278
+ // Relationship Map Prompt
279
+ server.prompt("relationship-map", "Get a list of relationships for a Power Platform entity", {
280
+ entityName: z.string().describe("The logical name of the entity"),
281
+ }, async (args) => {
282
+ try {
283
+ const service = getPowerPlatformService();
284
+ const entityName = args.entityName;
285
+ // Get relationships
286
+ const relationships = await service.getEntityRelationships(entityName);
287
+ // Format one-to-many relationships where this entity is primary
288
+ const oneToManyPrimary = relationships.oneToMany.value
289
+ .filter((rel) => rel.ReferencingEntity !== entityName)
290
+ .map((rel) => `- ${rel.SchemaName}: ${entityName} (1) → ${rel.ReferencingEntity} (N)`)
291
+ .join('\n');
292
+ // Format one-to-many relationships where this entity is related
293
+ const oneToManyRelated = relationships.oneToMany.value
294
+ .filter((rel) => rel.ReferencingEntity === entityName)
295
+ .map((rel) => `- ${rel.SchemaName}: ${rel.ReferencedEntity} (1) → ${entityName} (N)`)
296
+ .join('\n');
297
+ // Format many-to-many relationships
298
+ const manyToMany = relationships.manyToMany.value
299
+ .map((rel) => {
300
+ const otherEntity = rel.Entity1LogicalName === entityName ? rel.Entity2LogicalName : rel.Entity1LogicalName;
301
+ return `- ${rel.SchemaName}: ${entityName} (N) ↔ ${otherEntity} (N)`;
302
+ })
303
+ .join('\n');
304
+ let promptContent = powerPlatformPrompts.RELATIONSHIP_MAP(entityName);
305
+ promptContent = promptContent
306
+ .replace('{{one_to_many_primary}}', oneToManyPrimary || 'None found')
307
+ .replace('{{one_to_many_related}}', oneToManyRelated || 'None found')
308
+ .replace('{{many_to_many}}', manyToMany || 'None found');
309
+ return {
310
+ messages: [
311
+ {
312
+ role: "assistant",
313
+ content: {
314
+ type: "text",
315
+ text: promptContent
316
+ }
317
+ }
318
+ ]
319
+ };
320
+ }
321
+ catch (error) {
322
+ console.error(`Error handling relationship-map prompt:`, error);
323
+ return {
324
+ messages: [
325
+ {
326
+ role: "assistant",
327
+ content: {
328
+ type: "text",
329
+ text: `Error: ${error.message}`
330
+ }
331
+ }
332
+ ]
333
+ };
334
+ }
335
+ });
336
+ // Plugin Deployment Report Prompt
337
+ server.prompt("plugin-deployment-report", "Generate a comprehensive deployment report for a plugin assembly", {
338
+ assemblyName: z.string().describe("The name of the plugin assembly"),
339
+ }, async (args) => {
340
+ try {
341
+ const service = getPowerPlatformService();
342
+ const result = await service.getPluginAssemblyComplete(args.assemblyName, false);
343
+ // Build markdown report
344
+ let report = `# Plugin Deployment Report: ${result.assembly.name}\n\n`;
345
+ report += `## Assembly Information\n`;
346
+ report += `- **Version**: ${result.assembly.version}\n`;
347
+ report += `- **Isolation Mode**: ${result.assembly.isolationmode === 2 ? 'Sandbox' : 'None'}\n`;
348
+ report += `- **Source**: ${result.assembly.sourcetype === 0 ? 'Database' : result.assembly.sourcetype === 1 ? 'Disk' : 'GAC'}\n`;
349
+ report += `- **Last Modified**: ${result.assembly.modifiedon} by ${result.assembly.modifiedby?.fullname || 'Unknown'}\n`;
350
+ report += `- **Managed**: ${result.assembly.ismanaged ? 'Yes' : 'No'}\n\n`;
351
+ report += `## Plugin Types (${result.pluginTypes.length} total)\n`;
352
+ result.pluginTypes.forEach((type, idx) => {
353
+ report += `${idx + 1}. ${type.typename}\n`;
354
+ });
355
+ report += `\n`;
356
+ report += `## Registered Steps (${result.steps.length} total)\n\n`;
357
+ result.steps.forEach((step) => {
358
+ const stageName = step.stage === 10 ? 'PreValidation' : step.stage === 20 ? 'PreOperation' : 'PostOperation';
359
+ const modeName = step.mode === 0 ? 'Sync' : 'Async';
360
+ const status = step.statuscode === 1 ? '✓ Enabled' : '✗ Disabled';
361
+ report += `### ${step.sdkmessageid?.name || 'Unknown'} - ${step.sdkmessagefilterid?.primaryobjecttypecode || 'None'} (${stageName}, ${modeName}, Rank ${step.rank})\n`;
362
+ report += `- **Plugin**: ${step.plugintypeid?.typename || 'Unknown'}\n`;
363
+ report += `- **Status**: ${status}\n`;
364
+ report += `- **Filtering Attributes**: ${step.filteringattributes || '(none - runs on all changes)'}\n`;
365
+ report += `- **Deployment**: ${step.supporteddeployment === 0 ? 'Server Only' : step.supporteddeployment === 1 ? 'Offline Only' : 'Both'}\n`;
366
+ if (step.images.length > 0) {
367
+ report += `- **Images**:\n`;
368
+ step.images.forEach((img) => {
369
+ const imageType = img.imagetype === 0 ? 'PreImage' : img.imagetype === 1 ? 'PostImage' : 'Both';
370
+ report += ` - ${img.name} (${imageType}) → Attributes: ${img.attributes || '(all)'}\n`;
371
+ });
372
+ }
373
+ else {
374
+ report += `- **Images**: None\n`;
375
+ }
376
+ report += `\n`;
377
+ });
378
+ report += `## Validation Results\n\n`;
379
+ if (result.validation.hasDisabledSteps) {
380
+ report += `⚠ Some steps are disabled\n`;
381
+ }
382
+ else {
383
+ report += `✓ All steps are enabled\n`;
384
+ }
385
+ if (result.validation.stepsWithoutFilteringAttributes.length > 0) {
386
+ report += `⚠ Warning: ${result.validation.stepsWithoutFilteringAttributes.length} Update/Delete steps without filtering attributes:\n`;
387
+ result.validation.stepsWithoutFilteringAttributes.forEach((name) => {
388
+ report += ` - ${name}\n`;
389
+ });
390
+ }
391
+ else {
392
+ report += `✓ All Update/Delete steps have filtering attributes\n`;
393
+ }
394
+ if (result.validation.stepsWithoutImages.length > 0) {
395
+ report += `⚠ Warning: ${result.validation.stepsWithoutImages.length} Update/Delete steps without images:\n`;
396
+ result.validation.stepsWithoutImages.forEach((name) => {
397
+ report += ` - ${name}\n`;
398
+ });
399
+ }
400
+ if (result.validation.potentialIssues.length > 0) {
401
+ report += `\n### Potential Issues\n`;
402
+ result.validation.potentialIssues.forEach((issue) => {
403
+ report += `- ${issue}\n`;
404
+ });
405
+ }
406
+ return {
407
+ messages: [
408
+ {
409
+ role: "assistant",
410
+ content: {
411
+ type: "text",
412
+ text: report
413
+ }
414
+ }
415
+ ]
416
+ };
417
+ }
418
+ catch (error) {
419
+ console.error(`Error generating plugin deployment report:`, error);
420
+ return {
421
+ messages: [
422
+ {
423
+ role: "assistant",
424
+ content: {
425
+ type: "text",
426
+ text: `Error: ${error.message}`
427
+ }
428
+ }
429
+ ]
430
+ };
431
+ }
432
+ });
433
+ // Entity Plugin Pipeline Report Prompt
434
+ server.prompt("entity-plugin-pipeline-report", "Generate a visual execution pipeline showing all plugins for an entity", {
435
+ entityName: z.string().describe("The logical name of the entity"),
436
+ messageFilter: z.string().optional().describe("Optional filter by message name"),
437
+ }, async (args) => {
438
+ try {
439
+ const service = getPowerPlatformService();
440
+ const result = await service.getEntityPluginPipeline(args.entityName, args.messageFilter, false);
441
+ // Build markdown report
442
+ let report = `# Plugin Pipeline: ${result.entity} Entity\n\n`;
443
+ if (result.steps.length === 0) {
444
+ report += `No plugins registered for this entity.\n`;
445
+ }
446
+ else {
447
+ // Group by message
448
+ result.messages.forEach((msg) => {
449
+ report += `## ${msg.messageName} Message\n\n`;
450
+ // PreValidation stage
451
+ if (msg.stages.preValidation.length > 0) {
452
+ report += `### Stage 1: PreValidation (Synchronous)\n`;
453
+ msg.stages.preValidation.forEach((step, idx) => {
454
+ report += `${idx + 1}. **[Rank ${step.rank}]** ${step.pluginType}\n`;
455
+ report += ` - Assembly: ${step.assemblyName} v${step.assemblyVersion}\n`;
456
+ report += ` - Filtering: ${step.filteringAttributes.join(', ') || '(all columns)'}\n`;
457
+ if (step.hasPreImage || step.hasPostImage) {
458
+ const images = [];
459
+ if (step.hasPreImage)
460
+ images.push('PreImage');
461
+ if (step.hasPostImage)
462
+ images.push('PostImage');
463
+ report += ` - Images: ${images.join(', ')}\n`;
464
+ }
465
+ report += `\n`;
466
+ });
467
+ }
468
+ // PreOperation stage
469
+ if (msg.stages.preOperation.length > 0) {
470
+ report += `### Stage 2: PreOperation (Synchronous)\n`;
471
+ msg.stages.preOperation.forEach((step, idx) => {
472
+ report += `${idx + 1}. **[Rank ${step.rank}]** ${step.pluginType}\n`;
473
+ report += ` - Assembly: ${step.assemblyName} v${step.assemblyVersion}\n`;
474
+ report += ` - Filtering: ${step.filteringAttributes.join(', ') || '(all columns)'}\n`;
475
+ if (step.hasPreImage || step.hasPostImage) {
476
+ const images = [];
477
+ if (step.hasPreImage)
478
+ images.push('PreImage');
479
+ if (step.hasPostImage)
480
+ images.push('PostImage');
481
+ report += ` - Images: ${images.join(', ')}\n`;
482
+ }
483
+ report += `\n`;
484
+ });
485
+ }
486
+ // PostOperation stage
487
+ if (msg.stages.postOperation.length > 0) {
488
+ report += `### Stage 3: PostOperation\n`;
489
+ msg.stages.postOperation.forEach((step, idx) => {
490
+ const mode = step.modeName === 'Asynchronous' ? ' (Async)' : ' (Sync)';
491
+ report += `${idx + 1}. **[Rank ${step.rank}]** ${step.pluginType}${mode}\n`;
492
+ report += ` - Assembly: ${step.assemblyName} v${step.assemblyVersion}\n`;
493
+ report += ` - Filtering: ${step.filteringAttributes.join(', ') || '(all columns)'}\n`;
494
+ if (step.hasPreImage || step.hasPostImage) {
495
+ const images = [];
496
+ if (step.hasPreImage)
497
+ images.push('PreImage');
498
+ if (step.hasPostImage)
499
+ images.push('PostImage');
500
+ report += ` - Images: ${images.join(', ')}\n`;
501
+ }
502
+ report += `\n`;
503
+ });
504
+ }
505
+ report += `---\n\n`;
506
+ });
507
+ report += `## Execution Order\n\n`;
508
+ report += `Plugins execute in this order:\n`;
509
+ result.executionOrder.forEach((name, idx) => {
510
+ report += `${idx + 1}. ${name}\n`;
511
+ });
512
+ }
513
+ return {
514
+ messages: [
515
+ {
516
+ role: "assistant",
517
+ content: {
518
+ type: "text",
519
+ text: report
520
+ }
521
+ }
522
+ ]
523
+ };
524
+ }
525
+ catch (error) {
526
+ console.error(`Error generating entity plugin pipeline report:`, error);
527
+ return {
528
+ messages: [
529
+ {
530
+ role: "assistant",
531
+ content: {
532
+ type: "text",
533
+ text: `Error: ${error.message}`
534
+ }
535
+ }
536
+ ]
537
+ };
538
+ }
539
+ });
540
+ // Power Automate Flows Report Prompt
541
+ server.prompt("flows-report", "Generate a comprehensive report of all Power Automate flows in the environment", {
542
+ activeOnly: z.string().optional().describe("Set to 'true' to only include activated flows (default: false)"),
543
+ }, async (args) => {
544
+ try {
545
+ const service = getPowerPlatformService();
546
+ const result = await service.getFlows(args.activeOnly === 'true', 100);
547
+ // Build markdown report
548
+ let report = `# Power Automate Flows Report\n\n`;
549
+ report += `**Total Flows**: ${result.totalCount}\n\n`;
550
+ if (result.flows.length === 0) {
551
+ report += `No flows found in this environment.\n`;
552
+ }
553
+ else {
554
+ // Group by state
555
+ const activeFlows = result.flows.filter((f) => f.state === 'Activated');
556
+ const draftFlows = result.flows.filter((f) => f.state === 'Draft');
557
+ const suspendedFlows = result.flows.filter((f) => f.state === 'Suspended');
558
+ if (activeFlows.length > 0) {
559
+ report += `## Active Flows (${activeFlows.length})\n\n`;
560
+ activeFlows.forEach((flow) => {
561
+ report += `### ${flow.name}\n`;
562
+ report += `- **ID**: ${flow.workflowid}\n`;
563
+ report += `- **Description**: ${flow.description || 'No description'}\n`;
564
+ report += `- **Primary Entity**: ${flow.primaryEntity || 'None'}\n`;
565
+ report += `- **Owner**: ${flow.owner}\n`;
566
+ report += `- **Modified**: ${flow.modifiedOn} by ${flow.modifiedBy}\n`;
567
+ report += `- **Managed**: ${flow.isManaged ? 'Yes' : 'No'}\n\n`;
568
+ });
569
+ }
570
+ if (draftFlows.length > 0) {
571
+ report += `## Draft Flows (${draftFlows.length})\n\n`;
572
+ draftFlows.forEach((flow) => {
573
+ report += `- **${flow.name}** (${flow.workflowid})\n`;
574
+ report += ` - Owner: ${flow.owner}, Modified: ${flow.modifiedOn}\n`;
575
+ });
576
+ report += `\n`;
577
+ }
578
+ if (suspendedFlows.length > 0) {
579
+ report += `## Suspended Flows (${suspendedFlows.length})\n\n`;
580
+ suspendedFlows.forEach((flow) => {
581
+ report += `- **${flow.name}** (${flow.workflowid})\n`;
582
+ report += ` - Owner: ${flow.owner}, Modified: ${flow.modifiedOn}\n`;
583
+ });
584
+ report += `\n`;
585
+ }
586
+ }
587
+ return {
588
+ messages: [
589
+ {
590
+ role: "assistant",
591
+ content: {
592
+ type: "text",
593
+ text: report
594
+ }
595
+ }
596
+ ]
597
+ };
598
+ }
599
+ catch (error) {
600
+ console.error(`Error generating flows report:`, error);
601
+ return {
602
+ messages: [
603
+ {
604
+ role: "assistant",
605
+ content: {
606
+ type: "text",
607
+ text: `Error: ${error.message}`
608
+ }
609
+ }
610
+ ]
611
+ };
612
+ }
613
+ });
614
+ // Classic Workflows Report Prompt
615
+ server.prompt("workflows-report", "Generate a comprehensive report of all classic Dynamics workflows in the environment", {
616
+ activeOnly: z.string().optional().describe("Set to 'true' to only include activated workflows (default: false)"),
617
+ }, async (args) => {
618
+ try {
619
+ const service = getPowerPlatformService();
620
+ const result = await service.getWorkflows(args.activeOnly === 'true', 100);
621
+ // Build markdown report
622
+ let report = `# Classic Dynamics Workflows Report\n\n`;
623
+ report += `**Total Workflows**: ${result.totalCount}\n\n`;
624
+ if (result.workflows.length === 0) {
625
+ report += `No classic workflows found in this environment.\n`;
626
+ }
627
+ else {
628
+ // Group by state
629
+ const activeWorkflows = result.workflows.filter((w) => w.state === 'Activated');
630
+ const draftWorkflows = result.workflows.filter((w) => w.state === 'Draft');
631
+ const suspendedWorkflows = result.workflows.filter((w) => w.state === 'Suspended');
632
+ if (activeWorkflows.length > 0) {
633
+ report += `## Active Workflows (${activeWorkflows.length})\n\n`;
634
+ activeWorkflows.forEach((workflow) => {
635
+ report += `### ${workflow.name}\n`;
636
+ report += `- **ID**: ${workflow.workflowid}\n`;
637
+ report += `- **Description**: ${workflow.description || 'No description'}\n`;
638
+ report += `- **Primary Entity**: ${workflow.primaryEntity || 'None'}\n`;
639
+ report += `- **Mode**: ${workflow.mode}\n`;
640
+ report += `- **Triggers**:\n`;
641
+ if (workflow.triggerOnCreate)
642
+ report += ` - Create\n`;
643
+ if (workflow.triggerOnDelete)
644
+ report += ` - Delete\n`;
645
+ if (workflow.isOnDemand)
646
+ report += ` - On Demand\n`;
647
+ report += `- **Owner**: ${workflow.owner}\n`;
648
+ report += `- **Modified**: ${workflow.modifiedOn} by ${workflow.modifiedBy}\n`;
649
+ report += `- **Managed**: ${workflow.isManaged ? 'Yes' : 'No'}\n\n`;
650
+ });
651
+ }
652
+ if (draftWorkflows.length > 0) {
653
+ report += `## Draft Workflows (${draftWorkflows.length})\n\n`;
654
+ draftWorkflows.forEach((workflow) => {
655
+ report += `- **${workflow.name}** (${workflow.workflowid})\n`;
656
+ report += ` - Entity: ${workflow.primaryEntity}, Owner: ${workflow.owner}\n`;
657
+ });
658
+ report += `\n`;
659
+ }
660
+ if (suspendedWorkflows.length > 0) {
661
+ report += `## Suspended Workflows (${suspendedWorkflows.length})\n\n`;
662
+ suspendedWorkflows.forEach((workflow) => {
663
+ report += `- **${workflow.name}** (${workflow.workflowid})\n`;
664
+ report += ` - Entity: ${workflow.primaryEntity}, Owner: ${workflow.owner}\n`;
665
+ });
666
+ report += `\n`;
667
+ }
668
+ }
669
+ return {
670
+ messages: [
671
+ {
672
+ role: "assistant",
673
+ content: {
674
+ type: "text",
675
+ text: report
676
+ }
677
+ }
678
+ ]
679
+ };
680
+ }
681
+ catch (error) {
682
+ console.error(`Error generating workflows report:`, error);
683
+ return {
684
+ messages: [
685
+ {
686
+ role: "assistant",
687
+ content: {
688
+ type: "text",
689
+ text: `Error: ${error.message}`
690
+ }
691
+ }
692
+ ]
693
+ };
694
+ }
695
+ });
696
+ // ==================== AZURE DEVOPS PROMPTS ====================
697
+ // Wiki Search Results Prompt
698
+ server.prompt("wiki-search-results", "Search Azure DevOps wiki pages and get formatted results with content snippets", {
699
+ searchText: z.string().describe("The text to search for"),
700
+ project: z.string().optional().describe("Optional project filter"),
701
+ maxResults: z.string().optional().describe("Maximum number of results (default: 25)"),
702
+ }, async (args) => {
703
+ try {
704
+ const service = getAzureDevOpsService();
705
+ const { searchText, project, maxResults } = args;
706
+ const maxResultsNum = maxResults ? parseInt(maxResults, 10) : undefined;
707
+ const result = await service.searchWikiPages(searchText, project, maxResultsNum);
708
+ let report = `# Wiki Search Results: "${searchText}"\n\n`;
709
+ report += `**Project:** ${project || 'All allowed projects'}\n`;
710
+ report += `**Total Results:** ${result.totalCount}\n\n`;
711
+ if (result.results && result.results.length > 0) {
712
+ report += `## Results\n\n`;
713
+ result.results.forEach((item, index) => {
714
+ report += `### ${index + 1}. ${item.fileName}\n`;
715
+ report += `- **Path:** ${item.path}\n`;
716
+ report += `- **Wiki:** ${item.wikiName}\n`;
717
+ report += `- **Project:** ${item.project}\n`;
718
+ if (item.highlights && item.highlights.length > 0) {
719
+ report += `- **Highlights:**\n`;
720
+ item.highlights.forEach((highlight) => {
721
+ // Remove HTML tags for cleaner display
722
+ const cleanHighlight = highlight.replace(/<[^>]*>/g, '');
723
+ report += ` - ${cleanHighlight}\n`;
724
+ });
725
+ }
726
+ report += `\n`;
727
+ });
728
+ }
729
+ else {
730
+ report += `No results found for "${searchText}".\n`;
731
+ }
732
+ return {
733
+ messages: [
734
+ {
735
+ role: "assistant",
736
+ content: {
737
+ type: "text",
738
+ text: report
739
+ }
740
+ }
741
+ ]
742
+ };
743
+ }
744
+ catch (error) {
745
+ console.error(`Error generating wiki search results:`, error);
746
+ return {
747
+ messages: [
748
+ {
749
+ role: "assistant",
750
+ content: {
751
+ type: "text",
752
+ text: `Error: ${error.message}`
753
+ }
754
+ }
755
+ ]
756
+ };
757
+ }
758
+ });
759
+ // Wiki Page Content Prompt
760
+ server.prompt("wiki-page-content", "Get a formatted wiki page with navigation context from Azure DevOps", {
761
+ project: z.string().describe("The project name"),
762
+ wikiId: z.string().describe("The wiki identifier"),
763
+ pagePath: z.string().describe("The path to the page"),
764
+ }, async (args) => {
765
+ try {
766
+ const service = getAzureDevOpsService();
767
+ const { project, wikiId, pagePath } = args;
768
+ const result = await service.getWikiPage(project, wikiId, pagePath, true);
769
+ let report = `# Wiki Page: ${pagePath}\n\n`;
770
+ report += `**Project:** ${project}\n`;
771
+ report += `**Wiki:** ${wikiId}\n`;
772
+ report += `**Git Path:** ${result.gitItemPath || 'N/A'}\n\n`;
773
+ if (result.subPages && result.subPages.length > 0) {
774
+ report += `## Sub-pages\n`;
775
+ result.subPages.forEach((subPage) => {
776
+ report += `- ${subPage.path}\n`;
777
+ });
778
+ report += `\n`;
779
+ }
780
+ report += `## Content\n\n`;
781
+ report += result.content || '*No content available*';
782
+ return {
783
+ messages: [
784
+ {
785
+ role: "assistant",
786
+ content: {
787
+ type: "text",
788
+ text: report
789
+ }
790
+ }
791
+ ]
792
+ };
793
+ }
794
+ catch (error) {
795
+ console.error(`Error generating wiki page content:`, error);
796
+ return {
797
+ messages: [
798
+ {
799
+ role: "assistant",
800
+ content: {
801
+ type: "text",
802
+ text: `Error: ${error.message}`
803
+ }
804
+ }
805
+ ]
806
+ };
807
+ }
808
+ });
809
+ // Work Item Summary Prompt
810
+ server.prompt("work-item-summary", "Get a comprehensive summary of a work item with comments from Azure DevOps", {
811
+ project: z.string().describe("The project name"),
812
+ workItemId: z.string().describe("The work item ID"),
813
+ }, async (args) => {
814
+ try {
815
+ const service = getAzureDevOpsService();
816
+ const { project, workItemId } = args;
817
+ const workItemIdNum = parseInt(workItemId, 10);
818
+ // Get work item and comments in parallel
819
+ const [workItem, comments] = await Promise.all([
820
+ service.getWorkItem(project, workItemIdNum),
821
+ service.getWorkItemComments(project, workItemIdNum)
822
+ ]);
823
+ const fields = workItem.fields || {};
824
+ let report = `# Work Item #${workItemId}: ${fields['System.Title'] || 'Untitled'}\n\n`;
825
+ report += `## Details\n`;
826
+ report += `- **Type:** ${fields['System.WorkItemType'] || 'N/A'}\n`;
827
+ report += `- **State:** ${fields['System.State'] || 'N/A'}\n`;
828
+ report += `- **Assigned To:** ${fields['System.AssignedTo']?.displayName || 'Unassigned'}\n`;
829
+ report += `- **Created By:** ${fields['System.CreatedBy']?.displayName || 'N/A'}\n`;
830
+ report += `- **Created Date:** ${fields['System.CreatedDate'] || 'N/A'}\n`;
831
+ report += `- **Changed Date:** ${fields['System.ChangedDate'] || 'N/A'}\n`;
832
+ report += `- **Area Path:** ${fields['System.AreaPath'] || 'N/A'}\n`;
833
+ report += `- **Iteration Path:** ${fields['System.IterationPath'] || 'N/A'}\n`;
834
+ if (fields['System.Tags']) {
835
+ report += `- **Tags:** ${fields['System.Tags']}\n`;
836
+ }
837
+ report += `\n`;
838
+ if (fields['System.Description']) {
839
+ report += `## Description\n${fields['System.Description']}\n\n`;
840
+ }
841
+ if (fields['Microsoft.VSTS.TCM.ReproSteps']) {
842
+ report += `## Repro Steps\n${fields['Microsoft.VSTS.TCM.ReproSteps']}\n\n`;
843
+ }
844
+ if (workItem.relations && workItem.relations.length > 0) {
845
+ report += `## Related Items\n`;
846
+ workItem.relations.forEach((relation) => {
847
+ report += `- ${relation.rel}: ${relation.url}\n`;
848
+ });
849
+ report += `\n`;
850
+ }
851
+ if (comments.comments && comments.comments.length > 0) {
852
+ report += `## Comments (${comments.totalCount})\n\n`;
853
+ comments.comments.forEach((comment) => {
854
+ report += `### ${comment.createdBy} - ${new Date(comment.createdDate).toLocaleString()}\n`;
855
+ report += `${comment.text}\n\n`;
856
+ });
857
+ }
858
+ return {
859
+ messages: [
860
+ {
861
+ role: "assistant",
862
+ content: {
863
+ type: "text",
864
+ text: report
865
+ }
866
+ }
867
+ ]
868
+ };
869
+ }
870
+ catch (error) {
871
+ console.error(`Error generating work item summary:`, error);
872
+ return {
873
+ messages: [
874
+ {
875
+ role: "assistant",
876
+ content: {
877
+ type: "text",
878
+ text: `Error: ${error.message}`
879
+ }
880
+ }
881
+ ]
882
+ };
883
+ }
884
+ });
885
+ // Work Items Query Report Prompt
886
+ server.prompt("work-items-query-report", "Execute a WIQL query and get formatted results grouped by state/type", {
887
+ project: z.string().describe("The project name"),
888
+ wiql: z.string().describe("The WIQL query string"),
889
+ maxResults: z.string().optional().describe("Maximum number of results (default: 200)"),
890
+ }, async (args) => {
891
+ try {
892
+ const service = getAzureDevOpsService();
893
+ const { project, wiql, maxResults } = args;
894
+ const maxResultsNum = maxResults ? parseInt(maxResults, 10) : undefined;
895
+ const result = await service.queryWorkItems(project, wiql, maxResultsNum);
896
+ let report = `# Work Items Query Results\n\n`;
897
+ report += `**Project:** ${project}\n`;
898
+ report += `**Total Results:** ${result.totalCount}\n\n`;
899
+ if (result.workItems && result.workItems.length > 0) {
900
+ // Group by state
901
+ const groupedByState = new Map();
902
+ result.workItems.forEach((item) => {
903
+ const state = item.fields['System.State'] || 'Unknown';
904
+ if (!groupedByState.has(state)) {
905
+ groupedByState.set(state, []);
906
+ }
907
+ groupedByState.get(state).push(item);
908
+ });
909
+ // Sort states: Active, Resolved, Closed, others
910
+ const stateOrder = ['Active', 'New', 'Resolved', 'Closed'];
911
+ const sortedStates = Array.from(groupedByState.keys()).sort((a, b) => {
912
+ const aIndex = stateOrder.indexOf(a);
913
+ const bIndex = stateOrder.indexOf(b);
914
+ if (aIndex === -1 && bIndex === -1)
915
+ return a.localeCompare(b);
916
+ if (aIndex === -1)
917
+ return 1;
918
+ if (bIndex === -1)
919
+ return -1;
920
+ return aIndex - bIndex;
921
+ });
922
+ sortedStates.forEach(state => {
923
+ const items = groupedByState.get(state);
924
+ report += `## ${state} (${items.length})\n\n`;
925
+ items.forEach((item) => {
926
+ const fields = item.fields;
927
+ report += `- **#${item.id}**: ${fields['System.Title'] || 'Untitled'}\n`;
928
+ report += ` - Type: ${fields['System.WorkItemType'] || 'N/A'}`;
929
+ report += `, Assigned: ${fields['System.AssignedTo']?.displayName || 'Unassigned'}\n`;
930
+ });
931
+ report += `\n`;
932
+ });
933
+ }
934
+ else {
935
+ report += `No work items found matching the query.\n`;
936
+ }
937
+ return {
938
+ messages: [
939
+ {
940
+ role: "assistant",
941
+ content: {
942
+ type: "text",
943
+ text: report
944
+ }
945
+ }
946
+ ]
947
+ };
948
+ }
949
+ catch (error) {
950
+ console.error(`Error generating work items query report:`, error);
951
+ return {
952
+ messages: [
953
+ {
954
+ role: "assistant",
955
+ content: {
956
+ type: "text",
957
+ text: `Error: ${error.message}`
958
+ }
959
+ }
960
+ ]
961
+ };
962
+ }
963
+ });
964
+ // PowerPlatform entity metadata
965
+ server.tool("get-entity-metadata", "Get metadata about a PowerPlatform entity", {
966
+ entityName: z.string().describe("The logical name of the entity"),
967
+ }, async ({ entityName }) => {
968
+ try {
969
+ // Get or initialize PowerPlatformService
970
+ const service = getPowerPlatformService();
971
+ const metadata = await service.getEntityMetadata(entityName);
972
+ // Format the metadata as a string for text display
973
+ const metadataStr = JSON.stringify(metadata, null, 2);
974
+ return {
975
+ content: [
976
+ {
977
+ type: "text",
978
+ text: `Entity metadata for '${entityName}':\n\n${metadataStr}`,
979
+ },
980
+ ],
981
+ };
982
+ }
983
+ catch (error) {
984
+ console.error("Error getting entity metadata:", error);
985
+ return {
986
+ content: [
987
+ {
988
+ type: "text",
989
+ text: `Failed to get entity metadata: ${error.message}`,
990
+ },
991
+ ],
992
+ };
993
+ }
994
+ });
995
+ // PowerPlatform entity attributes
996
+ server.tool("get-entity-attributes", "Get attributes/fields of a PowerPlatform entity", {
997
+ entityName: z.string().describe("The logical name of the entity"),
998
+ }, async ({ entityName }) => {
999
+ try {
1000
+ // Get or initialize PowerPlatformService
1001
+ const service = getPowerPlatformService();
1002
+ const attributes = await service.getEntityAttributes(entityName);
1003
+ // Format the attributes as a string for text display
1004
+ const attributesStr = JSON.stringify(attributes, null, 2);
1005
+ return {
1006
+ content: [
1007
+ {
1008
+ type: "text",
1009
+ text: `Attributes for entity '${entityName}':\n\n${attributesStr}`,
1010
+ },
1011
+ ],
1012
+ };
1013
+ }
1014
+ catch (error) {
1015
+ console.error("Error getting entity attributes:", error);
1016
+ return {
1017
+ content: [
1018
+ {
1019
+ type: "text",
1020
+ text: `Failed to get entity attributes: ${error.message}`,
1021
+ },
1022
+ ],
1023
+ };
1024
+ }
1025
+ });
1026
+ // PowerPlatform specific entity attribute
1027
+ server.tool("get-entity-attribute", "Get a specific attribute/field of a PowerPlatform entity", {
1028
+ entityName: z.string().describe("The logical name of the entity"),
1029
+ attributeName: z.string().describe("The logical name of the attribute")
1030
+ }, async ({ entityName, attributeName }) => {
1031
+ try {
1032
+ // Get or initialize PowerPlatformService
1033
+ const service = getPowerPlatformService();
1034
+ const attribute = await service.getEntityAttribute(entityName, attributeName);
1035
+ // Format the attribute as a string for text display
1036
+ const attributeStr = JSON.stringify(attribute, null, 2);
1037
+ return {
1038
+ content: [
1039
+ {
1040
+ type: "text",
1041
+ text: `Attribute '${attributeName}' for entity '${entityName}':\n\n${attributeStr}`,
1042
+ },
1043
+ ],
1044
+ };
1045
+ }
1046
+ catch (error) {
1047
+ console.error("Error getting entity attribute:", error);
1048
+ return {
1049
+ content: [
1050
+ {
1051
+ type: "text",
1052
+ text: `Failed to get entity attribute: ${error.message}`,
1053
+ },
1054
+ ],
1055
+ };
1056
+ }
1057
+ });
1058
+ // PowerPlatform entity relationships
1059
+ server.tool("get-entity-relationships", "Get relationships (one-to-many and many-to-many) for a PowerPlatform entity", {
1060
+ entityName: z.string().describe("The logical name of the entity"),
1061
+ }, async ({ entityName }) => {
1062
+ try {
1063
+ // Get or initialize PowerPlatformService
1064
+ const service = getPowerPlatformService();
1065
+ const relationships = await service.getEntityRelationships(entityName);
1066
+ // Format the relationships as a string for text display
1067
+ const relationshipsStr = JSON.stringify(relationships, null, 2);
1068
+ return {
1069
+ content: [
1070
+ {
1071
+ type: "text",
1072
+ text: `Relationships for entity '${entityName}':\n\n${relationshipsStr}`,
1073
+ },
1074
+ ],
1075
+ };
1076
+ }
1077
+ catch (error) {
1078
+ console.error("Error getting entity relationships:", error);
1079
+ return {
1080
+ content: [
1081
+ {
1082
+ type: "text",
1083
+ text: `Failed to get entity relationships: ${error.message}`,
1084
+ },
1085
+ ],
1086
+ };
1087
+ }
1088
+ });
1089
+ // PowerPlatform global option set
1090
+ server.tool("get-global-option-set", "Get a global option set definition by name", {
1091
+ optionSetName: z.string().describe("The name of the global option set"),
1092
+ }, async ({ optionSetName }) => {
1093
+ try {
1094
+ // Get or initialize PowerPlatformService
1095
+ const service = getPowerPlatformService();
1096
+ const optionSet = await service.getGlobalOptionSet(optionSetName);
1097
+ // Format the option set as a string for text display
1098
+ const optionSetStr = JSON.stringify(optionSet, null, 2);
1099
+ return {
1100
+ content: [
1101
+ {
1102
+ type: "text",
1103
+ text: `Global option set '${optionSetName}':\n\n${optionSetStr}`,
1104
+ },
1105
+ ],
1106
+ };
1107
+ }
1108
+ catch (error) {
1109
+ console.error("Error getting global option set:", error);
1110
+ return {
1111
+ content: [
1112
+ {
1113
+ type: "text",
1114
+ text: `Failed to get global option set: ${error.message}`,
1115
+ },
1116
+ ],
1117
+ };
1118
+ }
1119
+ });
1120
+ // PowerPlatform record by ID
1121
+ server.tool("get-record", "Get a specific record by entity name (plural) and ID", {
1122
+ entityNamePlural: z.string().describe("The plural name of the entity (e.g., 'accounts', 'contacts')"),
1123
+ recordId: z.string().describe("The GUID of the record"),
1124
+ }, async ({ entityNamePlural, recordId }) => {
1125
+ try {
1126
+ // Get or initialize PowerPlatformService
1127
+ const service = getPowerPlatformService();
1128
+ const record = await service.getRecord(entityNamePlural, recordId);
1129
+ // Format the record as a string for text display
1130
+ const recordStr = JSON.stringify(record, null, 2);
1131
+ return {
1132
+ content: [
1133
+ {
1134
+ type: "text",
1135
+ text: `Record from '${entityNamePlural}' with ID '${recordId}':\n\n${recordStr}`,
1136
+ },
1137
+ ],
1138
+ };
1139
+ }
1140
+ catch (error) {
1141
+ console.error("Error getting record:", error);
1142
+ return {
1143
+ content: [
1144
+ {
1145
+ type: "text",
1146
+ text: `Failed to get record: ${error.message}`,
1147
+ },
1148
+ ],
1149
+ };
1150
+ }
1151
+ });
1152
+ // PowerPlatform query records with filter
1153
+ server.tool("query-records", "Query records using an OData filter expression", {
1154
+ entityNamePlural: z.string().describe("The plural name of the entity (e.g., 'accounts', 'contacts')"),
1155
+ filter: z.string().describe("OData filter expression (e.g., \"name eq 'test'\" or \"createdon gt 2023-01-01\")"),
1156
+ maxRecords: z.number().optional().describe("Maximum number of records to retrieve (default: 50)"),
1157
+ }, async ({ entityNamePlural, filter, maxRecords }) => {
1158
+ try {
1159
+ // Get or initialize PowerPlatformService
1160
+ const service = getPowerPlatformService();
1161
+ const records = await service.queryRecords(entityNamePlural, filter, maxRecords || 50);
1162
+ // Format the records as a string for text display
1163
+ const recordsStr = JSON.stringify(records, null, 2);
1164
+ const recordCount = records.value?.length || 0;
1165
+ return {
1166
+ content: [
1167
+ {
1168
+ type: "text",
1169
+ text: `Retrieved ${recordCount} records from '${entityNamePlural}' with filter '${filter}':\n\n${recordsStr}`,
1170
+ },
1171
+ ],
1172
+ };
1173
+ }
1174
+ catch (error) {
1175
+ console.error("Error querying records:", error);
1176
+ return {
1177
+ content: [
1178
+ {
1179
+ type: "text",
1180
+ text: `Failed to query records: ${error.message}`,
1181
+ },
1182
+ ],
1183
+ };
1184
+ }
1185
+ });
1186
+ // PowerPlatform MCP Prompts
1187
+ server.tool("use-powerplatform-prompt", "Use a predefined prompt template for PowerPlatform entities", {
1188
+ promptType: z.enum([
1189
+ "ENTITY_OVERVIEW",
1190
+ "ATTRIBUTE_DETAILS",
1191
+ "QUERY_TEMPLATE",
1192
+ "RELATIONSHIP_MAP"
1193
+ ]).describe("The type of prompt template to use"),
1194
+ entityName: z.string().describe("The logical name of the entity"),
1195
+ attributeName: z.string().optional().describe("The logical name of the attribute (required for ATTRIBUTE_DETAILS prompt)"),
1196
+ }, async ({ promptType, entityName, attributeName }) => {
1197
+ try {
1198
+ // Get or initialize PowerPlatformService
1199
+ const service = getPowerPlatformService();
1200
+ let promptContent = "";
1201
+ let replacements = {};
1202
+ switch (promptType) {
1203
+ case "ENTITY_OVERVIEW": {
1204
+ // Get entity metadata and key attributes
1205
+ const [metadata, attributes] = await Promise.all([
1206
+ service.getEntityMetadata(entityName),
1207
+ service.getEntityAttributes(entityName)
1208
+ ]);
1209
+ // Format entity details
1210
+ const entityDetails = `- Display Name: ${metadata.DisplayName?.UserLocalizedLabel?.Label || entityName}\n` +
1211
+ `- Schema Name: ${metadata.SchemaName}\n` +
1212
+ `- Description: ${metadata.Description?.UserLocalizedLabel?.Label || 'No description'}\n` +
1213
+ `- Primary Key: ${metadata.PrimaryIdAttribute}\n` +
1214
+ `- Primary Name: ${metadata.PrimaryNameAttribute}`;
1215
+ // Get key attributes
1216
+ const keyAttributes = attributes.value
1217
+ //.slice(0, 10) // Limit to first 10 important attributes
1218
+ .map((attr) => {
1219
+ const attrType = attr["@odata.type"] || attr.odata?.type || "Unknown type";
1220
+ return `- ${attr.LogicalName}: ${attrType}`;
1221
+ })
1222
+ .join('\n');
1223
+ // Get relationships summary
1224
+ const relationships = await service.getEntityRelationships(entityName);
1225
+ const oneToManyCount = relationships.oneToMany.value.length;
1226
+ const manyToManyCount = relationships.manyToMany.value.length;
1227
+ const relationshipsSummary = `- One-to-Many Relationships: ${oneToManyCount}\n` +
1228
+ `- Many-to-Many Relationships: ${manyToManyCount}`;
1229
+ promptContent = powerPlatformPrompts.ENTITY_OVERVIEW(entityName);
1230
+ replacements = {
1231
+ '{{entity_details}}': entityDetails,
1232
+ '{{key_attributes}}': keyAttributes,
1233
+ '{{relationships}}': relationshipsSummary
1234
+ };
1235
+ break;
1236
+ }
1237
+ case "ATTRIBUTE_DETAILS": {
1238
+ if (!attributeName) {
1239
+ throw new Error("attributeName is required for ATTRIBUTE_DETAILS prompt");
1240
+ }
1241
+ // Get attribute details
1242
+ const attribute = await service.getEntityAttribute(entityName, attributeName);
1243
+ // Format attribute details
1244
+ const attrDetails = `- Display Name: ${attribute.DisplayName?.UserLocalizedLabel?.Label || attributeName}\n` +
1245
+ `- Description: ${attribute.Description?.UserLocalizedLabel?.Label || 'No description'}\n` +
1246
+ `- Type: ${attribute.AttributeType}\n` +
1247
+ `- Format: ${attribute.Format || 'N/A'}\n` +
1248
+ `- Is Required: ${attribute.RequiredLevel?.Value || 'No'}\n` +
1249
+ `- Is Searchable: ${attribute.IsValidForAdvancedFind || false}`;
1250
+ promptContent = powerPlatformPrompts.ATTRIBUTE_DETAILS(entityName, attributeName);
1251
+ replacements = {
1252
+ '{{attribute_details}}': attrDetails,
1253
+ '{{data_type}}': attribute.AttributeType,
1254
+ '{{required}}': attribute.RequiredLevel?.Value || 'No',
1255
+ '{{max_length}}': attribute.MaxLength || 'N/A'
1256
+ };
1257
+ break;
1258
+ }
1259
+ case "QUERY_TEMPLATE": {
1260
+ // Get entity metadata to determine plural name
1261
+ const metadata = await service.getEntityMetadata(entityName);
1262
+ const entityNamePlural = metadata.EntitySetName;
1263
+ // Get a few important fields for the select example
1264
+ const attributes = await service.getEntityAttributes(entityName);
1265
+ const selectFields = attributes.value
1266
+ .slice(0, 5) // Just take first 5 for example
1267
+ .map((attr) => attr.LogicalName)
1268
+ .join(',');
1269
+ promptContent = powerPlatformPrompts.QUERY_TEMPLATE(entityNamePlural);
1270
+ replacements = {
1271
+ '{{selected_fields}}': selectFields,
1272
+ '{{filter_conditions}}': `${metadata.PrimaryNameAttribute} eq 'Example'`,
1273
+ '{{order_by}}': `${metadata.PrimaryNameAttribute} asc`,
1274
+ '{{max_records}}': '50'
1275
+ };
1276
+ break;
1277
+ }
1278
+ case "RELATIONSHIP_MAP": {
1279
+ // Get relationships
1280
+ const relationships = await service.getEntityRelationships(entityName);
1281
+ // Format one-to-many relationships where this entity is primary
1282
+ const oneToManyPrimary = relationships.oneToMany.value
1283
+ .filter((rel) => rel.ReferencingEntity !== entityName)
1284
+ //.slice(0, 10) // Limit to 10 for readability
1285
+ .map((rel) => `- ${rel.SchemaName}: ${entityName} (1) → ${rel.ReferencingEntity} (N)`)
1286
+ .join('\n');
1287
+ // Format one-to-many relationships where this entity is related
1288
+ const oneToManyRelated = relationships.oneToMany.value
1289
+ .filter((rel) => rel.ReferencingEntity === entityName)
1290
+ //.slice(0, 10) // Limit to 10 for readability
1291
+ .map((rel) => `- ${rel.SchemaName}: ${rel.ReferencedEntity} (1) → ${entityName} (N)`)
1292
+ .join('\n');
1293
+ // Format many-to-many relationships
1294
+ const manyToMany = relationships.manyToMany.value
1295
+ //.slice(0, 10) // Limit to 10 for readability
1296
+ .map((rel) => {
1297
+ const otherEntity = rel.Entity1LogicalName === entityName ? rel.Entity2LogicalName : rel.Entity1LogicalName;
1298
+ return `- ${rel.SchemaName}: ${entityName} (N) ↔ ${otherEntity} (N)`;
1299
+ })
1300
+ .join('\n');
1301
+ promptContent = powerPlatformPrompts.RELATIONSHIP_MAP(entityName);
1302
+ replacements = {
1303
+ '{{one_to_many_primary}}': oneToManyPrimary || 'None found',
1304
+ '{{one_to_many_related}}': oneToManyRelated || 'None found',
1305
+ '{{many_to_many}}': manyToMany || 'None found'
1306
+ };
1307
+ break;
1308
+ }
1309
+ }
1310
+ // Replace all placeholders in the template
1311
+ for (const [placeholder, value] of Object.entries(replacements)) {
1312
+ promptContent = promptContent.replace(placeholder, value);
1313
+ }
1314
+ return {
1315
+ content: [
1316
+ {
1317
+ type: "text",
1318
+ text: promptContent,
1319
+ },
1320
+ ],
1321
+ };
1322
+ }
1323
+ catch (error) {
1324
+ console.error("Error using PowerPlatform prompt:", error);
1325
+ return {
1326
+ content: [
1327
+ {
1328
+ type: "text",
1329
+ text: `Failed to use PowerPlatform prompt: ${error.message}`,
1330
+ },
1331
+ ],
1332
+ };
1333
+ }
1334
+ });
1335
+ // Plugin Assemblies List Tool
1336
+ server.tool("get-plugin-assemblies", "Get a list of all plugin assemblies in the environment", {
1337
+ includeManaged: z.boolean().optional().describe("Include managed assemblies (default: false)"),
1338
+ maxRecords: z.number().optional().describe("Maximum number of assemblies to return (default: 100)"),
1339
+ }, async ({ includeManaged, maxRecords }) => {
1340
+ try {
1341
+ const service = getPowerPlatformService();
1342
+ const result = await service.getPluginAssemblies(includeManaged || false, maxRecords || 100);
1343
+ const resultStr = JSON.stringify(result, null, 2);
1344
+ return {
1345
+ content: [
1346
+ {
1347
+ type: "text",
1348
+ text: `Found ${result.totalCount} plugin assemblies:\n\n${resultStr}`,
1349
+ },
1350
+ ],
1351
+ };
1352
+ }
1353
+ catch (error) {
1354
+ console.error("Error getting plugin assemblies:", error);
1355
+ return {
1356
+ content: [
1357
+ {
1358
+ type: "text",
1359
+ text: `Failed to get plugin assemblies: ${error.message}`,
1360
+ },
1361
+ ],
1362
+ };
1363
+ }
1364
+ });
1365
+ // Plugin Assembly Tool
1366
+ server.tool("get-plugin-assembly-complete", "Get comprehensive information about a plugin assembly including all types, steps, images, and validation", {
1367
+ assemblyName: z.string().describe("The name of the plugin assembly"),
1368
+ includeDisabled: z.boolean().optional().describe("Include disabled steps (default: false)"),
1369
+ }, async ({ assemblyName, includeDisabled }) => {
1370
+ try {
1371
+ const service = getPowerPlatformService();
1372
+ const result = await service.getPluginAssemblyComplete(assemblyName, includeDisabled || false);
1373
+ const resultStr = JSON.stringify(result, null, 2);
1374
+ return {
1375
+ content: [
1376
+ {
1377
+ type: "text",
1378
+ text: `Plugin assembly '${assemblyName}' complete information:\n\n${resultStr}`,
1379
+ },
1380
+ ],
1381
+ };
1382
+ }
1383
+ catch (error) {
1384
+ console.error("Error getting plugin assembly:", error);
1385
+ return {
1386
+ content: [
1387
+ {
1388
+ type: "text",
1389
+ text: `Failed to get plugin assembly: ${error.message}`,
1390
+ },
1391
+ ],
1392
+ };
1393
+ }
1394
+ });
1395
+ // Entity Plugin Pipeline Tool
1396
+ server.tool("get-entity-plugin-pipeline", "Get all plugins that execute on a specific entity, organized by message and execution order", {
1397
+ entityName: z.string().describe("The logical name of the entity"),
1398
+ messageFilter: z.string().optional().describe("Filter by message name (e.g., 'Create', 'Update', 'Delete')"),
1399
+ includeDisabled: z.boolean().optional().describe("Include disabled steps (default: false)"),
1400
+ }, async ({ entityName, messageFilter, includeDisabled }) => {
1401
+ try {
1402
+ const service = getPowerPlatformService();
1403
+ const result = await service.getEntityPluginPipeline(entityName, messageFilter, includeDisabled || false);
1404
+ const resultStr = JSON.stringify(result, null, 2);
1405
+ return {
1406
+ content: [
1407
+ {
1408
+ type: "text",
1409
+ text: `Plugin pipeline for entity '${entityName}':\n\n${resultStr}`,
1410
+ },
1411
+ ],
1412
+ };
1413
+ }
1414
+ catch (error) {
1415
+ console.error("Error getting entity plugin pipeline:", error);
1416
+ return {
1417
+ content: [
1418
+ {
1419
+ type: "text",
1420
+ text: `Failed to get entity plugin pipeline: ${error.message}`,
1421
+ },
1422
+ ],
1423
+ };
1424
+ }
1425
+ });
1426
+ // Plugin Trace Logs Tool
1427
+ server.tool("get-plugin-trace-logs", "Query plugin trace logs with filtering and exception parsing", {
1428
+ entityName: z.string().optional().describe("Filter by entity logical name"),
1429
+ messageName: z.string().optional().describe("Filter by message name (e.g., 'Update')"),
1430
+ correlationId: z.string().optional().describe("Filter by correlation ID"),
1431
+ pluginStepId: z.string().optional().describe("Filter by specific step ID"),
1432
+ exceptionOnly: z.boolean().optional().describe("Only return logs with exceptions (default: false)"),
1433
+ hoursBack: z.number().optional().describe("How many hours back to search (default: 24)"),
1434
+ maxRecords: z.number().optional().describe("Maximum number of logs to return (default: 50)"),
1435
+ }, async ({ entityName, messageName, correlationId, pluginStepId, exceptionOnly, hoursBack, maxRecords }) => {
1436
+ try {
1437
+ const service = getPowerPlatformService();
1438
+ const result = await service.getPluginTraceLogs({
1439
+ entityName,
1440
+ messageName,
1441
+ correlationId,
1442
+ pluginStepId,
1443
+ exceptionOnly: exceptionOnly || false,
1444
+ hoursBack: hoursBack || 24,
1445
+ maxRecords: maxRecords || 50
1446
+ });
1447
+ const resultStr = JSON.stringify(result, null, 2);
1448
+ return {
1449
+ content: [
1450
+ {
1451
+ type: "text",
1452
+ text: `Plugin trace logs (found ${result.totalCount}):\n\n${resultStr}`,
1453
+ },
1454
+ ],
1455
+ };
1456
+ }
1457
+ catch (error) {
1458
+ console.error("Error getting plugin trace logs:", error);
1459
+ return {
1460
+ content: [
1461
+ {
1462
+ type: "text",
1463
+ text: `Failed to get plugin trace logs: ${error.message}`,
1464
+ },
1465
+ ],
1466
+ };
1467
+ }
1468
+ });
1469
+ // Power Automate Flows Tool
1470
+ server.tool("get-flows", "Get a list of all Power Automate cloud flows in the environment", {
1471
+ activeOnly: z.boolean().optional().describe("Only return activated flows (default: false)"),
1472
+ maxRecords: z.number().optional().describe("Maximum number of flows to return (default: 100)"),
1473
+ }, async ({ activeOnly, maxRecords }) => {
1474
+ try {
1475
+ const service = getPowerPlatformService();
1476
+ const result = await service.getFlows(activeOnly || false, maxRecords || 100);
1477
+ const resultStr = JSON.stringify(result, null, 2);
1478
+ return {
1479
+ content: [
1480
+ {
1481
+ type: "text",
1482
+ text: `Found ${result.totalCount} Power Automate flows:\n\n${resultStr}`,
1483
+ },
1484
+ ],
1485
+ };
1486
+ }
1487
+ catch (error) {
1488
+ console.error("Error getting flows:", error);
1489
+ return {
1490
+ content: [
1491
+ {
1492
+ type: "text",
1493
+ text: `Failed to get flows: ${error.message}`,
1494
+ },
1495
+ ],
1496
+ };
1497
+ }
1498
+ });
1499
+ // Flow Definition Tool
1500
+ server.tool("get-flow-definition", "Get the complete definition of a specific Power Automate flow including its logic", {
1501
+ flowId: z.string().describe("The GUID of the flow (workflowid)"),
1502
+ }, async ({ flowId }) => {
1503
+ try {
1504
+ const service = getPowerPlatformService();
1505
+ const result = await service.getFlowDefinition(flowId);
1506
+ const resultStr = JSON.stringify(result, null, 2);
1507
+ return {
1508
+ content: [
1509
+ {
1510
+ type: "text",
1511
+ text: `Flow definition for '${result.name}':\n\n${resultStr}`,
1512
+ },
1513
+ ],
1514
+ };
1515
+ }
1516
+ catch (error) {
1517
+ console.error("Error getting flow definition:", error);
1518
+ return {
1519
+ content: [
1520
+ {
1521
+ type: "text",
1522
+ text: `Failed to get flow definition: ${error.message}`,
1523
+ },
1524
+ ],
1525
+ };
1526
+ }
1527
+ });
1528
+ // Flow Runs Tool
1529
+ server.tool("get-flow-runs", "Get the run history for a specific Power Automate flow with success/failure status", {
1530
+ flowId: z.string().describe("The GUID of the flow (workflowid)"),
1531
+ maxRecords: z.number().optional().describe("Maximum number of runs to return (default: 100)"),
1532
+ }, async ({ flowId, maxRecords }) => {
1533
+ try {
1534
+ const service = getPowerPlatformService();
1535
+ const result = await service.getFlowRuns(flowId, maxRecords || 100);
1536
+ const resultStr = JSON.stringify(result, null, 2);
1537
+ // Calculate success/failure stats
1538
+ const stats = result.runs.reduce((acc, run) => {
1539
+ if (run.status === 'Succeeded')
1540
+ acc.succeeded++;
1541
+ else if (run.status === 'Failed' || run.status === 'Faulted' || run.status === 'TimedOut')
1542
+ acc.failed++;
1543
+ else if (run.status === 'Running' || run.status === 'Waiting')
1544
+ acc.inProgress++;
1545
+ else
1546
+ acc.other++;
1547
+ return acc;
1548
+ }, { succeeded: 0, failed: 0, inProgress: 0, other: 0 });
1549
+ return {
1550
+ content: [
1551
+ {
1552
+ type: "text",
1553
+ text: `Found ${result.totalCount} flow runs for flow ${flowId}:\n\nStats:\n- Succeeded: ${stats.succeeded}\n- Failed: ${stats.failed}\n- In Progress: ${stats.inProgress}\n- Other: ${stats.other}\n\n${resultStr}`,
1554
+ },
1555
+ ],
1556
+ };
1557
+ }
1558
+ catch (error) {
1559
+ console.error("Error getting flow runs:", error);
1560
+ return {
1561
+ content: [
1562
+ {
1563
+ type: "text",
1564
+ text: `Failed to get flow runs: ${error.message}`,
1565
+ },
1566
+ ],
1567
+ };
1568
+ }
1569
+ });
1570
+ // Classic Dynamics Workflows Tool
1571
+ server.tool("get-workflows", "Get a list of all classic Dynamics workflows in the environment", {
1572
+ activeOnly: z.boolean().optional().describe("Only return activated workflows (default: false)"),
1573
+ maxRecords: z.number().optional().describe("Maximum number of workflows to return (default: 100)"),
1574
+ }, async ({ activeOnly, maxRecords }) => {
1575
+ try {
1576
+ const service = getPowerPlatformService();
1577
+ const result = await service.getWorkflows(activeOnly || false, maxRecords || 100);
1578
+ const resultStr = JSON.stringify(result, null, 2);
1579
+ return {
1580
+ content: [
1581
+ {
1582
+ type: "text",
1583
+ text: `Found ${result.totalCount} classic Dynamics workflows:\n\n${resultStr}`,
1584
+ },
1585
+ ],
1586
+ };
1587
+ }
1588
+ catch (error) {
1589
+ console.error("Error getting workflows:", error);
1590
+ return {
1591
+ content: [
1592
+ {
1593
+ type: "text",
1594
+ text: `Failed to get workflows: ${error.message}`,
1595
+ },
1596
+ ],
1597
+ };
1598
+ }
1599
+ });
1600
+ // Workflow Definition Tool
1601
+ server.tool("get-workflow-definition", "Get the complete definition of a specific classic Dynamics workflow including its XAML", {
1602
+ workflowId: z.string().describe("The GUID of the workflow (workflowid)"),
1603
+ }, async ({ workflowId }) => {
1604
+ try {
1605
+ const service = getPowerPlatformService();
1606
+ const result = await service.getWorkflowDefinition(workflowId);
1607
+ const resultStr = JSON.stringify(result, null, 2);
1608
+ return {
1609
+ content: [
1610
+ {
1611
+ type: "text",
1612
+ text: `Workflow definition for '${result.name}':\n\n${resultStr}`,
1613
+ },
1614
+ ],
1615
+ };
1616
+ }
1617
+ catch (error) {
1618
+ console.error("Error getting workflow definition:", error);
1619
+ return {
1620
+ content: [
1621
+ {
1622
+ type: "text",
1623
+ text: `Failed to get workflow definition: ${error.message}`,
1624
+ },
1625
+ ],
1626
+ };
1627
+ }
1628
+ });
1629
+ // ==================== AZURE DEVOPS WIKI TOOLS ====================
1630
+ // Get Wikis Tool
1631
+ server.tool("get-wikis", "Get all wikis in an Azure DevOps project", {
1632
+ project: z.string().describe("The project name"),
1633
+ }, async ({ project }) => {
1634
+ try {
1635
+ const service = getAzureDevOpsService();
1636
+ const result = await service.getWikis(project);
1637
+ const resultStr = JSON.stringify(result, null, 2);
1638
+ return {
1639
+ content: [
1640
+ {
1641
+ type: "text",
1642
+ text: `Wikis in project '${project}':\n\n${resultStr}`,
1643
+ },
1644
+ ],
1645
+ };
1646
+ }
1647
+ catch (error) {
1648
+ console.error("Error getting wikis:", error);
1649
+ return {
1650
+ content: [
1651
+ {
1652
+ type: "text",
1653
+ text: `Failed to get wikis: ${error.message}`,
1654
+ },
1655
+ ],
1656
+ };
1657
+ }
1658
+ });
1659
+ // Search Wiki Pages Tool
1660
+ server.tool("search-wiki-pages", "Search wiki pages across Azure DevOps projects", {
1661
+ searchText: z.string().describe("The text to search for"),
1662
+ project: z.string().optional().describe("Optional project filter"),
1663
+ maxResults: z.number().optional().describe("Maximum number of results (default: 25)"),
1664
+ }, async ({ searchText, project, maxResults }) => {
1665
+ try {
1666
+ const service = getAzureDevOpsService();
1667
+ const result = await service.searchWikiPages(searchText, project, maxResults);
1668
+ const resultStr = JSON.stringify(result, null, 2);
1669
+ return {
1670
+ content: [
1671
+ {
1672
+ type: "text",
1673
+ text: `Wiki search results for '${searchText}':\n\n${resultStr}`,
1674
+ },
1675
+ ],
1676
+ };
1677
+ }
1678
+ catch (error) {
1679
+ console.error("Error searching wiki pages:", error);
1680
+ return {
1681
+ content: [
1682
+ {
1683
+ type: "text",
1684
+ text: `Failed to search wiki pages: ${error.message}`,
1685
+ },
1686
+ ],
1687
+ };
1688
+ }
1689
+ });
1690
+ // Get Wiki Page Tool
1691
+ server.tool("get-wiki-page", "Get a specific wiki page with content from Azure DevOps", {
1692
+ project: z.string().describe("The project name"),
1693
+ wikiId: z.string().describe("The wiki identifier (ID or name)"),
1694
+ pagePath: z.string().describe("The path to the page (e.g., '/Setup/Authentication')"),
1695
+ includeContent: z.boolean().optional().describe("Include page content (default: true)"),
1696
+ }, async ({ project, wikiId, pagePath, includeContent }) => {
1697
+ try {
1698
+ const service = getAzureDevOpsService();
1699
+ const result = await service.getWikiPage(project, wikiId, pagePath, includeContent ?? true);
1700
+ const resultStr = JSON.stringify(result, null, 2);
1701
+ return {
1702
+ content: [
1703
+ {
1704
+ type: "text",
1705
+ text: `Wiki page '${pagePath}':\n\n${resultStr}`,
1706
+ },
1707
+ ],
1708
+ };
1709
+ }
1710
+ catch (error) {
1711
+ console.error("Error getting wiki page:", error);
1712
+ return {
1713
+ content: [
1714
+ {
1715
+ type: "text",
1716
+ text: `Failed to get wiki page: ${error.message}`,
1717
+ },
1718
+ ],
1719
+ };
1720
+ }
1721
+ });
1722
+ // Create Wiki Page Tool
1723
+ server.tool("create-wiki-page", "Create a new wiki page in Azure DevOps (requires AZUREDEVOPS_ENABLE_WIKI_WRITE=true)", {
1724
+ project: z.string().describe("The project name"),
1725
+ wikiId: z.string().describe("The wiki identifier"),
1726
+ pagePath: z.string().describe("The path for the new page (e.g., '/Setup/NewGuide')"),
1727
+ content: z.string().describe("The markdown content for the page"),
1728
+ }, async ({ project, wikiId, pagePath, content }) => {
1729
+ try {
1730
+ const service = getAzureDevOpsService();
1731
+ const result = await service.createWikiPage(project, wikiId, pagePath, content);
1732
+ const resultStr = JSON.stringify(result, null, 2);
1733
+ return {
1734
+ content: [
1735
+ {
1736
+ type: "text",
1737
+ text: `Created wiki page '${pagePath}':\n\n${resultStr}`,
1738
+ },
1739
+ ],
1740
+ };
1741
+ }
1742
+ catch (error) {
1743
+ console.error("Error creating wiki page:", error);
1744
+ return {
1745
+ content: [
1746
+ {
1747
+ type: "text",
1748
+ text: `Failed to create wiki page: ${error.message}`,
1749
+ },
1750
+ ],
1751
+ };
1752
+ }
1753
+ });
1754
+ // Update Wiki Page Tool
1755
+ server.tool("update-wiki-page", "Update an existing wiki page in Azure DevOps (requires AZUREDEVOPS_ENABLE_WIKI_WRITE=true)", {
1756
+ project: z.string().describe("The project name"),
1757
+ wikiId: z.string().describe("The wiki identifier"),
1758
+ pagePath: z.string().describe("The path to the page"),
1759
+ content: z.string().describe("The updated markdown content"),
1760
+ version: z.string().optional().describe("The ETag/version for optimistic concurrency"),
1761
+ }, async ({ project, wikiId, pagePath, content, version }) => {
1762
+ try {
1763
+ const service = getAzureDevOpsService();
1764
+ const result = await service.updateWikiPage(project, wikiId, pagePath, content, version);
1765
+ const resultStr = JSON.stringify(result, null, 2);
1766
+ return {
1767
+ content: [
1768
+ {
1769
+ type: "text",
1770
+ text: `Updated wiki page '${pagePath}':\n\n${resultStr}`,
1771
+ },
1772
+ ],
1773
+ };
1774
+ }
1775
+ catch (error) {
1776
+ console.error("Error updating wiki page:", error);
1777
+ return {
1778
+ content: [
1779
+ {
1780
+ type: "text",
1781
+ text: `Failed to update wiki page: ${error.message}`,
1782
+ },
1783
+ ],
1784
+ };
1785
+ }
1786
+ });
1787
+ // ==================== AZURE DEVOPS WORK ITEM TOOLS ====================
1788
+ // Get Work Item Tool
1789
+ server.tool("get-work-item", "Get a work item by ID with full details from Azure DevOps", {
1790
+ project: z.string().describe("The project name"),
1791
+ workItemId: z.number().describe("The work item ID"),
1792
+ }, async ({ project, workItemId }) => {
1793
+ try {
1794
+ const service = getAzureDevOpsService();
1795
+ const result = await service.getWorkItem(project, workItemId);
1796
+ const resultStr = JSON.stringify(result, null, 2);
1797
+ return {
1798
+ content: [
1799
+ {
1800
+ type: "text",
1801
+ text: `Work item ${workItemId}:\n\n${resultStr}`,
1802
+ },
1803
+ ],
1804
+ };
1805
+ }
1806
+ catch (error) {
1807
+ console.error("Error getting work item:", error);
1808
+ return {
1809
+ content: [
1810
+ {
1811
+ type: "text",
1812
+ text: `Failed to get work item: ${error.message}`,
1813
+ },
1814
+ ],
1815
+ };
1816
+ }
1817
+ });
1818
+ // Query Work Items Tool
1819
+ server.tool("query-work-items", "Query work items using WIQL (Work Item Query Language) in Azure DevOps", {
1820
+ project: z.string().describe("The project name"),
1821
+ wiql: z.string().describe("The WIQL query string (e.g., \"SELECT [System.Id], [System.Title] FROM WorkItems WHERE [System.State] = 'Active'\")"),
1822
+ maxResults: z.number().optional().describe("Maximum number of results (default: 200)"),
1823
+ }, async ({ project, wiql, maxResults }) => {
1824
+ try {
1825
+ const service = getAzureDevOpsService();
1826
+ const result = await service.queryWorkItems(project, wiql, maxResults);
1827
+ const resultStr = JSON.stringify(result, null, 2);
1828
+ return {
1829
+ content: [
1830
+ {
1831
+ type: "text",
1832
+ text: `Work items query results:\n\n${resultStr}`,
1833
+ },
1834
+ ],
1835
+ };
1836
+ }
1837
+ catch (error) {
1838
+ console.error("Error querying work items:", error);
1839
+ return {
1840
+ content: [
1841
+ {
1842
+ type: "text",
1843
+ text: `Failed to query work items: ${error.message}`,
1844
+ },
1845
+ ],
1846
+ };
1847
+ }
1848
+ });
1849
+ // Get Work Item Comments Tool
1850
+ server.tool("get-work-item-comments", "Get comments/discussion for a work item in Azure DevOps", {
1851
+ project: z.string().describe("The project name"),
1852
+ workItemId: z.number().describe("The work item ID"),
1853
+ }, async ({ project, workItemId }) => {
1854
+ try {
1855
+ const service = getAzureDevOpsService();
1856
+ const result = await service.getWorkItemComments(project, workItemId);
1857
+ const resultStr = JSON.stringify(result, null, 2);
1858
+ return {
1859
+ content: [
1860
+ {
1861
+ type: "text",
1862
+ text: `Comments for work item ${workItemId}:\n\n${resultStr}`,
1863
+ },
1864
+ ],
1865
+ };
1866
+ }
1867
+ catch (error) {
1868
+ console.error("Error getting work item comments:", error);
1869
+ return {
1870
+ content: [
1871
+ {
1872
+ type: "text",
1873
+ text: `Failed to get work item comments: ${error.message}`,
1874
+ },
1875
+ ],
1876
+ };
1877
+ }
1878
+ });
1879
+ // Add Work Item Comment Tool
1880
+ server.tool("add-work-item-comment", "Add a comment to a work item in Azure DevOps (requires AZUREDEVOPS_ENABLE_WORK_ITEM_WRITE=true)", {
1881
+ project: z.string().describe("The project name"),
1882
+ workItemId: z.number().describe("The work item ID"),
1883
+ commentText: z.string().describe("The comment text (supports markdown)"),
1884
+ }, async ({ project, workItemId, commentText }) => {
1885
+ try {
1886
+ const service = getAzureDevOpsService();
1887
+ const result = await service.addWorkItemComment(project, workItemId, commentText);
1888
+ const resultStr = JSON.stringify(result, null, 2);
1889
+ return {
1890
+ content: [
1891
+ {
1892
+ type: "text",
1893
+ text: `Added comment to work item ${workItemId}:\n\n${resultStr}`,
1894
+ },
1895
+ ],
1896
+ };
1897
+ }
1898
+ catch (error) {
1899
+ console.error("Error adding work item comment:", error);
1900
+ return {
1901
+ content: [
1902
+ {
1903
+ type: "text",
1904
+ text: `Failed to add work item comment: ${error.message}`,
1905
+ },
1906
+ ],
1907
+ };
1908
+ }
1909
+ });
1910
+ // Update Work Item Tool
1911
+ server.tool("update-work-item", "Update a work item in Azure DevOps using JSON Patch operations (requires AZUREDEVOPS_ENABLE_WORK_ITEM_WRITE=true)", {
1912
+ project: z.string().describe("The project name"),
1913
+ workItemId: z.number().describe("The work item ID"),
1914
+ patchOperations: z.array(z.any()).describe("Array of JSON Patch operations (e.g., [{\"op\": \"add\", \"path\": \"/fields/System.State\", \"value\": \"Resolved\"}])"),
1915
+ }, async ({ project, workItemId, patchOperations }) => {
1916
+ try {
1917
+ const service = getAzureDevOpsService();
1918
+ const result = await service.updateWorkItem(project, workItemId, patchOperations);
1919
+ const resultStr = JSON.stringify(result, null, 2);
1920
+ return {
1921
+ content: [
1922
+ {
1923
+ type: "text",
1924
+ text: `Updated work item ${workItemId}:\n\n${resultStr}`,
1925
+ },
1926
+ ],
1927
+ };
1928
+ }
1929
+ catch (error) {
1930
+ console.error("Error updating work item:", error);
1931
+ return {
1932
+ content: [
1933
+ {
1934
+ type: "text",
1935
+ text: `Failed to update work item: ${error.message}`,
1936
+ },
1937
+ ],
1938
+ };
1939
+ }
1940
+ });
1941
+ // Create Work Item Tool
1942
+ server.tool("create-work-item", "Create a new work item in Azure DevOps (requires AZUREDEVOPS_ENABLE_WORK_ITEM_WRITE=true)", {
1943
+ project: z.string().describe("The project name"),
1944
+ workItemType: z.string().describe("The work item type (e.g., 'Bug', 'Task', 'User Story')"),
1945
+ fields: z.record(z.any()).describe("Object with field values (e.g., {\"System.Title\": \"Bug title\", \"System.Description\": \"Details\"})"),
1946
+ }, async ({ project, workItemType, fields }) => {
1947
+ try {
1948
+ const service = getAzureDevOpsService();
1949
+ const result = await service.createWorkItem(project, workItemType, fields);
1950
+ const resultStr = JSON.stringify(result, null, 2);
1951
+ return {
1952
+ content: [
1953
+ {
1954
+ type: "text",
1955
+ text: `Created work item:\n\n${resultStr}`,
1956
+ },
1957
+ ],
1958
+ };
1959
+ }
1960
+ catch (error) {
1961
+ console.error("Error creating work item:", error);
1962
+ return {
1963
+ content: [
1964
+ {
1965
+ type: "text",
1966
+ text: `Failed to create work item: ${error.message}`,
1967
+ },
1968
+ ],
1969
+ };
1970
+ }
1971
+ });
1972
+ // Delete Work Item Tool
1973
+ server.tool("delete-work-item", "Delete a work item in Azure DevOps (requires AZUREDEVOPS_ENABLE_WORK_ITEM_DELETE=true)", {
1974
+ project: z.string().describe("The project name"),
1975
+ workItemId: z.number().describe("The work item ID"),
1976
+ }, async ({ project, workItemId }) => {
1977
+ try {
1978
+ const service = getAzureDevOpsService();
1979
+ const result = await service.deleteWorkItem(project, workItemId);
1980
+ const resultStr = JSON.stringify(result, null, 2);
1981
+ return {
1982
+ content: [
1983
+ {
1984
+ type: "text",
1985
+ text: `Deleted work item ${workItemId}:\n\n${resultStr}`,
1986
+ },
1987
+ ],
1988
+ };
1989
+ }
1990
+ catch (error) {
1991
+ console.error("Error deleting work item:", error);
1992
+ return {
1993
+ content: [
1994
+ {
1995
+ type: "text",
1996
+ text: `Failed to delete work item: ${error.message}`,
1997
+ },
1998
+ ],
1999
+ };
2000
+ }
2001
+ });
2002
+ async function main() {
2003
+ const transport = new StdioServerTransport();
2004
+ await server.connect(transport);
2005
+ console.error("Initializing PowerPlatform MCP Server...");
2006
+ }
2007
+ main().catch((error) => {
2008
+ console.error("Fatal error in main():", error);
2009
+ process.exit(1);
2010
+ });