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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,244 @@
1
+ import { z } from "zod";
2
+ import { powerPlatformPrompts } from "./templates.js";
3
+ /**
4
+ * Register entity prompts with the MCP server.
5
+ */
6
+ export function registerEntityPrompts(server, ctx) {
7
+ // Entity Overview Prompt
8
+ server.registerPrompt("entity-overview", {
9
+ title: "Entity Overview",
10
+ description: "Get an overview of a Power Platform entity",
11
+ argsSchema: {
12
+ entityName: z.string().describe("The logical name of the entity"),
13
+ },
14
+ }, async (args) => {
15
+ try {
16
+ const service = ctx.getEntityService();
17
+ const entityName = args.entityName;
18
+ // Get entity metadata and key attributes
19
+ const [metadata, attributes] = await Promise.all([
20
+ service.getEntityMetadata(entityName),
21
+ service.getEntityAttributes(entityName),
22
+ ]);
23
+ // Format entity details
24
+ const entityDetails = `- Display Name: ${metadata.DisplayName?.UserLocalizedLabel?.Label || entityName}\n` +
25
+ `- Schema Name: ${metadata.SchemaName}\n` +
26
+ `- Description: ${metadata.Description?.UserLocalizedLabel?.Label || "No description"}\n` +
27
+ `- Primary Key: ${metadata.PrimaryIdAttribute}\n` +
28
+ `- Primary Name: ${metadata.PrimaryNameAttribute}`;
29
+ // Get key attributes
30
+ const keyAttributes = attributes.value
31
+ .map((attr) => {
32
+ const attrType = attr["@odata.type"] || attr.odata?.type || "Unknown type";
33
+ return `- ${attr.LogicalName}: ${attrType}`;
34
+ })
35
+ .join("\n");
36
+ // Get relationships summary
37
+ const relationships = await service.getEntityRelationships(entityName);
38
+ const oneToManyCount = relationships.oneToMany.value.length;
39
+ const manyToManyCount = relationships.manyToMany.value.length;
40
+ const relationshipsSummary = `- One-to-Many Relationships: ${oneToManyCount}\n` +
41
+ `- Many-to-Many Relationships: ${manyToManyCount}`;
42
+ let promptContent = powerPlatformPrompts.ENTITY_OVERVIEW(entityName);
43
+ promptContent = promptContent
44
+ .replace("{{entity_details}}", entityDetails)
45
+ .replace("{{key_attributes}}", keyAttributes)
46
+ .replace("{{relationships}}", relationshipsSummary);
47
+ return {
48
+ messages: [
49
+ {
50
+ role: "assistant",
51
+ content: {
52
+ type: "text",
53
+ text: promptContent,
54
+ },
55
+ },
56
+ ],
57
+ };
58
+ }
59
+ catch (error) {
60
+ console.error(`Error handling entity-overview prompt:`, error);
61
+ return {
62
+ messages: [
63
+ {
64
+ role: "assistant",
65
+ content: {
66
+ type: "text",
67
+ text: `Error: ${error.message}`,
68
+ },
69
+ },
70
+ ],
71
+ };
72
+ }
73
+ });
74
+ // Attribute Details Prompt
75
+ server.registerPrompt("attribute-details", {
76
+ title: "Attribute Details",
77
+ description: "Get detailed information about a specific entity attribute/field",
78
+ argsSchema: {
79
+ entityName: z.string().describe("The logical name of the entity"),
80
+ attributeName: z.string().describe("The logical name of the attribute"),
81
+ },
82
+ }, async (args) => {
83
+ try {
84
+ const service = ctx.getEntityService();
85
+ const { entityName, attributeName } = args;
86
+ // Get attribute details
87
+ const attribute = await service.getEntityAttribute(entityName, attributeName);
88
+ // Format attribute details
89
+ const attrDetails = `- Display Name: ${attribute.DisplayName?.UserLocalizedLabel?.Label || attributeName}\n` +
90
+ `- Description: ${attribute.Description?.UserLocalizedLabel?.Label || "No description"}\n` +
91
+ `- Type: ${attribute.AttributeType}\n` +
92
+ `- Format: ${attribute.Format || "N/A"}\n` +
93
+ `- Is Required: ${attribute.RequiredLevel?.Value || "No"}\n` +
94
+ `- Is Searchable: ${attribute.IsValidForAdvancedFind || false}`;
95
+ let promptContent = powerPlatformPrompts.ATTRIBUTE_DETAILS(entityName, attributeName);
96
+ promptContent = promptContent
97
+ .replace("{{attribute_details}}", attrDetails)
98
+ .replace("{{data_type}}", attribute.AttributeType)
99
+ .replace("{{required}}", attribute.RequiredLevel?.Value || "No")
100
+ .replace("{{max_length}}", attribute.MaxLength || "N/A");
101
+ return {
102
+ messages: [
103
+ {
104
+ role: "assistant",
105
+ content: {
106
+ type: "text",
107
+ text: promptContent,
108
+ },
109
+ },
110
+ ],
111
+ };
112
+ }
113
+ catch (error) {
114
+ console.error(`Error handling attribute-details prompt:`, error);
115
+ return {
116
+ messages: [
117
+ {
118
+ role: "assistant",
119
+ content: {
120
+ type: "text",
121
+ text: `Error: ${error.message}`,
122
+ },
123
+ },
124
+ ],
125
+ };
126
+ }
127
+ });
128
+ // Query Template Prompt
129
+ server.registerPrompt("query-template", {
130
+ title: "Query Template",
131
+ description: "Get a template for querying a Power Platform entity",
132
+ argsSchema: {
133
+ entityName: z.string().describe("The logical name of the entity"),
134
+ },
135
+ }, async (args) => {
136
+ try {
137
+ const service = ctx.getEntityService();
138
+ const entityName = args.entityName;
139
+ // Get entity metadata to determine plural name
140
+ const metadata = await service.getEntityMetadata(entityName);
141
+ const entityNamePlural = metadata.EntitySetName;
142
+ // Get a few important fields for the select example
143
+ const attributes = await service.getEntityAttributes(entityName);
144
+ const selectFields = attributes.value
145
+ .filter((attr) => attr.IsValidForRead === true && !attr.AttributeOf)
146
+ .slice(0, 5) // Just take first 5 for example
147
+ .map((attr) => attr.LogicalName)
148
+ .join(",");
149
+ let promptContent = powerPlatformPrompts.QUERY_TEMPLATE(entityNamePlural);
150
+ promptContent = promptContent
151
+ .replace("{{selected_fields}}", selectFields)
152
+ .replace("{{filter_conditions}}", `${metadata.PrimaryNameAttribute} eq 'Example'`)
153
+ .replace("{{order_by}}", `${metadata.PrimaryNameAttribute} asc`)
154
+ .replace("{{max_records}}", "50");
155
+ return {
156
+ messages: [
157
+ {
158
+ role: "assistant",
159
+ content: {
160
+ type: "text",
161
+ text: promptContent,
162
+ },
163
+ },
164
+ ],
165
+ };
166
+ }
167
+ catch (error) {
168
+ console.error(`Error handling query-template prompt:`, error);
169
+ return {
170
+ messages: [
171
+ {
172
+ role: "assistant",
173
+ content: {
174
+ type: "text",
175
+ text: `Error: ${error.message}`,
176
+ },
177
+ },
178
+ ],
179
+ };
180
+ }
181
+ });
182
+ // Relationship Map Prompt
183
+ server.registerPrompt("relationship-map", {
184
+ title: "Relationship Map",
185
+ description: "Get a list of relationships for a Power Platform entity",
186
+ argsSchema: {
187
+ entityName: z.string().describe("The logical name of the entity"),
188
+ },
189
+ }, async (args) => {
190
+ try {
191
+ const service = ctx.getEntityService();
192
+ const entityName = args.entityName;
193
+ // Get relationships
194
+ const relationships = await service.getEntityRelationships(entityName);
195
+ // Format one-to-many relationships where this entity is primary
196
+ const oneToManyPrimary = relationships.oneToMany.value
197
+ .filter((rel) => rel.ReferencingEntity !== entityName)
198
+ .map((rel) => `- ${rel.SchemaName}: ${entityName} (1) → ${rel.ReferencingEntity} (N)`)
199
+ .join("\n");
200
+ // Format one-to-many relationships where this entity is related
201
+ const oneToManyRelated = relationships.oneToMany.value
202
+ .filter((rel) => rel.ReferencingEntity === entityName)
203
+ .map((rel) => `- ${rel.SchemaName}: ${rel.ReferencedEntity} (1) → ${entityName} (N)`)
204
+ .join("\n");
205
+ // Format many-to-many relationships
206
+ const manyToMany = relationships.manyToMany.value
207
+ .map((rel) => {
208
+ const otherEntity = rel.Entity1LogicalName === entityName ? rel.Entity2LogicalName : rel.Entity1LogicalName;
209
+ return `- ${rel.SchemaName}: ${entityName} (N) ↔ ${otherEntity} (N)`;
210
+ })
211
+ .join("\n");
212
+ let promptContent = powerPlatformPrompts.RELATIONSHIP_MAP(entityName);
213
+ promptContent = promptContent
214
+ .replace("{{one_to_many_primary}}", oneToManyPrimary || "None found")
215
+ .replace("{{one_to_many_related}}", oneToManyRelated || "None found")
216
+ .replace("{{many_to_many}}", manyToMany || "None found");
217
+ return {
218
+ messages: [
219
+ {
220
+ role: "assistant",
221
+ content: {
222
+ type: "text",
223
+ text: promptContent,
224
+ },
225
+ },
226
+ ],
227
+ };
228
+ }
229
+ catch (error) {
230
+ console.error(`Error handling relationship-map prompt:`, error);
231
+ return {
232
+ messages: [
233
+ {
234
+ role: "assistant",
235
+ content: {
236
+ type: "text",
237
+ text: `Error: ${error.message}`,
238
+ },
239
+ },
240
+ ],
241
+ };
242
+ }
243
+ });
244
+ }
@@ -0,0 +1,9 @@
1
+ import { registerEntityPrompts } from "./entityPrompts.js";
2
+ export { registerEntityPrompts } from "./entityPrompts.js";
3
+ export { powerPlatformPrompts } from "./templates.js";
4
+ /**
5
+ * Register all prompts with the MCP server.
6
+ */
7
+ export function registerAllPrompts(server, ctx) {
8
+ registerEntityPrompts(server, ctx);
9
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Pre-defined PowerPlatform prompt templates.
3
+ */
4
+ export const powerPlatformPrompts = {
5
+ // Entity exploration prompts
6
+ ENTITY_OVERVIEW: (entityName) => `## Power Platform Entity: ${entityName}\n\n` +
7
+ `This is an overview of the '${entityName}' entity in Microsoft Power Platform/Dataverse:\n\n` +
8
+ `### Entity Details\n{{entity_details}}\n\n` +
9
+ `### Attributes\n{{key_attributes}}\n\n` +
10
+ `### Relationships\n{{relationships}}\n\n` +
11
+ `You can query this entity using OData filters against the plural name.`,
12
+ ATTRIBUTE_DETAILS: (entityName, attributeName) => `## Attribute: ${attributeName}\n\n` +
13
+ `Details for the '${attributeName}' attribute of the '${entityName}' entity:\n\n` +
14
+ `{{attribute_details}}\n\n` +
15
+ `### Usage Notes\n` +
16
+ `- Data Type: {{data_type}}\n` +
17
+ `- Required: {{required}}\n` +
18
+ `- Max Length: {{max_length}}`,
19
+ // Query builder prompts
20
+ QUERY_TEMPLATE: (entityNamePlural) => `## OData Query Template for ${entityNamePlural}\n\n` +
21
+ `Use this template to build queries against the ${entityNamePlural} entity:\n\n` +
22
+ `\`\`\`\n${entityNamePlural}?$select={{selected_fields}}&$filter={{filter_conditions}}&$orderby={{order_by}}&$top={{max_records}}\n\`\`\`\n\n` +
23
+ `### Common Filter Examples\n` +
24
+ `- Equals: \`name eq 'Contoso'\`\n` +
25
+ `- Contains: \`contains(name, 'Contoso')\`\n` +
26
+ `- Greater than date: \`createdon gt 2023-01-01T00:00:00Z\`\n` +
27
+ `- Multiple conditions: \`name eq 'Contoso' and statecode eq 0\``,
28
+ // Relationship exploration prompts
29
+ RELATIONSHIP_MAP: (entityName) => `## Relationship Map for ${entityName}\n\n` +
30
+ `This shows all relationships for the '${entityName}' entity:\n\n` +
31
+ `### One-to-Many Relationships (${entityName} as Primary)\n{{one_to_many_primary}}\n\n` +
32
+ `### One-to-Many Relationships (${entityName} as Related)\n{{one_to_many_related}}\n\n` +
33
+ `### Many-to-Many Relationships\n{{many_to_many}}\n\n`,
34
+ };
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Service for entity metadata operations.
3
+ * Handles entity definitions, attributes, and relationships.
4
+ */
5
+ export class EntityService {
6
+ client;
7
+ constructor(client) {
8
+ this.client = client;
9
+ }
10
+ /**
11
+ * Get metadata about an entity
12
+ * @param entityName The logical name of the entity
13
+ */
14
+ async getEntityMetadata(entityName) {
15
+ const response = await this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')`);
16
+ // Remove Privileges property if it exists
17
+ if (response && typeof response === 'object' && 'Privileges' in response) {
18
+ delete response.Privileges;
19
+ }
20
+ return response;
21
+ }
22
+ /**
23
+ * Get metadata about entity attributes/fields
24
+ * @param entityName The logical name of the entity
25
+ */
26
+ async getEntityAttributes(entityName) {
27
+ const selectProperties = [
28
+ 'LogicalName',
29
+ ].join(',');
30
+ // Make the request to get attributes
31
+ const response = await this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes?$select=${selectProperties}&$filter=AttributeType ne 'Virtual'`);
32
+ if (response && response.value) {
33
+ // First pass: Filter out attributes that end with 'yominame'
34
+ response.value = response.value.filter((attribute) => {
35
+ const logicalName = attribute.LogicalName || '';
36
+ return !logicalName.endsWith('yominame');
37
+ });
38
+ // Filter out attributes that end with 'name' if there is another attribute with the same name without the 'name' suffix
39
+ const baseNames = new Set();
40
+ const namesAttributes = new Map();
41
+ for (const attribute of response.value) {
42
+ const logicalName = attribute.LogicalName || '';
43
+ if (logicalName.endsWith('name') && logicalName.length > 4) {
44
+ const baseName = logicalName.slice(0, -4); // Remove 'name' suffix
45
+ namesAttributes.set(baseName, attribute);
46
+ }
47
+ else {
48
+ // This is a potential base attribute
49
+ baseNames.add(logicalName);
50
+ }
51
+ }
52
+ // Find attributes to remove that match the pattern
53
+ const attributesToRemove = new Set();
54
+ for (const [baseName, nameAttribute] of namesAttributes.entries()) {
55
+ if (baseNames.has(baseName)) {
56
+ attributesToRemove.add(nameAttribute);
57
+ }
58
+ }
59
+ response.value = response.value.filter(attribute => !attributesToRemove.has(attribute));
60
+ }
61
+ return response;
62
+ }
63
+ /**
64
+ * Get metadata about a specific entity attribute/field
65
+ * @param entityName The logical name of the entity
66
+ * @param attributeName The logical name of the attribute
67
+ */
68
+ async getEntityAttribute(entityName, attributeName) {
69
+ return this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes(LogicalName='${attributeName}')`);
70
+ }
71
+ /**
72
+ * Get one-to-many relationships for an entity
73
+ * @param entityName The logical name of the entity
74
+ */
75
+ async getEntityOneToManyRelationships(entityName) {
76
+ const selectProperties = [
77
+ 'SchemaName',
78
+ 'RelationshipType',
79
+ 'ReferencedAttribute',
80
+ 'ReferencedEntity',
81
+ 'ReferencingAttribute',
82
+ 'ReferencingEntity',
83
+ 'ReferencedEntityNavigationPropertyName',
84
+ 'ReferencingEntityNavigationPropertyName'
85
+ ].join(',');
86
+ // Only filter by ReferencingAttribute in the OData query since startswith isn't supported
87
+ const response = await this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/OneToManyRelationships?$select=${selectProperties}&$filter=ReferencingAttribute ne 'regardingobjectid'`);
88
+ // Filter the response to exclude relationships with ReferencingEntity starting with 'msdyn_' or 'adx_'
89
+ if (response && response.value) {
90
+ response.value = response.value.filter((relationship) => {
91
+ const referencingEntity = relationship.ReferencingEntity || '';
92
+ return !(referencingEntity.startsWith('msdyn_') || referencingEntity.startsWith('adx_'));
93
+ });
94
+ }
95
+ return response;
96
+ }
97
+ /**
98
+ * Get many-to-many relationships for an entity
99
+ * @param entityName The logical name of the entity
100
+ */
101
+ async getEntityManyToManyRelationships(entityName) {
102
+ const selectProperties = [
103
+ 'SchemaName',
104
+ 'RelationshipType',
105
+ 'Entity1LogicalName',
106
+ 'Entity2LogicalName',
107
+ 'Entity1IntersectAttribute',
108
+ 'Entity2IntersectAttribute',
109
+ 'Entity1NavigationPropertyName',
110
+ 'Entity2NavigationPropertyName'
111
+ ].join(',');
112
+ return this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/ManyToManyRelationships?$select=${selectProperties}`);
113
+ }
114
+ /**
115
+ * Get all relationships (one-to-many and many-to-many) for an entity
116
+ * @param entityName The logical name of the entity
117
+ */
118
+ async getEntityRelationships(entityName) {
119
+ const [oneToMany, manyToMany] = await Promise.all([
120
+ this.getEntityOneToManyRelationships(entityName),
121
+ this.getEntityManyToManyRelationships(entityName)
122
+ ]);
123
+ return {
124
+ oneToMany,
125
+ manyToMany
126
+ };
127
+ }
128
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Service for option set operations.
3
+ * Handles global option set definitions.
4
+ */
5
+ export class OptionSetService {
6
+ client;
7
+ constructor(client) {
8
+ this.client = client;
9
+ }
10
+ /**
11
+ * Get a global option set definition by name
12
+ * @param optionSetName The name of the global option set
13
+ * @returns The global option set definition
14
+ */
15
+ async getGlobalOptionSet(optionSetName) {
16
+ return this.client.get(`api/data/v9.2/GlobalOptionSetDefinitions(Name='${optionSetName}')`);
17
+ }
18
+ }