powerplatform-mcp 0.4.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -49
- package/build/PowerPlatformClient.js +83 -0
- package/build/PowerPlatformService.js +2 -32
- package/build/index.js +46 -657
- package/build/models/ApiCollectionResponse.js +1 -0
- package/build/models/index.js +1 -0
- package/build/prompts/entityPrompts.js +244 -0
- package/build/prompts/index.js +9 -0
- package/build/prompts/templates.js +34 -0
- package/build/services/EntityService.js +128 -0
- package/build/services/OptionSetService.js +18 -0
- package/build/services/PluginService.js +288 -0
- package/build/services/RecordService.js +29 -0
- package/build/services/index.js +4 -0
- package/build/tools/entityTools.js +156 -0
- package/build/tools/index.js +17 -0
- package/build/tools/optionSetTools.js +43 -0
- package/build/tools/pluginTools.js +188 -0
- package/build/tools/recordTools.js +87 -0
- package/build/types.js +1 -0
- package/package.json +12 -7
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PluginService
|
|
3
|
+
*
|
|
4
|
+
* Read-only service for plugin assemblies, types, steps, images, and trace logs.
|
|
5
|
+
*/
|
|
6
|
+
export class PluginService {
|
|
7
|
+
client;
|
|
8
|
+
constructor(client) {
|
|
9
|
+
this.client = client;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Get all plugin assemblies in the environment
|
|
13
|
+
*/
|
|
14
|
+
async getPluginAssemblies(includeManaged = false, maxRecords = 100) {
|
|
15
|
+
const managedFilter = includeManaged ? '' : '$filter=ismanaged eq false&';
|
|
16
|
+
const assemblies = await this.client.get(`api/data/v9.2/pluginassemblies?${managedFilter}$select=pluginassemblyid,name,version,culture,publickeytoken,isolationmode,sourcetype,major,minor,createdon,modifiedon,ismanaged,ishidden&$expand=modifiedby($select=fullname)&$orderby=name&$top=${maxRecords}`);
|
|
17
|
+
// Filter out hidden assemblies and format results
|
|
18
|
+
const formattedAssemblies = assemblies.value
|
|
19
|
+
.filter((assembly) => {
|
|
20
|
+
const isHidden = assembly.ishidden?.Value !== undefined
|
|
21
|
+
? assembly.ishidden.Value
|
|
22
|
+
: assembly.ishidden;
|
|
23
|
+
return !isHidden;
|
|
24
|
+
})
|
|
25
|
+
.map((assembly) => ({
|
|
26
|
+
pluginassemblyid: assembly.pluginassemblyid,
|
|
27
|
+
name: assembly.name,
|
|
28
|
+
version: assembly.version,
|
|
29
|
+
isolationMode: assembly.isolationmode === 1
|
|
30
|
+
? 'None'
|
|
31
|
+
: assembly.isolationmode === 2
|
|
32
|
+
? 'Sandbox'
|
|
33
|
+
: 'External',
|
|
34
|
+
isManaged: assembly.ismanaged,
|
|
35
|
+
modifiedOn: assembly.modifiedon,
|
|
36
|
+
modifiedBy: assembly.modifiedby?.fullname,
|
|
37
|
+
major: assembly.major,
|
|
38
|
+
minor: assembly.minor,
|
|
39
|
+
}));
|
|
40
|
+
return {
|
|
41
|
+
totalCount: formattedAssemblies.length,
|
|
42
|
+
assemblies: formattedAssemblies,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get a plugin assembly by name with all related plugin types, steps, and images
|
|
47
|
+
*/
|
|
48
|
+
async getPluginAssemblyComplete(assemblyName, includeDisabled = false) {
|
|
49
|
+
// Get the plugin assembly
|
|
50
|
+
const assemblies = await this.client.get(`api/data/v9.2/pluginassemblies?$filter=name eq '${assemblyName}'&$select=pluginassemblyid,name,version,culture,publickeytoken,isolationmode,sourcetype,major,minor,createdon,modifiedon,ismanaged,ishidden,description&$expand=modifiedby($select=fullname)`);
|
|
51
|
+
if (!assemblies.value || assemblies.value.length === 0) {
|
|
52
|
+
throw new Error(`Plugin assembly '${assemblyName}' not found`);
|
|
53
|
+
}
|
|
54
|
+
const assembly = assemblies.value[0];
|
|
55
|
+
const assemblyId = assembly.pluginassemblyid;
|
|
56
|
+
// Get plugin types
|
|
57
|
+
const pluginTypes = await this.client.get(`api/data/v9.2/plugintypes?$filter=_pluginassemblyid_value eq ${assemblyId}&$select=plugintypeid,typename,friendlyname,name,assemblyname,description,workflowactivitygroupname`);
|
|
58
|
+
// Get all steps for each plugin type
|
|
59
|
+
const pluginTypeIds = pluginTypes.value.map((pt) => pt.plugintypeid);
|
|
60
|
+
let allSteps = [];
|
|
61
|
+
if (pluginTypeIds.length > 0) {
|
|
62
|
+
const statusFilter = includeDisabled ? '' : ' and statuscode eq 1';
|
|
63
|
+
const typeFilter = pluginTypeIds
|
|
64
|
+
.map((id) => `_plugintypeid_value eq ${id}`)
|
|
65
|
+
.join(' or ');
|
|
66
|
+
const steps = await this.client.get(`api/data/v9.2/sdkmessageprocessingsteps?$filter=(${typeFilter})${statusFilter}&$select=sdkmessageprocessingstepid,name,stage,mode,rank,statuscode,asyncautodelete,filteringattributes,supporteddeployment,configuration,description,invocationsource,_plugintypeid_value,_sdkmessagefilterid_value,_impersonatinguserid_value,_eventhandler_value&$expand=sdkmessageid($select=name),plugintypeid($select=typename),impersonatinguserid($select=fullname),modifiedby($select=fullname),sdkmessagefilterid($select=primaryobjecttypecode)&$orderby=stage,rank`);
|
|
67
|
+
allSteps = steps.value;
|
|
68
|
+
}
|
|
69
|
+
// Get all images for these steps
|
|
70
|
+
const stepIds = allSteps.map((s) => s.sdkmessageprocessingstepid);
|
|
71
|
+
let allImages = [];
|
|
72
|
+
if (stepIds.length > 0) {
|
|
73
|
+
const imageFilter = stepIds
|
|
74
|
+
.map((id) => `_sdkmessageprocessingstepid_value eq ${id}`)
|
|
75
|
+
.join(' or ');
|
|
76
|
+
const images = await this.client.get(`api/data/v9.2/sdkmessageprocessingstepimages?$filter=${imageFilter}&$select=sdkmessageprocessingstepimageid,name,imagetype,messagepropertyname,entityalias,attributes,_sdkmessageprocessingstepid_value`);
|
|
77
|
+
allImages = images.value;
|
|
78
|
+
}
|
|
79
|
+
// Attach images to their respective steps
|
|
80
|
+
const stepsWithImages = allSteps.map((step) => ({
|
|
81
|
+
...step,
|
|
82
|
+
images: allImages.filter((img) => img._sdkmessageprocessingstepid_value ===
|
|
83
|
+
step.sdkmessageprocessingstepid),
|
|
84
|
+
}));
|
|
85
|
+
// Validation checks
|
|
86
|
+
const validation = {
|
|
87
|
+
hasDisabledSteps: allSteps.some((s) => s.statuscode !== 1),
|
|
88
|
+
hasAsyncSteps: allSteps.some((s) => s.mode === 1),
|
|
89
|
+
hasSyncSteps: allSteps.some((s) => s.mode === 0),
|
|
90
|
+
stepsWithoutFilteringAttributes: stepsWithImages
|
|
91
|
+
.filter((s) => {
|
|
92
|
+
const sdkmsg = s.sdkmessageid;
|
|
93
|
+
const msgName = sdkmsg?.name;
|
|
94
|
+
return ((msgName === 'Update' || msgName === 'Delete') &&
|
|
95
|
+
!s.filteringattributes);
|
|
96
|
+
})
|
|
97
|
+
.map((s) => s.name),
|
|
98
|
+
stepsWithoutImages: stepsWithImages
|
|
99
|
+
.filter((s) => {
|
|
100
|
+
const sdkmsg = s.sdkmessageid;
|
|
101
|
+
const msgName = sdkmsg?.name;
|
|
102
|
+
return (s.images.length === 0 &&
|
|
103
|
+
(msgName === 'Update' || msgName === 'Delete'));
|
|
104
|
+
})
|
|
105
|
+
.map((s) => s.name),
|
|
106
|
+
potentialIssues: [],
|
|
107
|
+
};
|
|
108
|
+
if (validation.stepsWithoutFilteringAttributes.length > 0) {
|
|
109
|
+
validation.potentialIssues.push(`${validation.stepsWithoutFilteringAttributes.length} Update/Delete steps without filtering attributes (performance concern)`);
|
|
110
|
+
}
|
|
111
|
+
if (validation.stepsWithoutImages.length > 0) {
|
|
112
|
+
validation.potentialIssues.push(`${validation.stepsWithoutImages.length} Update/Delete steps without images (may need entity data)`);
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
assembly,
|
|
116
|
+
pluginTypes: pluginTypes.value,
|
|
117
|
+
steps: stepsWithImages,
|
|
118
|
+
validation,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get all plugins that execute on a specific entity
|
|
123
|
+
*/
|
|
124
|
+
async getEntityPluginPipeline(entityName, messageFilter, includeDisabled = false) {
|
|
125
|
+
const statusFilter = includeDisabled ? '' : ' and statuscode eq 1';
|
|
126
|
+
const msgFilter = messageFilter
|
|
127
|
+
? ` and sdkmessageid/name eq '${messageFilter}'`
|
|
128
|
+
: '';
|
|
129
|
+
const steps = await this.client.get(`api/data/v9.2/sdkmessageprocessingsteps?$filter=sdkmessagefilterid/primaryobjecttypecode eq '${entityName}'${statusFilter}${msgFilter}&$select=sdkmessageprocessingstepid,name,stage,mode,rank,statuscode,asyncautodelete,filteringattributes,supporteddeployment,configuration,description,_plugintypeid_value,_sdkmessagefilterid_value,_impersonatinguserid_value&$expand=sdkmessageid($select=name),plugintypeid($select=typename),impersonatinguserid($select=fullname),sdkmessagefilterid($select=primaryobjecttypecode)&$orderby=stage,rank`);
|
|
130
|
+
// Get assembly information for each plugin type
|
|
131
|
+
const pluginTypeIds = [
|
|
132
|
+
...new Set(steps.value
|
|
133
|
+
.map((s) => s._plugintypeid_value)
|
|
134
|
+
.filter((id) => id != null)),
|
|
135
|
+
];
|
|
136
|
+
const assemblyMap = new Map();
|
|
137
|
+
for (const typeId of pluginTypeIds) {
|
|
138
|
+
const pluginType = await this.client.get(`api/data/v9.2/plugintypes(${typeId})?$expand=pluginassemblyid($select=name,version)`);
|
|
139
|
+
assemblyMap.set(typeId, pluginType.pluginassemblyid);
|
|
140
|
+
}
|
|
141
|
+
// Get images for all steps
|
|
142
|
+
const stepIds = steps.value.map((s) => s.sdkmessageprocessingstepid);
|
|
143
|
+
let allImages = [];
|
|
144
|
+
if (stepIds.length > 0) {
|
|
145
|
+
const imageFilter = stepIds
|
|
146
|
+
.map((id) => `_sdkmessageprocessingstepid_value eq ${id}`)
|
|
147
|
+
.join(' or ');
|
|
148
|
+
const images = await this.client.get(`api/data/v9.2/sdkmessageprocessingstepimages?$filter=${imageFilter}&$select=sdkmessageprocessingstepimageid,name,imagetype,messagepropertyname,entityalias,attributes,_sdkmessageprocessingstepid_value`);
|
|
149
|
+
allImages = images.value;
|
|
150
|
+
}
|
|
151
|
+
// Format steps
|
|
152
|
+
const formattedSteps = steps.value.map((step) => {
|
|
153
|
+
const assembly = assemblyMap.get(step._plugintypeid_value);
|
|
154
|
+
const images = allImages.filter((img) => img._sdkmessageprocessingstepid_value ===
|
|
155
|
+
step.sdkmessageprocessingstepid);
|
|
156
|
+
return {
|
|
157
|
+
sdkmessageprocessingstepid: step.sdkmessageprocessingstepid,
|
|
158
|
+
name: step.name,
|
|
159
|
+
stage: step.stage,
|
|
160
|
+
stageName: step.stage === 10
|
|
161
|
+
? 'PreValidation'
|
|
162
|
+
: step.stage === 20
|
|
163
|
+
? 'PreOperation'
|
|
164
|
+
: 'PostOperation',
|
|
165
|
+
mode: step.mode,
|
|
166
|
+
modeName: step.mode === 0 ? 'Synchronous' : 'Asynchronous',
|
|
167
|
+
rank: step.rank,
|
|
168
|
+
message: step.sdkmessageid?.name,
|
|
169
|
+
pluginType: step.plugintypeid?.typename,
|
|
170
|
+
assemblyName: assembly?.name,
|
|
171
|
+
assemblyVersion: assembly?.version,
|
|
172
|
+
filteringAttributes: step.filteringattributes
|
|
173
|
+
? step.filteringattributes.split(',')
|
|
174
|
+
: [],
|
|
175
|
+
statuscode: step.statuscode,
|
|
176
|
+
enabled: step.statuscode === 1,
|
|
177
|
+
deployment: step.supporteddeployment === 0
|
|
178
|
+
? 'Server'
|
|
179
|
+
: step.supporteddeployment === 1
|
|
180
|
+
? 'Offline'
|
|
181
|
+
: 'Both',
|
|
182
|
+
impersonatingUser: step.impersonatinguserid
|
|
183
|
+
?.fullname,
|
|
184
|
+
hasPreImage: images.some((img) => img.imagetype === 0 || img.imagetype === 2),
|
|
185
|
+
hasPostImage: images.some((img) => img.imagetype === 1 || img.imagetype === 2),
|
|
186
|
+
images,
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
// Organize by message
|
|
190
|
+
const messageGroups = new Map();
|
|
191
|
+
formattedSteps.forEach((step) => {
|
|
192
|
+
if (!messageGroups.has(step.message)) {
|
|
193
|
+
messageGroups.set(step.message, {
|
|
194
|
+
messageName: step.message,
|
|
195
|
+
stages: {
|
|
196
|
+
preValidation: [],
|
|
197
|
+
preOperation: [],
|
|
198
|
+
postOperation: [],
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
const msg = messageGroups.get(step.message);
|
|
203
|
+
if (step.stage === 10)
|
|
204
|
+
msg.stages.preValidation.push(step);
|
|
205
|
+
else if (step.stage === 20)
|
|
206
|
+
msg.stages.preOperation.push(step);
|
|
207
|
+
else if (step.stage === 40)
|
|
208
|
+
msg.stages.postOperation.push(step);
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
entity: entityName,
|
|
212
|
+
messages: Array.from(messageGroups.values()),
|
|
213
|
+
steps: formattedSteps,
|
|
214
|
+
executionOrder: formattedSteps.map((s) => s.name),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Get plugin trace logs with filtering
|
|
219
|
+
*/
|
|
220
|
+
async getPluginTraceLogs(options) {
|
|
221
|
+
const { entityName, messageName, correlationId, pluginStepId, exceptionOnly = false, hoursBack = 24, maxRecords = 50, } = options;
|
|
222
|
+
// Build filter
|
|
223
|
+
const filters = [];
|
|
224
|
+
const dateThreshold = new Date();
|
|
225
|
+
dateThreshold.setHours(dateThreshold.getHours() - hoursBack);
|
|
226
|
+
filters.push(`createdon gt ${dateThreshold.toISOString()}`);
|
|
227
|
+
if (entityName)
|
|
228
|
+
filters.push(`primaryentity eq '${entityName}'`);
|
|
229
|
+
if (messageName)
|
|
230
|
+
filters.push(`messagename eq '${messageName}'`);
|
|
231
|
+
if (correlationId)
|
|
232
|
+
filters.push(`correlationid eq '${correlationId}'`);
|
|
233
|
+
if (pluginStepId)
|
|
234
|
+
filters.push(`_sdkmessageprocessingstepid_value eq ${pluginStepId}`);
|
|
235
|
+
if (exceptionOnly)
|
|
236
|
+
filters.push(`exceptiondetails ne null`);
|
|
237
|
+
const filterString = filters.join(' and ');
|
|
238
|
+
const logs = await this.client.get(`api/data/v9.2/plugintracelogs?$filter=${filterString}&$orderby=createdon desc&$top=${maxRecords}`);
|
|
239
|
+
// Parse logs for better readability
|
|
240
|
+
const parsedLogs = logs.value.map((log) => ({
|
|
241
|
+
...log,
|
|
242
|
+
modeName: log.mode === 0 ? 'Synchronous' : 'Asynchronous',
|
|
243
|
+
operationTypeName: this.getOperationTypeName(log.operationtype),
|
|
244
|
+
parsed: {
|
|
245
|
+
hasException: !!log.exceptiondetails,
|
|
246
|
+
exceptionType: log.exceptiondetails
|
|
247
|
+
? this.extractExceptionType(log.exceptiondetails)
|
|
248
|
+
: null,
|
|
249
|
+
exceptionMessage: log.exceptiondetails
|
|
250
|
+
? this.extractExceptionMessage(log.exceptiondetails)
|
|
251
|
+
: null,
|
|
252
|
+
stackTrace: log.exceptiondetails,
|
|
253
|
+
},
|
|
254
|
+
}));
|
|
255
|
+
return {
|
|
256
|
+
totalCount: parsedLogs.length,
|
|
257
|
+
logs: parsedLogs,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
getOperationTypeName(operationType) {
|
|
261
|
+
const types = {
|
|
262
|
+
0: 'None',
|
|
263
|
+
1: 'Create',
|
|
264
|
+
2: 'Update',
|
|
265
|
+
3: 'Delete',
|
|
266
|
+
4: 'Retrieve',
|
|
267
|
+
5: 'RetrieveMultiple',
|
|
268
|
+
6: 'Associate',
|
|
269
|
+
7: 'Disassociate',
|
|
270
|
+
};
|
|
271
|
+
return types[operationType] || 'Unknown';
|
|
272
|
+
}
|
|
273
|
+
extractExceptionType(exceptionDetails) {
|
|
274
|
+
const match = exceptionDetails.match(/^([^:]+):/);
|
|
275
|
+
return match ? match[1].trim() : null;
|
|
276
|
+
}
|
|
277
|
+
extractExceptionMessage(exceptionDetails) {
|
|
278
|
+
const lines = exceptionDetails.split('\n');
|
|
279
|
+
if (lines.length > 0) {
|
|
280
|
+
const firstLine = lines[0];
|
|
281
|
+
const colonIndex = firstLine.indexOf(':');
|
|
282
|
+
if (colonIndex > 0) {
|
|
283
|
+
return firstLine.substring(colonIndex + 1).trim();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for record operations.
|
|
3
|
+
* Handles CRUD operations on Dataverse records.
|
|
4
|
+
*/
|
|
5
|
+
export class RecordService {
|
|
6
|
+
client;
|
|
7
|
+
constructor(client) {
|
|
8
|
+
this.client = client;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Get a specific record by entity name (plural) and ID
|
|
12
|
+
* @param entityNamePlural The plural name of the entity (e.g., 'accounts', 'contacts')
|
|
13
|
+
* @param recordId The GUID of the record
|
|
14
|
+
* @returns The record data
|
|
15
|
+
*/
|
|
16
|
+
async getRecord(entityNamePlural, recordId) {
|
|
17
|
+
return this.client.get(`api/data/v9.2/${entityNamePlural}(${recordId})`);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Query records using entity name (plural) and a filter expression
|
|
21
|
+
* @param entityNamePlural The plural name of the entity (e.g., 'accounts', 'contacts')
|
|
22
|
+
* @param filter OData filter expression (e.g., "name eq 'test'")
|
|
23
|
+
* @param maxRecords Maximum number of records to retrieve (default: 50)
|
|
24
|
+
* @returns Filtered list of records
|
|
25
|
+
*/
|
|
26
|
+
async queryRecords(entityNamePlural, filter, maxRecords = 50) {
|
|
27
|
+
return this.client.get(`api/data/v9.2/${entityNamePlural}?$filter=${encodeURIComponent(filter)}&$top=${maxRecords}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Register entity metadata tools with the MCP server.
|
|
4
|
+
*/
|
|
5
|
+
export function registerEntityTools(server, ctx) {
|
|
6
|
+
// Get Entity Metadata
|
|
7
|
+
server.registerTool("get-entity-metadata", {
|
|
8
|
+
title: "Get Entity Metadata",
|
|
9
|
+
description: "Get metadata about a PowerPlatform entity",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
entityName: z.string().describe("The logical name of the entity"),
|
|
12
|
+
},
|
|
13
|
+
outputSchema: z.object({
|
|
14
|
+
entityName: z.string(),
|
|
15
|
+
metadata: z.any(),
|
|
16
|
+
}),
|
|
17
|
+
}, async ({ entityName }) => {
|
|
18
|
+
try {
|
|
19
|
+
const service = ctx.getEntityService();
|
|
20
|
+
const metadata = await service.getEntityMetadata(entityName);
|
|
21
|
+
return {
|
|
22
|
+
structuredContent: { entityName, metadata },
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: "text",
|
|
26
|
+
text: `Entity metadata for '${entityName}':\n\n${JSON.stringify(metadata, null, 2)}`,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error("Error getting entity metadata:", error);
|
|
33
|
+
return {
|
|
34
|
+
content: [
|
|
35
|
+
{
|
|
36
|
+
type: "text",
|
|
37
|
+
text: `Failed to get entity metadata: ${error.message}`,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
// Get Entity Attributes
|
|
44
|
+
server.registerTool("get-entity-attributes", {
|
|
45
|
+
title: "Get Entity Attributes",
|
|
46
|
+
description: "Get attributes/fields of a PowerPlatform entity",
|
|
47
|
+
inputSchema: {
|
|
48
|
+
entityName: z.string().describe("The logical name of the entity"),
|
|
49
|
+
},
|
|
50
|
+
outputSchema: z.object({
|
|
51
|
+
entityName: z.string(),
|
|
52
|
+
attributes: z.any(),
|
|
53
|
+
}),
|
|
54
|
+
}, async ({ entityName }) => {
|
|
55
|
+
try {
|
|
56
|
+
const service = ctx.getEntityService();
|
|
57
|
+
const attributes = await service.getEntityAttributes(entityName);
|
|
58
|
+
return {
|
|
59
|
+
structuredContent: { entityName, attributes },
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: `Attributes for entity '${entityName}':\n\n${JSON.stringify(attributes, null, 2)}`,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error("Error getting entity attributes:", error);
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: `Failed to get entity attributes: ${error.message}`,
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
// Get Entity Attribute
|
|
81
|
+
server.registerTool("get-entity-attribute", {
|
|
82
|
+
title: "Get Entity Attribute",
|
|
83
|
+
description: "Get a specific attribute/field of a PowerPlatform entity",
|
|
84
|
+
inputSchema: {
|
|
85
|
+
entityName: z.string().describe("The logical name of the entity"),
|
|
86
|
+
attributeName: z.string().describe("The logical name of the attribute"),
|
|
87
|
+
},
|
|
88
|
+
outputSchema: z.object({
|
|
89
|
+
entityName: z.string(),
|
|
90
|
+
attributeName: z.string(),
|
|
91
|
+
attribute: z.any(),
|
|
92
|
+
}),
|
|
93
|
+
}, async ({ entityName, attributeName }) => {
|
|
94
|
+
try {
|
|
95
|
+
const service = ctx.getEntityService();
|
|
96
|
+
const attribute = await service.getEntityAttribute(entityName, attributeName);
|
|
97
|
+
return {
|
|
98
|
+
structuredContent: { entityName, attributeName, attribute },
|
|
99
|
+
content: [
|
|
100
|
+
{
|
|
101
|
+
type: "text",
|
|
102
|
+
text: `Attribute '${attributeName}' for entity '${entityName}':\n\n${JSON.stringify(attribute, null, 2)}`,
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
console.error("Error getting entity attribute:", error);
|
|
109
|
+
return {
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: `Failed to get entity attribute: ${error.message}`,
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
// Get Entity Relationships
|
|
120
|
+
server.registerTool("get-entity-relationships", {
|
|
121
|
+
title: "Get Entity Relationships",
|
|
122
|
+
description: "Get relationships (one-to-many and many-to-many) for a PowerPlatform entity",
|
|
123
|
+
inputSchema: {
|
|
124
|
+
entityName: z.string().describe("The logical name of the entity"),
|
|
125
|
+
},
|
|
126
|
+
outputSchema: z.object({
|
|
127
|
+
entityName: z.string(),
|
|
128
|
+
relationships: z.any(),
|
|
129
|
+
}),
|
|
130
|
+
}, async ({ entityName }) => {
|
|
131
|
+
try {
|
|
132
|
+
const service = ctx.getEntityService();
|
|
133
|
+
const relationships = await service.getEntityRelationships(entityName);
|
|
134
|
+
return {
|
|
135
|
+
structuredContent: { entityName, relationships },
|
|
136
|
+
content: [
|
|
137
|
+
{
|
|
138
|
+
type: "text",
|
|
139
|
+
text: `Relationships for entity '${entityName}':\n\n${JSON.stringify(relationships, null, 2)}`,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.error("Error getting entity relationships:", error);
|
|
146
|
+
return {
|
|
147
|
+
content: [
|
|
148
|
+
{
|
|
149
|
+
type: "text",
|
|
150
|
+
text: `Failed to get entity relationships: ${error.message}`,
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { registerEntityTools } from "./entityTools.js";
|
|
2
|
+
import { registerRecordTools } from "./recordTools.js";
|
|
3
|
+
import { registerOptionSetTools } from "./optionSetTools.js";
|
|
4
|
+
import { registerPluginTools } from "./pluginTools.js";
|
|
5
|
+
export { registerEntityTools } from "./entityTools.js";
|
|
6
|
+
export { registerRecordTools } from "./recordTools.js";
|
|
7
|
+
export { registerOptionSetTools } from "./optionSetTools.js";
|
|
8
|
+
export { registerPluginTools } from "./pluginTools.js";
|
|
9
|
+
/**
|
|
10
|
+
* Register all tools with the MCP server.
|
|
11
|
+
*/
|
|
12
|
+
export function registerAllTools(server, ctx) {
|
|
13
|
+
registerEntityTools(server, ctx);
|
|
14
|
+
registerRecordTools(server, ctx);
|
|
15
|
+
registerOptionSetTools(server, ctx);
|
|
16
|
+
registerPluginTools(server, ctx);
|
|
17
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Register option set tools with the MCP server.
|
|
4
|
+
*/
|
|
5
|
+
export function registerOptionSetTools(server, ctx) {
|
|
6
|
+
// Get Global Option Set
|
|
7
|
+
server.registerTool("get-global-option-set", {
|
|
8
|
+
title: "Get Global Option Set",
|
|
9
|
+
description: "Get a global option set definition by name",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
optionSetName: z.string().describe("The name of the global option set"),
|
|
12
|
+
},
|
|
13
|
+
outputSchema: z.object({
|
|
14
|
+
optionSetName: z.string(),
|
|
15
|
+
optionSet: z.any(),
|
|
16
|
+
}),
|
|
17
|
+
}, async ({ optionSetName }) => {
|
|
18
|
+
try {
|
|
19
|
+
const service = ctx.getOptionSetService();
|
|
20
|
+
const optionSet = await service.getGlobalOptionSet(optionSetName);
|
|
21
|
+
return {
|
|
22
|
+
structuredContent: { optionSetName, optionSet },
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: "text",
|
|
26
|
+
text: `Global option set '${optionSetName}':\n\n${JSON.stringify(optionSet, null, 2)}`,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error("Error getting global option set:", error);
|
|
33
|
+
return {
|
|
34
|
+
content: [
|
|
35
|
+
{
|
|
36
|
+
type: "text",
|
|
37
|
+
text: `Failed to get global option set: ${error.message}`,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|