powerplatform-mcp 0.4.4 → 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/index.js +47 -632
- 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
package/build/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { PowerPlatformClient } from "./PowerPlatformClient.js";
|
|
5
|
+
import { EntityService, RecordService, OptionSetService, PluginService } from "./services/index.js";
|
|
6
|
+
import { registerAllTools } from "./tools/index.js";
|
|
7
|
+
import { registerAllPrompts } from "./prompts/index.js";
|
|
6
8
|
// Environment configuration
|
|
7
|
-
// These values can be set in environment variables or loaded from a configuration file
|
|
8
9
|
const POWERPLATFORM_CONFIG = {
|
|
9
10
|
organizationUrl: process.env.POWERPLATFORM_URL || "",
|
|
10
11
|
clientId: process.env.POWERPLATFORM_CLIENT_ID || "",
|
|
@@ -16,10 +17,15 @@ const server = new McpServer({
|
|
|
16
17
|
name: "powerplatform-mcp",
|
|
17
18
|
version: "1.0.0",
|
|
18
19
|
});
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
// Service instances (lazy initialized)
|
|
21
|
+
let powerPlatformClient = null;
|
|
22
|
+
let entityService = null;
|
|
23
|
+
let recordService = null;
|
|
24
|
+
let optionSetService = null;
|
|
25
|
+
let pluginService = null;
|
|
26
|
+
// Function to initialize the PowerPlatform client on demand
|
|
27
|
+
function getClient() {
|
|
28
|
+
if (!powerPlatformClient) {
|
|
23
29
|
// Check if configuration is complete
|
|
24
30
|
const missingConfig = [];
|
|
25
31
|
if (!POWERPLATFORM_CONFIG.organizationUrl)
|
|
@@ -33,637 +39,46 @@ function getPowerPlatformService() {
|
|
|
33
39
|
if (missingConfig.length > 0) {
|
|
34
40
|
throw new Error(`Missing PowerPlatform configuration: ${missingConfig.join(", ")}. Set these in environment variables.`);
|
|
35
41
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
console.error("PowerPlatform service initialized");
|
|
42
|
+
powerPlatformClient = new PowerPlatformClient(POWERPLATFORM_CONFIG);
|
|
43
|
+
console.error("PowerPlatform client initialized");
|
|
39
44
|
}
|
|
40
|
-
return
|
|
45
|
+
return powerPlatformClient;
|
|
41
46
|
}
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
`This is an overview of the '${entityName}' entity in Microsoft Power Platform/Dataverse:\n\n` +
|
|
47
|
-
`### Entity Details\n{{entity_details}}\n\n` +
|
|
48
|
-
`### Attributes\n{{key_attributes}}\n\n` +
|
|
49
|
-
`### Relationships\n{{relationships}}\n\n` +
|
|
50
|
-
`You can query this entity using OData filters against the plural name.`,
|
|
51
|
-
ATTRIBUTE_DETAILS: (entityName, attributeName) => `## Attribute: ${attributeName}\n\n` +
|
|
52
|
-
`Details for the '${attributeName}' attribute of the '${entityName}' entity:\n\n` +
|
|
53
|
-
`{{attribute_details}}\n\n` +
|
|
54
|
-
`### Usage Notes\n` +
|
|
55
|
-
`- Data Type: {{data_type}}\n` +
|
|
56
|
-
`- Required: {{required}}\n` +
|
|
57
|
-
`- Max Length: {{max_length}}`,
|
|
58
|
-
// Query builder prompts
|
|
59
|
-
QUERY_TEMPLATE: (entityNamePlural) => `## OData Query Template for ${entityNamePlural}\n\n` +
|
|
60
|
-
`Use this template to build queries against the ${entityNamePlural} entity:\n\n` +
|
|
61
|
-
`\`\`\`\n${entityNamePlural}?$select={{selected_fields}}&$filter={{filter_conditions}}&$orderby={{order_by}}&$top={{max_records}}\n\`\`\`\n\n` +
|
|
62
|
-
`### Common Filter Examples\n` +
|
|
63
|
-
`- Equals: \`name eq 'Contoso'\`\n` +
|
|
64
|
-
`- Contains: \`contains(name, 'Contoso')\`\n` +
|
|
65
|
-
`- Greater than date: \`createdon gt 2023-01-01T00:00:00Z\`\n` +
|
|
66
|
-
`- Multiple conditions: \`name eq 'Contoso' and statecode eq 0\``,
|
|
67
|
-
// Relationship exploration prompts
|
|
68
|
-
RELATIONSHIP_MAP: (entityName) => `## Relationship Map for ${entityName}\n\n` +
|
|
69
|
-
`This shows all relationships for the '${entityName}' entity:\n\n` +
|
|
70
|
-
`### One-to-Many Relationships (${entityName} as Primary)\n{{one_to_many_primary}}\n\n` +
|
|
71
|
-
`### One-to-Many Relationships (${entityName} as Related)\n{{one_to_many_related}}\n\n` +
|
|
72
|
-
`### Many-to-Many Relationships\n{{many_to_many}}\n\n`
|
|
73
|
-
};
|
|
74
|
-
// Register prompts with the server using the correct method signature
|
|
75
|
-
// Entity Overview Prompt
|
|
76
|
-
server.prompt("entity-overview", "Get an overview of a Power Platform entity", {
|
|
77
|
-
entityName: z.string().describe("The logical name of the entity")
|
|
78
|
-
}, async (args) => {
|
|
79
|
-
try {
|
|
80
|
-
const service = getPowerPlatformService();
|
|
81
|
-
const entityName = args.entityName;
|
|
82
|
-
// Get entity metadata and key attributes
|
|
83
|
-
const [metadata, attributes] = await Promise.all([
|
|
84
|
-
service.getEntityMetadata(entityName),
|
|
85
|
-
service.getEntityAttributes(entityName)
|
|
86
|
-
]);
|
|
87
|
-
// Format entity details
|
|
88
|
-
const entityDetails = `- Display Name: ${metadata.DisplayName?.UserLocalizedLabel?.Label || entityName}\n` +
|
|
89
|
-
`- Schema Name: ${metadata.SchemaName}\n` +
|
|
90
|
-
`- Description: ${metadata.Description?.UserLocalizedLabel?.Label || 'No description'}\n` +
|
|
91
|
-
`- Primary Key: ${metadata.PrimaryIdAttribute}\n` +
|
|
92
|
-
`- Primary Name: ${metadata.PrimaryNameAttribute}`;
|
|
93
|
-
// Get key attributes
|
|
94
|
-
const keyAttributes = attributes.value
|
|
95
|
-
.map((attr) => {
|
|
96
|
-
const attrType = attr["@odata.type"] || attr.odata?.type || "Unknown type";
|
|
97
|
-
return `- ${attr.LogicalName}: ${attrType}`;
|
|
98
|
-
})
|
|
99
|
-
.join('\n');
|
|
100
|
-
// Get relationships summary
|
|
101
|
-
const relationships = await service.getEntityRelationships(entityName);
|
|
102
|
-
const oneToManyCount = relationships.oneToMany.value.length;
|
|
103
|
-
const manyToManyCount = relationships.manyToMany.value.length;
|
|
104
|
-
const relationshipsSummary = `- One-to-Many Relationships: ${oneToManyCount}\n` +
|
|
105
|
-
`- Many-to-Many Relationships: ${manyToManyCount}`;
|
|
106
|
-
let promptContent = powerPlatformPrompts.ENTITY_OVERVIEW(entityName);
|
|
107
|
-
promptContent = promptContent
|
|
108
|
-
.replace('{{entity_details}}', entityDetails)
|
|
109
|
-
.replace('{{key_attributes}}', keyAttributes)
|
|
110
|
-
.replace('{{relationships}}', relationshipsSummary);
|
|
111
|
-
return {
|
|
112
|
-
messages: [
|
|
113
|
-
{
|
|
114
|
-
role: "assistant",
|
|
115
|
-
content: {
|
|
116
|
-
type: "text",
|
|
117
|
-
text: promptContent
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
]
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
catch (error) {
|
|
124
|
-
console.error(`Error handling entity-overview prompt:`, error);
|
|
125
|
-
return {
|
|
126
|
-
messages: [
|
|
127
|
-
{
|
|
128
|
-
role: "assistant",
|
|
129
|
-
content: {
|
|
130
|
-
type: "text",
|
|
131
|
-
text: `Error: ${error.message}`
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
]
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
// Attribute Details Prompt
|
|
139
|
-
server.prompt("attribute-details", "Get detailed information about a specific entity attribute/field", {
|
|
140
|
-
entityName: z.string().describe("The logical name of the entity"),
|
|
141
|
-
attributeName: z.string().describe("The logical name of the attribute"),
|
|
142
|
-
}, async (args) => {
|
|
143
|
-
try {
|
|
144
|
-
const service = getPowerPlatformService();
|
|
145
|
-
const { entityName, attributeName } = args;
|
|
146
|
-
// Get attribute details
|
|
147
|
-
const attribute = await service.getEntityAttribute(entityName, attributeName);
|
|
148
|
-
// Format attribute details
|
|
149
|
-
const attrDetails = `- Display Name: ${attribute.DisplayName?.UserLocalizedLabel?.Label || attributeName}\n` +
|
|
150
|
-
`- Description: ${attribute.Description?.UserLocalizedLabel?.Label || 'No description'}\n` +
|
|
151
|
-
`- Type: ${attribute.AttributeType}\n` +
|
|
152
|
-
`- Format: ${attribute.Format || 'N/A'}\n` +
|
|
153
|
-
`- Is Required: ${attribute.RequiredLevel?.Value || 'No'}\n` +
|
|
154
|
-
`- Is Searchable: ${attribute.IsValidForAdvancedFind || false}`;
|
|
155
|
-
let promptContent = powerPlatformPrompts.ATTRIBUTE_DETAILS(entityName, attributeName);
|
|
156
|
-
promptContent = promptContent
|
|
157
|
-
.replace('{{attribute_details}}', attrDetails)
|
|
158
|
-
.replace('{{data_type}}', attribute.AttributeType)
|
|
159
|
-
.replace('{{required}}', attribute.RequiredLevel?.Value || 'No')
|
|
160
|
-
.replace('{{max_length}}', attribute.MaxLength || 'N/A');
|
|
161
|
-
return {
|
|
162
|
-
messages: [
|
|
163
|
-
{
|
|
164
|
-
role: "assistant",
|
|
165
|
-
content: {
|
|
166
|
-
type: "text",
|
|
167
|
-
text: promptContent
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
]
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
catch (error) {
|
|
174
|
-
console.error(`Error handling attribute-details prompt:`, error);
|
|
175
|
-
return {
|
|
176
|
-
messages: [
|
|
177
|
-
{
|
|
178
|
-
role: "assistant",
|
|
179
|
-
content: {
|
|
180
|
-
type: "text",
|
|
181
|
-
text: `Error: ${error.message}`
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
]
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
// Query Template Prompt
|
|
189
|
-
server.prompt("query-template", "Get a template for querying a Power Platform entity", {
|
|
190
|
-
entityName: z.string().describe("The logical name of the entity"),
|
|
191
|
-
}, async (args) => {
|
|
192
|
-
try {
|
|
193
|
-
const service = getPowerPlatformService();
|
|
194
|
-
const entityName = args.entityName;
|
|
195
|
-
// Get entity metadata to determine plural name
|
|
196
|
-
const metadata = await service.getEntityMetadata(entityName);
|
|
197
|
-
const entityNamePlural = metadata.EntitySetName;
|
|
198
|
-
// Get a few important fields for the select example
|
|
199
|
-
const attributes = await service.getEntityAttributes(entityName);
|
|
200
|
-
const selectFields = attributes.value
|
|
201
|
-
.filter((attr) => attr.IsValidForRead === true && !attr.AttributeOf)
|
|
202
|
-
.slice(0, 5) // Just take first 5 for example
|
|
203
|
-
.map((attr) => attr.LogicalName)
|
|
204
|
-
.join(',');
|
|
205
|
-
let promptContent = powerPlatformPrompts.QUERY_TEMPLATE(entityNamePlural);
|
|
206
|
-
promptContent = promptContent
|
|
207
|
-
.replace('{{selected_fields}}', selectFields)
|
|
208
|
-
.replace('{{filter_conditions}}', `${metadata.PrimaryNameAttribute} eq 'Example'`)
|
|
209
|
-
.replace('{{order_by}}', `${metadata.PrimaryNameAttribute} asc`)
|
|
210
|
-
.replace('{{max_records}}', '50');
|
|
211
|
-
return {
|
|
212
|
-
messages: [
|
|
213
|
-
{
|
|
214
|
-
role: "assistant",
|
|
215
|
-
content: {
|
|
216
|
-
type: "text",
|
|
217
|
-
text: promptContent
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
]
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
catch (error) {
|
|
224
|
-
console.error(`Error handling query-template prompt:`, error);
|
|
225
|
-
return {
|
|
226
|
-
messages: [
|
|
227
|
-
{
|
|
228
|
-
role: "assistant",
|
|
229
|
-
content: {
|
|
230
|
-
type: "text",
|
|
231
|
-
text: `Error: ${error.message}`
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
]
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
// Relationship Map Prompt
|
|
239
|
-
server.prompt("relationship-map", "Get a list of relationships for a Power Platform entity", {
|
|
240
|
-
entityName: z.string().describe("The logical name of the entity"),
|
|
241
|
-
}, async (args) => {
|
|
242
|
-
try {
|
|
243
|
-
const service = getPowerPlatformService();
|
|
244
|
-
const entityName = args.entityName;
|
|
245
|
-
// Get relationships
|
|
246
|
-
const relationships = await service.getEntityRelationships(entityName);
|
|
247
|
-
// Format one-to-many relationships where this entity is primary
|
|
248
|
-
const oneToManyPrimary = relationships.oneToMany.value
|
|
249
|
-
.filter((rel) => rel.ReferencingEntity !== entityName)
|
|
250
|
-
.map((rel) => `- ${rel.SchemaName}: ${entityName} (1) → ${rel.ReferencingEntity} (N)`)
|
|
251
|
-
.join('\n');
|
|
252
|
-
// Format one-to-many relationships where this entity is related
|
|
253
|
-
const oneToManyRelated = relationships.oneToMany.value
|
|
254
|
-
.filter((rel) => rel.ReferencingEntity === entityName)
|
|
255
|
-
.map((rel) => `- ${rel.SchemaName}: ${rel.ReferencedEntity} (1) → ${entityName} (N)`)
|
|
256
|
-
.join('\n');
|
|
257
|
-
// Format many-to-many relationships
|
|
258
|
-
const manyToMany = relationships.manyToMany.value
|
|
259
|
-
.map((rel) => {
|
|
260
|
-
const otherEntity = rel.Entity1LogicalName === entityName ? rel.Entity2LogicalName : rel.Entity1LogicalName;
|
|
261
|
-
return `- ${rel.SchemaName}: ${entityName} (N) ↔ ${otherEntity} (N)`;
|
|
262
|
-
})
|
|
263
|
-
.join('\n');
|
|
264
|
-
let promptContent = powerPlatformPrompts.RELATIONSHIP_MAP(entityName);
|
|
265
|
-
promptContent = promptContent
|
|
266
|
-
.replace('{{one_to_many_primary}}', oneToManyPrimary || 'None found')
|
|
267
|
-
.replace('{{one_to_many_related}}', oneToManyRelated || 'None found')
|
|
268
|
-
.replace('{{many_to_many}}', manyToMany || 'None found');
|
|
269
|
-
return {
|
|
270
|
-
messages: [
|
|
271
|
-
{
|
|
272
|
-
role: "assistant",
|
|
273
|
-
content: {
|
|
274
|
-
type: "text",
|
|
275
|
-
text: promptContent
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
]
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
catch (error) {
|
|
282
|
-
console.error(`Error handling relationship-map prompt:`, error);
|
|
283
|
-
return {
|
|
284
|
-
messages: [
|
|
285
|
-
{
|
|
286
|
-
role: "assistant",
|
|
287
|
-
content: {
|
|
288
|
-
type: "text",
|
|
289
|
-
text: `Error: ${error.message}`
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
]
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
// PowerPlatform entity metadata
|
|
297
|
-
server.tool("get-entity-metadata", "Get metadata about a PowerPlatform entity", {
|
|
298
|
-
entityName: z.string().describe("The logical name of the entity"),
|
|
299
|
-
}, async ({ entityName }) => {
|
|
300
|
-
try {
|
|
301
|
-
// Get or initialize PowerPlatformService
|
|
302
|
-
const service = getPowerPlatformService();
|
|
303
|
-
const metadata = await service.getEntityMetadata(entityName);
|
|
304
|
-
// Format the metadata as a string for text display
|
|
305
|
-
const metadataStr = JSON.stringify(metadata, null, 2);
|
|
306
|
-
return {
|
|
307
|
-
content: [
|
|
308
|
-
{
|
|
309
|
-
type: "text",
|
|
310
|
-
text: `Entity metadata for '${entityName}':\n\n${metadataStr}`,
|
|
311
|
-
},
|
|
312
|
-
],
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
catch (error) {
|
|
316
|
-
console.error("Error getting entity metadata:", error);
|
|
317
|
-
return {
|
|
318
|
-
content: [
|
|
319
|
-
{
|
|
320
|
-
type: "text",
|
|
321
|
-
text: `Failed to get entity metadata: ${error.message}`,
|
|
322
|
-
},
|
|
323
|
-
],
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
// PowerPlatform entity attributes
|
|
328
|
-
server.tool("get-entity-attributes", "Get attributes/fields of a PowerPlatform entity", {
|
|
329
|
-
entityName: z.string().describe("The logical name of the entity"),
|
|
330
|
-
}, async ({ entityName }) => {
|
|
331
|
-
try {
|
|
332
|
-
// Get or initialize PowerPlatformService
|
|
333
|
-
const service = getPowerPlatformService();
|
|
334
|
-
const attributes = await service.getEntityAttributes(entityName);
|
|
335
|
-
// Format the attributes as a string for text display
|
|
336
|
-
const attributesStr = JSON.stringify(attributes, null, 2);
|
|
337
|
-
return {
|
|
338
|
-
content: [
|
|
339
|
-
{
|
|
340
|
-
type: "text",
|
|
341
|
-
text: `Attributes for entity '${entityName}':\n\n${attributesStr}`,
|
|
342
|
-
},
|
|
343
|
-
],
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
catch (error) {
|
|
347
|
-
console.error("Error getting entity attributes:", error);
|
|
348
|
-
return {
|
|
349
|
-
content: [
|
|
350
|
-
{
|
|
351
|
-
type: "text",
|
|
352
|
-
text: `Failed to get entity attributes: ${error.message}`,
|
|
353
|
-
},
|
|
354
|
-
],
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
// PowerPlatform specific entity attribute
|
|
359
|
-
server.tool("get-entity-attribute", "Get a specific attribute/field of a PowerPlatform entity", {
|
|
360
|
-
entityName: z.string().describe("The logical name of the entity"),
|
|
361
|
-
attributeName: z.string().describe("The logical name of the attribute")
|
|
362
|
-
}, async ({ entityName, attributeName }) => {
|
|
363
|
-
try {
|
|
364
|
-
// Get or initialize PowerPlatformService
|
|
365
|
-
const service = getPowerPlatformService();
|
|
366
|
-
const attribute = await service.getEntityAttribute(entityName, attributeName);
|
|
367
|
-
// Format the attribute as a string for text display
|
|
368
|
-
const attributeStr = JSON.stringify(attribute, null, 2);
|
|
369
|
-
return {
|
|
370
|
-
content: [
|
|
371
|
-
{
|
|
372
|
-
type: "text",
|
|
373
|
-
text: `Attribute '${attributeName}' for entity '${entityName}':\n\n${attributeStr}`,
|
|
374
|
-
},
|
|
375
|
-
],
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
catch (error) {
|
|
379
|
-
console.error("Error getting entity attribute:", error);
|
|
380
|
-
return {
|
|
381
|
-
content: [
|
|
382
|
-
{
|
|
383
|
-
type: "text",
|
|
384
|
-
text: `Failed to get entity attribute: ${error.message}`,
|
|
385
|
-
},
|
|
386
|
-
],
|
|
387
|
-
};
|
|
47
|
+
// Service getters with lazy initialization
|
|
48
|
+
function getEntityService() {
|
|
49
|
+
if (!entityService) {
|
|
50
|
+
entityService = new EntityService(getClient());
|
|
388
51
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
try {
|
|
395
|
-
// Get or initialize PowerPlatformService
|
|
396
|
-
const service = getPowerPlatformService();
|
|
397
|
-
const relationships = await service.getEntityRelationships(entityName);
|
|
398
|
-
// Format the relationships as a string for text display
|
|
399
|
-
const relationshipsStr = JSON.stringify(relationships, null, 2);
|
|
400
|
-
return {
|
|
401
|
-
content: [
|
|
402
|
-
{
|
|
403
|
-
type: "text",
|
|
404
|
-
text: `Relationships for entity '${entityName}':\n\n${relationshipsStr}`,
|
|
405
|
-
},
|
|
406
|
-
],
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
catch (error) {
|
|
410
|
-
console.error("Error getting entity relationships:", error);
|
|
411
|
-
return {
|
|
412
|
-
content: [
|
|
413
|
-
{
|
|
414
|
-
type: "text",
|
|
415
|
-
text: `Failed to get entity relationships: ${error.message}`,
|
|
416
|
-
},
|
|
417
|
-
],
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
// PowerPlatform global option set
|
|
422
|
-
server.tool("get-global-option-set", "Get a global option set definition by name", {
|
|
423
|
-
optionSetName: z.string().describe("The name of the global option set"),
|
|
424
|
-
}, async ({ optionSetName }) => {
|
|
425
|
-
try {
|
|
426
|
-
// Get or initialize PowerPlatformService
|
|
427
|
-
const service = getPowerPlatformService();
|
|
428
|
-
const optionSet = await service.getGlobalOptionSet(optionSetName);
|
|
429
|
-
// Format the option set as a string for text display
|
|
430
|
-
const optionSetStr = JSON.stringify(optionSet, null, 2);
|
|
431
|
-
return {
|
|
432
|
-
content: [
|
|
433
|
-
{
|
|
434
|
-
type: "text",
|
|
435
|
-
text: `Global option set '${optionSetName}':\n\n${optionSetStr}`,
|
|
436
|
-
},
|
|
437
|
-
],
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
catch (error) {
|
|
441
|
-
console.error("Error getting global option set:", error);
|
|
442
|
-
return {
|
|
443
|
-
content: [
|
|
444
|
-
{
|
|
445
|
-
type: "text",
|
|
446
|
-
text: `Failed to get global option set: ${error.message}`,
|
|
447
|
-
},
|
|
448
|
-
],
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
// PowerPlatform record by ID
|
|
453
|
-
server.tool("get-record", "Get a specific record by entity name (plural) and ID", {
|
|
454
|
-
entityNamePlural: z.string().describe("The plural name of the entity (e.g., 'accounts', 'contacts')"),
|
|
455
|
-
recordId: z.string().describe("The GUID of the record"),
|
|
456
|
-
}, async ({ entityNamePlural, recordId }) => {
|
|
457
|
-
try {
|
|
458
|
-
// Get or initialize PowerPlatformService
|
|
459
|
-
const service = getPowerPlatformService();
|
|
460
|
-
const record = await service.getRecord(entityNamePlural, recordId);
|
|
461
|
-
// Format the record as a string for text display
|
|
462
|
-
const recordStr = JSON.stringify(record, null, 2);
|
|
463
|
-
return {
|
|
464
|
-
content: [
|
|
465
|
-
{
|
|
466
|
-
type: "text",
|
|
467
|
-
text: `Record from '${entityNamePlural}' with ID '${recordId}':\n\n${recordStr}`,
|
|
468
|
-
},
|
|
469
|
-
],
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
catch (error) {
|
|
473
|
-
console.error("Error getting record:", error);
|
|
474
|
-
return {
|
|
475
|
-
content: [
|
|
476
|
-
{
|
|
477
|
-
type: "text",
|
|
478
|
-
text: `Failed to get record: ${error.message}`,
|
|
479
|
-
},
|
|
480
|
-
],
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
});
|
|
484
|
-
// PowerPlatform query records with filter
|
|
485
|
-
server.tool("query-records", "Query records using an OData filter expression", {
|
|
486
|
-
entityNamePlural: z.string().describe("The plural name of the entity (e.g., 'accounts', 'contacts')"),
|
|
487
|
-
filter: z.string().describe("OData filter expression (e.g., \"name eq 'test'\" or \"createdon gt 2023-01-01\")"),
|
|
488
|
-
maxRecords: z.number().optional().describe("Maximum number of records to retrieve (default: 50)"),
|
|
489
|
-
}, async ({ entityNamePlural, filter, maxRecords }) => {
|
|
490
|
-
try {
|
|
491
|
-
// Get or initialize PowerPlatformService
|
|
492
|
-
const service = getPowerPlatformService();
|
|
493
|
-
const records = await service.queryRecords(entityNamePlural, filter, maxRecords || 50);
|
|
494
|
-
// Format the records as a string for text display
|
|
495
|
-
const recordsStr = JSON.stringify(records, null, 2);
|
|
496
|
-
const recordCount = records.value?.length || 0;
|
|
497
|
-
return {
|
|
498
|
-
content: [
|
|
499
|
-
{
|
|
500
|
-
type: "text",
|
|
501
|
-
text: `Retrieved ${recordCount} records from '${entityNamePlural}' with filter '${filter}':\n\n${recordsStr}`,
|
|
502
|
-
},
|
|
503
|
-
],
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
catch (error) {
|
|
507
|
-
console.error("Error querying records:", error);
|
|
508
|
-
return {
|
|
509
|
-
content: [
|
|
510
|
-
{
|
|
511
|
-
type: "text",
|
|
512
|
-
text: `Failed to query records: ${error.message}`,
|
|
513
|
-
},
|
|
514
|
-
],
|
|
515
|
-
};
|
|
52
|
+
return entityService;
|
|
53
|
+
}
|
|
54
|
+
function getRecordService() {
|
|
55
|
+
if (!recordService) {
|
|
56
|
+
recordService = new RecordService(getClient());
|
|
516
57
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
"ATTRIBUTE_DETAILS",
|
|
523
|
-
"QUERY_TEMPLATE",
|
|
524
|
-
"RELATIONSHIP_MAP"
|
|
525
|
-
]).describe("The type of prompt template to use"),
|
|
526
|
-
entityName: z.string().describe("The logical name of the entity"),
|
|
527
|
-
attributeName: z.string().optional().describe("The logical name of the attribute (required for ATTRIBUTE_DETAILS prompt)"),
|
|
528
|
-
}, async ({ promptType, entityName, attributeName }) => {
|
|
529
|
-
try {
|
|
530
|
-
// Get or initialize PowerPlatformService
|
|
531
|
-
const service = getPowerPlatformService();
|
|
532
|
-
let promptContent = "";
|
|
533
|
-
let replacements = {};
|
|
534
|
-
switch (promptType) {
|
|
535
|
-
case "ENTITY_OVERVIEW": {
|
|
536
|
-
// Get entity metadata and key attributes
|
|
537
|
-
const [metadata, attributes] = await Promise.all([
|
|
538
|
-
service.getEntityMetadata(entityName),
|
|
539
|
-
service.getEntityAttributes(entityName)
|
|
540
|
-
]);
|
|
541
|
-
// Format entity details
|
|
542
|
-
const entityDetails = `- Display Name: ${metadata.DisplayName?.UserLocalizedLabel?.Label || entityName}\n` +
|
|
543
|
-
`- Schema Name: ${metadata.SchemaName}\n` +
|
|
544
|
-
`- Description: ${metadata.Description?.UserLocalizedLabel?.Label || 'No description'}\n` +
|
|
545
|
-
`- Primary Key: ${metadata.PrimaryIdAttribute}\n` +
|
|
546
|
-
`- Primary Name: ${metadata.PrimaryNameAttribute}`;
|
|
547
|
-
// Get key attributes
|
|
548
|
-
const keyAttributes = attributes.value
|
|
549
|
-
//.slice(0, 10) // Limit to first 10 important attributes
|
|
550
|
-
.map((attr) => {
|
|
551
|
-
const attrType = attr["@odata.type"] || attr.odata?.type || "Unknown type";
|
|
552
|
-
return `- ${attr.LogicalName}: ${attrType}`;
|
|
553
|
-
})
|
|
554
|
-
.join('\n');
|
|
555
|
-
// Get relationships summary
|
|
556
|
-
const relationships = await service.getEntityRelationships(entityName);
|
|
557
|
-
const oneToManyCount = relationships.oneToMany.value.length;
|
|
558
|
-
const manyToManyCount = relationships.manyToMany.value.length;
|
|
559
|
-
const relationshipsSummary = `- One-to-Many Relationships: ${oneToManyCount}\n` +
|
|
560
|
-
`- Many-to-Many Relationships: ${manyToManyCount}`;
|
|
561
|
-
promptContent = powerPlatformPrompts.ENTITY_OVERVIEW(entityName);
|
|
562
|
-
replacements = {
|
|
563
|
-
'{{entity_details}}': entityDetails,
|
|
564
|
-
'{{key_attributes}}': keyAttributes,
|
|
565
|
-
'{{relationships}}': relationshipsSummary
|
|
566
|
-
};
|
|
567
|
-
break;
|
|
568
|
-
}
|
|
569
|
-
case "ATTRIBUTE_DETAILS": {
|
|
570
|
-
if (!attributeName) {
|
|
571
|
-
throw new Error("attributeName is required for ATTRIBUTE_DETAILS prompt");
|
|
572
|
-
}
|
|
573
|
-
// Get attribute details
|
|
574
|
-
const attribute = await service.getEntityAttribute(entityName, attributeName);
|
|
575
|
-
// Format attribute details
|
|
576
|
-
const attrDetails = `- Display Name: ${attribute.DisplayName?.UserLocalizedLabel?.Label || attributeName}\n` +
|
|
577
|
-
`- Description: ${attribute.Description?.UserLocalizedLabel?.Label || 'No description'}\n` +
|
|
578
|
-
`- Type: ${attribute.AttributeType}\n` +
|
|
579
|
-
`- Format: ${attribute.Format || 'N/A'}\n` +
|
|
580
|
-
`- Is Required: ${attribute.RequiredLevel?.Value || 'No'}\n` +
|
|
581
|
-
`- Is Searchable: ${attribute.IsValidForAdvancedFind || false}`;
|
|
582
|
-
promptContent = powerPlatformPrompts.ATTRIBUTE_DETAILS(entityName, attributeName);
|
|
583
|
-
replacements = {
|
|
584
|
-
'{{attribute_details}}': attrDetails,
|
|
585
|
-
'{{data_type}}': attribute.AttributeType,
|
|
586
|
-
'{{required}}': attribute.RequiredLevel?.Value || 'No',
|
|
587
|
-
'{{max_length}}': attribute.MaxLength || 'N/A'
|
|
588
|
-
};
|
|
589
|
-
break;
|
|
590
|
-
}
|
|
591
|
-
case "QUERY_TEMPLATE": {
|
|
592
|
-
// Get entity metadata to determine plural name
|
|
593
|
-
const metadata = await service.getEntityMetadata(entityName);
|
|
594
|
-
const entityNamePlural = metadata.EntitySetName;
|
|
595
|
-
// Get a few important fields for the select example
|
|
596
|
-
const attributes = await service.getEntityAttributes(entityName);
|
|
597
|
-
const selectFields = attributes.value
|
|
598
|
-
.slice(0, 5) // Just take first 5 for example
|
|
599
|
-
.map((attr) => attr.LogicalName)
|
|
600
|
-
.join(',');
|
|
601
|
-
promptContent = powerPlatformPrompts.QUERY_TEMPLATE(entityNamePlural);
|
|
602
|
-
replacements = {
|
|
603
|
-
'{{selected_fields}}': selectFields,
|
|
604
|
-
'{{filter_conditions}}': `${metadata.PrimaryNameAttribute} eq 'Example'`,
|
|
605
|
-
'{{order_by}}': `${metadata.PrimaryNameAttribute} asc`,
|
|
606
|
-
'{{max_records}}': '50'
|
|
607
|
-
};
|
|
608
|
-
break;
|
|
609
|
-
}
|
|
610
|
-
case "RELATIONSHIP_MAP": {
|
|
611
|
-
// Get relationships
|
|
612
|
-
const relationships = await service.getEntityRelationships(entityName);
|
|
613
|
-
// Format one-to-many relationships where this entity is primary
|
|
614
|
-
const oneToManyPrimary = relationships.oneToMany.value
|
|
615
|
-
.filter((rel) => rel.ReferencingEntity !== entityName)
|
|
616
|
-
//.slice(0, 10) // Limit to 10 for readability
|
|
617
|
-
.map((rel) => `- ${rel.SchemaName}: ${entityName} (1) → ${rel.ReferencingEntity} (N)`)
|
|
618
|
-
.join('\n');
|
|
619
|
-
// Format one-to-many relationships where this entity is related
|
|
620
|
-
const oneToManyRelated = relationships.oneToMany.value
|
|
621
|
-
.filter((rel) => rel.ReferencingEntity === entityName)
|
|
622
|
-
//.slice(0, 10) // Limit to 10 for readability
|
|
623
|
-
.map((rel) => `- ${rel.SchemaName}: ${rel.ReferencedEntity} (1) → ${entityName} (N)`)
|
|
624
|
-
.join('\n');
|
|
625
|
-
// Format many-to-many relationships
|
|
626
|
-
const manyToMany = relationships.manyToMany.value
|
|
627
|
-
//.slice(0, 10) // Limit to 10 for readability
|
|
628
|
-
.map((rel) => {
|
|
629
|
-
const otherEntity = rel.Entity1LogicalName === entityName ? rel.Entity2LogicalName : rel.Entity1LogicalName;
|
|
630
|
-
return `- ${rel.SchemaName}: ${entityName} (N) ↔ ${otherEntity} (N)`;
|
|
631
|
-
})
|
|
632
|
-
.join('\n');
|
|
633
|
-
promptContent = powerPlatformPrompts.RELATIONSHIP_MAP(entityName);
|
|
634
|
-
replacements = {
|
|
635
|
-
'{{one_to_many_primary}}': oneToManyPrimary || 'None found',
|
|
636
|
-
'{{one_to_many_related}}': oneToManyRelated || 'None found',
|
|
637
|
-
'{{many_to_many}}': manyToMany || 'None found'
|
|
638
|
-
};
|
|
639
|
-
break;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
// Replace all placeholders in the template
|
|
643
|
-
for (const [placeholder, value] of Object.entries(replacements)) {
|
|
644
|
-
promptContent = promptContent.replace(placeholder, value);
|
|
645
|
-
}
|
|
646
|
-
return {
|
|
647
|
-
content: [
|
|
648
|
-
{
|
|
649
|
-
type: "text",
|
|
650
|
-
text: promptContent,
|
|
651
|
-
},
|
|
652
|
-
],
|
|
653
|
-
};
|
|
58
|
+
return recordService;
|
|
59
|
+
}
|
|
60
|
+
function getOptionSetService() {
|
|
61
|
+
if (!optionSetService) {
|
|
62
|
+
optionSetService = new OptionSetService(getClient());
|
|
654
63
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
type: "text",
|
|
661
|
-
text: `Failed to use PowerPlatform prompt: ${error.message}`,
|
|
662
|
-
},
|
|
663
|
-
],
|
|
664
|
-
};
|
|
64
|
+
return optionSetService;
|
|
65
|
+
}
|
|
66
|
+
function getPluginService() {
|
|
67
|
+
if (!pluginService) {
|
|
68
|
+
pluginService = new PluginService(getClient());
|
|
665
69
|
}
|
|
666
|
-
|
|
70
|
+
return pluginService;
|
|
71
|
+
}
|
|
72
|
+
// Create service context for tools and prompts
|
|
73
|
+
const ctx = {
|
|
74
|
+
getEntityService,
|
|
75
|
+
getRecordService,
|
|
76
|
+
getOptionSetService,
|
|
77
|
+
getPluginService,
|
|
78
|
+
};
|
|
79
|
+
// Register all tools and prompts
|
|
80
|
+
registerAllTools(server, ctx);
|
|
81
|
+
registerAllPrompts(server, ctx);
|
|
667
82
|
async function main() {
|
|
668
83
|
const transport = new StdioServerTransport();
|
|
669
84
|
await server.connect(transport);
|