powerplatform-mcp 0.2.0 → 0.3.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 +310 -1
- package/build/PowerPlatformService.js +63 -0
- package/build/index.js +562 -2
- package/package.json +47 -47
package/README.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# PowerPlatform MCP Server
|
|
2
2
|
|
|
3
|
-
A Model Context Protocol (MCP) server
|
|
3
|
+
A Model Context Protocol (MCP) server that provides intelligent access to PowerPlatform/Dataverse entities and records. This tool offers comprehensive entity exploration, metadata access, and AI-assisted query capabilities through GitHub Copilot integration.
|
|
4
|
+
|
|
5
|
+
Key features:
|
|
6
|
+
- Rich entity metadata exploration with formatted, context-aware prompts
|
|
7
|
+
- Advanced OData query support with intelligent filtering
|
|
8
|
+
- Comprehensive relationship mapping and visualization
|
|
9
|
+
- AI-assisted query building and data modeling through AI agent
|
|
10
|
+
- Full access to entity attributes, relationships, and global option sets
|
|
11
|
+
|
|
12
|
+
Perfect for developers working with PowerPlatform/Dataverse who want intelligent, context-aware assistance for entity exploration and data querying.
|
|
4
13
|
|
|
5
14
|
## Installation
|
|
6
15
|
|
|
@@ -46,6 +55,306 @@ This is an MCP server designed to work with MCP-compatible clients like GitHub C
|
|
|
46
55
|
|
|
47
56
|
- `get-entity-metadata`: Get metadata about a PowerPlatform entity
|
|
48
57
|
- `get-entity-attributes`: Get attributes/fields of a PowerPlatform entity
|
|
58
|
+
- `get-entity-attribute`: Get a specific attribute/field of a PowerPlatform entity
|
|
59
|
+
- `get-entity-relationships`: Get relationships for a PowerPlatform entity
|
|
60
|
+
- `get-global-option-set`: Get a global option set definition
|
|
61
|
+
- `get-record`: Get a specific record by entity name and ID
|
|
62
|
+
- `query-records`: Query records using an OData filter expression
|
|
63
|
+
- `use-powerplatform-prompt`: Use pre-defined prompt templates for PowerPlatform entities
|
|
64
|
+
|
|
65
|
+
## MCP Prompts
|
|
66
|
+
|
|
67
|
+
The server includes a prompts feature that provides formatted, context-rich information about PowerPlatform entities. These prompts are ideal for use with AI assistants like GitHub Copilot.
|
|
68
|
+
|
|
69
|
+
### Available Prompt Types
|
|
70
|
+
|
|
71
|
+
The `use-powerplatform-prompt` tool supports the following prompt types:
|
|
72
|
+
|
|
73
|
+
1. **ENTITY_OVERVIEW**: Comprehensive overview of an entity
|
|
74
|
+
2. **ATTRIBUTE_DETAILS**: Detailed information about a specific entity attribute
|
|
75
|
+
3. **QUERY_TEMPLATE**: OData query template for an entity with example filters
|
|
76
|
+
4. **RELATIONSHIP_MAP**: Visual map of entity relationships
|
|
77
|
+
|
|
78
|
+
### Examples
|
|
79
|
+
|
|
80
|
+
#### Entity Overview Prompt
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
// Example client code
|
|
84
|
+
await mcpClient.invoke("use-powerplatform-prompt", {
|
|
85
|
+
promptType: "ENTITY_OVERVIEW",
|
|
86
|
+
entityName: "account"
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Output:**
|
|
91
|
+
```
|
|
92
|
+
## Power Platform Entity: account
|
|
93
|
+
|
|
94
|
+
This is an overview of the 'account' entity in Microsoft Power Platform/Dataverse:
|
|
95
|
+
|
|
96
|
+
### Entity Details
|
|
97
|
+
- Display Name: Account
|
|
98
|
+
- Schema Name: Account
|
|
99
|
+
- Description: Business that represents a customer or potential customer
|
|
100
|
+
- Primary Key: accountid
|
|
101
|
+
- Primary Name: name
|
|
102
|
+
|
|
103
|
+
### Key Attributes
|
|
104
|
+
- name: String (Account Name)
|
|
105
|
+
- emailaddress1: String (Email)
|
|
106
|
+
- telephone1: String (Main Phone)
|
|
107
|
+
- address1_city: String (City)
|
|
108
|
+
- statecode: Status (Status)
|
|
109
|
+
- accountnumber: String (Account Number)
|
|
110
|
+
- industrycode: OptionSetValue (Industry)
|
|
111
|
+
- revenue: Money (Annual Revenue)
|
|
112
|
+
- ownerid: Owner (Owner)
|
|
113
|
+
- createdon: DateTime (Created On)
|
|
114
|
+
|
|
115
|
+
### Relationships
|
|
116
|
+
- One-to-Many Relationships: 42
|
|
117
|
+
- Many-to-Many Relationships: 8
|
|
118
|
+
|
|
119
|
+
You can query this entity using OData filters against the plural name.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### Attribute Details Prompt
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
await mcpClient.invoke("use-powerplatform-prompt", {
|
|
126
|
+
promptType: "ATTRIBUTE_DETAILS",
|
|
127
|
+
entityName: "account",
|
|
128
|
+
attributeName: "revenue"
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Output:**
|
|
133
|
+
```
|
|
134
|
+
## Attribute: revenue
|
|
135
|
+
|
|
136
|
+
Details for the 'revenue' attribute of the 'account' entity:
|
|
137
|
+
|
|
138
|
+
- Display Name: Annual Revenue
|
|
139
|
+
- Description: Annual revenue for the account
|
|
140
|
+
- Type: Money
|
|
141
|
+
- Format: Currency
|
|
142
|
+
- Is Required: No
|
|
143
|
+
- Is Searchable: true
|
|
144
|
+
|
|
145
|
+
### Usage Notes
|
|
146
|
+
- Data Type: Money
|
|
147
|
+
- Required: No
|
|
148
|
+
- Max Length: N/A
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Query Template Prompt
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
await mcpClient.invoke("use-powerplatform-prompt", {
|
|
155
|
+
promptType: "QUERY_TEMPLATE",
|
|
156
|
+
entityName: "account"
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Output:**
|
|
161
|
+
```
|
|
162
|
+
## OData Query Template for accounts
|
|
163
|
+
|
|
164
|
+
Use this template to build queries against the accounts entity:
|
|
165
|
+
accounts?$select=name,emailaddress1,telephone1, address1_city,statecode&$filter=name eq 'Example'&$orderby=name asc&$top=50
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Common Filter Examples
|
|
169
|
+
- Equals: `name eq 'Contoso'`
|
|
170
|
+
- Contains: `contains(name, 'Contoso')`
|
|
171
|
+
- Greater than date: `createdon gt 2023-01-01T00:00:00Z`
|
|
172
|
+
- Multiple conditions: `name eq 'Contoso' and statecode eq 0`
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
#### Relationship Map Prompt
|
|
176
|
+
|
|
177
|
+
```javascript
|
|
178
|
+
await mcpClient.invoke("use-powerplatform-prompt", {
|
|
179
|
+
promptType: "RELATIONSHIP_MAP",
|
|
180
|
+
entityName: "contact"
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Output:**
|
|
185
|
+
```
|
|
186
|
+
## Relationship Map for contact
|
|
187
|
+
|
|
188
|
+
This shows all relationships for the 'contact' entity:
|
|
189
|
+
|
|
190
|
+
### One-to-Many Relationships (contact as Primary)
|
|
191
|
+
- contact_activity_parties: contact (1) → activityparty (N)
|
|
192
|
+
- contact_connections1: contact (1) → connection (N)
|
|
193
|
+
- contact_connections2: contact (1) → connection (N)
|
|
194
|
+
- contact_customer_contacts: contact (1) → contact (N)
|
|
195
|
+
- contact_master_contact: contact (1) → contact (N)
|
|
196
|
+
|
|
197
|
+
### One-to-Many Relationships (contact as Related)
|
|
198
|
+
- account_primary_contact: account (1) → contact (N)
|
|
199
|
+
- customer_contacts: customer (1) → contact (N)
|
|
200
|
+
- system_user_contacts: systemuser (1) → contact (N)
|
|
201
|
+
|
|
202
|
+
### Many-to-Many Relationships
|
|
203
|
+
- contactleads_association: contact (N) ↔ lead (N)
|
|
204
|
+
- contactopportunities_association: contact (N) ↔ opportunity (N)
|
|
205
|
+
- contactquotes_association: contact (N) ↔ quote (N)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Integration with AI Assistants
|
|
209
|
+
|
|
210
|
+
These prompts are particularly valuable when used with AI assistants like GitHub Copilot that can integrate with MCP.
|
|
211
|
+
|
|
212
|
+
### GitHub Copilot Examples
|
|
213
|
+
|
|
214
|
+
Here are some examples of how you can interact with GitHub Copilot using the PowerPlatform MCP prompts:
|
|
215
|
+
|
|
216
|
+
#### 1. Entity Understanding
|
|
217
|
+
|
|
218
|
+
You can ask GitHub Copilot about entities in natural language:
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
User: "Tell me about the Account entity in our PowerPlatform environment"
|
|
222
|
+
|
|
223
|
+
GitHub Copilot: Let me fetch the entity overview for you...
|
|
224
|
+
[uses ENTITY_OVERVIEW prompt internally]
|
|
225
|
+
|
|
226
|
+
Here's what I found about the Account entity:
|
|
227
|
+
- It's a core business entity representing customers and potential customers
|
|
228
|
+
- The primary identifier is 'accountid' and the primary name field is 'name'
|
|
229
|
+
- Key fields include:
|
|
230
|
+
* name (Account Name)
|
|
231
|
+
* emailaddress1 (Email)
|
|
232
|
+
* telephone1 (Main Phone)
|
|
233
|
+
* revenue (Annual Revenue)
|
|
234
|
+
- It has 42 one-to-many relationships and 8 many-to-many relationships
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### 2. Building Queries
|
|
238
|
+
|
|
239
|
+
You can ask for help constructing queries:
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
User: "Help me write a query to find active accounts with revenue over $1M in California"
|
|
243
|
+
|
|
244
|
+
GitHub Copilot: I'll help you build that query...
|
|
245
|
+
[uses QUERY_TEMPLATE prompt internally]
|
|
246
|
+
|
|
247
|
+
Here's the OData query you need:
|
|
248
|
+
accounts?$select=name,revenue,statecode,address1_stateorprovince
|
|
249
|
+
&$filter=statecode eq 0
|
|
250
|
+
and revenue gt 1000000
|
|
251
|
+
and address1_stateorprovince eq 'California'
|
|
252
|
+
&$orderby=revenue desc
|
|
253
|
+
|
|
254
|
+
This query:
|
|
255
|
+
1. Selects relevant fields
|
|
256
|
+
2. Filters for active accounts (statecode eq 0)
|
|
257
|
+
3. Checks revenue > $1M
|
|
258
|
+
4. Filters for California location
|
|
259
|
+
5. Orders by revenue highest to lowest
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### 3. Understanding Relationships
|
|
263
|
+
|
|
264
|
+
You can explore entity relationships naturally:
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
User: "How are Accounts and Contacts related in our system?"
|
|
268
|
+
|
|
269
|
+
GitHub Copilot: Let me check the relationship maps...
|
|
270
|
+
[uses RELATIONSHIP_MAP prompt for both entities internally]
|
|
271
|
+
|
|
272
|
+
Here are the key relationships between Accounts and Contacts:
|
|
273
|
+
|
|
274
|
+
1. Primary Contact (One-to-Many):
|
|
275
|
+
- account_primary_contact: Account (1) → Contact (N)
|
|
276
|
+
- An account can have many contacts, but one designated as primary
|
|
277
|
+
|
|
278
|
+
2. Customer Relationship:
|
|
279
|
+
- contact_customer_accounts: Contact (N) ← Account (1)
|
|
280
|
+
- Contacts can be associated with customer accounts
|
|
281
|
+
|
|
282
|
+
3. Additional Relationships:
|
|
283
|
+
- accountleads_association: For lead tracking
|
|
284
|
+
- account_connections: For relationship management
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
#### 4. Field/Attribute Investigation
|
|
288
|
+
|
|
289
|
+
You can ask about specific fields:
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
User: "What kind of field is the revenue field on Account?"
|
|
293
|
+
|
|
294
|
+
GitHub Copilot: Let me check the attribute details...
|
|
295
|
+
[uses ATTRIBUTE_DETAILS prompt internally]
|
|
296
|
+
|
|
297
|
+
The revenue field on Account is:
|
|
298
|
+
- Data Type: Money
|
|
299
|
+
- Format: Currency
|
|
300
|
+
- Not required
|
|
301
|
+
- Searchable: Yes
|
|
302
|
+
- Used for: Tracking annual revenue for the account
|
|
303
|
+
- Can be used in advanced find and filtered views
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
#### 5. Complex Data Modeling
|
|
307
|
+
|
|
308
|
+
You can get help with data modeling decisions:
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
User: "I need to design a solution for tracking product reviews. Which entities should I relate to?"
|
|
312
|
+
|
|
313
|
+
GitHub Copilot: Let me analyze the relevant entities...
|
|
314
|
+
[uses multiple ENTITY_OVERVIEW and RELATIONSHIP_MAP prompts internally]
|
|
315
|
+
|
|
316
|
+
Based on the existing data model, here's a suggested approach:
|
|
317
|
+
|
|
318
|
+
1. Product (existing entity):
|
|
319
|
+
- Already has relationships with:
|
|
320
|
+
* Price lists
|
|
321
|
+
* Product categories
|
|
322
|
+
* Inventory units
|
|
323
|
+
|
|
324
|
+
2. New Review entity should relate to:
|
|
325
|
+
- Product (N:1) - Each review is for one product
|
|
326
|
+
- Contact (N:1) - Reviews are written by contacts
|
|
327
|
+
- Account (N:1) - Optional link to company if B2B review
|
|
328
|
+
|
|
329
|
+
Here's a sample lookup field structure...
|
|
330
|
+
[continues with detailed implementation guidance]
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
#### 6. Debugging Assistance
|
|
334
|
+
|
|
335
|
+
You can get help troubleshooting:
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
User: "Why am I getting 'invalid property' when querying account.customertypecode?"
|
|
339
|
+
|
|
340
|
+
GitHub Copilot: Let me check the attribute details...
|
|
341
|
+
[uses ATTRIBUTE_DETAILS prompt internally]
|
|
342
|
+
|
|
343
|
+
I see the issue. The field 'customertypecode' is actually named 'customertype' in your environment. Here's the correct field information:
|
|
344
|
+
- Logical Name: customertype
|
|
345
|
+
- Type: OptionSetValue
|
|
346
|
+
- Valid Values:
|
|
347
|
+
* 1 = Competitor
|
|
348
|
+
* 2 = Consultant
|
|
349
|
+
* 3 = Customer
|
|
350
|
+
* 4 = Investor
|
|
351
|
+
* 5 = Partner
|
|
352
|
+
* 6 = Influencer
|
|
353
|
+
|
|
354
|
+
Try updating your query to use 'customertype' instead of 'customertypecode'.
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
These examples show how GitHub Copilot can leverage the MCP prompts to provide context-aware, accurate assistance for PowerPlatform development tasks. The AI understands your environment's specific configuration and can help with both simple queries and complex architectural decisions.
|
|
49
358
|
|
|
50
359
|
## License
|
|
51
360
|
|
|
@@ -82,4 +82,67 @@ export class PowerPlatformService {
|
|
|
82
82
|
async getEntityAttributes(entityName) {
|
|
83
83
|
return this.makeRequest(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`);
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Get metadata about a specific entity attribute/field
|
|
87
|
+
* @param entityName The logical name of the entity
|
|
88
|
+
* @param attributeName The logical name of the attribute
|
|
89
|
+
*/
|
|
90
|
+
async getEntityAttribute(entityName, attributeName) {
|
|
91
|
+
return this.makeRequest(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes(LogicalName='${attributeName}')`);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get one-to-many relationships for an entity
|
|
95
|
+
* @param entityName The logical name of the entity
|
|
96
|
+
*/
|
|
97
|
+
async getEntityOneToManyRelationships(entityName) {
|
|
98
|
+
return this.makeRequest(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/OneToManyRelationships`);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get many-to-many relationships for an entity
|
|
102
|
+
* @param entityName The logical name of the entity
|
|
103
|
+
*/
|
|
104
|
+
async getEntityManyToManyRelationships(entityName) {
|
|
105
|
+
return this.makeRequest(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/ManyToManyRelationships`);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get all relationships (one-to-many and many-to-many) for an entity
|
|
109
|
+
* @param entityName The logical name of the entity
|
|
110
|
+
*/
|
|
111
|
+
async getEntityRelationships(entityName) {
|
|
112
|
+
const [oneToMany, manyToMany] = await Promise.all([
|
|
113
|
+
this.getEntityOneToManyRelationships(entityName),
|
|
114
|
+
this.getEntityManyToManyRelationships(entityName)
|
|
115
|
+
]);
|
|
116
|
+
return {
|
|
117
|
+
oneToMany,
|
|
118
|
+
manyToMany
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get a global option set definition by name
|
|
123
|
+
* @param optionSetName The name of the global option set
|
|
124
|
+
* @returns The global option set definition
|
|
125
|
+
*/
|
|
126
|
+
async getGlobalOptionSet(optionSetName) {
|
|
127
|
+
return this.makeRequest(`api/data/v9.2/GlobalOptionSetDefinitions(Name='${optionSetName}')`);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get a specific record by entity name (plural) and ID
|
|
131
|
+
* @param entityNamePlural The plural name of the entity (e.g., 'accounts', 'contacts')
|
|
132
|
+
* @param recordId The GUID of the record
|
|
133
|
+
* @returns The record data
|
|
134
|
+
*/
|
|
135
|
+
async getRecord(entityNamePlural, recordId) {
|
|
136
|
+
return this.makeRequest(`api/data/v9.2/${entityNamePlural}(${recordId})`);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Query records using entity name (plural) and a filter expression
|
|
140
|
+
* @param entityNamePlural The plural name of the entity (e.g., 'accounts', 'contacts')
|
|
141
|
+
* @param filter OData filter expression (e.g., "name eq 'test'")
|
|
142
|
+
* @param maxRecords Maximum number of records to retrieve (default: 50)
|
|
143
|
+
* @returns Filtered list of records
|
|
144
|
+
*/
|
|
145
|
+
async queryRecords(entityNamePlural, filter, maxRecords = 50) {
|
|
146
|
+
return this.makeRequest(`api/data/v9.2/${entityNamePlural}?$filter=${encodeURIComponent(filter)}&$top=${maxRecords}`);
|
|
147
|
+
}
|
|
85
148
|
}
|
package/build/index.js
CHANGED
|
@@ -39,6 +39,258 @@ function getPowerPlatformService() {
|
|
|
39
39
|
}
|
|
40
40
|
return powerPlatformService;
|
|
41
41
|
}
|
|
42
|
+
// Pre-defined PowerPlatform Prompts
|
|
43
|
+
const powerPlatformPrompts = {
|
|
44
|
+
// Entity exploration prompts
|
|
45
|
+
ENTITY_OVERVIEW: (entityName) => `## PowerPlatform Entity: ${entityName}\n\n` +
|
|
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 PowerPlatform 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
|
+
.filter((attr) => attr.IsValidForRead === true && !attr.AttributeOf)
|
|
96
|
+
.map((attr) => `- ${attr.LogicalName}: ${attr.AttributeType} (${attr.DisplayName?.UserLocalizedLabel?.Label || 'No display name'})`)
|
|
97
|
+
.join('\n');
|
|
98
|
+
// Get relationships summary
|
|
99
|
+
const relationships = await service.getEntityRelationships(entityName);
|
|
100
|
+
const oneToManyCount = relationships.oneToMany.value.length;
|
|
101
|
+
const manyToManyCount = relationships.manyToMany.value.length;
|
|
102
|
+
const relationshipsSummary = `- One-to-Many Relationships: ${oneToManyCount}\n` +
|
|
103
|
+
`- Many-to-Many Relationships: ${manyToManyCount}`;
|
|
104
|
+
let promptContent = powerPlatformPrompts.ENTITY_OVERVIEW(entityName);
|
|
105
|
+
promptContent = promptContent
|
|
106
|
+
.replace('{{entity_details}}', entityDetails)
|
|
107
|
+
.replace('{{key_attributes}}', keyAttributes)
|
|
108
|
+
.replace('{{relationships}}', relationshipsSummary);
|
|
109
|
+
return {
|
|
110
|
+
messages: [
|
|
111
|
+
{
|
|
112
|
+
role: "assistant",
|
|
113
|
+
content: {
|
|
114
|
+
type: "text",
|
|
115
|
+
text: promptContent
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
console.error(`Error handling entity-overview prompt:`, error);
|
|
123
|
+
return {
|
|
124
|
+
messages: [
|
|
125
|
+
{
|
|
126
|
+
role: "assistant",
|
|
127
|
+
content: {
|
|
128
|
+
type: "text",
|
|
129
|
+
text: `Error: ${error.message}`
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
// Attribute Details Prompt
|
|
137
|
+
server.prompt("attribute-details", "Get detailed information about a specific entity attribute/field", {
|
|
138
|
+
entityName: z.string().describe("The logical name of the entity"),
|
|
139
|
+
attributeName: z.string().describe("The logical name of the attribute"),
|
|
140
|
+
}, async (args) => {
|
|
141
|
+
try {
|
|
142
|
+
const service = getPowerPlatformService();
|
|
143
|
+
const { entityName, attributeName } = args;
|
|
144
|
+
// Get attribute details
|
|
145
|
+
const attribute = await service.getEntityAttribute(entityName, attributeName);
|
|
146
|
+
// Format attribute details
|
|
147
|
+
const attrDetails = `- Display Name: ${attribute.DisplayName?.UserLocalizedLabel?.Label || attributeName}\n` +
|
|
148
|
+
`- Description: ${attribute.Description?.UserLocalizedLabel?.Label || 'No description'}\n` +
|
|
149
|
+
`- Type: ${attribute.AttributeType}\n` +
|
|
150
|
+
`- Format: ${attribute.Format || 'N/A'}\n` +
|
|
151
|
+
`- Is Required: ${attribute.RequiredLevel?.Value || 'No'}\n` +
|
|
152
|
+
`- Is Searchable: ${attribute.IsValidForAdvancedFind || false}`;
|
|
153
|
+
let promptContent = powerPlatformPrompts.ATTRIBUTE_DETAILS(entityName, attributeName);
|
|
154
|
+
promptContent = promptContent
|
|
155
|
+
.replace('{{attribute_details}}', attrDetails)
|
|
156
|
+
.replace('{{data_type}}', attribute.AttributeType)
|
|
157
|
+
.replace('{{required}}', attribute.RequiredLevel?.Value || 'No')
|
|
158
|
+
.replace('{{max_length}}', attribute.MaxLength || 'N/A');
|
|
159
|
+
return {
|
|
160
|
+
messages: [
|
|
161
|
+
{
|
|
162
|
+
role: "assistant",
|
|
163
|
+
content: {
|
|
164
|
+
type: "text",
|
|
165
|
+
text: promptContent
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
console.error(`Error handling attribute-details prompt:`, error);
|
|
173
|
+
return {
|
|
174
|
+
messages: [
|
|
175
|
+
{
|
|
176
|
+
role: "assistant",
|
|
177
|
+
content: {
|
|
178
|
+
type: "text",
|
|
179
|
+
text: `Error: ${error.message}`
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
// Query Template Prompt
|
|
187
|
+
server.prompt("query-template", "Get a template for querying a PowerPlatform entity", {
|
|
188
|
+
entityName: z.string().describe("The logical name of the entity"),
|
|
189
|
+
}, async (args) => {
|
|
190
|
+
try {
|
|
191
|
+
const service = getPowerPlatformService();
|
|
192
|
+
const entityName = args.entityName;
|
|
193
|
+
// Get entity metadata to determine plural name
|
|
194
|
+
const metadata = await service.getEntityMetadata(entityName);
|
|
195
|
+
const entityNamePlural = metadata.EntitySetName;
|
|
196
|
+
// Get a few important fields for the select example
|
|
197
|
+
const attributes = await service.getEntityAttributes(entityName);
|
|
198
|
+
const selectFields = attributes.value
|
|
199
|
+
.filter((attr) => attr.IsValidForRead === true && !attr.AttributeOf)
|
|
200
|
+
.slice(0, 5) // Just take first 5 for example
|
|
201
|
+
.map((attr) => attr.LogicalName)
|
|
202
|
+
.join(',');
|
|
203
|
+
let promptContent = powerPlatformPrompts.QUERY_TEMPLATE(entityNamePlural);
|
|
204
|
+
promptContent = promptContent
|
|
205
|
+
.replace('{{selected_fields}}', selectFields)
|
|
206
|
+
.replace('{{filter_conditions}}', `${metadata.PrimaryNameAttribute} eq 'Example'`)
|
|
207
|
+
.replace('{{order_by}}', `${metadata.PrimaryNameAttribute} asc`)
|
|
208
|
+
.replace('{{max_records}}', '50');
|
|
209
|
+
return {
|
|
210
|
+
messages: [
|
|
211
|
+
{
|
|
212
|
+
role: "assistant",
|
|
213
|
+
content: {
|
|
214
|
+
type: "text",
|
|
215
|
+
text: promptContent
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
console.error(`Error handling query-template prompt:`, error);
|
|
223
|
+
return {
|
|
224
|
+
messages: [
|
|
225
|
+
{
|
|
226
|
+
role: "assistant",
|
|
227
|
+
content: {
|
|
228
|
+
type: "text",
|
|
229
|
+
text: `Error: ${error.message}`
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
// Relationship Map Prompt
|
|
237
|
+
server.prompt("relationship-map", "Get a list of relationships for a Power Platform entity", {
|
|
238
|
+
entityName: z.string().describe("The logical name of the entity"),
|
|
239
|
+
}, async (args) => {
|
|
240
|
+
try {
|
|
241
|
+
const service = getPowerPlatformService();
|
|
242
|
+
const entityName = args.entityName;
|
|
243
|
+
// Get relationships
|
|
244
|
+
const relationships = await service.getEntityRelationships(entityName);
|
|
245
|
+
// Format one-to-many relationships where this entity is primary
|
|
246
|
+
const oneToManyPrimary = relationships.oneToMany.value
|
|
247
|
+
.filter((rel) => rel.ReferencingEntity !== entityName)
|
|
248
|
+
.map((rel) => `- ${rel.SchemaName}: ${entityName} (1) → ${rel.ReferencingEntity} (N)`)
|
|
249
|
+
.join('\n');
|
|
250
|
+
// Format one-to-many relationships where this entity is related
|
|
251
|
+
const oneToManyRelated = relationships.oneToMany.value
|
|
252
|
+
.filter((rel) => rel.ReferencingEntity === entityName)
|
|
253
|
+
.map((rel) => `- ${rel.SchemaName}: ${rel.ReferencedEntity} (1) → ${entityName} (N)`)
|
|
254
|
+
.join('\n');
|
|
255
|
+
// Format many-to-many relationships
|
|
256
|
+
const manyToMany = relationships.manyToMany.value
|
|
257
|
+
.map((rel) => {
|
|
258
|
+
const otherEntity = rel.Entity1LogicalName === entityName ? rel.Entity2LogicalName : rel.Entity1LogicalName;
|
|
259
|
+
return `- ${rel.SchemaName}: ${entityName} (N) ↔ ${otherEntity} (N)`;
|
|
260
|
+
})
|
|
261
|
+
.join('\n');
|
|
262
|
+
let promptContent = powerPlatformPrompts.RELATIONSHIP_MAP(entityName);
|
|
263
|
+
promptContent = promptContent
|
|
264
|
+
.replace('{{one_to_many_primary}}', oneToManyPrimary || 'None found')
|
|
265
|
+
.replace('{{one_to_many_related}}', oneToManyRelated || 'None found')
|
|
266
|
+
.replace('{{many_to_many}}', manyToMany || 'None found');
|
|
267
|
+
return {
|
|
268
|
+
messages: [
|
|
269
|
+
{
|
|
270
|
+
role: "assistant",
|
|
271
|
+
content: {
|
|
272
|
+
type: "text",
|
|
273
|
+
text: promptContent
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
console.error(`Error handling relationship-map prompt:`, error);
|
|
281
|
+
return {
|
|
282
|
+
messages: [
|
|
283
|
+
{
|
|
284
|
+
role: "assistant",
|
|
285
|
+
content: {
|
|
286
|
+
type: "text",
|
|
287
|
+
text: `Error: ${error.message}`
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
});
|
|
42
294
|
// PowerPlatform entity metadata
|
|
43
295
|
server.tool("get-entity-metadata", "Get metadata about a PowerPlatform entity", {
|
|
44
296
|
entityName: z.string().describe("The logical name of the entity"),
|
|
@@ -64,7 +316,7 @@ server.tool("get-entity-metadata", "Get metadata about a PowerPlatform entity",
|
|
|
64
316
|
content: [
|
|
65
317
|
{
|
|
66
318
|
type: "text",
|
|
67
|
-
text: `Failed to get entity metadata: ${error}`,
|
|
319
|
+
text: `Failed to get entity metadata: ${error.message}`,
|
|
68
320
|
},
|
|
69
321
|
],
|
|
70
322
|
};
|
|
@@ -95,7 +347,315 @@ server.tool("get-entity-attributes", "Get attributes/fields of a PowerPlatform e
|
|
|
95
347
|
content: [
|
|
96
348
|
{
|
|
97
349
|
type: "text",
|
|
98
|
-
text: `Failed to get entity attributes: ${error}`,
|
|
350
|
+
text: `Failed to get entity attributes: ${error.message}`,
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
// PowerPlatform specific entity attribute
|
|
357
|
+
server.tool("get-entity-attribute", "Get a specific attribute/field of a PowerPlatform entity", {
|
|
358
|
+
entityName: z.string().describe("The logical name of the entity"),
|
|
359
|
+
attributeName: z.string().describe("The logical name of the attribute")
|
|
360
|
+
}, async ({ entityName, attributeName }) => {
|
|
361
|
+
try {
|
|
362
|
+
// Get or initialize PowerPlatformService
|
|
363
|
+
const service = getPowerPlatformService();
|
|
364
|
+
const attribute = await service.getEntityAttribute(entityName, attributeName);
|
|
365
|
+
// Format the attribute as a string for text display
|
|
366
|
+
const attributeStr = JSON.stringify(attribute, null, 2);
|
|
367
|
+
return {
|
|
368
|
+
content: [
|
|
369
|
+
{
|
|
370
|
+
type: "text",
|
|
371
|
+
text: `Attribute '${attributeName}' for entity '${entityName}':\n\n${attributeStr}`,
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
console.error("Error getting entity attribute:", error);
|
|
378
|
+
return {
|
|
379
|
+
content: [
|
|
380
|
+
{
|
|
381
|
+
type: "text",
|
|
382
|
+
text: `Failed to get entity attribute: ${error.message}`,
|
|
383
|
+
},
|
|
384
|
+
],
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
// PowerPlatform entity relationships
|
|
389
|
+
server.tool("get-entity-relationships", "Get relationships (one-to-many and many-to-many) for a PowerPlatform entity", {
|
|
390
|
+
entityName: z.string().describe("The logical name of the entity"),
|
|
391
|
+
}, async ({ entityName }) => {
|
|
392
|
+
try {
|
|
393
|
+
// Get or initialize PowerPlatformService
|
|
394
|
+
const service = getPowerPlatformService();
|
|
395
|
+
const relationships = await service.getEntityRelationships(entityName);
|
|
396
|
+
// Format the relationships as a string for text display
|
|
397
|
+
const relationshipsStr = JSON.stringify(relationships, null, 2);
|
|
398
|
+
return {
|
|
399
|
+
content: [
|
|
400
|
+
{
|
|
401
|
+
type: "text",
|
|
402
|
+
text: `Relationships for entity '${entityName}':\n\n${relationshipsStr}`,
|
|
403
|
+
},
|
|
404
|
+
],
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
console.error("Error getting entity relationships:", error);
|
|
409
|
+
return {
|
|
410
|
+
content: [
|
|
411
|
+
{
|
|
412
|
+
type: "text",
|
|
413
|
+
text: `Failed to get entity relationships: ${error.message}`,
|
|
414
|
+
},
|
|
415
|
+
],
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
// PowerPlatform global option set
|
|
420
|
+
server.tool("get-global-option-set", "Get a global option set definition by name", {
|
|
421
|
+
optionSetName: z.string().describe("The name of the global option set"),
|
|
422
|
+
}, async ({ optionSetName }) => {
|
|
423
|
+
try {
|
|
424
|
+
// Get or initialize PowerPlatformService
|
|
425
|
+
const service = getPowerPlatformService();
|
|
426
|
+
const optionSet = await service.getGlobalOptionSet(optionSetName);
|
|
427
|
+
// Format the option set as a string for text display
|
|
428
|
+
const optionSetStr = JSON.stringify(optionSet, null, 2);
|
|
429
|
+
return {
|
|
430
|
+
content: [
|
|
431
|
+
{
|
|
432
|
+
type: "text",
|
|
433
|
+
text: `Global option set '${optionSetName}':\n\n${optionSetStr}`,
|
|
434
|
+
},
|
|
435
|
+
],
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
console.error("Error getting global option set:", error);
|
|
440
|
+
return {
|
|
441
|
+
content: [
|
|
442
|
+
{
|
|
443
|
+
type: "text",
|
|
444
|
+
text: `Failed to get global option set: ${error.message}`,
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
// PowerPlatform record by ID
|
|
451
|
+
server.tool("get-record", "Get a specific record by entity name (plural) and ID", {
|
|
452
|
+
entityNamePlural: z.string().describe("The plural name of the entity (e.g., 'accounts', 'contacts')"),
|
|
453
|
+
recordId: z.string().describe("The GUID of the record"),
|
|
454
|
+
}, async ({ entityNamePlural, recordId }) => {
|
|
455
|
+
try {
|
|
456
|
+
// Get or initialize PowerPlatformService
|
|
457
|
+
const service = getPowerPlatformService();
|
|
458
|
+
const record = await service.getRecord(entityNamePlural, recordId);
|
|
459
|
+
// Format the record as a string for text display
|
|
460
|
+
const recordStr = JSON.stringify(record, null, 2);
|
|
461
|
+
return {
|
|
462
|
+
content: [
|
|
463
|
+
{
|
|
464
|
+
type: "text",
|
|
465
|
+
text: `Record from '${entityNamePlural}' with ID '${recordId}':\n\n${recordStr}`,
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
console.error("Error getting record:", error);
|
|
472
|
+
return {
|
|
473
|
+
content: [
|
|
474
|
+
{
|
|
475
|
+
type: "text",
|
|
476
|
+
text: `Failed to get record: ${error.message}`,
|
|
477
|
+
},
|
|
478
|
+
],
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
// PowerPlatform query records with filter
|
|
483
|
+
server.tool("query-records", "Query records using an OData filter expression", {
|
|
484
|
+
entityNamePlural: z.string().describe("The plural name of the entity (e.g., 'accounts', 'contacts')"),
|
|
485
|
+
filter: z.string().describe("OData filter expression (e.g., \"name eq 'test'\" or \"createdon gt 2023-01-01\")"),
|
|
486
|
+
maxRecords: z.number().optional().describe("Maximum number of records to retrieve (default: 50)"),
|
|
487
|
+
}, async ({ entityNamePlural, filter, maxRecords }) => {
|
|
488
|
+
try {
|
|
489
|
+
// Get or initialize PowerPlatformService
|
|
490
|
+
const service = getPowerPlatformService();
|
|
491
|
+
const records = await service.queryRecords(entityNamePlural, filter, maxRecords || 50);
|
|
492
|
+
// Format the records as a string for text display
|
|
493
|
+
const recordsStr = JSON.stringify(records, null, 2);
|
|
494
|
+
const recordCount = records.value?.length || 0;
|
|
495
|
+
return {
|
|
496
|
+
content: [
|
|
497
|
+
{
|
|
498
|
+
type: "text",
|
|
499
|
+
text: `Retrieved ${recordCount} records from '${entityNamePlural}' with filter '${filter}':\n\n${recordsStr}`,
|
|
500
|
+
},
|
|
501
|
+
],
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
console.error("Error querying records:", error);
|
|
506
|
+
return {
|
|
507
|
+
content: [
|
|
508
|
+
{
|
|
509
|
+
type: "text",
|
|
510
|
+
text: `Failed to query records: ${error.message}`,
|
|
511
|
+
},
|
|
512
|
+
],
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
// PowerPlatform MCP Prompts
|
|
517
|
+
server.tool("use-powerplatform-prompt", "Use a predefined prompt template for PowerPlatform entities", {
|
|
518
|
+
promptType: z.enum([
|
|
519
|
+
"ENTITY_OVERVIEW",
|
|
520
|
+
"ATTRIBUTE_DETAILS",
|
|
521
|
+
"QUERY_TEMPLATE",
|
|
522
|
+
"RELATIONSHIP_MAP"
|
|
523
|
+
]).describe("The type of prompt template to use"),
|
|
524
|
+
entityName: z.string().describe("The logical name of the entity"),
|
|
525
|
+
attributeName: z.string().optional().describe("The logical name of the attribute (required for ATTRIBUTE_DETAILS prompt)"),
|
|
526
|
+
}, async ({ promptType, entityName, attributeName }) => {
|
|
527
|
+
try {
|
|
528
|
+
// Get or initialize PowerPlatformService
|
|
529
|
+
const service = getPowerPlatformService();
|
|
530
|
+
let promptContent = "";
|
|
531
|
+
let replacements = {};
|
|
532
|
+
switch (promptType) {
|
|
533
|
+
case "ENTITY_OVERVIEW": {
|
|
534
|
+
// Get entity metadata and key attributes
|
|
535
|
+
const [metadata, attributes] = await Promise.all([
|
|
536
|
+
service.getEntityMetadata(entityName),
|
|
537
|
+
service.getEntityAttributes(entityName)
|
|
538
|
+
]);
|
|
539
|
+
// Format entity details
|
|
540
|
+
const entityDetails = `- Display Name: ${metadata.DisplayName?.UserLocalizedLabel?.Label || entityName}\n` +
|
|
541
|
+
`- Schema Name: ${metadata.SchemaName}\n` +
|
|
542
|
+
`- Description: ${metadata.Description?.UserLocalizedLabel?.Label || 'No description'}\n` +
|
|
543
|
+
`- Primary Key: ${metadata.PrimaryIdAttribute}\n` +
|
|
544
|
+
`- Primary Name: ${metadata.PrimaryNameAttribute}`;
|
|
545
|
+
// Get key attributes
|
|
546
|
+
const keyAttributes = attributes.value
|
|
547
|
+
.filter((attr) => attr.IsValidForRead === true && !attr.AttributeOf)
|
|
548
|
+
//.slice(0, 10) // Limit to first 10 important attributes
|
|
549
|
+
.map((attr) => `- ${attr.LogicalName}: ${attr.AttributeType} (${attr.DisplayName?.UserLocalizedLabel?.Label || 'No display name'})`)
|
|
550
|
+
.join('\n');
|
|
551
|
+
// Get relationships summary
|
|
552
|
+
const relationships = await service.getEntityRelationships(entityName);
|
|
553
|
+
const oneToManyCount = relationships.oneToMany.value.length;
|
|
554
|
+
const manyToManyCount = relationships.manyToMany.value.length;
|
|
555
|
+
const relationshipsSummary = `- One-to-Many Relationships: ${oneToManyCount}\n` +
|
|
556
|
+
`- Many-to-Many Relationships: ${manyToManyCount}`;
|
|
557
|
+
promptContent = powerPlatformPrompts.ENTITY_OVERVIEW(entityName);
|
|
558
|
+
replacements = {
|
|
559
|
+
'{{entity_details}}': entityDetails,
|
|
560
|
+
'{{key_attributes}}': keyAttributes,
|
|
561
|
+
'{{relationships}}': relationshipsSummary
|
|
562
|
+
};
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
case "ATTRIBUTE_DETAILS": {
|
|
566
|
+
if (!attributeName) {
|
|
567
|
+
throw new Error("attributeName is required for ATTRIBUTE_DETAILS prompt");
|
|
568
|
+
}
|
|
569
|
+
// Get attribute details
|
|
570
|
+
const attribute = await service.getEntityAttribute(entityName, attributeName);
|
|
571
|
+
// Format attribute details
|
|
572
|
+
const attrDetails = `- Display Name: ${attribute.DisplayName?.UserLocalizedLabel?.Label || attributeName}\n` +
|
|
573
|
+
`- Description: ${attribute.Description?.UserLocalizedLabel?.Label || 'No description'}\n` +
|
|
574
|
+
`- Type: ${attribute.AttributeType}\n` +
|
|
575
|
+
`- Format: ${attribute.Format || 'N/A'}\n` +
|
|
576
|
+
`- Is Required: ${attribute.RequiredLevel?.Value || 'No'}\n` +
|
|
577
|
+
`- Is Searchable: ${attribute.IsValidForAdvancedFind || false}`;
|
|
578
|
+
promptContent = powerPlatformPrompts.ATTRIBUTE_DETAILS(entityName, attributeName);
|
|
579
|
+
replacements = {
|
|
580
|
+
'{{attribute_details}}': attrDetails,
|
|
581
|
+
'{{data_type}}': attribute.AttributeType,
|
|
582
|
+
'{{required}}': attribute.RequiredLevel?.Value || 'No',
|
|
583
|
+
'{{max_length}}': attribute.MaxLength || 'N/A'
|
|
584
|
+
};
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
case "QUERY_TEMPLATE": {
|
|
588
|
+
// Get entity metadata to determine plural name
|
|
589
|
+
const metadata = await service.getEntityMetadata(entityName);
|
|
590
|
+
const entityNamePlural = metadata.EntitySetName;
|
|
591
|
+
// Get a few important fields for the select example
|
|
592
|
+
const attributes = await service.getEntityAttributes(entityName);
|
|
593
|
+
const selectFields = attributes.value
|
|
594
|
+
.filter((attr) => attr.IsValidForRead === true && !attr.AttributeOf)
|
|
595
|
+
.slice(0, 5) // Just take first 5 for example
|
|
596
|
+
.map((attr) => attr.LogicalName)
|
|
597
|
+
.join(',');
|
|
598
|
+
promptContent = powerPlatformPrompts.QUERY_TEMPLATE(entityNamePlural);
|
|
599
|
+
replacements = {
|
|
600
|
+
'{{selected_fields}}': selectFields,
|
|
601
|
+
'{{filter_conditions}}': `${metadata.PrimaryNameAttribute} eq 'Example'`,
|
|
602
|
+
'{{order_by}}': `${metadata.PrimaryNameAttribute} asc`,
|
|
603
|
+
'{{max_records}}': '50'
|
|
604
|
+
};
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
case "RELATIONSHIP_MAP": {
|
|
608
|
+
// Get relationships
|
|
609
|
+
const relationships = await service.getEntityRelationships(entityName);
|
|
610
|
+
// Format one-to-many relationships where this entity is primary
|
|
611
|
+
const oneToManyPrimary = relationships.oneToMany.value
|
|
612
|
+
.filter((rel) => rel.ReferencingEntity !== entityName)
|
|
613
|
+
//.slice(0, 10) // Limit to 10 for readability
|
|
614
|
+
.map((rel) => `- ${rel.SchemaName}: ${entityName} (1) → ${rel.ReferencingEntity} (N)`)
|
|
615
|
+
.join('\n');
|
|
616
|
+
// Format one-to-many relationships where this entity is related
|
|
617
|
+
const oneToManyRelated = relationships.oneToMany.value
|
|
618
|
+
.filter((rel) => rel.ReferencingEntity === entityName)
|
|
619
|
+
//.slice(0, 10) // Limit to 10 for readability
|
|
620
|
+
.map((rel) => `- ${rel.SchemaName}: ${rel.ReferencedEntity} (1) → ${entityName} (N)`)
|
|
621
|
+
.join('\n');
|
|
622
|
+
// Format many-to-many relationships
|
|
623
|
+
const manyToMany = relationships.manyToMany.value
|
|
624
|
+
//.slice(0, 10) // Limit to 10 for readability
|
|
625
|
+
.map((rel) => {
|
|
626
|
+
const otherEntity = rel.Entity1LogicalName === entityName ? rel.Entity2LogicalName : rel.Entity1LogicalName;
|
|
627
|
+
return `- ${rel.SchemaName}: ${entityName} (N) ↔ ${otherEntity} (N)`;
|
|
628
|
+
})
|
|
629
|
+
.join('\n');
|
|
630
|
+
promptContent = powerPlatformPrompts.RELATIONSHIP_MAP(entityName);
|
|
631
|
+
replacements = {
|
|
632
|
+
'{{one_to_many_primary}}': oneToManyPrimary || 'None found',
|
|
633
|
+
'{{one_to_many_related}}': oneToManyRelated || 'None found',
|
|
634
|
+
'{{many_to_many}}': manyToMany || 'None found'
|
|
635
|
+
};
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
// Replace all placeholders in the template
|
|
640
|
+
for (const [placeholder, value] of Object.entries(replacements)) {
|
|
641
|
+
promptContent = promptContent.replace(placeholder, value);
|
|
642
|
+
}
|
|
643
|
+
return {
|
|
644
|
+
content: [
|
|
645
|
+
{
|
|
646
|
+
type: "text",
|
|
647
|
+
text: promptContent,
|
|
648
|
+
},
|
|
649
|
+
],
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
catch (error) {
|
|
653
|
+
console.error("Error using PowerPlatform prompt:", error);
|
|
654
|
+
return {
|
|
655
|
+
content: [
|
|
656
|
+
{
|
|
657
|
+
type: "text",
|
|
658
|
+
text: `Failed to use PowerPlatform prompt: ${error.message}`,
|
|
99
659
|
},
|
|
100
660
|
],
|
|
101
661
|
};
|
package/package.json
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "powerplatform-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "PowerPlatform Model Context Protocol server",
|
|
5
|
-
"main": "build/index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"powerplatform-mcp": "./build/index.js"
|
|
8
|
-
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"build": "tsc",
|
|
11
|
-
"prepublishOnly": "npm run build"
|
|
12
|
-
},
|
|
13
|
-
"files": [
|
|
14
|
-
"build",
|
|
15
|
-
"README.md"
|
|
16
|
-
],
|
|
17
|
-
"keywords": [
|
|
18
|
-
"powerplatform",
|
|
19
|
-
"mcp",
|
|
20
|
-
"model-context-protocol",
|
|
21
|
-
"dynamics",
|
|
22
|
-
"dataverse"
|
|
23
|
-
],
|
|
24
|
-
"author": "Michal Sobieraj",
|
|
25
|
-
"license": "MIT",
|
|
26
|
-
"type": "module",
|
|
27
|
-
"publishConfig": {
|
|
28
|
-
"access": "public"
|
|
29
|
-
},
|
|
30
|
-
"repository": {
|
|
31
|
-
"type": "git",
|
|
32
|
-
"url": "git+https://github.com/michsob/powerplatform-mcp.git"
|
|
33
|
-
},
|
|
34
|
-
"engines": {
|
|
35
|
-
"node": ">=16.0.0"
|
|
36
|
-
},
|
|
37
|
-
"dependencies": {
|
|
38
|
-
"@azure/msal-node": "^3.3.0",
|
|
39
|
-
"@modelcontextprotocol/sdk": "^1.7.0",
|
|
40
|
-
"axios": "^1.8.3",
|
|
41
|
-
"zod": "^3.24.2"
|
|
42
|
-
},
|
|
43
|
-
"devDependencies": {
|
|
44
|
-
"@types/node": "^22.13.10",
|
|
45
|
-
"typescript": "^5.8.2"
|
|
46
|
-
}
|
|
47
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "powerplatform-mcp",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "PowerPlatform Model Context Protocol server",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"powerplatform-mcp": "./build/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"prepublishOnly": "npm run build"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"build",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"powerplatform",
|
|
19
|
+
"mcp",
|
|
20
|
+
"model-context-protocol",
|
|
21
|
+
"dynamics",
|
|
22
|
+
"dataverse"
|
|
23
|
+
],
|
|
24
|
+
"author": "Michal Sobieraj",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/michsob/powerplatform-mcp.git"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=16.0.0"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@azure/msal-node": "^3.3.0",
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.7.0",
|
|
40
|
+
"axios": "^1.8.3",
|
|
41
|
+
"zod": "^3.24.2"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^22.13.10",
|
|
45
|
+
"typescript": "^5.8.2"
|
|
46
|
+
}
|
|
47
|
+
}
|