@mcp-consultant-tools/powerplatform-data 27.0.0 → 28.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.d.ts +11 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +45 -557
- package/build/index.js.map +1 -1
- 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/tools/index.d.ts +8 -0
- package/build/tools/index.d.ts.map +1 -0
- package/build/tools/index.js +11 -0
- package/build/tools/index.js.map +1 -0
- package/build/tools/read-tools.d.ts +3 -0
- package/build/tools/read-tools.d.ts.map +1 -0
- package/build/tools/read-tools.js +341 -0
- package/build/tools/read-tools.js.map +1 -0
- package/build/tools/write-tools.d.ts +3 -0
- package/build/tools/write-tools.d.ts.map +1 -0
- package/build/tools/write-tools.js +202 -0
- package/build/tools/write-tools.js.map +1 -0
- package/build/types.d.ts +13 -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 +1 -1
package/build/index.d.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @mcp-consultant-tools/powerplatform-data
|
|
4
|
+
*
|
|
5
|
+
* MCP server for PowerPlatform/Dataverse data CRUD operations.
|
|
6
|
+
* Entry point: MCP server startup + backward-compatible registerPowerplatformDataTools().
|
|
7
|
+
*/
|
|
2
8
|
import { PowerPlatformService } from './PowerPlatformService.js';
|
|
3
9
|
/**
|
|
4
|
-
* Register powerplatform-data tools with an MCP server
|
|
10
|
+
* Register powerplatform-data tools with an MCP server.
|
|
11
|
+
* Backward-compatible API for the meta package.
|
|
5
12
|
* @param server - MCP server instance
|
|
6
13
|
* @param service - Optional pre-initialized PowerPlatformService (for testing)
|
|
7
14
|
*/
|
|
8
15
|
export declare function registerPowerplatformDataTools(server: any, service?: PowerPlatformService): void;
|
|
16
|
+
export { PowerPlatformService } from './PowerPlatformService.js';
|
|
17
|
+
export type { PowerPlatformConfig } from './PowerPlatformService.js';
|
|
18
|
+
export type { ServiceContext } from './types.js';
|
|
9
19
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;GAKG;AAMH,OAAO,EAAE,oBAAoB,EAAuB,MAAM,2BAA2B,CAAC;AA6DtF;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAGhG;AAGD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,YAAY,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
package/build/index.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @mcp-consultant-tools/powerplatform-data
|
|
4
|
+
*
|
|
5
|
+
* MCP server for PowerPlatform/Dataverse data CRUD operations.
|
|
6
|
+
* Entry point: MCP server startup + backward-compatible registerPowerplatformDataTools().
|
|
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';
|
|
13
|
+
import { registerAllTools } from './tools/index.js';
|
|
8
14
|
/**
|
|
9
|
-
*
|
|
10
|
-
* @param server - MCP server instance
|
|
11
|
-
* @param service - Optional pre-initialized PowerPlatformService (for testing)
|
|
15
|
+
* Build a ServiceContext with lazy service initialization.
|
|
12
16
|
*/
|
|
13
|
-
|
|
17
|
+
function createServiceContext(service) {
|
|
14
18
|
let ppService = service || null;
|
|
15
19
|
function getPowerPlatformService() {
|
|
16
20
|
if (!ppService) {
|
|
@@ -34,562 +38,46 @@ export function registerPowerplatformDataTools(server, service) {
|
|
|
34
38
|
}
|
|
35
39
|
return ppService;
|
|
36
40
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
function checkUpdateEnabled() {
|
|
44
|
-
if (process.env.POWERPLATFORM_ENABLE_UPDATE !== 'true') {
|
|
45
|
-
throw new Error('Update operations are disabled. Set POWERPLATFORM_ENABLE_UPDATE=true to enable.');
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function checkDeleteEnabled() {
|
|
49
|
-
if (process.env.POWERPLATFORM_ENABLE_DELETE !== 'true') {
|
|
50
|
-
throw new Error('Delete operations are disabled. Set POWERPLATFORM_ENABLE_DELETE=true to enable.');
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
function checkActionsEnabled() {
|
|
54
|
-
if (process.env.POWERPLATFORM_ENABLE_ACTIONS !== 'true') {
|
|
55
|
-
throw new Error('Action execution is disabled. Set POWERPLATFORM_ENABLE_ACTIONS=true to enable.');
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
// Tool registrations
|
|
59
|
-
// Read-only query tools (no permission flags required)
|
|
60
|
-
server.tool("query-records", "Query Dataverse records using an OData filter expression. Use the 'select' parameter to limit returned columns and reduce response size. No permission flag required (read-only).", {
|
|
61
|
-
entityNamePlural: z
|
|
62
|
-
.string()
|
|
63
|
-
.describe("The plural name of the entity (e.g., 'accounts', 'contacts', 'sic_applications')"),
|
|
64
|
-
filter: z
|
|
65
|
-
.string()
|
|
66
|
-
.describe("OData filter expression (e.g., \"name eq 'Acme Corp'\", \"createdon gt 2024-01-01\", \"statecode eq 0\")"),
|
|
67
|
-
select: z
|
|
68
|
-
.array(z.string())
|
|
69
|
-
.optional()
|
|
70
|
-
.describe("List of column names to return (e.g., ['name', 'accountid', 'statuscode']). Omit to return all columns (not recommended for large entities)."),
|
|
71
|
-
maxRecords: z
|
|
72
|
-
.number()
|
|
73
|
-
.optional()
|
|
74
|
-
.describe("Maximum number of records to retrieve (default: 50, max: 5000)"),
|
|
75
|
-
}, async ({ entityNamePlural, filter, select, maxRecords }) => {
|
|
76
|
-
try {
|
|
77
|
-
const service = getPowerPlatformService();
|
|
78
|
-
const result = await service.queryRecords(entityNamePlural, filter, maxRecords || 50, select);
|
|
79
|
-
const recordsStr = JSON.stringify(result, null, 2);
|
|
80
|
-
let message = `📋 Retrieved ${result.returnedCount} records from '${entityNamePlural}' with filter '${filter}'`;
|
|
81
|
-
if (result.hasMore) {
|
|
82
|
-
message += `\n⚠️ More records available - increase maxRecords (currently ${result.requestedMax}) to retrieve more`;
|
|
41
|
+
return {
|
|
42
|
+
get pp() { return getPowerPlatformService(); },
|
|
43
|
+
checkCreateEnabled() {
|
|
44
|
+
if (process.env.POWERPLATFORM_ENABLE_CREATE !== 'true') {
|
|
45
|
+
throw new Error('Create operations are disabled. Set POWERPLATFORM_ENABLE_CREATE=true to enable.');
|
|
83
46
|
}
|
|
84
|
-
|
|
85
|
-
|
|
47
|
+
},
|
|
48
|
+
checkUpdateEnabled() {
|
|
49
|
+
if (process.env.POWERPLATFORM_ENABLE_UPDATE !== 'true') {
|
|
50
|
+
throw new Error('Update operations are disabled. Set POWERPLATFORM_ENABLE_UPDATE=true to enable.');
|
|
86
51
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
text: `${message}:\n\n\`\`\`json\n${recordsStr}\n\`\`\``,
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
catch (error) {
|
|
97
|
-
console.error("Error querying records:", error);
|
|
98
|
-
return {
|
|
99
|
-
content: [
|
|
100
|
-
{
|
|
101
|
-
type: "text",
|
|
102
|
-
text: `❌ Failed to query records: ${error.message}`,
|
|
103
|
-
},
|
|
104
|
-
],
|
|
105
|
-
isError: true,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
server.tool("get-record", "Get a specific Dataverse record by entity name and ID. Use this to retrieve a record before updating/deleting or to validate changes after operations. No permission flag required (read-only).", {
|
|
110
|
-
entityNamePlural: z
|
|
111
|
-
.string()
|
|
112
|
-
.describe("The plural name of the entity (e.g., 'accounts', 'contacts', 'sic_applications')"),
|
|
113
|
-
recordId: z
|
|
114
|
-
.string()
|
|
115
|
-
.describe("The GUID of the record to retrieve"),
|
|
116
|
-
}, async ({ entityNamePlural, recordId }) => {
|
|
117
|
-
try {
|
|
118
|
-
const service = getPowerPlatformService();
|
|
119
|
-
const record = await service.getRecord(entityNamePlural, recordId);
|
|
120
|
-
const recordStr = JSON.stringify(record, null, 2);
|
|
121
|
-
return {
|
|
122
|
-
content: [
|
|
123
|
-
{
|
|
124
|
-
type: "text",
|
|
125
|
-
text: `📋 Record from '${entityNamePlural}' with ID '${recordId}':\n\n\`\`\`json\n${recordStr}\n\`\`\``,
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
console.error("Error getting record:", error);
|
|
132
|
-
return {
|
|
133
|
-
content: [
|
|
134
|
-
{
|
|
135
|
-
type: "text",
|
|
136
|
-
text: `❌ Failed to get record: ${error.message}`,
|
|
137
|
-
},
|
|
138
|
-
],
|
|
139
|
-
isError: true,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
// Metadata discovery tools (read-only, essential for CRUD operations)
|
|
144
|
-
server.tool("get-entity-metadata", "Get metadata about a Dataverse entity including EntitySetName (plural name for API), PrimaryIdAttribute, PrimaryNameAttribute, and other key properties. Essential for discovering the correct EntitySetName to use in CRUD operations. No permission flag required (read-only).", {
|
|
145
|
-
entityLogicalName: z
|
|
146
|
-
.string()
|
|
147
|
-
.describe("The logical name of the entity (e.g., 'account', 'contact', 'ste_transaction')"),
|
|
148
|
-
}, async ({ entityLogicalName }) => {
|
|
149
|
-
try {
|
|
150
|
-
const service = getPowerPlatformService();
|
|
151
|
-
const metadata = await service.getEntityMetadata(entityLogicalName);
|
|
152
|
-
// Extract the most useful properties for CRUD operations
|
|
153
|
-
const summary = {
|
|
154
|
-
LogicalName: metadata.LogicalName,
|
|
155
|
-
EntitySetName: metadata.EntitySetName,
|
|
156
|
-
PrimaryIdAttribute: metadata.PrimaryIdAttribute,
|
|
157
|
-
PrimaryNameAttribute: metadata.PrimaryNameAttribute,
|
|
158
|
-
DisplayName: metadata.DisplayName?.UserLocalizedLabel?.Label,
|
|
159
|
-
DisplayCollectionName: metadata.DisplayCollectionName?.UserLocalizedLabel?.Label,
|
|
160
|
-
SchemaName: metadata.SchemaName,
|
|
161
|
-
LogicalCollectionName: metadata.LogicalCollectionName,
|
|
162
|
-
IsCustomEntity: metadata.IsCustomEntity,
|
|
163
|
-
MetadataId: metadata.MetadataId
|
|
164
|
-
};
|
|
165
|
-
return {
|
|
166
|
-
content: [
|
|
167
|
-
{
|
|
168
|
-
type: "text",
|
|
169
|
-
text: `📋 Entity Metadata for '${entityLogicalName}':\n\n` +
|
|
170
|
-
`**EntitySetName (for API):** \`${summary.EntitySetName}\`\n` +
|
|
171
|
-
`**Primary ID Attribute:** \`${summary.PrimaryIdAttribute}\`\n` +
|
|
172
|
-
`**Primary Name Attribute:** \`${summary.PrimaryNameAttribute}\`\n\n` +
|
|
173
|
-
`**Full Details:**\n\`\`\`json\n${JSON.stringify(summary, null, 2)}\n\`\`\`\n\n` +
|
|
174
|
-
`**Usage:** Use \`${summary.EntitySetName}\` as the entityNamePlural parameter for query-records, create-record, update-record, delete-record tools.`,
|
|
175
|
-
},
|
|
176
|
-
],
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
catch (error) {
|
|
180
|
-
console.error("Error getting entity metadata:", error);
|
|
181
|
-
return {
|
|
182
|
-
content: [
|
|
183
|
-
{
|
|
184
|
-
type: "text",
|
|
185
|
-
text: `❌ Failed to get entity metadata: ${error.message}`,
|
|
186
|
-
},
|
|
187
|
-
],
|
|
188
|
-
isError: true,
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
server.tool("get-lookup-target", "Get the target entity information for a lookup field. Returns the SchemaName (for @odata.bind syntax) and EntitySetName needed to set lookup values. Essential for setting lookup fields in create/update operations. No permission flag required (read-only).", {
|
|
193
|
-
entityLogicalName: z
|
|
194
|
-
.string()
|
|
195
|
-
.describe("The logical name of the entity containing the lookup (e.g., 'ste_transaction')"),
|
|
196
|
-
lookupAttributeName: z
|
|
197
|
-
.string()
|
|
198
|
-
.describe("The logical name of the lookup attribute (e.g., 'ste_categoryid', 'parentaccountid')"),
|
|
199
|
-
}, async ({ entityLogicalName, lookupAttributeName }) => {
|
|
200
|
-
try {
|
|
201
|
-
const service = getPowerPlatformService();
|
|
202
|
-
// Get the lookup attribute details including SchemaName
|
|
203
|
-
const attribute = await service.getEntityAttribute(entityLogicalName, lookupAttributeName);
|
|
204
|
-
if (!attribute.Targets || attribute.Targets.length === 0) {
|
|
205
|
-
return {
|
|
206
|
-
content: [
|
|
207
|
-
{
|
|
208
|
-
type: "text",
|
|
209
|
-
text: `❌ Attribute '${lookupAttributeName}' on entity '${entityLogicalName}' is not a lookup field or has no targets.`,
|
|
210
|
-
},
|
|
211
|
-
],
|
|
212
|
-
isError: true,
|
|
213
|
-
};
|
|
52
|
+
},
|
|
53
|
+
checkDeleteEnabled() {
|
|
54
|
+
if (process.env.POWERPLATFORM_ENABLE_DELETE !== 'true') {
|
|
55
|
+
throw new Error('Delete operations are disabled. Set POWERPLATFORM_ENABLE_DELETE=true to enable.');
|
|
214
56
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const targetResults = await Promise.all(attribute.Targets.map(async (targetEntity) => {
|
|
220
|
-
try {
|
|
221
|
-
const targetMetadata = await service.getEntityMetadata(targetEntity);
|
|
222
|
-
// For polymorphic lookups, the navigation property is SchemaName_targetLogicalName
|
|
223
|
-
// For simple lookups, it's just the SchemaName
|
|
224
|
-
const bindingPropertyName = isPolymorphic
|
|
225
|
-
? `${lookupSchemaName}_${targetEntity}`
|
|
226
|
-
: lookupSchemaName;
|
|
227
|
-
return {
|
|
228
|
-
LogicalName: targetEntity,
|
|
229
|
-
EntitySetName: targetMetadata.EntitySetName,
|
|
230
|
-
PrimaryIdAttribute: targetMetadata.PrimaryIdAttribute,
|
|
231
|
-
DisplayName: targetMetadata.DisplayName?.UserLocalizedLabel?.Label,
|
|
232
|
-
BindingPropertyName: bindingPropertyName
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
catch (err) {
|
|
236
|
-
return {
|
|
237
|
-
LogicalName: targetEntity,
|
|
238
|
-
EntitySetName: `${targetEntity}s`, // fallback guess
|
|
239
|
-
BindingPropertyName: isPolymorphic ? `${lookupSchemaName}_${targetEntity}` : lookupSchemaName,
|
|
240
|
-
error: 'Could not fetch metadata'
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
}));
|
|
244
|
-
const primaryTarget = targetResults[0];
|
|
245
|
-
// Build response with lookup attribute info
|
|
246
|
-
let responseText = `📋 Lookup Target for '${lookupAttributeName}' on '${entityLogicalName}':\n\n`;
|
|
247
|
-
responseText += `## Lookup Attribute\n`;
|
|
248
|
-
responseText += `| Property | Value |\n`;
|
|
249
|
-
responseText += `|----------|-------|\n`;
|
|
250
|
-
responseText += `| **SchemaName** | \`${lookupSchemaName}\` |\n`;
|
|
251
|
-
responseText += `| **LogicalName** | \`${lookupAttributeName}\` |\n`;
|
|
252
|
-
responseText += `| **Polymorphic** | ${isPolymorphic ? 'Yes' : 'No'} |\n\n`;
|
|
253
|
-
responseText += `## Target Entity\n`;
|
|
254
|
-
responseText += `| Property | Value |\n`;
|
|
255
|
-
responseText += `|----------|-------|\n`;
|
|
256
|
-
responseText += `| **Entity** | \`${primaryTarget.LogicalName}\` |\n`;
|
|
257
|
-
responseText += `| **EntitySetName** | \`${primaryTarget.EntitySetName}\` |\n`;
|
|
258
|
-
responseText += `| **Primary ID** | \`${primaryTarget.PrimaryIdAttribute}\` |\n\n`;
|
|
259
|
-
responseText += `## Usage\n\n`;
|
|
260
|
-
responseText += `**To set this lookup, use:**\n`;
|
|
261
|
-
responseText += `\`\`\`json\n{\n "${primaryTarget.BindingPropertyName}@odata.bind": "/${primaryTarget.EntitySetName}(<record-guid>)"\n}\n\`\`\`\n\n`;
|
|
262
|
-
responseText += `⚠️ **Important:** The \`@odata.bind\` syntax requires the **SchemaName** (mixed case: \`${lookupSchemaName}\`), NOT the LogicalName (lowercase: \`${lookupAttributeName}\`).\n\n`;
|
|
263
|
-
if (isPolymorphic) {
|
|
264
|
-
responseText += `## Polymorphic Lookup Targets\n\n`;
|
|
265
|
-
responseText += `This lookup can reference ${targetResults.length} different entity types. Use the appropriate binding property for each:\n\n`;
|
|
266
|
-
responseText += `| Target Entity | Binding Property | EntitySetName |\n`;
|
|
267
|
-
responseText += `|---------------|------------------|---------------|\n`;
|
|
268
|
-
for (const target of targetResults) {
|
|
269
|
-
responseText += `| \`${target.LogicalName}\` | \`${target.BindingPropertyName}@odata.bind\` | \`${target.EntitySetName}\` |\n`;
|
|
270
|
-
}
|
|
271
|
-
responseText += `\n**Example for each target:**\n`;
|
|
272
|
-
for (const target of targetResults) {
|
|
273
|
-
responseText += `\`\`\`json\n// To set ${target.LogicalName}:\n{"${target.BindingPropertyName}@odata.bind": "/${target.EntitySetName}(<guid>)"}\n\`\`\`\n`;
|
|
274
|
-
}
|
|
57
|
+
},
|
|
58
|
+
checkActionsEnabled() {
|
|
59
|
+
if (process.env.POWERPLATFORM_ENABLE_ACTIONS !== 'true') {
|
|
60
|
+
throw new Error('Action execution is disabled. Set POWERPLATFORM_ENABLE_ACTIONS=true to enable.');
|
|
275
61
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
{
|
|
279
|
-
type: "text",
|
|
280
|
-
text: responseText,
|
|
281
|
-
},
|
|
282
|
-
],
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
catch (error) {
|
|
286
|
-
console.error("Error getting lookup target:", error);
|
|
287
|
-
return {
|
|
288
|
-
content: [
|
|
289
|
-
{
|
|
290
|
-
type: "text",
|
|
291
|
-
text: `❌ Failed to get lookup target: ${error.message}`,
|
|
292
|
-
},
|
|
293
|
-
],
|
|
294
|
-
isError: true,
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
server.tool("get-flow-runs", "Get the run history for a specific Power Automate flow using the Management API. " +
|
|
299
|
-
"Returns run status, timestamps, trigger info, and error details for failed runs. " +
|
|
300
|
-
"Use this to investigate flow failures during incident triage. No permission flag required (read-only).", {
|
|
301
|
-
flowId: z.string().describe("GUID of the flow (workflowid)"),
|
|
302
|
-
status: z.string().optional().describe("Filter by status: Succeeded, Failed, Running, Waiting, Cancelled"),
|
|
303
|
-
startedAfter: z.string().optional().describe("Only return runs started after this date (ISO 8601 format, e.g., '2026-01-21T00:00:00Z')"),
|
|
304
|
-
startedBefore: z.string().optional().describe("Only return runs started before this date (ISO 8601 format)"),
|
|
305
|
-
maxRecords: z.number().optional().describe("Maximum number of runs to return (default: 50, max: 250)")
|
|
306
|
-
}, async ({ flowId, status, startedAfter, startedBefore, maxRecords }) => {
|
|
307
|
-
try {
|
|
308
|
-
const service = getPowerPlatformService();
|
|
309
|
-
const result = await service.getFlowRuns(flowId, {
|
|
310
|
-
status,
|
|
311
|
-
startedAfter,
|
|
312
|
-
startedBefore,
|
|
313
|
-
maxRecords: maxRecords || 50,
|
|
314
|
-
});
|
|
315
|
-
// Calculate success/failure stats
|
|
316
|
-
const stats = (result.runs || []).reduce((acc, run) => {
|
|
317
|
-
if (run.status === 'Succeeded')
|
|
318
|
-
acc.succeeded++;
|
|
319
|
-
else if (run.status === 'Failed' || run.status === 'Faulted' || run.status === 'TimedOut')
|
|
320
|
-
acc.failed++;
|
|
321
|
-
else if (run.status === 'Running' || run.status === 'Waiting')
|
|
322
|
-
acc.inProgress++;
|
|
323
|
-
else if (run.status === 'Cancelled')
|
|
324
|
-
acc.cancelled++;
|
|
325
|
-
else
|
|
326
|
-
acc.other++;
|
|
327
|
-
return acc;
|
|
328
|
-
}, { succeeded: 0, failed: 0, inProgress: 0, cancelled: 0, other: 0 });
|
|
329
|
-
// Build summary for failed runs
|
|
330
|
-
const failedRuns = result.runs.filter((r) => r.status === 'Failed' || r.error);
|
|
331
|
-
const failedSummary = failedRuns.length > 0
|
|
332
|
-
? `\n\nFailed Runs (${failedRuns.length}):\n` + failedRuns.map((r) => ` - ${r.runId}: ${r.error?.message || 'Unknown error'} (${r.startTime})`).join('\n')
|
|
333
|
-
: '';
|
|
334
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
335
|
-
return {
|
|
336
|
-
content: [{
|
|
337
|
-
type: "text",
|
|
338
|
-
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}`
|
|
339
|
-
}]
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
catch (error) {
|
|
343
|
-
console.error("Error getting flow runs:", error);
|
|
344
|
-
return {
|
|
345
|
-
content: [{
|
|
346
|
-
type: "text",
|
|
347
|
-
text: `❌ Failed to get flow runs: ${error.message}`
|
|
348
|
-
}],
|
|
349
|
-
isError: true
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
server.tool("get-flow-run-details", "Get detailed information about a specific flow run, including action-level outputs and error messages. " +
|
|
354
|
-
"Use this after get-flow-runs to investigate why a specific run failed. " +
|
|
355
|
-
"Returns trigger details, action statuses, and detailed error messages for each action. " +
|
|
356
|
-
"NOTE: Requires Environment Maker role or flow co-owner permissions (uses Management API).", {
|
|
357
|
-
flowId: z.string().describe("GUID of the flow (workflowid)"),
|
|
358
|
-
runId: z.string().describe("GUID of the flow run (from get-flow-runs results)")
|
|
359
|
-
}, async ({ flowId, runId }) => {
|
|
360
|
-
try {
|
|
361
|
-
const service = getPowerPlatformService();
|
|
362
|
-
const result = await service.getFlowRunDetails(flowId, runId);
|
|
363
|
-
// Build action summary
|
|
364
|
-
const actions = result.actions || {};
|
|
365
|
-
const actionList = Object.entries(actions).map(([name, action]) => {
|
|
366
|
-
const status = action.status || 'unknown';
|
|
367
|
-
const error = action.error ? ` - Error: ${JSON.stringify(action.error)}` : '';
|
|
368
|
-
return ` - ${name}: ${status}${error}`;
|
|
369
|
-
}).join('\n');
|
|
370
|
-
const summary = result.actionsSummary || {};
|
|
371
|
-
const statusText = `Status: ${result.status}\nStart: ${result.startTime}\nEnd: ${result.endTime || 'N/A'}`;
|
|
372
|
-
const triggerText = result.trigger ? `\n\nTrigger: ${result.trigger.name} (${result.trigger.status})` : '';
|
|
373
|
-
const actionsText = actionList ? `\n\nActions (${summary.total || 0} total, ${summary.failed || 0} failed):\n${actionList}` : '';
|
|
374
|
-
const resultStr = JSON.stringify(result, null, 2);
|
|
375
|
-
return {
|
|
376
|
-
content: [{
|
|
377
|
-
type: "text",
|
|
378
|
-
text: `Flow Run Details for ${flowId} / ${runId}:\n\n${statusText}${triggerText}${actionsText}\n\nFull Details:\n${resultStr}`
|
|
379
|
-
}]
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
catch (error) {
|
|
383
|
-
console.error("Error getting flow run details:", error);
|
|
384
|
-
const envDebug = `[DEBUG: POWERPLATFORM_ENVIRONMENT_ID=${process.env.POWERPLATFORM_ENVIRONMENT_ID || 'NOT SET'}]`;
|
|
385
|
-
return {
|
|
386
|
-
content: [{
|
|
387
|
-
type: "text",
|
|
388
|
-
text: `❌ Failed to get flow run details: ${error.message}\n\n${envDebug}`
|
|
389
|
-
}],
|
|
390
|
-
isError: true
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
});
|
|
394
|
-
// Data modification tools (require permission flags)
|
|
395
|
-
server.tool("create-record", "Create a new record in Dataverse. Lookup fields accept both logical names (lowercase) and SchemaNames (mixed case) - the server automatically converts to the correct format. Requires POWERPLATFORM_ENABLE_CREATE=true.", {
|
|
396
|
-
entityNamePlural: z
|
|
397
|
-
.string()
|
|
398
|
-
.describe("The plural name of the entity (e.g., 'accounts', 'contacts', 'sic_applications')"),
|
|
399
|
-
data: z
|
|
400
|
-
.record(z.any())
|
|
401
|
-
.describe("Record data as JSON object. Field names must match logical names (e.g., {'name': 'Acme Corp', 'telephone1': '555-1234'}). " +
|
|
402
|
-
"For lookup fields, use '@odata.bind' syntax with either logical names or SchemaNames: {'si_titleid@odata.bind': '/si_titles(guid)'} or {'si_TitleId@odata.bind': '/si_titles(guid)'}. " +
|
|
403
|
-
"For polymorphic lookups, add entity suffix: {'si_customerid_contact@odata.bind': '/contacts(guid)'}. " +
|
|
404
|
-
"For option sets, use integer values."),
|
|
405
|
-
}, async ({ entityNamePlural, data }) => {
|
|
406
|
-
try {
|
|
407
|
-
checkCreateEnabled();
|
|
408
|
-
const service = getPowerPlatformService();
|
|
409
|
-
const result = await service.createRecord(entityNamePlural, data);
|
|
410
|
-
return {
|
|
411
|
-
content: [
|
|
412
|
-
{
|
|
413
|
-
type: "text",
|
|
414
|
-
text: `✅ Record created successfully in ${entityNamePlural}\n\n` +
|
|
415
|
-
`**Record ID:** ${result.id || result[Object.keys(result).find(k => k.endsWith('id')) || ''] || 'N/A'}\n\n` +
|
|
416
|
-
`**Created Record:**\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\``,
|
|
417
|
-
},
|
|
418
|
-
],
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
catch (error) {
|
|
422
|
-
console.error("Error creating record:", error);
|
|
423
|
-
return {
|
|
424
|
-
content: [
|
|
425
|
-
{
|
|
426
|
-
type: "text",
|
|
427
|
-
text: `❌ Failed to create record: ${error.message}`,
|
|
428
|
-
},
|
|
429
|
-
],
|
|
430
|
-
isError: true,
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
});
|
|
434
|
-
server.tool("update-record", "Update an existing record in Dataverse. Lookup fields accept both logical names (lowercase) and SchemaNames (mixed case) - the server automatically converts to the correct format. Requires POWERPLATFORM_ENABLE_UPDATE=true.", {
|
|
435
|
-
entityNamePlural: z
|
|
436
|
-
.string()
|
|
437
|
-
.describe("The plural name of the entity (e.g., 'accounts', 'contacts', 'sic_applications')"),
|
|
438
|
-
recordId: z
|
|
439
|
-
.string()
|
|
440
|
-
.describe("The GUID of the record to update"),
|
|
441
|
-
data: z
|
|
442
|
-
.record(z.any())
|
|
443
|
-
.describe("Partial record data to update (only fields being changed). " +
|
|
444
|
-
"Field names must match logical names. " +
|
|
445
|
-
"For lookup fields, use '@odata.bind' syntax with either logical names or SchemaNames: {'si_titleid@odata.bind': '/si_titles(guid)'} or {'si_TitleId@odata.bind': '/si_titles(guid)'}. " +
|
|
446
|
-
"For option sets, use integer values."),
|
|
447
|
-
}, async ({ entityNamePlural, recordId, data }) => {
|
|
448
|
-
try {
|
|
449
|
-
checkUpdateEnabled();
|
|
450
|
-
const service = getPowerPlatformService();
|
|
451
|
-
const result = await service.updateRecord(entityNamePlural, recordId, data);
|
|
452
|
-
return {
|
|
453
|
-
content: [
|
|
454
|
-
{
|
|
455
|
-
type: "text",
|
|
456
|
-
text: `✅ Record updated successfully in ${entityNamePlural}\n\n` +
|
|
457
|
-
`**Record ID:** ${recordId}\n\n` +
|
|
458
|
-
`**Updated Record:**\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\``,
|
|
459
|
-
},
|
|
460
|
-
],
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
catch (error) {
|
|
464
|
-
console.error("Error updating record:", error);
|
|
465
|
-
return {
|
|
466
|
-
content: [
|
|
467
|
-
{
|
|
468
|
-
type: "text",
|
|
469
|
-
text: `❌ Failed to update record: ${error.message}`,
|
|
470
|
-
},
|
|
471
|
-
],
|
|
472
|
-
isError: true,
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
});
|
|
476
|
-
server.tool("delete-record", "Delete a record from Dataverse. Requires POWERPLATFORM_ENABLE_DELETE=true. WARNING: This operation is permanent and cannot be undone.", {
|
|
477
|
-
entityNamePlural: z
|
|
478
|
-
.string()
|
|
479
|
-
.describe("The plural name of the entity (e.g., 'accounts', 'contacts', 'sic_applications')"),
|
|
480
|
-
recordId: z
|
|
481
|
-
.string()
|
|
482
|
-
.describe("The GUID of the record to delete"),
|
|
483
|
-
confirm: z
|
|
484
|
-
.boolean()
|
|
485
|
-
.optional()
|
|
486
|
-
.describe("Confirmation flag - must be true to proceed with deletion (safety check)"),
|
|
487
|
-
}, async ({ entityNamePlural, recordId, confirm }) => {
|
|
488
|
-
try {
|
|
489
|
-
checkDeleteEnabled();
|
|
490
|
-
// Require explicit confirmation for deletion
|
|
491
|
-
if (confirm !== true) {
|
|
492
|
-
return {
|
|
493
|
-
content: [
|
|
494
|
-
{
|
|
495
|
-
type: "text",
|
|
496
|
-
text: `⚠️ Delete operation requires explicit confirmation.\n\n` +
|
|
497
|
-
`You are about to delete record **${recordId}** from **${entityNamePlural}**.\n\n` +
|
|
498
|
-
`This operation is **permanent** and **cannot be undone**.\n\n` +
|
|
499
|
-
`To proceed, call this tool again with \`confirm: true\`.`,
|
|
500
|
-
},
|
|
501
|
-
],
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
const service = getPowerPlatformService();
|
|
505
|
-
await service.deleteRecord(entityNamePlural, recordId);
|
|
506
|
-
return {
|
|
507
|
-
content: [
|
|
508
|
-
{
|
|
509
|
-
type: "text",
|
|
510
|
-
text: `✅ Record deleted successfully\n\n` +
|
|
511
|
-
`**Entity:** ${entityNamePlural}\n` +
|
|
512
|
-
`**Record ID:** ${recordId}\n\n` +
|
|
513
|
-
`⚠️ This operation is permanent.`,
|
|
514
|
-
},
|
|
515
|
-
],
|
|
516
|
-
};
|
|
517
|
-
}
|
|
518
|
-
catch (error) {
|
|
519
|
-
console.error("Error deleting record:", error);
|
|
520
|
-
return {
|
|
521
|
-
content: [
|
|
522
|
-
{
|
|
523
|
-
type: "text",
|
|
524
|
-
text: `❌ Failed to delete record: ${error.message}`,
|
|
525
|
-
},
|
|
526
|
-
],
|
|
527
|
-
isError: true,
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
});
|
|
531
|
-
server.tool("execute-action", "Execute a Custom API or Action in Dataverse. Supports both unbound actions (not tied to any entity) and bound actions (tied to a specific record). Requires POWERPLATFORM_ENABLE_ACTIONS=true.", {
|
|
532
|
-
actionName: z
|
|
533
|
-
.string()
|
|
534
|
-
.describe("The unique name of the Custom API or Action to execute (e.g., 'new_MyCustomAction', 'WhoAmI', 'WinOpportunity'). " +
|
|
535
|
-
"For bound actions, do NOT include the 'Microsoft.Dynamics.CRM.' prefix - it will be added automatically."),
|
|
536
|
-
parameters: z
|
|
537
|
-
.record(z.any())
|
|
538
|
-
.optional()
|
|
539
|
-
.describe("Input parameters for the action as JSON object. Parameter names and types must match the action definition. " +
|
|
540
|
-
"Example: { 'Amount': 100, 'Description': 'Test' }. Leave empty for actions with no input parameters."),
|
|
541
|
-
boundTo: z
|
|
542
|
-
.object({
|
|
543
|
-
entityNamePlural: z.string().describe("The plural name of the entity (e.g., 'opportunities', 'accounts')"),
|
|
544
|
-
recordId: z.string().describe("The GUID of the record to bind the action to"),
|
|
545
|
-
})
|
|
546
|
-
.optional()
|
|
547
|
-
.describe("For bound actions only: specify the entity and record the action is bound to. " +
|
|
548
|
-
"Leave empty for unbound actions. Example: { entityNamePlural: 'opportunities', recordId: '12345678-...' }"),
|
|
549
|
-
}, async ({ actionName, parameters, boundTo }) => {
|
|
550
|
-
try {
|
|
551
|
-
checkActionsEnabled();
|
|
552
|
-
const service = getPowerPlatformService();
|
|
553
|
-
const result = await service.executeAction(actionName, parameters, boundTo);
|
|
554
|
-
const boundInfo = boundTo
|
|
555
|
-
? `\n**Bound To:** ${boundTo.entityNamePlural}(${boundTo.recordId})`
|
|
556
|
-
: '\n**Type:** Unbound action';
|
|
557
|
-
const paramsInfo = parameters && Object.keys(parameters).length > 0
|
|
558
|
-
? `\n**Input Parameters:**\n\`\`\`json\n${JSON.stringify(parameters, null, 2)}\n\`\`\``
|
|
559
|
-
: '';
|
|
560
|
-
const responseInfo = result && Object.keys(result).length > 0
|
|
561
|
-
? `\n**Response:**\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\``
|
|
562
|
-
: '\n**Response:** (no output parameters)';
|
|
563
|
-
return {
|
|
564
|
-
content: [
|
|
565
|
-
{
|
|
566
|
-
type: "text",
|
|
567
|
-
text: `✅ Action executed successfully\n\n` +
|
|
568
|
-
`**Action:** ${actionName}` +
|
|
569
|
-
boundInfo +
|
|
570
|
-
paramsInfo +
|
|
571
|
-
responseInfo,
|
|
572
|
-
},
|
|
573
|
-
],
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
catch (error) {
|
|
577
|
-
console.error("Error executing action:", error);
|
|
578
|
-
return {
|
|
579
|
-
content: [
|
|
580
|
-
{
|
|
581
|
-
type: "text",
|
|
582
|
-
text: `❌ Failed to execute action: ${error.message}`,
|
|
583
|
-
},
|
|
584
|
-
],
|
|
585
|
-
isError: true,
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
});
|
|
589
|
-
console.error(`✅ powerplatform-data tools registered (${9} tools)`);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
590
64
|
}
|
|
591
|
-
|
|
592
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Register powerplatform-data tools with an MCP server.
|
|
67
|
+
* Backward-compatible API for the meta package.
|
|
68
|
+
* @param server - MCP server instance
|
|
69
|
+
* @param service - Optional pre-initialized PowerPlatformService (for testing)
|
|
70
|
+
*/
|
|
71
|
+
export function registerPowerplatformDataTools(server, service) {
|
|
72
|
+
const ctx = createServiceContext(service);
|
|
73
|
+
registerAllTools(server, ctx);
|
|
74
|
+
}
|
|
75
|
+
// Backward-compatible exports
|
|
76
|
+
export { PowerPlatformService } from './PowerPlatformService.js';
|
|
77
|
+
/**
|
|
78
|
+
* Standalone CLI server (when run directly)
|
|
79
|
+
* Uses realpathSync to resolve symlinks created by npx
|
|
80
|
+
*/
|
|
593
81
|
if (import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
594
82
|
const loadEnv = createEnvLoader();
|
|
595
83
|
loadEnv();
|
|
@@ -604,6 +92,6 @@ if (import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
|
604
92
|
console.error('Failed to start powerplatform-data MCP server:', error);
|
|
605
93
|
process.exit(1);
|
|
606
94
|
});
|
|
607
|
-
console.error('powerplatform-data
|
|
95
|
+
console.error('@mcp-consultant-tools/powerplatform-data server running on stdio');
|
|
608
96
|
}
|
|
609
97
|
//# sourceMappingURL=index.js.map
|