Skip to main content
Glama

Dataverse MCP Server

by mwhesse
powerpages-resources.ts23.9 kB
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; import { DataverseClient } from "../dataverse-client.js"; // Helper function to build OData query parameters for PowerPages function buildPowerPagesODataQuery(options: { select?: string[]; filter?: string; orderby?: string; top?: number; skip?: number; expand?: string; count?: boolean; }): string { const params: string[] = []; if (options.select && options.select.length > 0) { params.push(`$select=${options.select.join(',')}`); } if (options.filter) { params.push(`$filter=${encodeURIComponent(options.filter)}`); } if (options.orderby) { params.push(`$orderby=${encodeURIComponent(options.orderby)}`); } if (options.top) { params.push(`$top=${options.top}`); } if (options.skip) { params.push(`$skip=${options.skip}`); } if (options.expand) { params.push(`$expand=${encodeURIComponent(options.expand)}`); } if (options.count) { params.push(`$count=true`); } return params.length > 0 ? `?${params.join('&')}` : ''; } // Helper function to generate headers for PowerPages function generatePowerPagesHeaders(options: { contentType?: string; accept?: string; requestVerificationToken?: string; customHeaders?: Record<string, string>; }): Record<string, string> { const headers: Record<string, string> = { 'Content-Type': options.contentType || 'application/json', 'Accept': options.accept || 'application/json' }; if (options.requestVerificationToken) { headers['__RequestVerificationToken'] = options.requestVerificationToken; } if (options.customHeaders) { Object.assign(headers, options.customHeaders); } return headers; } // Helper function to ensure PowerPages entity name has proper suffix function formatPowerPagesEntityName(logicalEntityName: string): string { return logicalEntityName.endsWith('s') ? logicalEntityName : `${logicalEntityName}s`; } // Helper function to format the complete PowerPages WebAPI call function formatPowerPagesWebAPICall( baseUrl: string, method: string, endpoint: string, headers: Record<string, string>, body?: any ): string { const fullUrl = `${baseUrl}${endpoint}`; let result = `HTTP Method: ${method}\n`; result += `URL: ${fullUrl}\n\n`; result += `Headers:\n`; Object.entries(headers).forEach(([key, value]) => { result += ` ${key}: ${value}\n`; }); if (body) { result += `\nRequest Body:\n`; result += JSON.stringify(body, null, 2); } return result; } // Generate PowerPages WebAPI code examples function generatePowerPagesExamples( operation: string, logicalEntityName?: string, entityId?: string, options: { select?: string[]; filter?: string; orderby?: string; top?: number; skip?: number; expand?: string; count?: boolean; data?: any; baseUrl?: string; requestVerificationToken?: boolean; includeAuthContext?: boolean; customHeaders?: Record<string, string>; } = {} ): string { try { const baseUrl = options.baseUrl || 'https://yoursite.powerappsportals.com'; let method = 'GET'; let endpoint = ''; let body: any = undefined; // Build headers const headerOptions: any = { customHeaders: options.customHeaders }; if (options.requestVerificationToken && (operation === 'create' || operation === 'update' || operation === 'delete')) { headerOptions.requestVerificationToken = '{REQUEST_VERIFICATION_TOKEN}'; } const headers = generatePowerPagesHeaders(headerOptions); // Build endpoint based on operation type const formattedEntityName = logicalEntityName ? formatPowerPagesEntityName(logicalEntityName) : 'entities'; switch (operation) { case 'retrieve': if (!entityId) { throw new Error('entityId is required for retrieve operation'); } method = 'GET'; endpoint = `/_api/${formattedEntityName}(${entityId})`; const retrieveQuery = buildPowerPagesODataQuery({ select: options.select, expand: options.expand }); endpoint += retrieveQuery; break; case 'retrieveMultiple': method = 'GET'; endpoint = `/_api/${formattedEntityName}`; const retrieveMultipleQuery = buildPowerPagesODataQuery({ select: options.select, filter: options.filter, orderby: options.orderby, top: options.top, skip: options.skip, expand: options.expand, count: options.count }); endpoint += retrieveMultipleQuery; break; case 'create': if (!options.data) { throw new Error('data is required for create operation'); } method = 'POST'; endpoint = `/_api/${formattedEntityName}`; body = options.data; break; case 'update': if (!entityId || !options.data) { throw new Error('entityId and data are required for update operation'); } method = 'PATCH'; endpoint = `/_api/${formattedEntityName}(${entityId})`; body = options.data; break; case 'delete': if (!entityId) { throw new Error('entityId is required for delete operation'); } method = 'DELETE'; endpoint = `/_api/${formattedEntityName}(${entityId})`; break; default: throw new Error(`Unsupported operation: ${operation}`); } const webApiCall = formatPowerPagesWebAPICall(baseUrl, method, endpoint, headers, body); // Additional information let additionalInfo = '\n\n--- Additional Information ---\n'; additionalInfo += `Operation Type: ${operation}\n`; if (logicalEntityName) { additionalInfo += `Entity: ${logicalEntityName}\n`; additionalInfo += `Formatted Entity Name: ${formattedEntityName}\n`; } additionalInfo += `PowerPages WebAPI Format: /_api/[logicalEntityName]s (note: 's' suffix required)\n`; if (entityId) { additionalInfo += `Entity ID: ${entityId}\n`; } // Include JavaScript fetch example for PowerPages let fetchExample = `// PowerPages WebAPI Call\n`; fetchExample += `const fetchData = async () => {\n`; if (options.requestVerificationToken && (operation === 'create' || operation === 'update' || operation === 'delete')) { fetchExample += ` // Get the request verification token\n`; fetchExample += ` const token = document.querySelector('input[name="__RequestVerificationToken"]')?.value;\n\n`; } fetchExample += ` try {\n`; fetchExample += ` const response = await fetch('${endpoint}', {\n`; fetchExample += ` method: '${method}',\n`; // Build headers for fetch example const fetchHeaders: Record<string, string> = { ...headers }; if (options.requestVerificationToken && (operation === 'create' || operation === 'update' || operation === 'delete')) { fetchHeaders['__RequestVerificationToken'] = '${token}'; } fetchExample += ` headers: ${JSON.stringify(fetchHeaders, null, 8).replace(/"/g, "'").replace(/'(\$\{[^}]+\})'/g, '$1')},\n`; if (body) { fetchExample += ` body: JSON.stringify(${JSON.stringify(body, null, 8)})\n`; } fetchExample += ` });\n\n`; fetchExample += ` if (!response.ok) {\n`; fetchExample += ` throw new Error(\`HTTP error! status: \${response.status}\`);\n`; fetchExample += ` }\n\n`; if (operation === 'retrieveMultiple') { fetchExample += ` const data = await response.json();\n`; fetchExample += ` const records = data.value; // Array of records\n`; fetchExample += ` console.log('Records:', records);\n`; fetchExample += ` return records;\n`; } else if (operation === 'retrieve') { fetchExample += ` const record = await response.json();\n`; fetchExample += ` console.log('Record:', record);\n`; fetchExample += ` return record;\n`; } else if (operation === 'create') { fetchExample += ` const createdRecord = await response.json();\n`; fetchExample += ` console.log('Created record:', createdRecord);\n`; fetchExample += ` return createdRecord;\n`; } else if (operation === 'update') { fetchExample += ` console.log('Record updated successfully');\n`; fetchExample += ` return true;\n`; } else if (operation === 'delete') { fetchExample += ` console.log('Record deleted successfully');\n`; fetchExample += ` return true;\n`; } fetchExample += ` } catch (error) {\n`; fetchExample += ` console.error('Error:', error);\n`; fetchExample += ` throw error;\n`; fetchExample += ` }\n`; fetchExample += `};\n\n`; fetchExample += `// Call the function\n`; fetchExample += `fetchData();`; additionalInfo += `\nPowerPages JavaScript Example:\n${fetchExample}\n`; // Include React example if applicable if (operation === 'retrieveMultiple' && logicalEntityName) { let reactExample = `// React Hook Example\n`; reactExample += `import React, { useState, useEffect } from 'react';\n\n`; reactExample += `const ${logicalEntityName.charAt(0).toUpperCase() + logicalEntityName.slice(1)}List = () => {\n`; reactExample += ` const [records, setRecords] = useState([]);\n`; reactExample += ` const [loading, setLoading] = useState(true);\n\n`; reactExample += ` useEffect(() => {\n`; reactExample += ` const fetchRecords = async () => {\n`; reactExample += ` try {\n`; reactExample += ` const response = await fetch('${endpoint}');\n`; reactExample += ` const data = await response.json();\n`; reactExample += ` setRecords(data.value);\n`; reactExample += ` } catch (error) {\n`; reactExample += ` console.error('Error fetching records:', error);\n`; reactExample += ` } finally {\n`; reactExample += ` setLoading(false);\n`; reactExample += ` }\n`; reactExample += ` };\n\n`; reactExample += ` fetchRecords();\n`; reactExample += ` }, []);\n\n`; reactExample += ` if (loading) return <div>Loading...</div>;\n\n`; reactExample += ` return (\n`; reactExample += ` <div>\n`; reactExample += ` <h2>${logicalEntityName} Records</h2>\n`; reactExample += ` {records.map((record, index) => (\n`; reactExample += ` <div key={record.${logicalEntityName}id || index}>\n`; reactExample += ` {/* Render record properties */}\n`; reactExample += ` <pre>{JSON.stringify(record, null, 2)}</pre>\n`; reactExample += ` </div>\n`; reactExample += ` ))}\n`; reactExample += ` </div>\n`; reactExample += ` );\n`; reactExample += `};\n\n`; reactExample += `export default ${logicalEntityName.charAt(0).toUpperCase() + logicalEntityName.slice(1)}List;`; additionalInfo += `\nReact Component Example:\n${reactExample}\n`; } // Include authentication context information if (options.includeAuthContext) { let authInfo = `\n--- Authentication Context ---\n`; authInfo += `// Access user information in PowerPages\n`; authInfo += `const user = window["Microsoft"]?.Dynamic365?.Portal?.User;\n`; authInfo += `const userName = user?.userName || "";\n`; authInfo += `const firstName = user?.firstName || "";\n`; authInfo += `const lastName = user?.lastName || "";\n`; authInfo += `const isAuthenticated = userName !== "";\n\n`; authInfo += `// Get authentication token (if needed)\n`; authInfo += `const getToken = async () => {\n`; authInfo += ` try {\n`; authInfo += ` const token = await window.shell.getTokenDeferred();\n`; authInfo += ` return token;\n`; authInfo += ` } catch (error) {\n`; authInfo += ` console.error('Error fetching token:', error);\n`; authInfo += ` return null;\n`; authInfo += ` }\n`; authInfo += `};\n`; additionalInfo += authInfo; } return `${webApiCall}${additionalInfo}`; } catch (error) { return `Error generating PowerPages WebAPI call: ${error instanceof Error ? error.message : 'Unknown error'}`; } } export function registerPowerPagesResources(server: McpServer, client: DataverseClient) { // Register PowerPages WebAPI code generation resource server.registerResource( "powerpages-webapi", new ResourceTemplate("powerpages://{operation}/{entityName?}/{entityId?}", { list: async () => { // Return some example URIs for discovery return { resources: [ { uri: "powerpages://retrieve/contacts/{id}", name: "powerpages-retrieve-contact", title: "Retrieve Contact" }, { uri: "powerpages://retrieveMultiple/contacts", name: "powerpages-list-contacts", title: "List Contacts" }, { uri: "powerpages://create/contacts", name: "powerpages-create-contact", title: "Create Contact" }, { uri: "powerpages://update/contacts/{id}", name: "powerpages-update-contact", title: "Update Contact" }, { uri: "powerpages://delete/contacts/{id}", name: "powerpages-delete-contact", title: "Delete Contact" } ] }; }, complete: { operation: (value) => { const operations = ["retrieve", "retrieveMultiple", "create", "update", "delete"]; return operations.filter(op => op.toLowerCase().startsWith(value.toLowerCase())); }, entityName: (value) => { // Common custom entity patterns for PowerPages const entities = ["contacts", "accounts", "cr123_customentities", "new_customtables"]; return entities.filter(e => e.toLowerCase().startsWith(value.toLowerCase())); } } }), { title: "PowerPages WebAPI Call Generator", description: "Generate PowerPages-specific API calls, JavaScript examples, and React components" }, async (uri, params) => { // Debug logging to see what we're receiving console.log('PowerPages Resource called with URI:', uri.href); console.log('PowerPages Resource params:', JSON.stringify(params, null, 2)); // Extract parameters from the URI template const { operation, entityName, entityId } = params || {}; // Convert arrays to strings (take first element) const opStr = Array.isArray(operation) ? operation[0] : operation; const entityStr = Array.isArray(entityName) ? entityName[0] : entityName; const idStr = Array.isArray(entityId) ? entityId[0] : entityId; // If parameters are not being extracted properly, try manual parsing if (!opStr || !entityStr) { const uriParts = uri.href.replace('powerpages://', '').split('/'); const manualOp = uriParts[0]; const manualEntity = uriParts[1]; const manualId = uriParts[2]; console.log('PowerPages Manual parsing:', { manualOp, manualEntity, manualId }); const codeExamples = generatePowerPagesExamples(manualOp, manualEntity, manualId); return { contents: [{ uri: uri.href, text: codeExamples, mimeType: "text/plain" }] }; } const codeExamples = generatePowerPagesExamples(opStr, entityStr, idStr); return { contents: [{ uri: uri.href, text: codeExamples, mimeType: "text/plain" }] }; } ); // Register PowerPages examples resource with common patterns server.registerResource( "powerpages-examples", new ResourceTemplate("powerpages-examples://{operation}/{entityName?}", { list: async () => { // Return example URIs for different operations return { resources: [ { uri: "powerpages-examples://retrieve/contacts", name: "powerpages-examples-retrieve", title: "Contact Retrieve Examples" }, { uri: "powerpages-examples://retrieveMultiple/contacts", name: "powerpages-examples-list", title: "Contact List Examples" }, { uri: "powerpages-examples://create/contacts", name: "powerpages-examples-create", title: "Contact Creation Examples" }, { uri: "powerpages-examples://update/contacts", name: "powerpages-examples-update", title: "Contact Update Examples" }, { uri: "powerpages-examples://delete/contacts", name: "powerpages-examples-delete", title: "Contact Deletion Examples" } ] }; }, complete: { operation: (value) => { const operations = ["retrieve", "retrieveMultiple", "create", "update", "delete"]; return operations.filter(op => op.toLowerCase().startsWith(value.toLowerCase())); } } }), { title: "PowerPages WebAPI Examples", description: "Common PowerPages WebAPI patterns with authentication and React components" }, async (uri, params) => { // Extract parameters from the URI template const { operation, entityName } = params || {}; // Convert arrays to strings (take first element) const opStr = Array.isArray(operation) ? operation[0] : operation; const entityStr = Array.isArray(entityName) ? entityName[0] : entityName; let examples = `# PowerPages WebAPI Examples - ${opStr}\n\n`; switch (opStr) { case 'retrieve': examples += `## Retrieve Single Record from PowerPages\n\n`; examples += generatePowerPagesExamples('retrieve', entityStr || 'contacts', '{record-id}', { select: ['fullname', 'emailaddress1'], requestVerificationToken: false, includeAuthContext: true }); break; case 'retrieveMultiple': examples += `## Retrieve Multiple Records from PowerPages\n\n`; examples += generatePowerPagesExamples('retrieveMultiple', entityStr || 'contacts', undefined, { select: ['fullname', 'emailaddress1', 'telephone1'], filter: 'statecode eq 0', orderby: 'fullname asc', top: 10, includeAuthContext: true }); break; case 'create': examples += `## Create New Record in PowerPages\n\n`; examples += generatePowerPagesExamples('create', entityStr || 'contacts', undefined, { data: { fullname: 'John Doe', emailaddress1: 'john@example.com', telephone1: '555-0123' }, requestVerificationToken: true, includeAuthContext: true }); break; case 'update': examples += `## Update Record in PowerPages\n\n`; examples += generatePowerPagesExamples('update', entityStr || 'contacts', '{record-id}', { data: { fullname: 'John Updated', emailaddress1: 'john.updated@example.com' }, requestVerificationToken: true, includeAuthContext: true }); break; case 'delete': examples += `## Delete Record in PowerPages\n\n`; examples += generatePowerPagesExamples('delete', entityStr || 'contacts', '{record-id}', { requestVerificationToken: true, includeAuthContext: true }); break; default: examples += `## ${opStr} Operation in PowerPages\n\n`; examples += generatePowerPagesExamples(opStr, entityStr, undefined, { includeAuthContext: true }); } return { contents: [{ uri: uri.href, text: examples, mimeType: "text/markdown" }] }; } ); // Register PowerPages authentication patterns resource server.registerResource( "powerpages-auth", "powerpages-auth://patterns", { title: "PowerPages Authentication Patterns", description: "Common authentication and user context patterns for PowerPages" }, async (uri) => { const authPatterns = `# PowerPages Authentication Patterns ## User Context Access \`\`\`javascript // Access current user information const user = window["Microsoft"]?.Dynamic365?.Portal?.User; const userName = user?.userName || ""; const firstName = user?.firstName || ""; const lastName = user?.lastName || ""; const emailAddress = user?.emailAddress || ""; const isAuthenticated = userName !== ""; console.log('Current user:', { userName, firstName, lastName, emailAddress, isAuthenticated }); \`\`\` ## Request Verification Token \`\`\`javascript // Get request verification token for POST operations const getRequestVerificationToken = () => { const tokenInput = document.querySelector('input[name="__RequestVerificationToken"]'); return tokenInput ? tokenInput.value : null; }; // Use in fetch requests const token = getRequestVerificationToken(); if (token) { headers['__RequestVerificationToken'] = token; } \`\`\` ## Authentication Check Hook (React) \`\`\`javascript import { useState, useEffect } from 'react'; const usePortalAuth = () => { const [user, setUser] = useState(null); const [isAuthenticated, setIsAuthenticated] = useState(false); const [loading, setLoading] = useState(true); useEffect(() => { const checkAuth = () => { try { const portalUser = window["Microsoft"]?.Dynamic365?.Portal?.User; if (portalUser && portalUser.userName) { setUser({ userName: portalUser.userName, firstName: portalUser.firstName, lastName: portalUser.lastName, emailAddress: portalUser.emailAddress }); setIsAuthenticated(true); } else { setIsAuthenticated(false); } } catch (error) { console.error('Error checking authentication:', error); setIsAuthenticated(false); } finally { setLoading(false); } }; checkAuth(); }, []); return { user, isAuthenticated, loading }; }; export default usePortalAuth; \`\`\` ## Protected API Call Pattern \`\`\`javascript const makeProtectedApiCall = async (endpoint, options = {}) => { // Check authentication const user = window["Microsoft"]?.Dynamic365?.Portal?.User; if (!user || !user.userName) { throw new Error('User not authenticated'); } // Get verification token for write operations const token = getRequestVerificationToken(); const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', ...options.headers }; if (token && ['POST', 'PATCH', 'DELETE'].includes(options.method?.toUpperCase())) { headers['__RequestVerificationToken'] = token; } try { const response = await fetch(endpoint, { ...options, headers }); if (!response.ok) { if (response.status === 401) { throw new Error('Authentication required'); } if (response.status === 403) { throw new Error('Access denied'); } throw new Error(\`HTTP error! status: \${response.status}\`); } return await response.json(); } catch (error) { console.error('API call failed:', error); throw error; } }; \`\`\``; return { contents: [{ uri: uri.href, text: authPatterns, mimeType: "text/markdown" }] }; } ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mwhesse/mcp-dataverse'

If you have feedback or need assistance with the MCP directory API, please join our Discord server