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/README.md +968 -0
- package/build/AzureDevOpsService.js +394 -0
- package/build/PowerPlatformService.js +655 -0
- package/build/index.js +2010 -0
- package/package.json +48 -0
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
|
+
});
|