@mcp-consultant-tools/powerplatform 27.0.0 → 28.0.0-beta.13
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/PowerPlatformService.d.ts +23 -2
- package/build/PowerPlatformService.d.ts.map +1 -1
- package/build/PowerPlatformService.js +23 -1
- package/build/PowerPlatformService.js.map +1 -1
- package/build/cli/commands/app-commands.d.ts +7 -0
- package/build/cli/commands/app-commands.d.ts.map +1 -0
- package/build/cli/commands/app-commands.js +65 -0
- package/build/cli/commands/app-commands.js.map +1 -0
- package/build/cli/commands/flow-commands.d.ts +7 -0
- package/build/cli/commands/flow-commands.d.ts.map +1 -0
- package/build/cli/commands/flow-commands.js +170 -0
- package/build/cli/commands/flow-commands.js.map +1 -0
- package/build/cli/commands/form-commands.d.ts +7 -0
- package/build/cli/commands/form-commands.d.ts.map +1 -0
- package/build/cli/commands/form-commands.js +104 -0
- package/build/cli/commands/form-commands.js.map +1 -0
- package/build/cli/commands/index.d.ts +15 -0
- package/build/cli/commands/index.d.ts.map +1 -0
- package/build/cli/commands/index.js +30 -0
- package/build/cli/commands/index.js.map +1 -0
- package/build/cli/commands/integration-commands.d.ts +7 -0
- package/build/cli/commands/integration-commands.d.ts.map +1 -0
- package/build/cli/commands/integration-commands.js +97 -0
- package/build/cli/commands/integration-commands.js.map +1 -0
- package/build/cli/commands/metadata-commands.d.ts +7 -0
- package/build/cli/commands/metadata-commands.d.ts.map +1 -0
- package/build/cli/commands/metadata-commands.js +83 -0
- package/build/cli/commands/metadata-commands.js.map +1 -0
- package/build/cli/commands/plugin-commands.d.ts +7 -0
- package/build/cli/commands/plugin-commands.d.ts.map +1 -0
- package/build/cli/commands/plugin-commands.js +79 -0
- package/build/cli/commands/plugin-commands.js.map +1 -0
- package/build/cli/commands/security-commands.d.ts +7 -0
- package/build/cli/commands/security-commands.d.ts.map +1 -0
- package/build/cli/commands/security-commands.js +89 -0
- package/build/cli/commands/security-commands.js.map +1 -0
- package/build/cli/commands/solution-commands.d.ts +7 -0
- package/build/cli/commands/solution-commands.d.ts.map +1 -0
- package/build/cli/commands/solution-commands.js +146 -0
- package/build/cli/commands/solution-commands.js.map +1 -0
- package/build/cli/output.d.ts +11 -0
- package/build/cli/output.d.ts.map +1 -0
- package/build/cli/output.js +10 -0
- package/build/cli/output.js.map +1 -0
- package/build/cli.d.ts +9 -0
- package/build/cli.d.ts.map +1 -0
- package/build/cli.js +30 -0
- package/build/cli.js.map +1 -0
- package/build/context-factory.d.ts +11 -0
- package/build/context-factory.d.ts.map +1 -0
- package/build/context-factory.js +39 -0
- package/build/context-factory.js.map +1 -0
- package/build/index.d.ts +10 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +26 -2394
- package/build/index.js.map +1 -1
- package/build/prompts/analysis-prompts.d.ts +3 -0
- package/build/prompts/analysis-prompts.d.ts.map +1 -0
- package/build/prompts/analysis-prompts.js +286 -0
- package/build/prompts/analysis-prompts.js.map +1 -0
- package/build/prompts/entity-prompts.d.ts +3 -0
- package/build/prompts/entity-prompts.d.ts.map +1 -0
- package/build/prompts/entity-prompts.js +304 -0
- package/build/prompts/entity-prompts.js.map +1 -0
- package/build/prompts/index.d.ts +8 -0
- package/build/prompts/index.d.ts.map +1 -0
- package/build/prompts/index.js +11 -0
- package/build/prompts/index.js.map +1 -0
- package/build/services/index.d.ts +5 -0
- package/build/services/index.d.ts.map +1 -0
- package/build/services/index.js +5 -0
- package/build/services/index.js.map +1 -0
- package/build/tool-examples.d.ts +48 -0
- package/build/tool-examples.d.ts.map +1 -0
- package/build/tool-examples.js +91 -0
- package/build/tool-examples.js.map +1 -0
- package/build/tools/app-tools.d.ts +3 -0
- package/build/tools/app-tools.d.ts.map +1 -0
- package/build/tools/app-tools.js +127 -0
- package/build/tools/app-tools.js.map +1 -0
- package/build/tools/flow-tools.d.ts +3 -0
- package/build/tools/flow-tools.d.ts.map +1 -0
- package/build/tools/flow-tools.js +374 -0
- package/build/tools/flow-tools.js.map +1 -0
- package/build/tools/form-view-tools.d.ts +3 -0
- package/build/tools/form-view-tools.d.ts.map +1 -0
- package/build/tools/form-view-tools.js +162 -0
- package/build/tools/form-view-tools.js.map +1 -0
- package/build/tools/index.d.ts +14 -0
- package/build/tools/index.d.ts.map +1 -0
- package/build/tools/index.js +29 -0
- package/build/tools/index.js.map +1 -0
- package/build/tools/integration-tools.d.ts +3 -0
- package/build/tools/integration-tools.d.ts.map +1 -0
- package/build/tools/integration-tools.js +325 -0
- package/build/tools/integration-tools.js.map +1 -0
- package/build/tools/metadata-tools.d.ts +3 -0
- package/build/tools/metadata-tools.d.ts.map +1 -0
- package/build/tools/metadata-tools.js +166 -0
- package/build/tools/metadata-tools.js.map +1 -0
- package/build/tools/plugin-tools.d.ts +3 -0
- package/build/tools/plugin-tools.d.ts.map +1 -0
- package/build/tools/plugin-tools.js +138 -0
- package/build/tools/plugin-tools.js.map +1 -0
- package/build/tools/security-tools.d.ts +3 -0
- package/build/tools/security-tools.d.ts.map +1 -0
- package/build/tools/security-tools.js +188 -0
- package/build/tools/security-tools.js.map +1 -0
- package/build/tools/solution-tools.d.ts +3 -0
- package/build/tools/solution-tools.d.ts.map +1 -0
- package/build/tools/solution-tools.js +284 -0
- package/build/tools/solution-tools.js.map +1 -0
- package/build/types.d.ts +9 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/package.json +6 -4
package/build/index.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @mcp-consultant-tools/powerplatform
|
|
4
|
+
*
|
|
5
|
+
* MCP server for PowerPlatform read-only integration (46 tools, 12 prompts).
|
|
6
|
+
* Entry point: MCP server startup + backward-compatible registerPowerPlatformTools().
|
|
7
|
+
*/
|
|
2
8
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import { z } from 'zod';
|
|
4
9
|
import { pathToFileURL } from 'node:url';
|
|
5
10
|
import { realpathSync } from 'node:fs';
|
|
6
11
|
import { createMcpServer, createEnvLoader } from '@mcp-consultant-tools/core';
|
|
7
12
|
import { PowerPlatformService } from './PowerPlatformService.js';
|
|
8
|
-
import {
|
|
13
|
+
import { TokenCache } from '@mcp-consultant-tools/powerplatform-core';
|
|
14
|
+
import { registerAllTools } from './tools/index.js';
|
|
15
|
+
import { registerAllPrompts } from './prompts/index.js';
|
|
9
16
|
const POWERPLATFORM_DEFAULT_SOLUTION = process.env.POWERPLATFORM_DEFAULT_SOLUTION || "";
|
|
10
17
|
/**
|
|
11
|
-
*
|
|
12
|
-
* @param server - MCP server instance
|
|
13
|
-
* @param service - Optional pre-initialized PowerPlatformService (for testing)
|
|
18
|
+
* Build a ServiceContext with lazy initialization.
|
|
14
19
|
*/
|
|
15
|
-
|
|
20
|
+
function createServiceContext(service) {
|
|
16
21
|
let ppService = service || null;
|
|
17
22
|
function getPowerPlatformService() {
|
|
18
23
|
if (!ppService) {
|
|
19
|
-
// Required for all auth modes
|
|
20
24
|
const coreRequiredVars = [
|
|
21
25
|
'POWERPLATFORM_URL',
|
|
22
26
|
'POWERPLATFORM_CLIENT_ID',
|
|
@@ -26,2406 +30,35 @@ export function registerPowerPlatformTools(server, service) {
|
|
|
26
30
|
if (missing.length > 0) {
|
|
27
31
|
throw new Error(`Missing required PowerPlatform configuration: ${missing.join(', ')}`);
|
|
28
32
|
}
|
|
29
|
-
// Determine auth mode based on presence of client secret
|
|
30
33
|
const hasClientSecret = !!process.env.POWERPLATFORM_CLIENT_SECRET;
|
|
31
34
|
const config = {
|
|
32
35
|
organizationUrl: process.env.POWERPLATFORM_URL,
|
|
33
36
|
clientId: process.env.POWERPLATFORM_CLIENT_ID,
|
|
34
|
-
clientSecret: process.env.POWERPLATFORM_CLIENT_SECRET,
|
|
37
|
+
clientSecret: process.env.POWERPLATFORM_CLIENT_SECRET,
|
|
35
38
|
tenantId: process.env.POWERPLATFORM_TENANT_ID,
|
|
36
39
|
};
|
|
37
40
|
ppService = new PowerPlatformService(config);
|
|
38
|
-
// Log auth mode being used (to stderr for MCP protocol compatibility)
|
|
39
41
|
const authMode = hasClientSecret ? 'service-principal' : 'interactive';
|
|
40
42
|
console.error(`PowerPlatform auth mode: ${authMode}`);
|
|
41
43
|
}
|
|
42
44
|
return ppService;
|
|
43
45
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}, async ({ entityName }) => {
|
|
48
|
-
try {
|
|
49
|
-
// Get or initialize PowerPlatformService
|
|
50
|
-
const service = getPowerPlatformService();
|
|
51
|
-
const metadata = await service.getEntityMetadata(entityName);
|
|
52
|
-
// Format the metadata as a string for text display
|
|
53
|
-
const metadataStr = JSON.stringify(metadata, null, 2);
|
|
54
|
-
return {
|
|
55
|
-
content: [
|
|
56
|
-
{
|
|
57
|
-
type: "text",
|
|
58
|
-
text: `Entity metadata for '${entityName}':\n\n${metadataStr}`,
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
catch (error) {
|
|
64
|
-
console.error("Error getting entity metadata:", error);
|
|
65
|
-
return {
|
|
66
|
-
content: [
|
|
67
|
-
{
|
|
68
|
-
type: "text",
|
|
69
|
-
text: `Failed to get entity metadata: ${error.message}`,
|
|
70
|
-
},
|
|
71
|
-
],
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
server.tool("get-entity-attributes", "Get attributes/fields of a PowerPlatform entity. Use filtering options to reduce response size for large entities.", {
|
|
76
|
-
entityName: z.string().describe("The logical name of the entity"),
|
|
77
|
-
prefix: z.string().optional().describe("Filter attributes by schema name prefix (e.g., 'si_' to get only custom columns)"),
|
|
78
|
-
attributeType: z.enum(['String', 'Integer', 'Boolean', 'DateTime', 'Decimal', 'Double', 'Money', 'Lookup', 'Picklist', 'State', 'Status', 'Uniqueidentifier', 'Memo', 'BigInt', 'Owner', 'Customer', 'PartyList']).optional().describe("Filter by attribute type"),
|
|
79
|
-
maxAttributes: z.number().optional().describe("Maximum number of attributes to return (omit for all)"),
|
|
80
|
-
}, async ({ entityName, prefix, attributeType, maxAttributes }) => {
|
|
81
|
-
try {
|
|
82
|
-
// Get or initialize PowerPlatformService
|
|
83
|
-
const service = getPowerPlatformService();
|
|
84
|
-
const result = await service.getEntityAttributes(entityName, {
|
|
85
|
-
prefix,
|
|
86
|
-
attributeType,
|
|
87
|
-
maxAttributes
|
|
88
|
-
});
|
|
89
|
-
// Format the attributes as a string for text display
|
|
90
|
-
const attributesStr = JSON.stringify(result, null, 2);
|
|
91
|
-
let message = `Attributes for entity '${entityName}' (${result.returnedCount} returned)`;
|
|
92
|
-
if (prefix) {
|
|
93
|
-
message += `\nFiltered by prefix: ${prefix}`;
|
|
94
|
-
}
|
|
95
|
-
if (attributeType) {
|
|
96
|
-
message += `\nFiltered by type: ${attributeType}`;
|
|
97
|
-
}
|
|
98
|
-
if (result.hasMore) {
|
|
99
|
-
message += `\n⚠️ More attributes available - ${result.totalBeforeFilter} total before limit`;
|
|
100
|
-
}
|
|
101
|
-
return {
|
|
102
|
-
content: [
|
|
103
|
-
{
|
|
104
|
-
type: "text",
|
|
105
|
-
text: `${message}:\n\n${attributesStr}`,
|
|
106
|
-
},
|
|
107
|
-
],
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
console.error("Error getting entity attributes:", error);
|
|
112
|
-
return {
|
|
113
|
-
content: [
|
|
114
|
-
{
|
|
115
|
-
type: "text",
|
|
116
|
-
text: `Failed to get entity attributes: ${error.message}`,
|
|
117
|
-
},
|
|
118
|
-
],
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
server.tool("get-entity-attribute", "Get a specific attribute/field of a PowerPlatform entity", {
|
|
123
|
-
entityName: z.string().describe("The logical name of the entity"),
|
|
124
|
-
attributeName: z.string().describe("The logical name of the attribute")
|
|
125
|
-
}, async ({ entityName, attributeName }) => {
|
|
126
|
-
try {
|
|
127
|
-
// Get or initialize PowerPlatformService
|
|
128
|
-
const service = getPowerPlatformService();
|
|
129
|
-
const attribute = await service.getEntityAttribute(entityName, attributeName);
|
|
130
|
-
// Format the attribute as a string for text display
|
|
131
|
-
const attributeStr = JSON.stringify(attribute, null, 2);
|
|
132
|
-
return {
|
|
133
|
-
content: [
|
|
134
|
-
{
|
|
135
|
-
type: "text",
|
|
136
|
-
text: `Attribute '${attributeName}' for entity '${entityName}':\n\n${attributeStr}`,
|
|
137
|
-
},
|
|
138
|
-
],
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
catch (error) {
|
|
142
|
-
console.error("Error getting entity attribute:", error);
|
|
143
|
-
return {
|
|
144
|
-
content: [
|
|
145
|
-
{
|
|
146
|
-
type: "text",
|
|
147
|
-
text: `Failed to get entity attribute: ${error.message}`,
|
|
148
|
-
},
|
|
149
|
-
],
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
server.tool("get-entity-relationships", "Get relationships (one-to-many and many-to-many) for a PowerPlatform entity", {
|
|
154
|
-
entityName: z.string().describe("The logical name of the entity"),
|
|
155
|
-
}, async ({ entityName }) => {
|
|
156
|
-
try {
|
|
157
|
-
// Get or initialize PowerPlatformService
|
|
158
|
-
const service = getPowerPlatformService();
|
|
159
|
-
const relationships = await service.getEntityRelationships(entityName);
|
|
160
|
-
// Format the relationships as a string for text display
|
|
161
|
-
const relationshipsStr = JSON.stringify(relationships, null, 2);
|
|
162
|
-
return {
|
|
163
|
-
content: [
|
|
164
|
-
{
|
|
165
|
-
type: "text",
|
|
166
|
-
text: `Relationships for entity '${entityName}':\n\n${relationshipsStr}`,
|
|
167
|
-
},
|
|
168
|
-
],
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
catch (error) {
|
|
172
|
-
console.error("Error getting entity relationships:", error);
|
|
173
|
-
return {
|
|
174
|
-
content: [
|
|
175
|
-
{
|
|
176
|
-
type: "text",
|
|
177
|
-
text: `Failed to get entity relationships: ${error.message}`,
|
|
178
|
-
},
|
|
179
|
-
],
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
server.tool("get-global-option-set", "Get a global option set definition by name", {
|
|
184
|
-
optionSetName: z.string().describe("The name of the global option set"),
|
|
185
|
-
}, async ({ optionSetName }) => {
|
|
186
|
-
try {
|
|
187
|
-
// Get or initialize PowerPlatformService
|
|
188
|
-
const service = getPowerPlatformService();
|
|
189
|
-
const optionSet = await service.getGlobalOptionSet(optionSetName);
|
|
190
|
-
// Format the option set as a string for text display
|
|
191
|
-
const optionSetStr = JSON.stringify(optionSet, null, 2);
|
|
192
|
-
return {
|
|
193
|
-
content: [
|
|
194
|
-
{
|
|
195
|
-
type: "text",
|
|
196
|
-
text: `Global option set '${optionSetName}':\n\n${optionSetStr}`,
|
|
197
|
-
},
|
|
198
|
-
],
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
catch (error) {
|
|
202
|
-
console.error("Error getting global option set:", error);
|
|
203
|
-
return {
|
|
204
|
-
content: [
|
|
205
|
-
{
|
|
206
|
-
type: "text",
|
|
207
|
-
text: `Failed to get global option set: ${error.message}`,
|
|
208
|
-
},
|
|
209
|
-
],
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
// Note: get-record and query-records tools removed
|
|
214
|
-
// These are data CRUD operations and belong in powerplatform-data package
|
|
215
|
-
server.tool("get-plugin-assemblies", "Get a list of all plugin assemblies in the environment", {
|
|
216
|
-
includeManaged: z.boolean().optional().describe("Include managed assemblies (default: false)"),
|
|
217
|
-
maxRecords: z.number().optional().describe("Maximum number of assemblies to return (default: 100)"),
|
|
218
|
-
}, async ({ includeManaged, maxRecords }) => {
|
|
219
|
-
try {
|
|
220
|
-
const service = getPowerPlatformService();
|
|
221
|
-
const result = await service.getPluginAssemblies(includeManaged || false, maxRecords || 100);
|
|
222
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
223
|
-
return {
|
|
224
|
-
content: [
|
|
225
|
-
{
|
|
226
|
-
type: "text",
|
|
227
|
-
text: `Found ${result.totalCount} plugin assemblies:\n\n${resultStr}`,
|
|
228
|
-
},
|
|
229
|
-
],
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
catch (error) {
|
|
233
|
-
console.error("Error getting plugin assemblies:", error);
|
|
234
|
-
return {
|
|
235
|
-
content: [
|
|
236
|
-
{
|
|
237
|
-
type: "text",
|
|
238
|
-
text: `Failed to get plugin assemblies: ${error.message}`,
|
|
239
|
-
},
|
|
240
|
-
],
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
server.tool("get-plugin-asm-full", "Get comprehensive information about a plugin assembly including all types, steps, images, and validation", {
|
|
245
|
-
assemblyName: z.string().describe("The name of the plugin assembly"),
|
|
246
|
-
includeDisabled: z.boolean().optional().describe("Include disabled steps (default: false)"),
|
|
247
|
-
}, async ({ assemblyName, includeDisabled }) => {
|
|
248
|
-
try {
|
|
249
|
-
const service = getPowerPlatformService();
|
|
250
|
-
const result = await service.getPluginAssemblyComplete(assemblyName, includeDisabled || false);
|
|
251
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
252
|
-
return {
|
|
253
|
-
content: [
|
|
254
|
-
{
|
|
255
|
-
type: "text",
|
|
256
|
-
text: `Plugin assembly '${assemblyName}' complete information:\n\n${resultStr}`,
|
|
257
|
-
},
|
|
258
|
-
],
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
catch (error) {
|
|
262
|
-
console.error("Error getting plugin assembly:", error);
|
|
263
|
-
return {
|
|
264
|
-
content: [
|
|
265
|
-
{
|
|
266
|
-
type: "text",
|
|
267
|
-
text: `Failed to get plugin assembly: ${error.message}`,
|
|
268
|
-
},
|
|
269
|
-
],
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
server.tool("get-entity-plugins", "Get all plugins that execute on a specific entity, organized by message and execution order", {
|
|
274
|
-
entityName: z.string().describe("The logical name of the entity"),
|
|
275
|
-
messageFilter: z.string().optional().describe("Filter by message name (e.g., 'Create', 'Update', 'Delete')"),
|
|
276
|
-
includeDisabled: z.boolean().optional().describe("Include disabled steps (default: false)"),
|
|
277
|
-
}, async ({ entityName, messageFilter, includeDisabled }) => {
|
|
278
|
-
try {
|
|
279
|
-
const service = getPowerPlatformService();
|
|
280
|
-
const result = await service.getEntityPluginPipeline(entityName, messageFilter, includeDisabled || false);
|
|
281
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
282
|
-
return {
|
|
283
|
-
content: [
|
|
284
|
-
{
|
|
285
|
-
type: "text",
|
|
286
|
-
text: `Plugin pipeline for entity '${entityName}':\n\n${resultStr}`,
|
|
287
|
-
},
|
|
288
|
-
],
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
catch (error) {
|
|
292
|
-
console.error("Error getting entity plugin pipeline:", error);
|
|
293
|
-
return {
|
|
294
|
-
content: [
|
|
295
|
-
{
|
|
296
|
-
type: "text",
|
|
297
|
-
text: `Failed to get entity plugin pipeline: ${error.message}`,
|
|
298
|
-
},
|
|
299
|
-
],
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
});
|
|
303
|
-
server.tool("get-plugin-trace-logs", "Query plugin trace logs with filtering and exception parsing", {
|
|
304
|
-
entityName: z.string().optional().describe("Filter by entity logical name"),
|
|
305
|
-
messageName: z.string().optional().describe("Filter by message name (e.g., 'Update')"),
|
|
306
|
-
correlationId: z.string().optional().describe("Filter by correlation ID"),
|
|
307
|
-
pluginStepId: z.string().optional().describe("Filter by specific step ID"),
|
|
308
|
-
exceptionOnly: z.boolean().optional().describe("Only return logs with exceptions (default: false)"),
|
|
309
|
-
hoursBack: z.number().optional().describe("How many hours back to search (default: 24)"),
|
|
310
|
-
maxRecords: z.number().optional().describe("Maximum number of logs to return (default: 50)"),
|
|
311
|
-
}, async ({ entityName, messageName, correlationId, pluginStepId, exceptionOnly, hoursBack, maxRecords }) => {
|
|
312
|
-
try {
|
|
313
|
-
const service = getPowerPlatformService();
|
|
314
|
-
const result = await service.getPluginTraceLogs({
|
|
315
|
-
entityName,
|
|
316
|
-
messageName,
|
|
317
|
-
correlationId,
|
|
318
|
-
pluginStepId,
|
|
319
|
-
exceptionOnly: exceptionOnly || false,
|
|
320
|
-
hoursBack: hoursBack || 24,
|
|
321
|
-
maxRecords: maxRecords || 50
|
|
322
|
-
});
|
|
323
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
324
|
-
return {
|
|
325
|
-
content: [
|
|
326
|
-
{
|
|
327
|
-
type: "text",
|
|
328
|
-
text: `Plugin trace logs (found ${result.totalCount}):\n\n${resultStr}`,
|
|
329
|
-
},
|
|
330
|
-
],
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
catch (error) {
|
|
334
|
-
console.error("Error getting plugin trace logs:", error);
|
|
335
|
-
return {
|
|
336
|
-
content: [
|
|
337
|
-
{
|
|
338
|
-
type: "text",
|
|
339
|
-
text: `Failed to get plugin trace logs: ${error.message}`,
|
|
340
|
-
},
|
|
341
|
-
],
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
server.tool("get-flows", "Get Power Automate cloud flows. By default excludes non-custom flows (Customer Insights CXP_, SYSTEM, Copilot for Sales) to show only custom flows. Use exclude* parameters to override. Returns summary info only - use get-flow-definition for full flow details.", {
|
|
346
|
-
activeOnly: z.boolean().optional().describe("Only return activated flows (default: false)"),
|
|
347
|
-
maxRecords: z.number().optional().describe("Maximum number of flows to return (default: 25)"),
|
|
348
|
-
excludeCustomerInsights: z.boolean().optional().describe("Exclude Customer Insights flows (CXP_ prefix) (default: true)"),
|
|
349
|
-
excludeSystem: z.boolean().optional().describe("Exclude SYSTEM-modified flows (default: true)"),
|
|
350
|
-
excludeCopilotSales: z.boolean().optional().describe("Exclude Copilot for Sales flows (default: true)"),
|
|
351
|
-
nameContains: z.string().optional().describe("Filter flows by name (case-insensitive contains)"),
|
|
352
|
-
}, async ({ activeOnly, maxRecords, excludeCustomerInsights, excludeSystem, excludeCopilotSales, nameContains }) => {
|
|
353
|
-
try {
|
|
354
|
-
const service = getPowerPlatformService();
|
|
355
|
-
const result = await service.getFlows({
|
|
356
|
-
activeOnly: activeOnly ?? false,
|
|
357
|
-
maxRecords: maxRecords ?? 25,
|
|
358
|
-
excludeCustomerInsights: excludeCustomerInsights ?? true,
|
|
359
|
-
excludeSystem: excludeSystem ?? true,
|
|
360
|
-
excludeCopilotSales: excludeCopilotSales ?? true,
|
|
361
|
-
nameContains,
|
|
362
|
-
});
|
|
363
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
364
|
-
let message = `Found ${result.totalCount} Power Automate flows`;
|
|
365
|
-
// Add exclusion summary if any filters applied
|
|
366
|
-
if (result.excluded.total > 0) {
|
|
367
|
-
const exclusions = [];
|
|
368
|
-
if (result.excluded.system > 0) {
|
|
369
|
-
exclusions.push(`${result.excluded.system} SYSTEM`);
|
|
370
|
-
}
|
|
371
|
-
if (result.excluded.copilotSales > 0) {
|
|
372
|
-
exclusions.push(`${result.excluded.copilotSales} Copilot for Sales`);
|
|
373
|
-
}
|
|
374
|
-
message += ` (excluded: ${exclusions.join(', ')})`;
|
|
375
|
-
}
|
|
376
|
-
if (result.filterApplied.excludeCustomerInsights) {
|
|
377
|
-
message += '\nNote: Customer Insights (CXP_) flows filtered server-side';
|
|
378
|
-
}
|
|
379
|
-
if (result.hasMore) {
|
|
380
|
-
message += `\n⚠️ More flows available - increase maxRecords (currently ${result.requestedMax}) to retrieve more`;
|
|
381
|
-
}
|
|
382
|
-
return {
|
|
383
|
-
content: [
|
|
384
|
-
{
|
|
385
|
-
type: "text",
|
|
386
|
-
text: `${message}:\n\n${resultStr}`,
|
|
387
|
-
},
|
|
388
|
-
],
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
catch (error) {
|
|
392
|
-
console.error("Error getting flows:", error);
|
|
393
|
-
return {
|
|
394
|
-
content: [
|
|
395
|
-
{
|
|
396
|
-
type: "text",
|
|
397
|
-
text: `Failed to get flows: ${error.message}`,
|
|
398
|
-
},
|
|
399
|
-
],
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
});
|
|
403
|
-
server.tool("search-workflows", "Search workflows (both classic workflows and Power Automate flows) with flexible filtering. Supports searching by name, entity, description, category, and state. Useful for finding documented workflows (search description for 'AUTO-DOCS'), finding workflows by entity, or locating specific workflows by name.", {
|
|
404
|
-
name: z.string().optional().describe("Filter by workflow name (case-insensitive partial match)"),
|
|
405
|
-
primaryEntity: z.string().optional().describe("Filter by primary entity logical name (e.g., 'sic_contactpractitionertype')"),
|
|
406
|
-
description: z.string().optional().describe("Search in description field (e.g., 'AUTO-DOCS:v1' for documented workflows)"),
|
|
407
|
-
category: z.number().optional().describe("Filter by category: 0=Classic Workflow, 5=Power Automate Flow"),
|
|
408
|
-
statecode: z.number().optional().describe("Filter by state: 0=Draft, 1=Activated, 2=Suspended"),
|
|
409
|
-
includeDescription: z.boolean().optional().describe("Include full description field in results (default: true)"),
|
|
410
|
-
maxResults: z.number().optional().describe("Maximum number of workflows to return (default: 50, max: 1000)"),
|
|
411
|
-
}, async ({ name, primaryEntity, description, category, statecode, includeDescription, maxResults }) => {
|
|
412
|
-
try {
|
|
413
|
-
const service = getPowerPlatformService();
|
|
414
|
-
// Validate maxResults
|
|
415
|
-
const validatedMaxResults = maxResults ? Math.min(maxResults, 1000) : 50;
|
|
416
|
-
const result = await service.searchWorkflows({
|
|
417
|
-
name,
|
|
418
|
-
primaryEntity,
|
|
419
|
-
description,
|
|
420
|
-
category,
|
|
421
|
-
statecode,
|
|
422
|
-
includeDescription: includeDescription ?? true,
|
|
423
|
-
maxResults: validatedMaxResults,
|
|
424
|
-
});
|
|
425
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
426
|
-
let message = `Found ${result.totalCount} workflow(s)`;
|
|
427
|
-
const filters = [];
|
|
428
|
-
if (name)
|
|
429
|
-
filters.push(`name contains '${name}'`);
|
|
430
|
-
if (primaryEntity)
|
|
431
|
-
filters.push(`entity = '${primaryEntity}'`);
|
|
432
|
-
if (description)
|
|
433
|
-
filters.push(`description contains '${description}'`);
|
|
434
|
-
if (category !== undefined)
|
|
435
|
-
filters.push(`category = ${category}`);
|
|
436
|
-
if (statecode !== undefined)
|
|
437
|
-
filters.push(`state = ${statecode}`);
|
|
438
|
-
if (filters.length > 0) {
|
|
439
|
-
message += ` matching: ${filters.join(', ')}`;
|
|
440
|
-
}
|
|
441
|
-
if (result.hasMore) {
|
|
442
|
-
message += `\n⚠️ More workflows available - increase maxResults (currently ${result.requestedMax}) to retrieve more`;
|
|
443
|
-
}
|
|
444
|
-
return {
|
|
445
|
-
content: [
|
|
446
|
-
{
|
|
447
|
-
type: "text",
|
|
448
|
-
text: `${message}:\n\n${resultStr}`,
|
|
449
|
-
},
|
|
450
|
-
],
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
catch (error) {
|
|
454
|
-
console.error("Error searching workflows:", error);
|
|
455
|
-
return {
|
|
456
|
-
content: [
|
|
457
|
-
{
|
|
458
|
-
type: "text",
|
|
459
|
-
text: `Failed to search workflows: ${error.message}`,
|
|
460
|
-
},
|
|
461
|
-
],
|
|
462
|
-
isError: true
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
});
|
|
466
|
-
server.tool("get-flow-definition", "Get the definition of a specific Power Automate flow. Use summary=true to get a parsed summary (trigger, actions, connectors) instead of the full JSON definition.", {
|
|
467
|
-
flowId: z.string().describe("The GUID of the flow (workflowid)"),
|
|
468
|
-
summary: z.boolean().optional().describe("Return parsed summary instead of full definition (default: false). Recommended for initial analysis to reduce response size."),
|
|
469
|
-
}, async ({ flowId, summary }) => {
|
|
470
|
-
try {
|
|
471
|
-
const service = getPowerPlatformService();
|
|
472
|
-
const result = await service.getFlowDefinition(flowId, summary || false);
|
|
473
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
474
|
-
let message = `Flow definition for '${result.name}'`;
|
|
475
|
-
if (summary) {
|
|
476
|
-
message += ' (summary mode)';
|
|
477
|
-
}
|
|
478
|
-
return {
|
|
479
|
-
content: [
|
|
480
|
-
{
|
|
481
|
-
type: "text",
|
|
482
|
-
text: `${message}:\n\n${resultStr}`,
|
|
483
|
-
},
|
|
484
|
-
],
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
catch (error) {
|
|
488
|
-
console.error("Error getting flow definition:", error);
|
|
489
|
-
return {
|
|
490
|
-
content: [
|
|
491
|
-
{
|
|
492
|
-
type: "text",
|
|
493
|
-
text: `Failed to get flow definition: ${error.message}`,
|
|
494
|
-
},
|
|
495
|
-
],
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
});
|
|
499
|
-
server.tool("get-flow-runs", "Get the run history for a specific Power Automate flow using the Management API. Returns run status, timestamps, trigger info, and error details for failed runs. Use this to investigate flow failures during incident triage.", {
|
|
500
|
-
flowId: z.string().describe("The GUID of the flow (workflowid)"),
|
|
501
|
-
status: z.string().optional().describe("Filter by status: Succeeded, Failed, Running, Waiting, Cancelled"),
|
|
502
|
-
startedAfter: z.string().optional().describe("Only return runs started after this date (ISO 8601 format, e.g., '2026-01-21T00:00:00Z')"),
|
|
503
|
-
startedBefore: z.string().optional().describe("Only return runs started before this date (ISO 8601 format)"),
|
|
504
|
-
maxRecords: z.number().optional().describe("Maximum number of runs to return (default: 50, max: 250)"),
|
|
505
|
-
}, async ({ flowId, status, startedAfter, startedBefore, maxRecords }) => {
|
|
506
|
-
try {
|
|
507
|
-
const service = getPowerPlatformService();
|
|
508
|
-
const result = await service.getFlowRuns(flowId, {
|
|
509
|
-
status,
|
|
510
|
-
startedAfter,
|
|
511
|
-
startedBefore,
|
|
512
|
-
maxRecords: maxRecords || 50,
|
|
513
|
-
});
|
|
514
|
-
// Calculate success/failure stats
|
|
515
|
-
const stats = (result.runs || []).reduce((acc, run) => {
|
|
516
|
-
if (run.status === 'Succeeded')
|
|
517
|
-
acc.succeeded++;
|
|
518
|
-
else if (run.status === 'Failed' || run.status === 'Faulted' || run.status === 'TimedOut')
|
|
519
|
-
acc.failed++;
|
|
520
|
-
else if (run.status === 'Running' || run.status === 'Waiting')
|
|
521
|
-
acc.inProgress++;
|
|
522
|
-
else if (run.status === 'Cancelled')
|
|
523
|
-
acc.cancelled++;
|
|
524
|
-
else
|
|
525
|
-
acc.other++;
|
|
526
|
-
return acc;
|
|
527
|
-
}, { succeeded: 0, failed: 0, inProgress: 0, cancelled: 0, other: 0 });
|
|
528
|
-
// Build summary for failed runs
|
|
529
|
-
const failedRuns = result.runs.filter((r) => r.status === 'Failed' || r.error);
|
|
530
|
-
const failedSummary = failedRuns.length > 0
|
|
531
|
-
? `\n\nFailed Runs (${failedRuns.length}):\n` + failedRuns.map((r) => ` - ${r.runId}: ${r.error?.message || 'Unknown error'} (${r.startTime})`).join('\n')
|
|
532
|
-
: '';
|
|
533
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
534
|
-
return {
|
|
535
|
-
content: [
|
|
536
|
-
{
|
|
537
|
-
type: "text",
|
|
538
|
-
text: `Found ${result.totalCount} flow runs for flow ${flowId}${result.hasMore ? ' (more available)' : ''}:\n\nStats:\n- Succeeded: ${stats.succeeded}\n- Failed: ${stats.failed}\n- In Progress: ${stats.inProgress}\n- Cancelled: ${stats.cancelled}\n- Other: ${stats.other}${failedSummary}\n\nFilters Applied: ${JSON.stringify(result.filterApplied)}\n\n${resultStr}`,
|
|
539
|
-
},
|
|
540
|
-
],
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
catch (error) {
|
|
544
|
-
console.error("Error getting flow runs:", error);
|
|
545
|
-
return {
|
|
546
|
-
content: [
|
|
547
|
-
{
|
|
548
|
-
type: "text",
|
|
549
|
-
text: `Failed to get flow runs: ${error.message}`,
|
|
550
|
-
},
|
|
551
|
-
],
|
|
552
|
-
isError: true,
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
});
|
|
556
|
-
server.tool("get-flow-run-details", "Get detailed action-level execution information for a specific Power Automate flow run to verify which business logic steps were executed", {
|
|
557
|
-
flowId: z.string().describe("The GUID of the flow (workflowid)"),
|
|
558
|
-
runId: z.string().describe("The GUID of the flow run (flowrunid) - get this from get-flow-runs tool"),
|
|
559
|
-
}, async ({ flowId, runId }) => {
|
|
560
|
-
try {
|
|
561
|
-
const service = getPowerPlatformService();
|
|
562
|
-
const result = await service.getFlowRunDetails(flowId, runId);
|
|
563
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
564
|
-
// Build a summary of action execution
|
|
565
|
-
const r = result;
|
|
566
|
-
const actionsList = Object.entries(r.actions || {})
|
|
567
|
-
.map(([name, action]) => {
|
|
568
|
-
const statusIcon = action.status === 'Succeeded' ? '✓' : action.status === 'Failed' ? '✗' : action.status === 'Skipped' ? '⊘' : '?';
|
|
569
|
-
return ` ${statusIcon} ${name}: ${action.status}${action.error ? ' - ' + JSON.stringify(action.error) : ''}`;
|
|
570
|
-
})
|
|
571
|
-
.join('\n');
|
|
572
|
-
return {
|
|
573
|
-
content: [
|
|
574
|
-
{
|
|
575
|
-
type: "text",
|
|
576
|
-
text: `Flow Run Details for ${flowId}/${runId}:\n\nOverall Status: ${r.status}\nStart Time: ${r.startTime}\nEnd Time: ${r.endTime}\n\nTrigger:\n Name: ${r.trigger?.name}\n Status: ${r.trigger?.status}\n\nActions Summary:\n- Total: ${r.actionsSummary?.total}\n- Succeeded: ${r.actionsSummary?.succeeded}\n- Failed: ${r.actionsSummary?.failed}\n- Skipped: ${r.actionsSummary?.skipped}\n- Other: ${r.actionsSummary?.other}\n\nAction Execution Details:\n${actionsList}\n\nFull JSON Response:\n${resultStr}`,
|
|
577
|
-
},
|
|
578
|
-
],
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
catch (error) {
|
|
582
|
-
console.error("Error getting flow run details:", error);
|
|
583
|
-
return {
|
|
584
|
-
content: [
|
|
585
|
-
{
|
|
586
|
-
type: "text",
|
|
587
|
-
text: `Failed to get flow run details: ${error.message}`,
|
|
588
|
-
},
|
|
589
|
-
],
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
});
|
|
593
|
-
server.tool("get-workflows", "Get a list of classic Dynamics workflows in the environment. Returns summary info only - use get-workflow-definition for full XAML.", {
|
|
594
|
-
activeOnly: z.boolean().optional().describe("Only return activated workflows (default: false)"),
|
|
595
|
-
maxRecords: z.number().optional().describe("Maximum number of workflows to return (default: 25)"),
|
|
596
|
-
}, async ({ activeOnly, maxRecords }) => {
|
|
597
|
-
try {
|
|
598
|
-
const service = getPowerPlatformService();
|
|
599
|
-
const result = await service.getWorkflows(activeOnly || false, maxRecords || 25);
|
|
600
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
601
|
-
let message = `Found ${result.totalCount} classic Dynamics workflows`;
|
|
602
|
-
if (result.hasMore) {
|
|
603
|
-
message += `\n⚠️ More workflows available - increase maxRecords (currently ${result.requestedMax}) to retrieve more`;
|
|
604
|
-
}
|
|
605
|
-
return {
|
|
606
|
-
content: [
|
|
607
|
-
{
|
|
608
|
-
type: "text",
|
|
609
|
-
text: `${message}:\n\n${resultStr}`,
|
|
610
|
-
},
|
|
611
|
-
],
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
catch (error) {
|
|
615
|
-
console.error("Error getting workflows:", error);
|
|
616
|
-
return {
|
|
617
|
-
content: [
|
|
618
|
-
{
|
|
619
|
-
type: "text",
|
|
620
|
-
text: `Failed to get workflows: ${error.message}`,
|
|
621
|
-
},
|
|
622
|
-
],
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
});
|
|
626
|
-
server.tool("get-workflow-definition", "Get the definition of a specific classic Dynamics workflow. Use summary=true to get a parsed summary (activities, conditions, email sends) instead of raw XAML.", {
|
|
627
|
-
workflowId: z.string().describe("The GUID of the workflow (workflowid)"),
|
|
628
|
-
summary: z.boolean().optional().describe("Return parsed summary instead of full XAML (default: false). Recommended for initial analysis to reduce response size."),
|
|
629
|
-
}, async ({ workflowId, summary }) => {
|
|
630
|
-
try {
|
|
631
|
-
const service = getPowerPlatformService();
|
|
632
|
-
const result = await service.getWorkflowDefinition(workflowId, summary || false);
|
|
633
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
634
|
-
let message = `Workflow definition for '${result.name}'`;
|
|
635
|
-
if (summary) {
|
|
636
|
-
message += ' (summary mode)';
|
|
637
|
-
}
|
|
638
|
-
return {
|
|
639
|
-
content: [
|
|
640
|
-
{
|
|
641
|
-
type: "text",
|
|
642
|
-
text: `${message}:\n\n${resultStr}`,
|
|
643
|
-
},
|
|
644
|
-
],
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
catch (error) {
|
|
648
|
-
console.error("Error getting workflow definition:", error);
|
|
649
|
-
return {
|
|
650
|
-
content: [
|
|
651
|
-
{
|
|
652
|
-
type: "text",
|
|
653
|
-
text: `Failed to get workflow definition: ${error.message}`,
|
|
654
|
-
},
|
|
655
|
-
],
|
|
656
|
-
};
|
|
657
|
-
}
|
|
658
|
-
});
|
|
659
|
-
server.tool("get-business-rules", "Get a list of all business rules in the environment (read-only for troubleshooting)", {
|
|
660
|
-
activeOnly: z.boolean().optional().describe("Only return activated business rules (default: false)"),
|
|
661
|
-
maxRecords: z.number().optional().describe("Maximum number of business rules to return (default: 100)"),
|
|
662
|
-
}, async ({ activeOnly, maxRecords }) => {
|
|
663
|
-
try {
|
|
664
|
-
const service = getPowerPlatformService();
|
|
665
|
-
const result = await service.getBusinessRules(activeOnly || false, maxRecords || 100);
|
|
666
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
667
|
-
return {
|
|
668
|
-
content: [
|
|
669
|
-
{
|
|
670
|
-
type: "text",
|
|
671
|
-
text: `Found ${result.totalCount} business rules:\n\n${resultStr}`,
|
|
672
|
-
},
|
|
673
|
-
],
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
catch (error) {
|
|
677
|
-
console.error("Error getting business rules:", error);
|
|
678
|
-
return {
|
|
679
|
-
content: [
|
|
680
|
-
{
|
|
681
|
-
type: "text",
|
|
682
|
-
text: `Failed to get business rules: ${error.message}`,
|
|
683
|
-
},
|
|
684
|
-
],
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
});
|
|
688
|
-
server.tool("get-business-rule", "Get the complete definition of a specific business rule including its XAML (read-only for troubleshooting)", {
|
|
689
|
-
workflowId: z.string().describe("The GUID of the business rule (workflowid)"),
|
|
690
|
-
}, async ({ workflowId }) => {
|
|
691
|
-
try {
|
|
692
|
-
const service = getPowerPlatformService();
|
|
693
|
-
const result = await service.getBusinessRule(workflowId);
|
|
694
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
695
|
-
return {
|
|
696
|
-
content: [
|
|
697
|
-
{
|
|
698
|
-
type: "text",
|
|
699
|
-
text: `Business rule definition for '${result.name}':\n\n${resultStr}`,
|
|
700
|
-
},
|
|
701
|
-
],
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
catch (error) {
|
|
705
|
-
console.error("Error getting business rule:", error);
|
|
706
|
-
return {
|
|
707
|
-
content: [
|
|
708
|
-
{
|
|
709
|
-
type: "text",
|
|
710
|
-
text: `Failed to get business rule: ${error.message}`,
|
|
711
|
-
},
|
|
712
|
-
],
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
});
|
|
716
|
-
server.tool("get-apps", "Get all model-driven apps in the PowerPlatform environment", {
|
|
717
|
-
activeOnly: z.boolean().optional().describe("Only return active apps (default: false)"),
|
|
718
|
-
maxRecords: z.number().optional().describe("Maximum number of apps to return (default: 100)"),
|
|
719
|
-
includeUnpublished: z.boolean().optional().describe("Include unpublished/draft apps (default: true)"),
|
|
720
|
-
solutionUniqueName: z.string().optional().describe("Filter apps by solution unique name (e.g., 'MCPTestCore')"),
|
|
721
|
-
}, async ({ activeOnly, maxRecords, includeUnpublished, solutionUniqueName }) => {
|
|
722
|
-
try {
|
|
723
|
-
const service = getPowerPlatformService();
|
|
724
|
-
const result = await service.getApps(activeOnly || false, maxRecords || 100, includeUnpublished !== undefined ? includeUnpublished : true, solutionUniqueName);
|
|
725
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
726
|
-
return {
|
|
727
|
-
content: [
|
|
728
|
-
{
|
|
729
|
-
type: "text",
|
|
730
|
-
text: `Model-Driven Apps (found ${result.totalCount}):\n\n${resultStr}`,
|
|
731
|
-
},
|
|
732
|
-
],
|
|
733
|
-
};
|
|
734
|
-
}
|
|
735
|
-
catch (error) {
|
|
736
|
-
console.error("Error getting apps:", error);
|
|
737
|
-
return {
|
|
738
|
-
content: [
|
|
739
|
-
{
|
|
740
|
-
type: "text",
|
|
741
|
-
text: `Failed to get apps: ${error.message}`,
|
|
742
|
-
},
|
|
743
|
-
],
|
|
744
|
-
isError: true
|
|
745
|
-
};
|
|
746
|
-
}
|
|
747
|
-
});
|
|
748
|
-
server.tool("get-app", "Get detailed information about a specific model-driven app", {
|
|
749
|
-
appId: z.string().describe("The GUID of the app (appmoduleid)"),
|
|
750
|
-
}, async ({ appId }) => {
|
|
751
|
-
try {
|
|
752
|
-
const service = getPowerPlatformService();
|
|
753
|
-
const result = await service.getApp(appId);
|
|
754
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
755
|
-
return {
|
|
756
|
-
content: [
|
|
757
|
-
{
|
|
758
|
-
type: "text",
|
|
759
|
-
text: `Model-Driven App '${result.name}':\n\n${resultStr}`,
|
|
760
|
-
},
|
|
761
|
-
],
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
catch (error) {
|
|
765
|
-
console.error("Error getting app:", error);
|
|
766
|
-
return {
|
|
767
|
-
content: [
|
|
768
|
-
{
|
|
769
|
-
type: "text",
|
|
770
|
-
text: `Failed to get app: ${error.message}`,
|
|
771
|
-
},
|
|
772
|
-
],
|
|
773
|
-
isError: true
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
});
|
|
777
|
-
server.tool("get-app-components", "Get all components (entities, forms, views, sitemaps) in a model-driven app", {
|
|
778
|
-
appId: z.string().describe("The GUID of the app (appmoduleid)"),
|
|
779
|
-
}, async ({ appId }) => {
|
|
780
|
-
try {
|
|
781
|
-
const service = getPowerPlatformService();
|
|
782
|
-
const result = await service.getAppComponents(appId);
|
|
783
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
784
|
-
return {
|
|
785
|
-
content: [
|
|
786
|
-
{
|
|
787
|
-
type: "text",
|
|
788
|
-
text: `App Components (found ${result.totalCount}):\n\n${resultStr}`,
|
|
789
|
-
},
|
|
790
|
-
],
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
catch (error) {
|
|
794
|
-
console.error("Error getting app components:", error);
|
|
795
|
-
return {
|
|
796
|
-
content: [
|
|
797
|
-
{
|
|
798
|
-
type: "text",
|
|
799
|
-
text: `Failed to get app components: ${error.message}`,
|
|
800
|
-
},
|
|
801
|
-
],
|
|
802
|
-
isError: true
|
|
803
|
-
};
|
|
804
|
-
}
|
|
805
|
-
});
|
|
806
|
-
server.tool("get-app-sitemap", "Get the sitemap (navigation) configuration for a model-driven app", {
|
|
807
|
-
appId: z.string().describe("The GUID of the app (appmoduleid)"),
|
|
808
|
-
}, async ({ appId }) => {
|
|
809
|
-
try {
|
|
810
|
-
const service = getPowerPlatformService();
|
|
811
|
-
const result = await service.getAppSitemap(appId);
|
|
812
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
813
|
-
return {
|
|
814
|
-
content: [
|
|
815
|
-
{
|
|
816
|
-
type: "text",
|
|
817
|
-
text: `App Sitemap:\n\n${resultStr}`,
|
|
818
|
-
},
|
|
819
|
-
],
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
catch (error) {
|
|
823
|
-
console.error("Error getting app sitemap:", error);
|
|
824
|
-
return {
|
|
825
|
-
content: [
|
|
826
|
-
{
|
|
827
|
-
type: "text",
|
|
828
|
-
text: `Failed to get app sitemap: ${error.message}`,
|
|
829
|
-
},
|
|
830
|
-
],
|
|
831
|
-
isError: true
|
|
832
|
-
};
|
|
833
|
-
}
|
|
834
|
-
});
|
|
835
|
-
// Note: get-relationship-details tool removed
|
|
836
|
-
// This is a customization-related tool and belongs in powerplatform-customization package
|
|
837
|
-
server.tool("get-webres-deps", "Get all dependencies for a web resource", {
|
|
838
|
-
webResourceId: z.string().describe("Web resource ID (GUID)")
|
|
839
|
-
}, async ({ webResourceId }) => {
|
|
840
|
-
try {
|
|
841
|
-
const service = getPowerPlatformService();
|
|
842
|
-
const dependencies = await service.getWebResourceDependencies(webResourceId);
|
|
843
|
-
return {
|
|
844
|
-
content: [{ type: "text", text: `Web Resource Dependencies:\n${JSON.stringify(dependencies, null, 2)}` }]
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
catch (error) {
|
|
848
|
-
console.error("Error getting web resource dependencies:", error);
|
|
849
|
-
return { content: [{ type: "text", text: `Failed to get web resource dependencies: ${error.message}` }], isError: true };
|
|
850
|
-
}
|
|
851
|
-
});
|
|
852
|
-
server.tool("preview-unpublished", "Preview all components with unpublished customizations", {}, async () => {
|
|
853
|
-
try {
|
|
854
|
-
const service = getPowerPlatformService();
|
|
855
|
-
const unpublished = await service.previewUnpublishedChanges();
|
|
856
|
-
return {
|
|
857
|
-
content: [{ type: "text", text: `Unpublished Changes:\n${JSON.stringify(unpublished, null, 2)}` }]
|
|
858
|
-
};
|
|
859
|
-
}
|
|
860
|
-
catch (error) {
|
|
861
|
-
console.error("Error previewing unpublished changes:", error);
|
|
862
|
-
return { content: [{ type: "text", text: `Failed to preview unpublished changes: ${error.message}` }], isError: true };
|
|
863
|
-
}
|
|
864
|
-
});
|
|
865
|
-
// Note: validate-solution-integrity tool removed
|
|
866
|
-
// This is a customization-related tool and belongs in powerplatform-customization package
|
|
867
|
-
server.tool("get-forms", "Get all forms for an entity", {
|
|
868
|
-
entityLogicalName: z.string().describe("Entity logical name")
|
|
869
|
-
}, async ({ entityLogicalName }) => {
|
|
870
|
-
try {
|
|
871
|
-
const service = getPowerPlatformService();
|
|
872
|
-
const result = await service.getForms(entityLogicalName);
|
|
873
|
-
const forms = result.value || [];
|
|
874
|
-
const typeNames = { 2: "Main", 7: "QuickCreate", 8: "QuickView", 10: "Card" };
|
|
875
|
-
return {
|
|
876
|
-
content: [
|
|
877
|
-
{
|
|
878
|
-
type: "text",
|
|
879
|
-
text: `Found ${forms.length} form(s) for entity '${entityLogicalName}':\n\n` +
|
|
880
|
-
forms.map((f) => `- ${f.name} (${typeNames[f.type] || f.type})\n ID: ${f.formid}`).join('\n')
|
|
881
|
-
}
|
|
882
|
-
]
|
|
883
|
-
};
|
|
884
|
-
}
|
|
885
|
-
catch (error) {
|
|
886
|
-
console.error("Error getting forms:", error);
|
|
887
|
-
return {
|
|
888
|
-
content: [{ type: "text", text: `Failed to get forms: ${error.message}` }],
|
|
889
|
-
isError: true
|
|
890
|
-
};
|
|
891
|
-
}
|
|
892
|
-
});
|
|
893
|
-
server.tool("get-views", "Get all views for an entity", {
|
|
894
|
-
entityLogicalName: z.string().describe("Entity logical name")
|
|
895
|
-
}, async ({ entityLogicalName }) => {
|
|
896
|
-
try {
|
|
897
|
-
const service = getPowerPlatformService();
|
|
898
|
-
const result = await service.getViews(entityLogicalName);
|
|
899
|
-
const views = result.value || [];
|
|
900
|
-
return {
|
|
901
|
-
content: [
|
|
902
|
-
{
|
|
903
|
-
type: "text",
|
|
904
|
-
text: `Found ${views.length} view(s) for entity '${entityLogicalName}':\n\n` +
|
|
905
|
-
views.map((v) => `- ${v.name}${v.isdefault ? ' [DEFAULT]' : ''}\n ID: ${v.savedqueryid}\n Query Type: ${v.querytype}`).join('\n')
|
|
906
|
-
}
|
|
907
|
-
]
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
catch (error) {
|
|
911
|
-
console.error("Error getting views:", error);
|
|
912
|
-
return {
|
|
913
|
-
content: [{ type: "text", text: `Failed to get views: ${error.message}` }],
|
|
914
|
-
isError: true
|
|
915
|
-
};
|
|
916
|
-
}
|
|
917
|
-
});
|
|
918
|
-
server.tool("get-view-fetchxml", "Get the FetchXML query from a view", {
|
|
919
|
-
viewId: z.string().describe("View ID (GUID)")
|
|
920
|
-
}, async ({ viewId }) => {
|
|
921
|
-
try {
|
|
922
|
-
const service = getPowerPlatformService();
|
|
923
|
-
const view = await service.getViewFetchXml(viewId);
|
|
924
|
-
return {
|
|
925
|
-
content: [
|
|
926
|
-
{
|
|
927
|
-
type: "text",
|
|
928
|
-
text: `View: ${view.name}\nEntity: ${view.returnedtypecode}\nQuery Type: ${view.querytype}\n\nFetchXML:\n${view.fetchxml}`
|
|
929
|
-
}
|
|
930
|
-
]
|
|
931
|
-
};
|
|
932
|
-
}
|
|
933
|
-
catch (error) {
|
|
934
|
-
console.error("Error getting view FetchXML:", error);
|
|
935
|
-
return {
|
|
936
|
-
content: [{ type: "text", text: `Failed to get view FetchXML: ${error.message}` }],
|
|
937
|
-
isError: true
|
|
938
|
-
};
|
|
939
|
-
}
|
|
940
|
-
});
|
|
941
|
-
server.tool("get-web-resource", "Get a web resource by ID", {
|
|
942
|
-
webResourceId: z.string().describe("Web resource ID (GUID)")
|
|
943
|
-
}, async ({ webResourceId }) => {
|
|
944
|
-
try {
|
|
945
|
-
const service = getPowerPlatformService();
|
|
946
|
-
const result = await service.getWebResource(webResourceId);
|
|
947
|
-
return {
|
|
948
|
-
content: [
|
|
949
|
-
{
|
|
950
|
-
type: "text",
|
|
951
|
-
text: `Web Resource: ${result.name}\n` +
|
|
952
|
-
`Display Name: ${result.displayname}\n` +
|
|
953
|
-
`Type: ${result.webresourcetype}\n` +
|
|
954
|
-
`Description: ${result.description || 'N/A'}\n` +
|
|
955
|
-
`Modified: ${result.modifiedon}`
|
|
956
|
-
}
|
|
957
|
-
]
|
|
958
|
-
};
|
|
959
|
-
}
|
|
960
|
-
catch (error) {
|
|
961
|
-
console.error("Error getting web resource:", error);
|
|
962
|
-
return {
|
|
963
|
-
content: [{ type: "text", text: `Failed to get web resource: ${error.message}` }],
|
|
964
|
-
isError: true
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
});
|
|
968
|
-
server.tool("get-web-resources", "Get web resources by name pattern (optional)", {
|
|
969
|
-
nameFilter: z.string().optional().describe("Name filter (contains)")
|
|
970
|
-
}, async ({ nameFilter }) => {
|
|
971
|
-
try {
|
|
972
|
-
const service = getPowerPlatformService();
|
|
973
|
-
const result = await service.getWebResources(nameFilter);
|
|
974
|
-
const webResources = result.value || [];
|
|
975
|
-
return {
|
|
976
|
-
content: [
|
|
977
|
-
{
|
|
978
|
-
type: "text",
|
|
979
|
-
text: `Found ${webResources.length} web resource(s):\n\n` +
|
|
980
|
-
webResources.map((wr) => `- ${wr.name}\n Type: ${wr.webresourcetype}\n ID: ${wr.webresourceid}`).join('\n')
|
|
981
|
-
}
|
|
982
|
-
]
|
|
983
|
-
};
|
|
984
|
-
}
|
|
985
|
-
catch (error) {
|
|
986
|
-
console.error("Error getting web resources:", error);
|
|
987
|
-
return {
|
|
988
|
-
content: [{ type: "text", text: `Failed to get web resources: ${error.message}` }],
|
|
989
|
-
isError: true
|
|
990
|
-
};
|
|
991
|
-
}
|
|
992
|
-
});
|
|
993
|
-
server.tool("get-publishers", "Get all solution publishers (excluding system publishers)", {}, async () => {
|
|
994
|
-
try {
|
|
995
|
-
const service = getPowerPlatformService();
|
|
996
|
-
const result = await service.getPublishers();
|
|
997
|
-
const publishers = result.value || [];
|
|
998
|
-
return {
|
|
999
|
-
content: [
|
|
1000
|
-
{
|
|
1001
|
-
type: "text",
|
|
1002
|
-
text: `Found ${publishers.length} publisher(s):\n\n` +
|
|
1003
|
-
publishers.map((p) => `- ${p.friendlyname} (${p.uniquename})\n Prefix: ${p.customizationprefix}\n ID: ${p.publisherid}`).join('\n')
|
|
1004
|
-
}
|
|
1005
|
-
]
|
|
1006
|
-
};
|
|
1007
|
-
}
|
|
1008
|
-
catch (error) {
|
|
1009
|
-
console.error("Error getting publishers:", error);
|
|
1010
|
-
return {
|
|
1011
|
-
content: [{ type: "text", text: `Failed to get publishers: ${error.message}` }],
|
|
1012
|
-
isError: true
|
|
1013
|
-
};
|
|
1014
|
-
}
|
|
1015
|
-
});
|
|
1016
|
-
server.tool("get-solutions", "Get all visible solutions in the environment", {}, async () => {
|
|
1017
|
-
try {
|
|
1018
|
-
const service = getPowerPlatformService();
|
|
1019
|
-
const result = await service.getSolutions();
|
|
1020
|
-
const solutions = result.value || [];
|
|
1021
|
-
return {
|
|
1022
|
-
content: [
|
|
1023
|
-
{
|
|
1024
|
-
type: "text",
|
|
1025
|
-
text: `Found ${solutions.length} solution(s):\n\n` +
|
|
1026
|
-
solutions.map((s) => `- ${s.friendlyname} (${s.uniquename})\n Version: ${s.version}\n ID: ${s.solutionid}`).join('\n')
|
|
1027
|
-
}
|
|
1028
|
-
]
|
|
1029
|
-
};
|
|
1030
|
-
}
|
|
1031
|
-
catch (error) {
|
|
1032
|
-
console.error("Error getting solutions:", error);
|
|
1033
|
-
return {
|
|
1034
|
-
content: [{ type: "text", text: `Failed to get solutions: ${error.message}` }],
|
|
1035
|
-
isError: true
|
|
1036
|
-
};
|
|
1037
|
-
}
|
|
1038
|
-
});
|
|
1039
|
-
server.tool("get-solution-components", "List all components in a solution, grouped by component type. Returns component IDs, types, and behavior settings.", {
|
|
1040
|
-
solutionUniqueName: z.string().describe("The unique name of the solution to list components for"),
|
|
1041
|
-
}, async ({ solutionUniqueName }) => {
|
|
1042
|
-
try {
|
|
1043
|
-
const service = getPowerPlatformService();
|
|
1044
|
-
const result = await service.getSolutionComponents(solutionUniqueName);
|
|
1045
|
-
const components = result.value || [];
|
|
1046
|
-
const componentTypeNames = {
|
|
1047
|
-
1: 'Entity', 2: 'Attribute', 3: 'Relationship', 9: 'OptionSet',
|
|
1048
|
-
10: 'EntityRelationship', 13: 'ManagedProperty', 20: 'Policy',
|
|
1049
|
-
24: 'Privilege', 25: 'PrivilegeObjectTypeCode', 26: 'Role',
|
|
1050
|
-
29: 'Workflow', 31: 'Report', 36: 'Template', 37: 'Contract Template',
|
|
1051
|
-
38: 'Article Template', 39: 'Mail Merge Template', 44: 'Duplicate Rule',
|
|
1052
|
-
46: 'Duplicate Rule Condition', 48: 'Entity Map', 49: 'Attribute Map',
|
|
1053
|
-
59: 'SavedQuery', 60: 'Form', 61: 'WebResource', 62: 'SiteMap',
|
|
1054
|
-
63: 'Connection Role', 65: 'Hierarchy Rule', 66: 'Custom Control',
|
|
1055
|
-
70: 'FieldSecurityProfile', 71: 'FieldPermission', 80: 'AppModule',
|
|
1056
|
-
91: 'PluginAssembly', 92: 'PluginType', 93: 'SDKMessageProcessingStep',
|
|
1057
|
-
95: 'ServiceEndpoint', 150: 'RoutingRule', 152: 'SLA',
|
|
1058
|
-
154: 'ConvertRule', 300: 'Canvas App', 371: 'Connector',
|
|
1059
|
-
372: 'EnvironmentVariableDefinition', 373: 'EnvironmentVariableValue',
|
|
1060
|
-
380: 'AIModel', 381: 'AIConfiguration',
|
|
1061
|
-
};
|
|
1062
|
-
// Group by component type
|
|
1063
|
-
const grouped = {};
|
|
1064
|
-
for (const c of components) {
|
|
1065
|
-
const type = c.componenttype;
|
|
1066
|
-
if (!grouped[type])
|
|
1067
|
-
grouped[type] = [];
|
|
1068
|
-
grouped[type].push(c);
|
|
1069
|
-
}
|
|
1070
|
-
const lines = [`Found ${components.length} component(s) in solution '${solutionUniqueName}':\n`];
|
|
1071
|
-
for (const [type, items] of Object.entries(grouped)) {
|
|
1072
|
-
const typeName = componentTypeNames[Number(type)] || `Type ${type}`;
|
|
1073
|
-
lines.push(`\n${typeName} (${items.length}):`);
|
|
1074
|
-
for (const item of items) {
|
|
1075
|
-
lines.push(` - ${item.objectid} (behavior: ${item.rootcomponentbehavior ?? 'include subcomponents'})`);
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
return {
|
|
1079
|
-
content: [{ type: "text", text: lines.join('\n') }]
|
|
1080
|
-
};
|
|
1081
|
-
}
|
|
1082
|
-
catch (error) {
|
|
1083
|
-
console.error("Error getting solution components:", error);
|
|
1084
|
-
return {
|
|
1085
|
-
content: [{ type: "text", text: `Failed to get solution components: ${error.message}` }],
|
|
1086
|
-
isError: true
|
|
1087
|
-
};
|
|
1088
|
-
}
|
|
1089
|
-
});
|
|
1090
|
-
server.tool("check-dependencies", "Check dependencies before deleting a component", {
|
|
1091
|
-
componentId: z.string().describe("Component ID (GUID or MetadataId)"),
|
|
1092
|
-
componentType: z.number().describe("Component type: 1=Entity, 2=Attribute, 9=OptionSet, 24=Form, 26=SavedQuery, 29=Workflow, 60=SystemForm, 61=WebResource")
|
|
1093
|
-
}, async ({ componentId, componentType }) => {
|
|
1094
|
-
try {
|
|
1095
|
-
const service = getPowerPlatformService();
|
|
1096
|
-
const result = await service.checkDependencies(componentId, componentType);
|
|
1097
|
-
const dependencies = result.EntityCollection?.Entities || [];
|
|
1098
|
-
return {
|
|
1099
|
-
content: [
|
|
1100
|
-
{
|
|
1101
|
-
type: "text",
|
|
1102
|
-
text: `Found ${dependencies.length} dependenc${dependencies.length === 1 ? 'y' : 'ies'} for component '${componentId}':\n\n` +
|
|
1103
|
-
(dependencies.length > 0
|
|
1104
|
-
? dependencies.map((d) => `- ${d.Attributes?.dependentcomponentobjectid || 'Unknown'}\n Type: ${d.Attributes?.dependentcomponenttype || 'Unknown'}`).join('\n')
|
|
1105
|
-
: 'No dependencies found - component can be safely deleted')
|
|
1106
|
-
}
|
|
1107
|
-
]
|
|
1108
|
-
};
|
|
1109
|
-
}
|
|
1110
|
-
catch (error) {
|
|
1111
|
-
console.error("Error checking dependencies:", error);
|
|
1112
|
-
return {
|
|
1113
|
-
content: [{ type: "text", text: `Failed to check dependencies: ${error.message}` }],
|
|
1114
|
-
isError: true
|
|
1115
|
-
};
|
|
1116
|
-
}
|
|
1117
|
-
});
|
|
1118
|
-
// Note: check-entity-dependencies and get-entity-customization-info tools removed
|
|
1119
|
-
// These are customization-related and belong in powerplatform-customization package
|
|
1120
|
-
server.tool("validate-schema-name", "Validate a schema name against PowerPlatform naming rules", {
|
|
1121
|
-
schemaName: z.string().describe("Schema name to validate"),
|
|
1122
|
-
prefix: z.string().describe("Required customization prefix")
|
|
1123
|
-
}, async ({ schemaName, prefix }) => {
|
|
1124
|
-
try {
|
|
1125
|
-
const service = getPowerPlatformService();
|
|
1126
|
-
const result = service.validateSchemaName(schemaName, prefix);
|
|
1127
|
-
return {
|
|
1128
|
-
content: [
|
|
1129
|
-
{
|
|
1130
|
-
type: "text",
|
|
1131
|
-
text: `Schema Name Validation for '${schemaName}':\n\n` +
|
|
1132
|
-
`Valid: ${result.valid ? '✅' : '❌'}\n\n` +
|
|
1133
|
-
(result.errors.length > 0
|
|
1134
|
-
? `Errors:\n${result.errors.map(e => `- ${e}`).join('\n')}`
|
|
1135
|
-
: 'No validation errors')
|
|
1136
|
-
}
|
|
1137
|
-
]
|
|
1138
|
-
};
|
|
1139
|
-
}
|
|
1140
|
-
catch (error) {
|
|
1141
|
-
console.error("Error validating schema name:", error);
|
|
1142
|
-
return {
|
|
1143
|
-
content: [{ type: "text", text: `Failed to validate schema name: ${error.message}` }],
|
|
1144
|
-
isError: true
|
|
1145
|
-
};
|
|
1146
|
-
}
|
|
1147
|
-
});
|
|
1148
|
-
server.tool("check-delete-eligibility", "Check if a component can be safely deleted", {
|
|
1149
|
-
componentId: z.string().describe("Component ID (GUID or MetadataId)"),
|
|
1150
|
-
componentType: z.number().describe("Component type: 1=Entity, 2=Attribute, 9=OptionSet, 24=Form, 26=SavedQuery, 29=Workflow, 60=SystemForm, 61=WebResource")
|
|
1151
|
-
}, async ({ componentId, componentType }) => {
|
|
1152
|
-
try {
|
|
1153
|
-
const service = getPowerPlatformService();
|
|
1154
|
-
const result = await service.checkDeleteEligibility(componentId, componentType);
|
|
1155
|
-
return {
|
|
1156
|
-
content: [
|
|
1157
|
-
{
|
|
1158
|
-
type: "text",
|
|
1159
|
-
text: `Delete Eligibility for component '${componentId}':\n\n` +
|
|
1160
|
-
`Can Delete: ${result.canDelete ? '✅ Yes' : '❌ No'}\n` +
|
|
1161
|
-
`Dependencies: ${result.dependencies.length}\n\n` +
|
|
1162
|
-
(result.dependencies.length > 0
|
|
1163
|
-
? `Blocking Dependencies:\n${result.dependencies.map((d) => `- ${d.Attributes?.dependentcomponentobjectid || 'Unknown'}`).join('\n')}`
|
|
1164
|
-
: 'No blocking dependencies - component can be safely deleted')
|
|
1165
|
-
}
|
|
1166
|
-
]
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
catch (error) {
|
|
1170
|
-
console.error("Error checking delete eligibility:", error);
|
|
1171
|
-
return {
|
|
1172
|
-
content: [{ type: "text", text: `Failed to check delete eligibility: ${error.message}` }],
|
|
1173
|
-
isError: true
|
|
1174
|
-
};
|
|
1175
|
-
}
|
|
1176
|
-
});
|
|
1177
|
-
server.tool("validate-dataverse", "Validate Dataverse entities against internal best practices for column naming, prefixes, configuration, and entity icons. Checks schema name casing, lookup naming conventions, option set scope (all must be global), required columns, publisher prefix compliance, and entity icon assignment. Supports solution-based validation or explicit entity list with configurable time range filtering.", {
|
|
1178
|
-
solutionUniqueName: z.string().optional().describe("Solution unique name to validate (e.g., 'RTPICore', 'MCPTestCore'). Mutually exclusive with entityLogicalNames."),
|
|
1179
|
-
entityLogicalNames: z.array(z.string()).optional().describe("Explicit list of entity logical names to validate (e.g., ['sic_strikeaction', 'sic_application']). Mutually exclusive with solutionUniqueName."),
|
|
1180
|
-
publisherPrefix: z.string().describe("Publisher prefix to validate against (e.g., 'sic_'). Required."),
|
|
1181
|
-
recentDays: z.number().optional().describe("Only validate columns created in the last N days. Set to 0 to validate all columns regardless of age. Default: 30."),
|
|
1182
|
-
includeRefDataTables: z.boolean().optional().describe("Include RefData tables (schema starts with prefix + 'ref_') in validation. Default: true."),
|
|
1183
|
-
rules: z.array(z.string()).optional().describe("Specific rules to validate: 'prefix', 'lowercase', 'lookup', 'optionset', 'required-column', 'entity-icon'. Default: all rules."),
|
|
1184
|
-
maxEntities: z.number().optional().describe("Maximum number of entities to validate (safety limit). Default: 0 (unlimited)."),
|
|
1185
|
-
requiredColumns: z.array(z.string()).optional().describe("List of required column schema names to check for (without prefix). Use '{prefix}' placeholder which will be replaced with publisherPrefix at runtime. Default: ['{prefix}updatedbyprocess']. Example: ['{prefix}sqlcreatedon', '{prefix}sqlmodifiedon'] for SQL timestamp columns.")
|
|
1186
|
-
}, async ({ solutionUniqueName, entityLogicalNames, publisherPrefix, recentDays, includeRefDataTables, rules, maxEntities, requiredColumns }) => {
|
|
1187
|
-
try {
|
|
1188
|
-
// Validate input
|
|
1189
|
-
if (!solutionUniqueName && !entityLogicalNames) {
|
|
1190
|
-
return {
|
|
1191
|
-
content: [{ type: "text", text: "Error: Either solutionUniqueName or entityLogicalNames must be provided" }],
|
|
1192
|
-
isError: true
|
|
1193
|
-
};
|
|
1194
|
-
}
|
|
1195
|
-
if (solutionUniqueName && entityLogicalNames) {
|
|
1196
|
-
return {
|
|
1197
|
-
content: [{ type: "text", text: "Error: solutionUniqueName and entityLogicalNames are mutually exclusive" }],
|
|
1198
|
-
isError: true
|
|
1199
|
-
};
|
|
1200
|
-
}
|
|
1201
|
-
const service = getPowerPlatformService();
|
|
1202
|
-
const result = await service.validateBestPractices(solutionUniqueName, entityLogicalNames, publisherPrefix, recentDays ?? 30, includeRefDataTables ?? true, rules ?? ['prefix', 'lowercase', 'lookup', 'optionset', 'required-column', 'entity-icon'], maxEntities ?? 0, requiredColumns ?? ['{prefix}updatedbyprocess']);
|
|
1203
|
-
return {
|
|
1204
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1205
|
-
};
|
|
1206
|
-
}
|
|
1207
|
-
catch (error) {
|
|
1208
|
-
console.error("Error validating best practices:", error);
|
|
1209
|
-
return {
|
|
1210
|
-
content: [{ type: "text", text: `Failed to validate best practices: ${error.message}` }],
|
|
1211
|
-
isError: true
|
|
1212
|
-
};
|
|
1213
|
-
}
|
|
1214
|
-
});
|
|
1215
|
-
// Prompt registrations (10 prompts)
|
|
1216
|
-
server.prompt("entity-overview", "Get an overview of a Power Platform entity", {
|
|
1217
|
-
entityName: z.string().describe("The logical name of the entity")
|
|
1218
|
-
}, async (args) => {
|
|
1219
|
-
try {
|
|
1220
|
-
const service = getPowerPlatformService();
|
|
1221
|
-
const entityName = args.entityName;
|
|
1222
|
-
// Get entity metadata and key attributes
|
|
1223
|
-
const [rawMetadata, attributes] = await Promise.all([
|
|
1224
|
-
service.getEntityMetadata(entityName),
|
|
1225
|
-
service.getEntityAttributes(entityName)
|
|
1226
|
-
]);
|
|
1227
|
-
const metadata = rawMetadata;
|
|
1228
|
-
// Format entity details
|
|
1229
|
-
const entityDetails = `- Display Name: ${metadata.DisplayName?.UserLocalizedLabel?.Label || entityName}\n` +
|
|
1230
|
-
`- Schema Name: ${metadata.SchemaName}\n` +
|
|
1231
|
-
`- Description: ${metadata.Description?.UserLocalizedLabel?.Label || 'No description'}\n` +
|
|
1232
|
-
`- Primary Key: ${metadata.PrimaryIdAttribute}\n` +
|
|
1233
|
-
`- Primary Name: ${metadata.PrimaryNameAttribute}`;
|
|
1234
|
-
// Get key attributes
|
|
1235
|
-
const keyAttributes = attributes.value
|
|
1236
|
-
.map((attr) => {
|
|
1237
|
-
const attrType = attr["@odata.type"] || attr.odata?.type || "Unknown type";
|
|
1238
|
-
return `- ${attr.LogicalName}: ${attrType}`;
|
|
1239
|
-
})
|
|
1240
|
-
.join('\n');
|
|
1241
|
-
// Get relationships summary
|
|
1242
|
-
const relationships = await service.getEntityRelationships(entityName);
|
|
1243
|
-
const oneToManyCount = relationships.oneToMany.value.length;
|
|
1244
|
-
const manyToManyCount = relationships.manyToMany.value.length;
|
|
1245
|
-
const relationshipsSummary = `- One-to-Many Relationships: ${oneToManyCount}\n` +
|
|
1246
|
-
`- Many-to-Many Relationships: ${manyToManyCount}`;
|
|
1247
|
-
let promptContent = ENTITY_OVERVIEW(entityName);
|
|
1248
|
-
promptContent = promptContent
|
|
1249
|
-
.replace('{{entity_details}}', entityDetails)
|
|
1250
|
-
.replace('{{key_attributes}}', keyAttributes)
|
|
1251
|
-
.replace('{{relationships}}', relationshipsSummary);
|
|
1252
|
-
return {
|
|
1253
|
-
messages: [
|
|
1254
|
-
{
|
|
1255
|
-
role: "assistant",
|
|
1256
|
-
content: {
|
|
1257
|
-
type: "text",
|
|
1258
|
-
text: promptContent
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
]
|
|
1262
|
-
};
|
|
1263
|
-
}
|
|
1264
|
-
catch (error) {
|
|
1265
|
-
console.error(`Error handling entity-overview prompt:`, error);
|
|
1266
|
-
return {
|
|
1267
|
-
messages: [
|
|
1268
|
-
{
|
|
1269
|
-
role: "assistant",
|
|
1270
|
-
content: {
|
|
1271
|
-
type: "text",
|
|
1272
|
-
text: `Error: ${error.message}`
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
]
|
|
1276
|
-
};
|
|
1277
|
-
}
|
|
1278
|
-
});
|
|
1279
|
-
server.prompt("attribute-details", "Get detailed information about a specific entity attribute/field", {
|
|
1280
|
-
entityName: z.string().describe("The logical name of the entity"),
|
|
1281
|
-
attributeName: z.string().describe("The logical name of the attribute"),
|
|
1282
|
-
}, async (args) => {
|
|
1283
|
-
try {
|
|
1284
|
-
const service = getPowerPlatformService();
|
|
1285
|
-
const { entityName, attributeName } = args;
|
|
1286
|
-
// Get attribute details
|
|
1287
|
-
const attribute = await service.getEntityAttribute(entityName, attributeName);
|
|
1288
|
-
// Format attribute details
|
|
1289
|
-
const attrDetails = `- Display Name: ${attribute.DisplayName?.UserLocalizedLabel?.Label || attributeName}\n` +
|
|
1290
|
-
`- Description: ${attribute.Description?.UserLocalizedLabel?.Label || 'No description'}\n` +
|
|
1291
|
-
`- Type: ${attribute.AttributeType}\n` +
|
|
1292
|
-
`- Format: ${attribute.Format || 'N/A'}\n` +
|
|
1293
|
-
`- Is Required: ${attribute.RequiredLevel?.Value || 'No'}\n` +
|
|
1294
|
-
`- Is Searchable: ${attribute.IsValidForAdvancedFind || false}`;
|
|
1295
|
-
let promptContent = ATTRIBUTE_DETAILS(entityName, attributeName);
|
|
1296
|
-
promptContent = promptContent
|
|
1297
|
-
.replace('{{attribute_details}}', attrDetails)
|
|
1298
|
-
.replace('{{data_type}}', attribute.AttributeType)
|
|
1299
|
-
.replace('{{required}}', attribute.RequiredLevel?.Value || 'No')
|
|
1300
|
-
.replace('{{max_length}}', attribute.MaxLength || 'N/A');
|
|
1301
|
-
return {
|
|
1302
|
-
messages: [
|
|
1303
|
-
{
|
|
1304
|
-
role: "assistant",
|
|
1305
|
-
content: {
|
|
1306
|
-
type: "text",
|
|
1307
|
-
text: promptContent
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
]
|
|
1311
|
-
};
|
|
1312
|
-
}
|
|
1313
|
-
catch (error) {
|
|
1314
|
-
console.error(`Error handling attribute-details prompt:`, error);
|
|
1315
|
-
return {
|
|
1316
|
-
messages: [
|
|
1317
|
-
{
|
|
1318
|
-
role: "assistant",
|
|
1319
|
-
content: {
|
|
1320
|
-
type: "text",
|
|
1321
|
-
text: `Error: ${error.message}`
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
]
|
|
1325
|
-
};
|
|
1326
|
-
}
|
|
1327
|
-
});
|
|
1328
|
-
server.prompt("query-template", "Get a template for querying a Power Platform entity", {
|
|
1329
|
-
entityName: z.string().describe("The logical name of the entity"),
|
|
1330
|
-
}, async (args) => {
|
|
1331
|
-
try {
|
|
1332
|
-
const service = getPowerPlatformService();
|
|
1333
|
-
const entityName = args.entityName;
|
|
1334
|
-
// Get entity metadata to determine plural name
|
|
1335
|
-
const metadata = await service.getEntityMetadata(entityName);
|
|
1336
|
-
const entityNamePlural = metadata.EntitySetName;
|
|
1337
|
-
// Get a few important fields for the select example
|
|
1338
|
-
const attributes = await service.getEntityAttributes(entityName);
|
|
1339
|
-
const selectFields = attributes.value
|
|
1340
|
-
.filter((attr) => attr.IsValidForRead === true && !attr.AttributeOf)
|
|
1341
|
-
.slice(0, 5) // Just take first 5 for example
|
|
1342
|
-
.map((attr) => attr.LogicalName)
|
|
1343
|
-
.join(',');
|
|
1344
|
-
let promptContent = QUERY_TEMPLATE(entityNamePlural);
|
|
1345
|
-
promptContent = promptContent
|
|
1346
|
-
.replace('{{selected_fields}}', selectFields)
|
|
1347
|
-
.replace('{{filter_conditions}}', `${metadata.PrimaryNameAttribute} eq 'Example'`)
|
|
1348
|
-
.replace('{{order_by}}', `${metadata.PrimaryNameAttribute} asc`)
|
|
1349
|
-
.replace('{{max_records}}', '50');
|
|
1350
|
-
return {
|
|
1351
|
-
messages: [
|
|
1352
|
-
{
|
|
1353
|
-
role: "assistant",
|
|
1354
|
-
content: {
|
|
1355
|
-
type: "text",
|
|
1356
|
-
text: promptContent
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
]
|
|
1360
|
-
};
|
|
1361
|
-
}
|
|
1362
|
-
catch (error) {
|
|
1363
|
-
console.error(`Error handling query-template prompt:`, error);
|
|
1364
|
-
return {
|
|
1365
|
-
messages: [
|
|
1366
|
-
{
|
|
1367
|
-
role: "assistant",
|
|
1368
|
-
content: {
|
|
1369
|
-
type: "text",
|
|
1370
|
-
text: `Error: ${error.message}`
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
]
|
|
1374
|
-
};
|
|
1375
|
-
}
|
|
1376
|
-
});
|
|
1377
|
-
server.prompt("relationship-map", "Get a list of relationships for a Power Platform entity", {
|
|
1378
|
-
entityName: z.string().describe("The logical name of the entity"),
|
|
1379
|
-
}, async (args) => {
|
|
1380
|
-
try {
|
|
1381
|
-
const service = getPowerPlatformService();
|
|
1382
|
-
const entityName = args.entityName;
|
|
1383
|
-
// Get relationships
|
|
1384
|
-
const relationships = await service.getEntityRelationships(entityName);
|
|
1385
|
-
// Format one-to-many relationships where this entity is primary
|
|
1386
|
-
const oneToManyPrimary = relationships.oneToMany.value
|
|
1387
|
-
.filter((rel) => rel.ReferencingEntity !== entityName)
|
|
1388
|
-
.map((rel) => `- ${rel.SchemaName}: ${entityName} (1) → ${rel.ReferencingEntity} (N)`)
|
|
1389
|
-
.join('\n');
|
|
1390
|
-
// Format one-to-many relationships where this entity is related
|
|
1391
|
-
const oneToManyRelated = relationships.oneToMany.value
|
|
1392
|
-
.filter((rel) => rel.ReferencingEntity === entityName)
|
|
1393
|
-
.map((rel) => `- ${rel.SchemaName}: ${rel.ReferencedEntity} (1) → ${entityName} (N)`)
|
|
1394
|
-
.join('\n');
|
|
1395
|
-
// Format many-to-many relationships
|
|
1396
|
-
const manyToMany = relationships.manyToMany.value
|
|
1397
|
-
.map((rel) => {
|
|
1398
|
-
const otherEntity = rel.Entity1LogicalName === entityName ? rel.Entity2LogicalName : rel.Entity1LogicalName;
|
|
1399
|
-
return `- ${rel.SchemaName}: ${entityName} (N) ↔ ${otherEntity} (N)`;
|
|
1400
|
-
})
|
|
1401
|
-
.join('\n');
|
|
1402
|
-
let promptContent = RELATIONSHIP_MAP(entityName);
|
|
1403
|
-
promptContent = promptContent
|
|
1404
|
-
.replace('{{one_to_many_primary}}', oneToManyPrimary || 'None found')
|
|
1405
|
-
.replace('{{one_to_many_related}}', oneToManyRelated || 'None found')
|
|
1406
|
-
.replace('{{many_to_many}}', manyToMany || 'None found');
|
|
1407
|
-
return {
|
|
1408
|
-
messages: [
|
|
1409
|
-
{
|
|
1410
|
-
role: "assistant",
|
|
1411
|
-
content: {
|
|
1412
|
-
type: "text",
|
|
1413
|
-
text: promptContent
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
]
|
|
1417
|
-
};
|
|
1418
|
-
}
|
|
1419
|
-
catch (error) {
|
|
1420
|
-
console.error(`Error handling relationship-map prompt:`, error);
|
|
1421
|
-
return {
|
|
1422
|
-
messages: [
|
|
1423
|
-
{
|
|
1424
|
-
role: "assistant",
|
|
1425
|
-
content: {
|
|
1426
|
-
type: "text",
|
|
1427
|
-
text: `Error: ${error.message}`
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
]
|
|
1431
|
-
};
|
|
1432
|
-
}
|
|
1433
|
-
});
|
|
1434
|
-
server.prompt("plugin-deployment-report", "Generate a comprehensive deployment report for a plugin assembly", {
|
|
1435
|
-
assemblyName: z.string().describe("The name of the plugin assembly"),
|
|
1436
|
-
}, async (args) => {
|
|
1437
|
-
try {
|
|
1438
|
-
const service = getPowerPlatformService();
|
|
1439
|
-
const result = await service.getPluginAssemblyComplete(args.assemblyName, false);
|
|
1440
|
-
const assembly = result.assembly;
|
|
1441
|
-
// Build markdown report
|
|
1442
|
-
let report = `# Plugin Deployment Report: ${assembly.name}\n\n`;
|
|
1443
|
-
report += `## Assembly Information\n`;
|
|
1444
|
-
report += `- **Version**: ${assembly.version}\n`;
|
|
1445
|
-
report += `- **Isolation Mode**: ${assembly.isolationmode === 2 ? 'Sandbox' : 'None'}\n`;
|
|
1446
|
-
report += `- **Source**: ${assembly.sourcetype === 0 ? 'Database' : assembly.sourcetype === 1 ? 'Disk' : 'GAC'}\n`;
|
|
1447
|
-
report += `- **Last Modified**: ${assembly.modifiedon} by ${assembly.modifiedby?.fullname || 'Unknown'}\n`;
|
|
1448
|
-
report += `- **Managed**: ${assembly.ismanaged ? 'Yes' : 'No'}\n\n`;
|
|
1449
|
-
report += `## Plugin Types (${result.pluginTypes.length} total)\n`;
|
|
1450
|
-
result.pluginTypes.forEach((type, idx) => {
|
|
1451
|
-
report += `${idx + 1}. ${type.typename}\n`;
|
|
1452
|
-
});
|
|
1453
|
-
report += `\n`;
|
|
1454
|
-
report += `## Registered Steps (${result.steps.length} total)\n\n`;
|
|
1455
|
-
result.steps.forEach((step) => {
|
|
1456
|
-
const stageName = step.stage === 10 ? 'PreValidation' : step.stage === 20 ? 'PreOperation' : 'PostOperation';
|
|
1457
|
-
const modeName = step.mode === 0 ? 'Sync' : 'Async';
|
|
1458
|
-
const status = step.statuscode === 1 ? '✓ Enabled' : '✗ Disabled';
|
|
1459
|
-
report += `### ${step.sdkmessageid?.name || 'Unknown'} - ${step.sdkmessagefilterid?.primaryobjecttypecode || 'None'} (${stageName}, ${modeName}, Rank ${step.rank})\n`;
|
|
1460
|
-
report += `- **Plugin**: ${step.plugintypeid?.typename || 'Unknown'}\n`;
|
|
1461
|
-
report += `- **Status**: ${status}\n`;
|
|
1462
|
-
report += `- **Filtering Attributes**: ${step.filteringattributes || '(none - runs on all changes)'}\n`;
|
|
1463
|
-
report += `- **Deployment**: ${step.supporteddeployment === 0 ? 'Server Only' : step.supporteddeployment === 1 ? 'Offline Only' : 'Both'}\n`;
|
|
1464
|
-
if (step.images.length > 0) {
|
|
1465
|
-
report += `- **Images**:\n`;
|
|
1466
|
-
step.images.forEach((img) => {
|
|
1467
|
-
const imageType = img.imagetype === 0 ? 'PreImage' : img.imagetype === 1 ? 'PostImage' : 'Both';
|
|
1468
|
-
report += ` - ${img.name} (${imageType}) → Attributes: ${img.attributes || '(all)'}\n`;
|
|
1469
|
-
});
|
|
1470
|
-
}
|
|
1471
|
-
else {
|
|
1472
|
-
report += `- **Images**: None\n`;
|
|
1473
|
-
}
|
|
1474
|
-
report += `\n`;
|
|
1475
|
-
});
|
|
1476
|
-
report += `## Validation Results\n\n`;
|
|
1477
|
-
if (result.validation.hasDisabledSteps) {
|
|
1478
|
-
report += `⚠ Some steps are disabled\n`;
|
|
1479
|
-
}
|
|
1480
|
-
else {
|
|
1481
|
-
report += `✓ All steps are enabled\n`;
|
|
1482
|
-
}
|
|
1483
|
-
if (result.validation.stepsWithoutFilteringAttributes.length > 0) {
|
|
1484
|
-
report += `⚠ Warning: ${result.validation.stepsWithoutFilteringAttributes.length} Update/Delete steps without filtering attributes:\n`;
|
|
1485
|
-
result.validation.stepsWithoutFilteringAttributes.forEach((name) => {
|
|
1486
|
-
report += ` - ${name}\n`;
|
|
1487
|
-
});
|
|
1488
|
-
}
|
|
1489
|
-
else {
|
|
1490
|
-
report += `✓ All Update/Delete steps have filtering attributes\n`;
|
|
1491
|
-
}
|
|
1492
|
-
if (result.validation.stepsWithoutImages.length > 0) {
|
|
1493
|
-
report += `⚠ Warning: ${result.validation.stepsWithoutImages.length} Update/Delete steps without images:\n`;
|
|
1494
|
-
result.validation.stepsWithoutImages.forEach((name) => {
|
|
1495
|
-
report += ` - ${name}\n`;
|
|
1496
|
-
});
|
|
1497
|
-
}
|
|
1498
|
-
if (result.validation.potentialIssues.length > 0) {
|
|
1499
|
-
report += `\n### Potential Issues\n`;
|
|
1500
|
-
result.validation.potentialIssues.forEach((issue) => {
|
|
1501
|
-
report += `- ${issue}\n`;
|
|
1502
|
-
});
|
|
1503
|
-
}
|
|
1504
|
-
return {
|
|
1505
|
-
messages: [
|
|
1506
|
-
{
|
|
1507
|
-
role: "assistant",
|
|
1508
|
-
content: {
|
|
1509
|
-
type: "text",
|
|
1510
|
-
text: report
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
]
|
|
1514
|
-
};
|
|
1515
|
-
}
|
|
1516
|
-
catch (error) {
|
|
1517
|
-
console.error(`Error generating plugin deployment report:`, error);
|
|
1518
|
-
return {
|
|
1519
|
-
messages: [
|
|
1520
|
-
{
|
|
1521
|
-
role: "assistant",
|
|
1522
|
-
content: {
|
|
1523
|
-
type: "text",
|
|
1524
|
-
text: `Error: ${error.message}`
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
]
|
|
1528
|
-
};
|
|
1529
|
-
}
|
|
1530
|
-
});
|
|
1531
|
-
server.prompt("entity-plugin-pipeline-report", "Generate a visual execution pipeline showing all plugins for an entity", {
|
|
1532
|
-
entityName: z.string().describe("The logical name of the entity"),
|
|
1533
|
-
messageFilter: z.string().optional().describe("Optional filter by message name"),
|
|
1534
|
-
}, async (args) => {
|
|
1535
|
-
try {
|
|
1536
|
-
const service = getPowerPlatformService();
|
|
1537
|
-
const result = await service.getEntityPluginPipeline(args.entityName, args.messageFilter, false);
|
|
1538
|
-
// Build markdown report
|
|
1539
|
-
let report = `# Plugin Pipeline: ${result.entity} Entity\n\n`;
|
|
1540
|
-
if (result.steps.length === 0) {
|
|
1541
|
-
report += `No plugins registered for this entity.\n`;
|
|
1542
|
-
}
|
|
1543
|
-
else {
|
|
1544
|
-
// Group by message
|
|
1545
|
-
result.messages.forEach((msg) => {
|
|
1546
|
-
report += `## ${msg.messageName} Message\n\n`;
|
|
1547
|
-
// PreValidation stage
|
|
1548
|
-
if (msg.stages.preValidation.length > 0) {
|
|
1549
|
-
report += `### Stage 1: PreValidation (Synchronous)\n`;
|
|
1550
|
-
msg.stages.preValidation.forEach((step, idx) => {
|
|
1551
|
-
report += `${idx + 1}. **[Rank ${step.rank}]** ${step.pluginType}\n`;
|
|
1552
|
-
report += ` - Assembly: ${step.assemblyName} v${step.assemblyVersion}\n`;
|
|
1553
|
-
report += ` - Filtering: ${step.filteringAttributes.join(', ') || '(all columns)'}\n`;
|
|
1554
|
-
if (step.hasPreImage || step.hasPostImage) {
|
|
1555
|
-
const images = [];
|
|
1556
|
-
if (step.hasPreImage)
|
|
1557
|
-
images.push('PreImage');
|
|
1558
|
-
if (step.hasPostImage)
|
|
1559
|
-
images.push('PostImage');
|
|
1560
|
-
report += ` - Images: ${images.join(', ')}\n`;
|
|
1561
|
-
}
|
|
1562
|
-
report += `\n`;
|
|
1563
|
-
});
|
|
1564
|
-
}
|
|
1565
|
-
// PreOperation stage
|
|
1566
|
-
if (msg.stages.preOperation.length > 0) {
|
|
1567
|
-
report += `### Stage 2: PreOperation (Synchronous)\n`;
|
|
1568
|
-
msg.stages.preOperation.forEach((step, idx) => {
|
|
1569
|
-
report += `${idx + 1}. **[Rank ${step.rank}]** ${step.pluginType}\n`;
|
|
1570
|
-
report += ` - Assembly: ${step.assemblyName} v${step.assemblyVersion}\n`;
|
|
1571
|
-
report += ` - Filtering: ${step.filteringAttributes.join(', ') || '(all columns)'}\n`;
|
|
1572
|
-
if (step.hasPreImage || step.hasPostImage) {
|
|
1573
|
-
const images = [];
|
|
1574
|
-
if (step.hasPreImage)
|
|
1575
|
-
images.push('PreImage');
|
|
1576
|
-
if (step.hasPostImage)
|
|
1577
|
-
images.push('PostImage');
|
|
1578
|
-
report += ` - Images: ${images.join(', ')}\n`;
|
|
1579
|
-
}
|
|
1580
|
-
report += `\n`;
|
|
1581
|
-
});
|
|
1582
|
-
}
|
|
1583
|
-
// PostOperation stage
|
|
1584
|
-
if (msg.stages.postOperation.length > 0) {
|
|
1585
|
-
report += `### Stage 3: PostOperation\n`;
|
|
1586
|
-
msg.stages.postOperation.forEach((step, idx) => {
|
|
1587
|
-
const mode = step.modeName === 'Asynchronous' ? ' (Async)' : ' (Sync)';
|
|
1588
|
-
report += `${idx + 1}. **[Rank ${step.rank}]** ${step.pluginType}${mode}\n`;
|
|
1589
|
-
report += ` - Assembly: ${step.assemblyName} v${step.assemblyVersion}\n`;
|
|
1590
|
-
report += ` - Filtering: ${step.filteringAttributes.join(', ') || '(all columns)'}\n`;
|
|
1591
|
-
if (step.hasPreImage || step.hasPostImage) {
|
|
1592
|
-
const images = [];
|
|
1593
|
-
if (step.hasPreImage)
|
|
1594
|
-
images.push('PreImage');
|
|
1595
|
-
if (step.hasPostImage)
|
|
1596
|
-
images.push('PostImage');
|
|
1597
|
-
report += ` - Images: ${images.join(', ')}\n`;
|
|
1598
|
-
}
|
|
1599
|
-
report += `\n`;
|
|
1600
|
-
});
|
|
1601
|
-
}
|
|
1602
|
-
report += `---\n\n`;
|
|
1603
|
-
});
|
|
1604
|
-
report += `## Execution Order\n\n`;
|
|
1605
|
-
report += `Plugins execute in this order:\n`;
|
|
1606
|
-
result.executionOrder.forEach((name, idx) => {
|
|
1607
|
-
report += `${idx + 1}. ${name}\n`;
|
|
1608
|
-
});
|
|
1609
|
-
}
|
|
1610
|
-
return {
|
|
1611
|
-
messages: [
|
|
1612
|
-
{
|
|
1613
|
-
role: "assistant",
|
|
1614
|
-
content: {
|
|
1615
|
-
type: "text",
|
|
1616
|
-
text: report
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
]
|
|
1620
|
-
};
|
|
1621
|
-
}
|
|
1622
|
-
catch (error) {
|
|
1623
|
-
console.error(`Error generating entity plugin pipeline report:`, error);
|
|
1624
|
-
return {
|
|
1625
|
-
messages: [
|
|
1626
|
-
{
|
|
1627
|
-
role: "assistant",
|
|
1628
|
-
content: {
|
|
1629
|
-
type: "text",
|
|
1630
|
-
text: `Error: ${error.message}`
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
]
|
|
1634
|
-
};
|
|
1635
|
-
}
|
|
1636
|
-
});
|
|
1637
|
-
server.prompt("flows-report", "Generate a comprehensive report of all Power Automate flows in the environment", {
|
|
1638
|
-
activeOnly: z.string().optional().describe("Set to 'true' to only include activated flows (default: false)"),
|
|
1639
|
-
}, async (args) => {
|
|
1640
|
-
try {
|
|
1641
|
-
const service = getPowerPlatformService();
|
|
1642
|
-
const result = await service.getFlows({
|
|
1643
|
-
activeOnly: args.activeOnly === 'true',
|
|
1644
|
-
maxRecords: 100,
|
|
1645
|
-
// For the flows report, include all flows (no filtering)
|
|
1646
|
-
excludeCustomerInsights: false,
|
|
1647
|
-
excludeSystem: false,
|
|
1648
|
-
excludeCopilotSales: false,
|
|
1649
|
-
});
|
|
1650
|
-
// Build markdown report
|
|
1651
|
-
let report = `# Power Automate Flows Report\n\n`;
|
|
1652
|
-
report += `**Total Flows**: ${result.totalCount}\n\n`;
|
|
1653
|
-
if (result.flows.length === 0) {
|
|
1654
|
-
report += `No flows found in this environment.\n`;
|
|
1655
|
-
}
|
|
1656
|
-
else {
|
|
1657
|
-
// Group by state
|
|
1658
|
-
const activeFlows = result.flows.filter((f) => f.state === 'Activated');
|
|
1659
|
-
const draftFlows = result.flows.filter((f) => f.state === 'Draft');
|
|
1660
|
-
const suspendedFlows = result.flows.filter((f) => f.state === 'Suspended');
|
|
1661
|
-
if (activeFlows.length > 0) {
|
|
1662
|
-
report += `## Active Flows (${activeFlows.length})\n\n`;
|
|
1663
|
-
activeFlows.forEach((flow) => {
|
|
1664
|
-
report += `### ${flow.name}\n`;
|
|
1665
|
-
report += `- **ID**: ${flow.workflowid}\n`;
|
|
1666
|
-
report += `- **Description**: ${flow.description || 'No description'}\n`;
|
|
1667
|
-
report += `- **Primary Entity**: ${flow.primaryEntity || 'None'}\n`;
|
|
1668
|
-
report += `- **Owner**: ${flow.owner}\n`;
|
|
1669
|
-
report += `- **Modified**: ${flow.modifiedOn} by ${flow.modifiedBy}\n`;
|
|
1670
|
-
report += `- **Managed**: ${flow.isManaged ? 'Yes' : 'No'}\n\n`;
|
|
1671
|
-
});
|
|
1672
|
-
}
|
|
1673
|
-
if (draftFlows.length > 0) {
|
|
1674
|
-
report += `## Draft Flows (${draftFlows.length})\n\n`;
|
|
1675
|
-
draftFlows.forEach((flow) => {
|
|
1676
|
-
report += `- **${flow.name}** (${flow.workflowid})\n`;
|
|
1677
|
-
report += ` - Owner: ${flow.owner}, Modified: ${flow.modifiedOn}\n`;
|
|
1678
|
-
});
|
|
1679
|
-
report += `\n`;
|
|
1680
|
-
}
|
|
1681
|
-
if (suspendedFlows.length > 0) {
|
|
1682
|
-
report += `## Suspended Flows (${suspendedFlows.length})\n\n`;
|
|
1683
|
-
suspendedFlows.forEach((flow) => {
|
|
1684
|
-
report += `- **${flow.name}** (${flow.workflowid})\n`;
|
|
1685
|
-
report += ` - Owner: ${flow.owner}, Modified: ${flow.modifiedOn}\n`;
|
|
1686
|
-
});
|
|
1687
|
-
report += `\n`;
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
return {
|
|
1691
|
-
messages: [
|
|
1692
|
-
{
|
|
1693
|
-
role: "assistant",
|
|
1694
|
-
content: {
|
|
1695
|
-
type: "text",
|
|
1696
|
-
text: report
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
]
|
|
1700
|
-
};
|
|
1701
|
-
}
|
|
1702
|
-
catch (error) {
|
|
1703
|
-
console.error(`Error generating flows report:`, error);
|
|
1704
|
-
return {
|
|
1705
|
-
messages: [
|
|
1706
|
-
{
|
|
1707
|
-
role: "assistant",
|
|
1708
|
-
content: {
|
|
1709
|
-
type: "text",
|
|
1710
|
-
text: `Error: ${error.message}`
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
|
-
]
|
|
1714
|
-
};
|
|
1715
|
-
}
|
|
1716
|
-
});
|
|
1717
|
-
server.prompt("workflows-report", "Generate a comprehensive report of all classic Dynamics workflows in the environment", {
|
|
1718
|
-
activeOnly: z.string().optional().describe("Set to 'true' to only include activated workflows (default: false)"),
|
|
1719
|
-
}, async (args) => {
|
|
1720
|
-
try {
|
|
1721
|
-
const service = getPowerPlatformService();
|
|
1722
|
-
const result = await service.getWorkflows(args.activeOnly === 'true', 100);
|
|
1723
|
-
// Build markdown report
|
|
1724
|
-
let report = `# Classic Dynamics Workflows Report\n\n`;
|
|
1725
|
-
report += `**Total Workflows**: ${result.totalCount}\n\n`;
|
|
1726
|
-
if (result.workflows.length === 0) {
|
|
1727
|
-
report += `No classic workflows found in this environment.\n`;
|
|
1728
|
-
}
|
|
1729
|
-
else {
|
|
1730
|
-
// Group by state
|
|
1731
|
-
const activeWorkflows = result.workflows.filter((w) => w.state === 'Activated');
|
|
1732
|
-
const draftWorkflows = result.workflows.filter((w) => w.state === 'Draft');
|
|
1733
|
-
const suspendedWorkflows = result.workflows.filter((w) => w.state === 'Suspended');
|
|
1734
|
-
if (activeWorkflows.length > 0) {
|
|
1735
|
-
report += `## Active Workflows (${activeWorkflows.length})\n\n`;
|
|
1736
|
-
activeWorkflows.forEach((workflow) => {
|
|
1737
|
-
report += `### ${workflow.name}\n`;
|
|
1738
|
-
report += `- **ID**: ${workflow.workflowid}\n`;
|
|
1739
|
-
report += `- **Description**: ${workflow.description || 'No description'}\n`;
|
|
1740
|
-
report += `- **Primary Entity**: ${workflow.primaryEntity || 'None'}\n`;
|
|
1741
|
-
report += `- **Mode**: ${workflow.mode}\n`;
|
|
1742
|
-
report += `- **Triggers**:\n`;
|
|
1743
|
-
if (workflow.triggerOnCreate)
|
|
1744
|
-
report += ` - Create\n`;
|
|
1745
|
-
if (workflow.triggerOnDelete)
|
|
1746
|
-
report += ` - Delete\n`;
|
|
1747
|
-
if (workflow.isOnDemand)
|
|
1748
|
-
report += ` - On Demand\n`;
|
|
1749
|
-
report += `- **Owner**: ${workflow.owner}\n`;
|
|
1750
|
-
report += `- **Modified**: ${workflow.modifiedOn} by ${workflow.modifiedBy}\n`;
|
|
1751
|
-
report += `- **Managed**: ${workflow.isManaged ? 'Yes' : 'No'}\n\n`;
|
|
1752
|
-
});
|
|
1753
|
-
}
|
|
1754
|
-
if (draftWorkflows.length > 0) {
|
|
1755
|
-
report += `## Draft Workflows (${draftWorkflows.length})\n\n`;
|
|
1756
|
-
draftWorkflows.forEach((workflow) => {
|
|
1757
|
-
report += `- **${workflow.name}** (${workflow.workflowid})\n`;
|
|
1758
|
-
report += ` - Entity: ${workflow.primaryEntity}, Owner: ${workflow.owner}\n`;
|
|
1759
|
-
});
|
|
1760
|
-
report += `\n`;
|
|
1761
|
-
}
|
|
1762
|
-
if (suspendedWorkflows.length > 0) {
|
|
1763
|
-
report += `## Suspended Workflows (${suspendedWorkflows.length})\n\n`;
|
|
1764
|
-
suspendedWorkflows.forEach((workflow) => {
|
|
1765
|
-
report += `- **${workflow.name}** (${workflow.workflowid})\n`;
|
|
1766
|
-
report += ` - Entity: ${workflow.primaryEntity}, Owner: ${workflow.owner}\n`;
|
|
1767
|
-
});
|
|
1768
|
-
report += `\n`;
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
return {
|
|
1772
|
-
messages: [
|
|
1773
|
-
{
|
|
1774
|
-
role: "assistant",
|
|
1775
|
-
content: {
|
|
1776
|
-
type: "text",
|
|
1777
|
-
text: report
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
]
|
|
1781
|
-
};
|
|
1782
|
-
}
|
|
1783
|
-
catch (error) {
|
|
1784
|
-
console.error(`Error generating workflows report:`, error);
|
|
1785
|
-
return {
|
|
1786
|
-
messages: [
|
|
1787
|
-
{
|
|
1788
|
-
role: "assistant",
|
|
1789
|
-
content: {
|
|
1790
|
-
type: "text",
|
|
1791
|
-
text: `Error: ${error.message}`
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
]
|
|
1795
|
-
};
|
|
1796
|
-
}
|
|
1797
|
-
});
|
|
1798
|
-
server.prompt("business-rules-report", "Generate a comprehensive report of all business rules in the environment (read-only for troubleshooting)", {
|
|
1799
|
-
activeOnly: z.string().optional().describe("Set to 'true' to only include activated business rules (default: false)"),
|
|
1800
|
-
}, async (args) => {
|
|
1801
|
-
try {
|
|
1802
|
-
const service = getPowerPlatformService();
|
|
1803
|
-
const result = await service.getBusinessRules(args.activeOnly === 'true', 100);
|
|
1804
|
-
// Build markdown report
|
|
1805
|
-
let report = `# Business Rules Report\n\n`;
|
|
1806
|
-
report += `**Total Business Rules**: ${result.totalCount}\n\n`;
|
|
1807
|
-
if (result.businessRules.length === 0) {
|
|
1808
|
-
report += `No business rules found in this environment.\n`;
|
|
1809
|
-
}
|
|
1810
|
-
else {
|
|
1811
|
-
// Group by state
|
|
1812
|
-
const activeRules = result.businessRules.filter((r) => r.state === 'Activated');
|
|
1813
|
-
const draftRules = result.businessRules.filter((r) => r.state === 'Draft');
|
|
1814
|
-
const suspendedRules = result.businessRules.filter((r) => r.state === 'Suspended');
|
|
1815
|
-
if (activeRules.length > 0) {
|
|
1816
|
-
report += `## Active Business Rules (${activeRules.length})\n\n`;
|
|
1817
|
-
activeRules.forEach((rule) => {
|
|
1818
|
-
report += `### ${rule.name}\n`;
|
|
1819
|
-
report += `- **ID**: ${rule.workflowid}\n`;
|
|
1820
|
-
report += `- **Description**: ${rule.description || 'No description'}\n`;
|
|
1821
|
-
report += `- **Primary Entity**: ${rule.primaryEntity || 'None'}\n`;
|
|
1822
|
-
report += `- **Owner**: ${rule.owner}\n`;
|
|
1823
|
-
report += `- **Modified**: ${rule.modifiedOn} by ${rule.modifiedBy}\n`;
|
|
1824
|
-
report += `- **Managed**: ${rule.isManaged ? 'Yes' : 'No'}\n\n`;
|
|
1825
|
-
});
|
|
1826
|
-
}
|
|
1827
|
-
if (draftRules.length > 0) {
|
|
1828
|
-
report += `## Draft Business Rules (${draftRules.length})\n\n`;
|
|
1829
|
-
draftRules.forEach((rule) => {
|
|
1830
|
-
report += `- **${rule.name}** (${rule.workflowid})\n`;
|
|
1831
|
-
report += ` - Entity: ${rule.primaryEntity}, Owner: ${rule.owner}\n`;
|
|
1832
|
-
});
|
|
1833
|
-
report += `\n`;
|
|
1834
|
-
}
|
|
1835
|
-
if (suspendedRules.length > 0) {
|
|
1836
|
-
report += `## Suspended Business Rules (${suspendedRules.length})\n\n`;
|
|
1837
|
-
suspendedRules.forEach((rule) => {
|
|
1838
|
-
report += `- **${rule.name}** (${rule.workflowid})\n`;
|
|
1839
|
-
report += ` - Entity: ${rule.primaryEntity}, Owner: ${rule.owner}\n`;
|
|
1840
|
-
});
|
|
1841
|
-
report += `\n`;
|
|
1842
|
-
}
|
|
1843
|
-
}
|
|
1844
|
-
report += `\n---\n\n`;
|
|
1845
|
-
report += `*Note: Business rules are read-only in this MCP server. Use the PowerPlatform UI to create or modify business rules.*\n`;
|
|
1846
|
-
return {
|
|
1847
|
-
messages: [
|
|
1848
|
-
{
|
|
1849
|
-
role: "assistant",
|
|
1850
|
-
content: {
|
|
1851
|
-
type: "text",
|
|
1852
|
-
text: report
|
|
1853
|
-
}
|
|
1854
|
-
}
|
|
1855
|
-
]
|
|
1856
|
-
};
|
|
1857
|
-
}
|
|
1858
|
-
catch (error) {
|
|
1859
|
-
console.error(`Error generating business rules report:`, error);
|
|
1860
|
-
return {
|
|
1861
|
-
messages: [
|
|
1862
|
-
{
|
|
1863
|
-
role: "assistant",
|
|
1864
|
-
content: {
|
|
1865
|
-
type: "text",
|
|
1866
|
-
text: `Error: ${error.message}`
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
]
|
|
1870
|
-
};
|
|
1871
|
-
}
|
|
1872
|
-
});
|
|
1873
|
-
server.prompt("app-overview", "Generate a comprehensive overview report for a model-driven app including components and configuration", {
|
|
1874
|
-
appId: z.string().describe("The GUID of the app (appmoduleid)"),
|
|
1875
|
-
}, async (args) => {
|
|
1876
|
-
try {
|
|
1877
|
-
const service = getPowerPlatformService();
|
|
1878
|
-
// Get app details, components, and sitemap
|
|
1879
|
-
const app = await service.getApp(args.appId);
|
|
1880
|
-
const components = await service.getAppComponents(args.appId);
|
|
1881
|
-
const sitemap = await service.getAppSitemap(args.appId);
|
|
1882
|
-
// Build markdown report
|
|
1883
|
-
let report = `# Model-Driven App Overview: ${app.name}\n\n`;
|
|
1884
|
-
// Basic Information
|
|
1885
|
-
report += `## Basic Information\n`;
|
|
1886
|
-
report += `- **App ID**: ${app.appmoduleid}\n`;
|
|
1887
|
-
report += `- **Unique Name**: ${app.uniquename}\n`;
|
|
1888
|
-
report += `- **Description**: ${app.description || 'No description'}\n`;
|
|
1889
|
-
report += `- **State**: ${app.state}\n`;
|
|
1890
|
-
report += `- **Navigation Type**: ${app.navigationtype}\n`;
|
|
1891
|
-
report += `- **Featured**: ${app.isfeatured ? 'Yes' : 'No'}\n`;
|
|
1892
|
-
report += `- **Default App**: ${app.isdefault ? 'Yes' : 'No'}\n`;
|
|
1893
|
-
report += `- **Published On**: ${app.publishedon || 'Not published'}\n`;
|
|
1894
|
-
report += `- **Created**: ${app.createdon} by ${app.createdBy || 'Unknown'}\n`;
|
|
1895
|
-
report += `- **Modified**: ${app.modifiedon} by ${app.modifiedBy || 'Unknown'}\n\n`;
|
|
1896
|
-
// Publisher Information
|
|
1897
|
-
if (app.publisher) {
|
|
1898
|
-
report += `## Publisher\n`;
|
|
1899
|
-
report += `- **Name**: ${app.publisher.friendlyname}\n`;
|
|
1900
|
-
report += `- **Unique Name**: ${app.publisher.uniquename}\n`;
|
|
1901
|
-
report += `- **Prefix**: ${app.publisher.customizationprefix}\n\n`;
|
|
1902
|
-
}
|
|
1903
|
-
// Components Summary
|
|
1904
|
-
report += `## Components Summary\n`;
|
|
1905
|
-
report += `**Total Components**: ${components.totalCount}\n\n`;
|
|
1906
|
-
if (components.totalCount > 0) {
|
|
1907
|
-
// Group by type
|
|
1908
|
-
Object.keys(components.groupedByType).forEach((typeName) => {
|
|
1909
|
-
const typeComponents = components.groupedByType[typeName];
|
|
1910
|
-
report += `- **${typeName}**: ${typeComponents.length}\n`;
|
|
1911
|
-
});
|
|
1912
|
-
report += `\n`;
|
|
1913
|
-
// Detailed component list by type
|
|
1914
|
-
report += `## Detailed Components\n\n`;
|
|
1915
|
-
Object.keys(components.groupedByType).forEach((typeName) => {
|
|
1916
|
-
const typeComponents = components.groupedByType[typeName];
|
|
1917
|
-
report += `### ${typeName} (${typeComponents.length})\n`;
|
|
1918
|
-
typeComponents.forEach((comp, idx) => {
|
|
1919
|
-
report += `${idx + 1}. ID: ${comp.objectid}\n`;
|
|
1920
|
-
});
|
|
1921
|
-
report += `\n`;
|
|
1922
|
-
});
|
|
1923
|
-
}
|
|
1924
|
-
// Sitemap Information
|
|
1925
|
-
if (sitemap.hasSitemap) {
|
|
1926
|
-
report += `## Navigation (Sitemap)\n`;
|
|
1927
|
-
report += `- **Sitemap Name**: ${sitemap.sitemapname}\n`;
|
|
1928
|
-
report += `- **App Aware**: ${sitemap.isappaware ? 'Yes' : 'No'}\n`;
|
|
1929
|
-
report += `- **Collapsible Groups**: ${sitemap.enablecollapsiblegroups ? 'Yes' : 'No'}\n`;
|
|
1930
|
-
report += `- **Show Home**: ${sitemap.showhome ? 'Yes' : 'No'}\n`;
|
|
1931
|
-
report += `- **Show Pinned**: ${sitemap.showpinned ? 'Yes' : 'No'}\n`;
|
|
1932
|
-
report += `- **Show Recents**: ${sitemap.showrecents ? 'Yes' : 'No'}\n`;
|
|
1933
|
-
report += `- **Managed**: ${sitemap.ismanaged ? 'Yes' : 'No'}\n\n`;
|
|
1934
|
-
}
|
|
1935
|
-
else {
|
|
1936
|
-
report += `## Navigation (Sitemap)\n`;
|
|
1937
|
-
report += `⚠ No sitemap configured for this app\n\n`;
|
|
1938
|
-
}
|
|
1939
|
-
// Next Steps
|
|
1940
|
-
report += `## Available Actions\n`;
|
|
1941
|
-
report += `- Add entities: Use \`add-entities-to-app\` tool\n`;
|
|
1942
|
-
report += `- Validate app: Use \`validate-app\` tool\n`;
|
|
1943
|
-
report += `- Publish app: Use \`publish-app\` tool\n`;
|
|
1944
|
-
report += `- View sitemap XML: Use \`get-app-sitemap\` tool\n\n`;
|
|
1945
|
-
report += `---\n\n`;
|
|
1946
|
-
report += `*Generated by MCP Consultant Tools*\n`;
|
|
1947
|
-
return {
|
|
1948
|
-
messages: [
|
|
1949
|
-
{
|
|
1950
|
-
role: "assistant",
|
|
1951
|
-
content: {
|
|
1952
|
-
type: "text",
|
|
1953
|
-
text: report
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
]
|
|
1957
|
-
};
|
|
1958
|
-
}
|
|
1959
|
-
catch (error) {
|
|
1960
|
-
console.error(`Error generating app overview:`, error);
|
|
1961
|
-
return {
|
|
1962
|
-
messages: [
|
|
1963
|
-
{
|
|
1964
|
-
role: "assistant",
|
|
1965
|
-
content: {
|
|
1966
|
-
type: "text",
|
|
1967
|
-
text: `Error: ${error.message}`
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1970
|
-
]
|
|
1971
|
-
};
|
|
1972
|
-
}
|
|
1973
|
-
});
|
|
1974
|
-
server.prompt("dataverse-best-practices-report", "Generate formatted markdown report from Dataverse best practice validation results. Groups violations by severity, provides actionable recommendations, and highlights compliant entities.", {
|
|
1975
|
-
validationResult: z.string().describe("JSON result from validate-dataverse tool")
|
|
1976
|
-
}, async (args) => {
|
|
1977
|
-
try {
|
|
1978
|
-
// Parse the validation result JSON
|
|
1979
|
-
const result = JSON.parse(args.validationResult);
|
|
1980
|
-
// Format as markdown report
|
|
1981
|
-
const report = formatBestPracticesReport(result);
|
|
1982
|
-
return {
|
|
1983
|
-
messages: [
|
|
1984
|
-
{
|
|
1985
|
-
role: "assistant",
|
|
1986
|
-
content: {
|
|
1987
|
-
type: "text",
|
|
1988
|
-
text: report
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
]
|
|
1992
|
-
};
|
|
1993
|
-
}
|
|
1994
|
-
catch (error) {
|
|
1995
|
-
console.error("Error generating best practices report:", error);
|
|
1996
|
-
return {
|
|
1997
|
-
messages: [
|
|
1998
|
-
{
|
|
1999
|
-
role: "assistant",
|
|
2000
|
-
content: {
|
|
2001
|
-
type: "text",
|
|
2002
|
-
text: `Error generating report: ${error.message}\n\nPlease ensure the validationResult is valid JSON from the validate-dataverse tool.`
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
]
|
|
2006
|
-
};
|
|
2007
|
-
}
|
|
2008
|
-
});
|
|
2009
|
-
// Note: find-documented-automations-by-table and analyze-undocumented-automations
|
|
2010
|
-
// tools removed - these automation analysis tools are not yet implemented in core services
|
|
2011
|
-
server.tool("generate-dbml-schema", `Generates DBML (Database Markup Language) schema from Dataverse entities.
|
|
2012
|
-
|
|
2013
|
-
Accepts solution names, explicit entity lists, or both. Returns DBML text
|
|
2014
|
-
and a clickable dbdiagram.io URL for visualization.
|
|
2015
|
-
|
|
2016
|
-
DBML output includes:
|
|
2017
|
-
- Table definitions with columns and types
|
|
2018
|
-
- Primary key markers [pk]
|
|
2019
|
-
- Foreign key relationships (Ref: statements) for all lookups
|
|
2020
|
-
|
|
2021
|
-
Example output:
|
|
2022
|
-
\`\`\`dbml
|
|
2023
|
-
Table si_directdebit {
|
|
2024
|
-
si_directdebitid uniqueidentifier [pk]
|
|
2025
|
-
si_name nvarchar
|
|
2026
|
-
si_accountid lookup
|
|
46
|
+
return {
|
|
47
|
+
get pp() { return getPowerPlatformService(); }
|
|
48
|
+
};
|
|
2027
49
|
}
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
.describe('Include statecode/statuscode columns (default: false)'),
|
|
2038
|
-
prefix: z.string().optional()
|
|
2039
|
-
.describe('Only include columns matching this prefix (e.g., "si_")'),
|
|
2040
|
-
depth: z.number().optional()
|
|
2041
|
-
.describe('Relationship traversal depth for discovering related entities (default: 0)'),
|
|
2042
|
-
includePolymorphicLookups: z.boolean().optional()
|
|
2043
|
-
.describe('Include Customer/Owner/PartyList lookups (default: true)'),
|
|
2044
|
-
}, async (params) => {
|
|
2045
|
-
try {
|
|
2046
|
-
const service = getPowerPlatformService();
|
|
2047
|
-
const result = await service.generateDbmlSchema(params);
|
|
2048
|
-
return {
|
|
2049
|
-
content: [
|
|
2050
|
-
{
|
|
2051
|
-
type: "text",
|
|
2052
|
-
text: JSON.stringify(result, null, 2),
|
|
2053
|
-
},
|
|
2054
|
-
],
|
|
2055
|
-
};
|
|
2056
|
-
}
|
|
2057
|
-
catch (error) {
|
|
2058
|
-
console.error("Error generating DBML schema:", error);
|
|
2059
|
-
return {
|
|
2060
|
-
content: [
|
|
2061
|
-
{
|
|
2062
|
-
type: "text",
|
|
2063
|
-
text: `Failed to generate DBML schema: ${error.message}`,
|
|
2064
|
-
},
|
|
2065
|
-
],
|
|
2066
|
-
};
|
|
2067
|
-
}
|
|
2068
|
-
});
|
|
2069
|
-
// ============================================================================
|
|
2070
|
-
// INTEGRATION AUDIT TOOLS (4 tools)
|
|
2071
|
-
// ============================================================================
|
|
2072
|
-
server.tool("get-service-endpoints", `Get all service endpoints (webhooks, Azure Service Bus, REST) configured in the environment.
|
|
2073
|
-
|
|
2074
|
-
Service endpoints are external URLs that Dataverse can call via SDK message processing steps.
|
|
2075
|
-
Use this to discover outbound integration touchpoints.
|
|
2076
|
-
|
|
2077
|
-
Optionally validate endpoint URLs against required patterns (e.g. your organization's domains).
|
|
2078
|
-
Flagged endpoints are those whose URLs do NOT match any of the provided patterns.
|
|
2079
|
-
|
|
2080
|
-
Returns:
|
|
2081
|
-
- Endpoint name, full URL, contract type (OneWay/TwoWay/Queue/Topic/REST/EventHub/Webhook/EventGrid)
|
|
2082
|
-
- Authentication type (Anonymous/HttpHeader/WebKey/SASKey/etc.)
|
|
2083
|
-
- Number of SDK message steps using each endpoint
|
|
2084
|
-
- Summary statistics by type and auth method
|
|
2085
|
-
- Flagged endpoints (when requiredUrlStrings provided)`, {
|
|
2086
|
-
maxRecords: z.number().optional().describe("Maximum endpoints to return (default: 100)"),
|
|
2087
|
-
requiredUrlStrings: z.array(z.string()).optional().describe("URL patterns to validate against. Endpoints not matching any pattern are flagged.\n\nExamples:\n - Validate production URLs: `[\"mycompany.com\"]`\n - Multiple environments: `[\"prod.mycompany.com\", \"staging.mycompany.com\"]`"),
|
|
2088
|
-
outputFormat: z.enum(["summary", "full"]).optional().describe("Output format: 'summary' shows flagged items only, 'full' shows everything (default: full)"),
|
|
2089
|
-
excludeOotb: z.boolean().optional().describe("Exclude Microsoft out-of-the-box (OOTB) components from results (default: true). Set to false to include all items."),
|
|
2090
|
-
}, async ({ maxRecords, requiredUrlStrings, outputFormat, excludeOotb }) => {
|
|
2091
|
-
try {
|
|
2092
|
-
const service = getPowerPlatformService();
|
|
2093
|
-
const ootb = excludeOotb ?? true;
|
|
2094
|
-
if (requiredUrlStrings && requiredUrlStrings.length > 0) {
|
|
2095
|
-
const result = await service.getServiceEndpointsValidated(maxRecords ?? 100, requiredUrlStrings, ootb);
|
|
2096
|
-
const lines = [];
|
|
2097
|
-
lines.push(`# Service Endpoints (${result.summary.total} found, ${result.summary.flagged} flagged)`);
|
|
2098
|
-
lines.push('');
|
|
2099
|
-
if (result.flaggedEndpoints.length > 0) {
|
|
2100
|
-
lines.push('## Flagged Endpoints');
|
|
2101
|
-
lines.push('');
|
|
2102
|
-
lines.push('| Name | URL | Issue |');
|
|
2103
|
-
lines.push('|------|-----|-------|');
|
|
2104
|
-
for (const f of result.flaggedEndpoints) {
|
|
2105
|
-
lines.push(`| ${f.endpoint.name} | ${f.endpoint.url} | ${f.urlIssue} |`);
|
|
2106
|
-
}
|
|
2107
|
-
lines.push('');
|
|
2108
|
-
}
|
|
2109
|
-
if (outputFormat !== 'summary') {
|
|
2110
|
-
lines.push('## All Endpoints');
|
|
2111
|
-
lines.push('');
|
|
2112
|
-
lines.push('| Name | URL |');
|
|
2113
|
-
lines.push('|------|-----|');
|
|
2114
|
-
for (const ep of result.allEndpoints) {
|
|
2115
|
-
lines.push(`| ${ep.name} | ${ep.url} |`);
|
|
2116
|
-
}
|
|
2117
|
-
lines.push('');
|
|
2118
|
-
}
|
|
2119
|
-
return { content: [{ type: "text", text: lines.join('\n') }] };
|
|
2120
|
-
}
|
|
2121
|
-
const result = await service.getServiceEndpoints(maxRecords ?? 100, ootb);
|
|
2122
|
-
const ootbNote = result.summary.ootbExcluded ? `, ${result.summary.ootbExcluded} OOTB excluded` : '';
|
|
2123
|
-
return {
|
|
2124
|
-
content: [
|
|
2125
|
-
{
|
|
2126
|
-
type: "text",
|
|
2127
|
-
text: `Service Endpoints (${result.summary.total} found${ootbNote}):\n\n${JSON.stringify(result, null, 2)}`,
|
|
2128
|
-
},
|
|
2129
|
-
],
|
|
2130
|
-
};
|
|
2131
|
-
}
|
|
2132
|
-
catch (error) {
|
|
2133
|
-
console.error("Error getting service endpoints:", error);
|
|
2134
|
-
return {
|
|
2135
|
-
content: [
|
|
2136
|
-
{
|
|
2137
|
-
type: "text",
|
|
2138
|
-
text: `Failed to get service endpoints: ${error.message}`,
|
|
2139
|
-
},
|
|
2140
|
-
],
|
|
2141
|
-
};
|
|
2142
|
-
}
|
|
2143
|
-
});
|
|
2144
|
-
server.tool("get-webhook-registrations", `Get all webhook-type SDK message processing steps in the environment.
|
|
2145
|
-
|
|
2146
|
-
Webhooks are registrations that call external service endpoints when Dataverse events occur.
|
|
2147
|
-
This identifies inbound integration patterns where external systems receive notifications.
|
|
2148
|
-
|
|
2149
|
-
Returns:
|
|
2150
|
-
- Webhook name and endpoint URL
|
|
2151
|
-
- Trigger entity and message (Create/Update/Delete)
|
|
2152
|
-
- Filtering attributes and execution stage
|
|
2153
|
-
- Enabled/disabled status
|
|
2154
|
-
- Summary by entity and message type`, {
|
|
2155
|
-
maxRecords: z.number().optional().describe("Maximum webhooks to return (default: 100)"),
|
|
2156
|
-
excludeOotb: z.boolean().optional().describe("Exclude Microsoft out-of-the-box (OOTB) components from results (default: true). Set to false to include all items."),
|
|
2157
|
-
}, async ({ maxRecords, excludeOotb }) => {
|
|
2158
|
-
try {
|
|
2159
|
-
const service = getPowerPlatformService();
|
|
2160
|
-
const result = await service.getWebhookRegistrations(maxRecords ?? 100, excludeOotb ?? true);
|
|
2161
|
-
const ootbNote = result.summary.ootbExcluded ? `, ${result.summary.ootbExcluded} OOTB excluded` : '';
|
|
2162
|
-
return {
|
|
2163
|
-
content: [
|
|
2164
|
-
{
|
|
2165
|
-
type: "text",
|
|
2166
|
-
text: `Webhook Registrations (${result.summary.total} found, ${result.summary.enabledCount} enabled${ootbNote}):\n\n${JSON.stringify(result, null, 2)}`,
|
|
2167
|
-
},
|
|
2168
|
-
],
|
|
2169
|
-
};
|
|
2170
|
-
}
|
|
2171
|
-
catch (error) {
|
|
2172
|
-
console.error("Error getting webhook registrations:", error);
|
|
2173
|
-
return {
|
|
2174
|
-
content: [
|
|
2175
|
-
{
|
|
2176
|
-
type: "text",
|
|
2177
|
-
text: `Failed to get webhook registrations: ${error.message}`,
|
|
2178
|
-
},
|
|
2179
|
-
],
|
|
2180
|
-
};
|
|
2181
|
-
}
|
|
2182
|
-
});
|
|
2183
|
-
server.tool("analyze-flow-complexity", `Analyze Power Automate flow complexity and calculate risk scores.
|
|
2184
|
-
|
|
2185
|
-
Now includes URL extraction and hardcoded secret detection per flow.
|
|
2186
|
-
|
|
2187
|
-
Complexity scoring factors:
|
|
2188
|
-
- Action count (base complexity)
|
|
2189
|
-
- Unique connectors (integration surface, +2 each)
|
|
2190
|
-
- HTTP/REST connectors (external dependency, +5 each)
|
|
2191
|
-
- Premium connectors (licensing concern, +3 each)
|
|
2192
|
-
- Conditions/switches (+2 each)
|
|
2193
|
-
- Loops (+3 each)
|
|
2194
|
-
- Parallel branches (+3 each)
|
|
2195
|
-
- Error handling scopes (+1 each)
|
|
2196
|
-
|
|
2197
|
-
Risk levels: Low (0-20), Medium (21-50), High (51-100), Critical (>100)
|
|
2198
|
-
|
|
2199
|
-
Use flowId to analyze a single flow, or omit to analyze all flows.`, {
|
|
2200
|
-
flowId: z.string().optional().describe("Specific flow ID to analyze (omit for all flows)"),
|
|
2201
|
-
maxFlows: z.number().optional().describe("Maximum flows to analyze when flowId not specified (default: 0 = unlimited)"),
|
|
2202
|
-
outputFormat: z.enum(["summary", "full"]).optional().describe("Output format: 'summary' shows flagged items and stats only, 'full' shows all details (default: full)"),
|
|
2203
|
-
excludeOotb: z.boolean().optional().describe("Exclude Microsoft out-of-the-box (OOTB/managed) flows from analysis (default: true). Set to false to include all flows."),
|
|
2204
|
-
}, async ({ flowId, maxFlows, outputFormat, excludeOotb }) => {
|
|
2205
|
-
try {
|
|
2206
|
-
const service = getPowerPlatformService();
|
|
2207
|
-
const result = await service.analyzeFlowComplexity(flowId, maxFlows ?? 0, excludeOotb ?? true);
|
|
2208
|
-
const lines = [];
|
|
2209
|
-
const ootbNote = result.summary.ootbExcluded ? `, ${result.summary.ootbExcluded} OOTB excluded` : '';
|
|
2210
|
-
lines.push(`# Flow Complexity Analysis (${result.summary.total} flows${ootbNote})`);
|
|
2211
|
-
lines.push('');
|
|
2212
|
-
lines.push(`- Average Score: ${result.summary.averageComplexity}`);
|
|
2213
|
-
lines.push(`- By Risk: Low=${result.summary.byRiskLevel.Low}, Medium=${result.summary.byRiskLevel.Medium}, High=${result.summary.byRiskLevel.High}, Critical=${result.summary.byRiskLevel.Critical}`);
|
|
2214
|
-
lines.push(`- URLs Found: ${result.summary.totalUrlsFound ?? 0}`);
|
|
2215
|
-
lines.push(`- Secret Warnings: ${result.summary.totalSecretWarnings ?? 0} in ${result.summary.flowsWithSecretWarnings ?? 0} flows`);
|
|
2216
|
-
if (result.summary.uniqueEnvironmentVariables && result.summary.uniqueEnvironmentVariables.length > 0) {
|
|
2217
|
-
lines.push(`- Environment Variables Referenced: ${result.summary.uniqueEnvironmentVariables.join(', ')}`);
|
|
2218
|
-
}
|
|
2219
|
-
lines.push('');
|
|
2220
|
-
if (result.summary.highRiskFlows.length > 0) {
|
|
2221
|
-
lines.push('## High/Critical Risk Flows');
|
|
2222
|
-
for (const name of result.summary.highRiskFlows) {
|
|
2223
|
-
const flow = result.flows.find(f => f.name === name);
|
|
2224
|
-
if (flow) {
|
|
2225
|
-
lines.push(`- **${name}** - Score: ${flow.complexity.score} (${flow.complexity.riskLevel})`);
|
|
2226
|
-
}
|
|
2227
|
-
}
|
|
2228
|
-
lines.push('');
|
|
2229
|
-
}
|
|
2230
|
-
// Show secret warnings
|
|
2231
|
-
const flowsWithSecrets = result.flows.filter(f => f.secretWarnings && f.secretWarnings.length > 0);
|
|
2232
|
-
if (flowsWithSecrets.length > 0) {
|
|
2233
|
-
lines.push('## Security Warnings');
|
|
2234
|
-
lines.push('');
|
|
2235
|
-
lines.push('| Flow | Action | Field | Warning |');
|
|
2236
|
-
lines.push('|------|--------|-------|---------|');
|
|
2237
|
-
for (const flow of flowsWithSecrets) {
|
|
2238
|
-
for (const w of flow.secretWarnings) {
|
|
2239
|
-
lines.push(`| ${flow.name} | ${w.actionName} | ${w.fieldPath} | ${w.message} |`);
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
|
-
lines.push('');
|
|
2243
|
-
}
|
|
2244
|
-
if (outputFormat !== 'summary') {
|
|
2245
|
-
lines.push('## Full Details');
|
|
2246
|
-
lines.push('');
|
|
2247
|
-
lines.push('```json');
|
|
2248
|
-
lines.push(JSON.stringify(result, null, 2));
|
|
2249
|
-
lines.push('```');
|
|
2250
|
-
}
|
|
2251
|
-
return { content: [{ type: "text", text: lines.join('\n') }] };
|
|
2252
|
-
}
|
|
2253
|
-
catch (error) {
|
|
2254
|
-
console.error("Error analyzing flow complexity:", error);
|
|
2255
|
-
return {
|
|
2256
|
-
content: [
|
|
2257
|
-
{
|
|
2258
|
-
type: "text",
|
|
2259
|
-
text: `Failed to analyze flow complexity: ${error.message}`,
|
|
2260
|
-
},
|
|
2261
|
-
],
|
|
2262
|
-
};
|
|
2263
|
-
}
|
|
2264
|
-
});
|
|
2265
|
-
server.tool("gen-integration-audit", `Generate a comprehensive integration audit report for the PowerPlatform environment.
|
|
2266
|
-
|
|
2267
|
-
This top-down report aggregates all integration touchpoints:
|
|
2268
|
-
|
|
2269
|
-
OUTBOUND: Service endpoints, flows with HTTP/external calls, plugins with external access
|
|
2270
|
-
INBOUND: Webhook registrations, flows with external triggers
|
|
2271
|
-
COMPLEXITY: Flow complexity scores, risk levels, URL extraction, secret detection
|
|
2272
|
-
ENVIRONMENT: Environment variable inventory with URL validation
|
|
2273
|
-
PLUGINS: Plugin assembly inventory
|
|
2274
|
-
|
|
2275
|
-
Includes URL validation (flag endpoints/variables not matching required patterns),
|
|
2276
|
-
hardcoded secret detection in flows, and environment variable analysis.
|
|
2277
|
-
|
|
2278
|
-
Returns a pre-formatted Markdown report. Use outputFormat="summary" for a compact view.`, {
|
|
2279
|
-
maxFlows: z.number().optional().describe("Maximum flows to analyze (default: 0 = unlimited)"),
|
|
2280
|
-
maxRecords: z.number().optional().describe("Maximum records to return for service endpoints, webhooks, and plugin assemblies (default: 100). Increase to get all items in large environments."),
|
|
2281
|
-
requiredUrlStrings: z.array(z.string()).optional().describe("URL patterns to validate against. Endpoints and env vars not matching are flagged.\n\nExamples:\n - `[\"mycompany.com\"]`\n - `[\"prod.api.com\", \"staging.api.com\"]`"),
|
|
2282
|
-
outputFormat: z.enum(["summary", "full"]).optional().describe("Output format: 'summary' shows flagged items and stats only, 'full' shows everything (default: full)"),
|
|
2283
|
-
excludeOotb: z.boolean().optional().describe("Exclude Microsoft out-of-the-box (OOTB) components from results (default: true). Set to false to include all items."),
|
|
2284
|
-
}, async ({ maxFlows, maxRecords, requiredUrlStrings, outputFormat, excludeOotb }) => {
|
|
2285
|
-
try {
|
|
2286
|
-
const service = getPowerPlatformService();
|
|
2287
|
-
const result = await service.generateIntegrationAuditReport(maxFlows ?? 0, requiredUrlStrings, outputFormat, excludeOotb ?? true, maxRecords ?? 100);
|
|
2288
|
-
// Return both the markdown report and the JSON data
|
|
2289
|
-
return {
|
|
2290
|
-
content: [
|
|
2291
|
-
{
|
|
2292
|
-
type: "text",
|
|
2293
|
-
text: result.markdownReport,
|
|
2294
|
-
},
|
|
2295
|
-
{
|
|
2296
|
-
type: "text",
|
|
2297
|
-
text: `\n\n---\n\n**Full JSON Data:**\n\n${JSON.stringify({
|
|
2298
|
-
summary: result.summary,
|
|
2299
|
-
riskAssessment: result.riskAssessment,
|
|
2300
|
-
outbound: {
|
|
2301
|
-
serviceEndpointCount: result.outbound.serviceEndpoints.length,
|
|
2302
|
-
httpFlowCount: result.outbound.httpFlows.length,
|
|
2303
|
-
externalPluginCount: result.outbound.externalPlugins.length,
|
|
2304
|
-
},
|
|
2305
|
-
inbound: {
|
|
2306
|
-
webhookCount: result.inbound.webhooks.length,
|
|
2307
|
-
externalTriggerFlowCount: result.inbound.externalTriggerFlows.length,
|
|
2308
|
-
},
|
|
2309
|
-
complexity: result.complexity.summary,
|
|
2310
|
-
}, null, 2)}`,
|
|
2311
|
-
},
|
|
2312
|
-
],
|
|
2313
|
-
};
|
|
2314
|
-
}
|
|
2315
|
-
catch (error) {
|
|
2316
|
-
console.error("Error generating integration audit report:", error);
|
|
2317
|
-
return {
|
|
2318
|
-
content: [
|
|
2319
|
-
{
|
|
2320
|
-
type: "text",
|
|
2321
|
-
text: `Failed to generate integration audit report: ${error.message}`,
|
|
2322
|
-
},
|
|
2323
|
-
],
|
|
2324
|
-
};
|
|
2325
|
-
}
|
|
2326
|
-
});
|
|
2327
|
-
server.tool("get-env-variables", `Get all environment variable definitions from the PowerPlatform environment.
|
|
2328
|
-
|
|
2329
|
-
Environment variables store configuration values (URLs, connection strings, feature flags)
|
|
2330
|
-
that can be referenced by flows and other components. Sensitive variables are masked.
|
|
2331
|
-
|
|
2332
|
-
Optionally validate URL-type variables against required patterns to flag diverging values
|
|
2333
|
-
(e.g. variables pointing to wrong environments).
|
|
2334
|
-
|
|
2335
|
-
Returns:
|
|
2336
|
-
- Schema name, display name, type (String/Number/Boolean/JSON/Secret)
|
|
2337
|
-
- Current value, default value, and effective value
|
|
2338
|
-
- Managed status and sensitivity flag
|
|
2339
|
-
- Diverging variables (when requiredUrlStrings provided)`, {
|
|
2340
|
-
maxRecords: z.number().optional().describe("Maximum variables to return (default: 500)"),
|
|
2341
|
-
requiredUrlStrings: z.array(z.string()).optional().describe("URL patterns to validate against. URL-type variables not matching any pattern are flagged.\n\nExamples:\n - `[\"mycompany.com\"]`\n - `[\"prod.api.com\", \"staging.api.com\"]`"),
|
|
2342
|
-
outputFormat: z.enum(["summary", "full"]).optional().describe("Output format: 'summary' shows diverging variables only, 'full' shows everything (default: full)"),
|
|
2343
|
-
excludeOotb: z.boolean().optional().describe("Exclude Microsoft out-of-the-box (OOTB) components from results (default: true). Set to false to include all items."),
|
|
2344
|
-
}, async ({ maxRecords, requiredUrlStrings, outputFormat, excludeOotb }) => {
|
|
2345
|
-
try {
|
|
2346
|
-
const service = getPowerPlatformService();
|
|
2347
|
-
const result = await service.getEnvironmentVariables(maxRecords ?? 500, requiredUrlStrings, excludeOotb ?? true);
|
|
2348
|
-
const ootbNote = result.summary.ootbExcluded ? `, ${result.summary.ootbExcluded} OOTB excluded` : '';
|
|
2349
|
-
const lines = [];
|
|
2350
|
-
lines.push(`# Environment Variables (${result.summary.total} found${ootbNote})`);
|
|
2351
|
-
lines.push('');
|
|
2352
|
-
lines.push(`Types: ${Object.entries(result.summary.byType).map(([k, v]) => `${k}=${v}`).join(', ')}`);
|
|
2353
|
-
lines.push('');
|
|
2354
|
-
if (result.divergingVariables.length > 0) {
|
|
2355
|
-
lines.push('## Diverging Variables');
|
|
2356
|
-
lines.push('');
|
|
2357
|
-
lines.push('| Schema Name | Display Name | Value | Reason |');
|
|
2358
|
-
lines.push('|-------------|-------------|-------|--------|');
|
|
2359
|
-
for (const d of result.divergingVariables) {
|
|
2360
|
-
const val = d.variable.isSensitive ? '***' : (d.variable.effectiveValue ?? '(none)');
|
|
2361
|
-
lines.push(`| ${d.variable.schemaName} | ${d.variable.displayName} | ${val} | ${d.reason} |`);
|
|
2362
|
-
}
|
|
2363
|
-
lines.push('');
|
|
2364
|
-
}
|
|
2365
|
-
if (outputFormat !== 'summary') {
|
|
2366
|
-
lines.push('## All Variables');
|
|
2367
|
-
lines.push('');
|
|
2368
|
-
lines.push('| Schema Name | Display Name | Type | Effective Value | Managed |');
|
|
2369
|
-
lines.push('|-------------|-------------|------|-----------------|---------|');
|
|
2370
|
-
for (const v of result.allVariables) {
|
|
2371
|
-
const val = v.isSensitive ? (v.maskedValue ?? '***') : (v.effectiveValue ?? v.defaultValue ?? '(none)');
|
|
2372
|
-
const managed = v.isManaged ? 'Yes' : 'No';
|
|
2373
|
-
lines.push(`| ${v.schemaName} | ${v.displayName} | ${v.type} | ${val} | ${managed} |`);
|
|
2374
|
-
}
|
|
2375
|
-
lines.push('');
|
|
2376
|
-
}
|
|
2377
|
-
return { content: [{ type: "text", text: lines.join('\n') }] };
|
|
2378
|
-
}
|
|
2379
|
-
catch (error) {
|
|
2380
|
-
console.error("Error getting environment variables:", error);
|
|
2381
|
-
return {
|
|
2382
|
-
content: [
|
|
2383
|
-
{
|
|
2384
|
-
type: "text",
|
|
2385
|
-
text: `Failed to get environment variables: ${error.message}`,
|
|
2386
|
-
},
|
|
2387
|
-
],
|
|
2388
|
-
};
|
|
2389
|
-
}
|
|
2390
|
-
});
|
|
2391
|
-
// ============================================================================
|
|
2392
|
-
// INTEGRATION AUDIT PROMPT (1 prompt)
|
|
2393
|
-
// ============================================================================
|
|
2394
|
-
server.prompt("integration-audit-report", "Generate a comprehensive integration audit report with drill-down capability", {}, async () => {
|
|
2395
|
-
try {
|
|
2396
|
-
const service = getPowerPlatformService();
|
|
2397
|
-
const result = await service.generateIntegrationAuditReport(50);
|
|
2398
|
-
return {
|
|
2399
|
-
messages: [
|
|
2400
|
-
{
|
|
2401
|
-
role: "assistant",
|
|
2402
|
-
content: {
|
|
2403
|
-
type: "text",
|
|
2404
|
-
text: result.markdownReport
|
|
2405
|
-
}
|
|
2406
|
-
}
|
|
2407
|
-
]
|
|
2408
|
-
};
|
|
2409
|
-
}
|
|
2410
|
-
catch (error) {
|
|
2411
|
-
console.error("Error generating integration audit report:", error);
|
|
2412
|
-
return {
|
|
2413
|
-
messages: [
|
|
2414
|
-
{
|
|
2415
|
-
role: "assistant",
|
|
2416
|
-
content: {
|
|
2417
|
-
type: "text",
|
|
2418
|
-
text: `Error: ${error.message}`
|
|
2419
|
-
}
|
|
2420
|
-
}
|
|
2421
|
-
]
|
|
2422
|
-
};
|
|
2423
|
-
}
|
|
2424
|
-
});
|
|
2425
|
-
console.error(`✅ PowerPlatform read-only tools registered (${42} tools, ${12} prompts)`);
|
|
50
|
+
/**
|
|
51
|
+
* Register PowerPlatform read-only tools with an MCP server
|
|
52
|
+
* @param server - MCP server instance
|
|
53
|
+
* @param service - Optional pre-initialized PowerPlatformService (for testing)
|
|
54
|
+
*/
|
|
55
|
+
export function registerPowerPlatformTools(server, service) {
|
|
56
|
+
const ctx = createServiceContext(service);
|
|
57
|
+
registerAllTools(server, ctx);
|
|
58
|
+
registerAllPrompts(server, ctx);
|
|
2426
59
|
}
|
|
2427
|
-
//
|
|
2428
|
-
export { PowerPlatformService };
|
|
60
|
+
// Backward-compatible exports
|
|
61
|
+
export { PowerPlatformService } from './PowerPlatformService.js';
|
|
2429
62
|
// CLI entry point (standalone execution)
|
|
2430
63
|
// Uses realpathSync to resolve symlinks created by npx
|
|
2431
64
|
if (import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
@@ -2434,7 +67,6 @@ if (import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
|
2434
67
|
// Handle CLI commands
|
|
2435
68
|
const args = process.argv.slice(2);
|
|
2436
69
|
if (args.includes('--logout') || args.includes('-l')) {
|
|
2437
|
-
// Logout: Clear cached tokens
|
|
2438
70
|
const clientId = process.env.POWERPLATFORM_CLIENT_ID;
|
|
2439
71
|
if (!clientId) {
|
|
2440
72
|
console.error('Error: POWERPLATFORM_CLIENT_ID is required for logout');
|