Skip to main content
Glama
mwhesse

Dataverse MCP Server

by mwhesse

generate_powerpages_webapi_call

Create PowerPages API calls and JavaScript examples for Dataverse operations including retrieve, create, update, and delete with authentication context and portal-specific patterns.

Instructions

Generate PowerPages-specific API calls, JavaScript examples, and React components for Dataverse operations through PowerPages portals. Includes authentication context and portal-specific patterns.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
baseUrlNoPowerPages site base URL (e.g., 'https://yoursite.powerappsportals.com')
countNoInclude count of records
customHeadersNoCustom headers to include in the request
dataNoData to send in request body for create/update operations
entityIdNoEntity ID for single record operations (GUID)
expandNoRelated entities to expand
filterNoOData filter expression
includeAuthContextNoInclude authentication context information
logicalEntityNameYesLogical entity name (e.g., 'cr7ae_creditcardse', 'contact') - will be automatically suffixed with 's' for PowerPages API URLs
operationYesType of operation to perform
orderbyNoOData orderby expression
requestVerificationTokenNoInclude __RequestVerificationToken placeholder for POST operations
selectNoFields to select (e.g., ['cr7ae_name', 'cr7ae_type'])
skipNoNumber of records to skip
topNoNumber of records to return

Implementation Reference

  • The main execution handler for the tool. Handles all operations (retrieve, create, etc.), resolves entity schema, builds OData queries and bodies with @odata.bind processing, generates HTTP/cURL/JS/React code examples, and additional context like auth and schema.
        async (params: any) => {
          try {
            const baseUrl = params.baseUrl || 'https://yoursite.powerappsportals.com';
            
            // Resolve entity metadata for schema-aware capabilities
            let entityInfo: any = null;
            let entitySetName = '';
            let targetSetCache = new Map<string, string>();
            
            try {
              entityInfo = await resolvePowerPagesEntityInfo(client, params.logicalEntityName);
              entitySetName = entityInfo.entitySetName;
            } catch (error) {
              // Fallback to naive pluralization if metadata fails
              entitySetName = params.logicalEntityName ? 
                (params.logicalEntityName.endsWith('s') ? params.logicalEntityName : `${params.logicalEntityName}s`) : '';
            }
    
            let url = `${baseUrl}/_api/${entitySetName}`;
            let method = 'GET';
            let body: any = null;
            let headers: Record<string, string> = {
              'Accept': 'application/json',
              'Content-Type': 'application/json',
              ...params.customHeaders
            };
    
            // Add request verification token for POST operations if requested
            if (params.requestVerificationToken && ['create', 'update', 'delete'].includes(params.operation)) {
              headers['__RequestVerificationToken'] = '{{REQUEST_VERIFICATION_TOKEN}}';
            }
    
            // Helper to resolve target entity set names
            const resolveTargetSet = async (targetLogicalName: string): Promise<string> => {
              return await getPowerPagesTargetEntitySetName(client, targetSetCache, targetLogicalName);
            };
    
            switch (params.operation) {
              case 'retrieve':
                if (!params.entityId) {
                  throw new Error("entityId is required for retrieve operation");
                }
                url += `(${params.entityId})`;
                
                // Auto-select primary fields if no select specified and we have schema
                let finalSelect = params.select;
                if (!params.select && entityInfo && entityInfo.primaryIdAttribute) {
                  const autoFields = [entityInfo.primaryIdAttribute];
                  if (entityInfo.primaryNameAttribute) {
                    autoFields.push(entityInfo.primaryNameAttribute);
                  }
                  finalSelect = autoFields;
                }
                
                const queryParams = buildPowerPagesODataQuery({ select: finalSelect, expand: params.expand });
                if (queryParams) {
                  url += queryParams;
                }
                break;
    
              case 'retrieveMultiple':
                // Auto-select primary fields if no select specified and we have schema
                let finalListSelect = params.select;
                if (!params.select && entityInfo && entityInfo.primaryIdAttribute) {
                  const autoFields = [entityInfo.primaryIdAttribute];
                  if (entityInfo.primaryNameAttribute) {
                    autoFields.push(entityInfo.primaryNameAttribute);
                  }
                  finalListSelect = autoFields;
                }
                
                const listQueryParams = buildPowerPagesODataQuery({ 
                  select: finalListSelect, 
                  filter: params.filter, 
                  orderby: params.orderby, 
                  top: params.top, 
                  skip: params.skip, 
                  expand: params.expand, 
                  count: params.count 
                });
                if (listQueryParams) {
                  url += listQueryParams;
                }
                break;
    
              case 'create':
                method = 'POST';
                
                // Process @odata.bind properties and generate sample if no data provided
                if (params.data) {
                  body = processPowerPagesODataBindProperties(params.data, baseUrl, entityInfo);
                } else if (entityInfo) {
                  // Generate schema-aware sample body
                  body = await generatePowerPagesSampleBodyFromSchema(entityInfo, baseUrl, resolveTargetSet, 'create');
                } else {
                  body = {};
                }
                break;
    
              case 'update':
                if (!params.entityId) {
                  throw new Error("entityId is required for update operation");
                }
                method = 'PATCH';
                url += `(${params.entityId})`;
                
                // Process @odata.bind properties and generate sample if no data provided
                if (params.data) {
                  body = processPowerPagesODataBindProperties(params.data, baseUrl, entityInfo);
                } else if (entityInfo) {
                  // Generate schema-aware sample body
                  body = await generatePowerPagesSampleBodyFromSchema(entityInfo, baseUrl, resolveTargetSet, 'update');
                } else {
                  body = {};
                }
                break;
    
              case 'delete':
                if (!params.entityId) {
                  throw new Error("entityId is required for delete operation");
                }
                method = 'DELETE';
                url += `(${params.entityId})`;
                break;
    
              default:
                throw new Error(`Unsupported operation: ${params.operation}`);
            }
    
            // Generate examples
            const examples = [];
    
            // HTTP Request
            const httpRequest = [
              `${method} ${url} HTTP/1.1`,
              `Host: ${new URL(baseUrl).host}`,
              ...Object.entries(headers).map(([key, value]) => `${key}: ${value}`)
            ];
    
            if (body) {
              httpRequest.push('');
              httpRequest.push(JSON.stringify(body, null, 2));
            }
    
            examples.push({
              title: "HTTP Request",
              content: httpRequest.join('\n')
            });
    
            // cURL Command
            const curlParts = [`curl -X ${method}`];
            Object.entries(headers).forEach(([key, value]) => {
              curlParts.push(`-H "${key}: ${value}"`);
            });
            
            if (body) {
              curlParts.push(`-d '${JSON.stringify(body)}'`);
            }
            
            curlParts.push(`"${url}"`);
    
            examples.push({
              title: "cURL Command",
              content: curlParts.join(' \\\n  ')
            });
    
            // JavaScript Fetch
            const fetchOptions: any = {
              method,
              headers
            };
    
            if (body) {
              fetchOptions.body = JSON.stringify(body);
            }
    
            const jsCode = `
    // PowerPages WebAPI ${params.operation} operation
    fetch('${url}', ${JSON.stringify(fetchOptions, null, 2)})
      .then(response => {
        if (!response.ok) {
          throw new Error(\`HTTP error! status: \${response.status}\`);
        }
        return response.json();
      })
      .then(data => {
        console.log('Success:', data);
      })
      .catch(error => {
        console.error('Error:', error);
      });`;
    
            examples.push({
              title: "JavaScript (Fetch API)",
              content: jsCode.trim()
            });
    
            // React Component Example
            const reactCode = `
    import React, { useState, useEffect } from 'react';
    
    const ${params.operation.charAt(0).toUpperCase() + params.operation.slice(1)}Component = () => {
      const [data, setData] = useState(null);
      const [loading, setLoading] = useState(false);
      const [error, setError] = useState(null);
    
      const perform${params.operation.charAt(0).toUpperCase() + params.operation.slice(1)} = async () => {
        setLoading(true);
        setError(null);
        
        try {
          const response = await fetch('${url}', ${JSON.stringify(fetchOptions, null, 6)});
          
          if (!response.ok) {
            throw new Error(\`HTTP error! status: \${response.status}\`);
          }
          
          const result = await response.json();
          setData(result);
        } catch (err) {
          setError(err.message);
        } finally {
          setLoading(false);
        }
      };
    
      ${params.operation === 'retrieveMultiple' || params.operation === 'retrieve' ? `
      useEffect(() => {
        perform${params.operation.charAt(0).toUpperCase() + params.operation.slice(1)}();
      }, []);` : ''}
    
      return (
        <div>
          <h3>${params.operation.charAt(0).toUpperCase() + params.operation.slice(1)} ${params.logicalEntityName || 'Entity'}</h3>
          ${params.operation !== 'retrieveMultiple' && params.operation !== 'retrieve' ? `
          <button onClick={perform${params.operation.charAt(0).toUpperCase() + params.operation.slice(1)}} disabled={loading}>
            {loading ? 'Processing...' : '${params.operation.charAt(0).toUpperCase() + params.operation.slice(1)}'}
          </button>` : ''}
          
          {loading && <p>Loading...</p>}
          {error && <p style={{color: 'red'}}>Error: {error}</p>}
          {data && (
            <div>
              <h4>Result:</h4>
              <pre>{JSON.stringify(data, null, 2)}</pre>
            </div>
          )}
        </div>
      );
    };
    
    export default ${params.operation.charAt(0).toUpperCase() + params.operation.slice(1)}Component;`;
    
            examples.push({
              title: "React Component",
              content: reactCode.trim()
            });
    
            // Add @odata.bind examples if present in the body
            if (body && hasODataBindProperties(body)) {
              const bindExamples = extractNavigationPropertyExamples(body);
              if (bindExamples.length > 0) {
                const odataBindInfo = `
    ## @odata.bind Relationship Examples
    
    The request body includes relationship associations using @odata.bind:
    
    \`\`\`javascript
    ${bindExamples.join('\n')}
    \`\`\`
    
    ### @odata.bind Usage Patterns:
    
    1. **Associate with existing record:**
       \`"navigationProperty@odata.bind": "/_api/entityset(guid)"\`
    
    2. **Disassociate relationship:**
       \`"navigationProperty@odata.bind": null\`
    
    3. **PowerPages URL Format:**
       - Use relative paths: \`/_api/contacts(guid)\`
       - Entity set names are typically plural: \`contacts\`, \`accounts\`, etc.
    
    ### Navigation Property Names:
    ${entityInfo && entityInfo.lookupNavMap.size > 0 ? 
      Array.from(entityInfo.lookupNavMap.entries())
        .map((entry: any) => {
          const [attr, nav] = entry as [string, string];
          return `- Lookup attribute \`${attr}\` → Navigation property \`${nav}\``;
        })
        .join('\n') :
      '- Navigation properties are automatically resolved from table schema'}`;
    
                examples.push({
                  title: "@odata.bind Relationships",
                  content: odataBindInfo.trim()
                });
              }
            }
    
            // Authentication context if requested
            if (params.includeAuthContext) {
              const authInfo = `
    ## Authentication Context for PowerPages
    
    PowerPages uses different authentication mechanisms:
    
    1. **Anonymous Access**: No authentication required for public data
    2. **Authenticated Users**: Session-based authentication via portal login
    3. **Request Verification Token**: Anti-CSRF protection for state-changing operations
    
    ### Getting Request Verification Token (JavaScript):
    \`\`\`javascript
    // Get the token from the page (usually in a hidden input or meta tag)
    const token = document.querySelector('input[name="__RequestVerificationToken"]')?.value ||
                  document.querySelector('meta[name="__RequestVerificationToken"]')?.content;
    
    // Include in headers for POST/PATCH/DELETE operations
    headers['__RequestVerificationToken'] = token;
    \`\`\`
    
    ### User Context:
    \`\`\`javascript
    // Access current user information (if available)
    const userContext = {
      isAuthenticated: window.Shell?.user?.isAuthenticated || false,
      userId: window.Shell?.user?.id,
      userName: window.Shell?.user?.displayName
    };
    \`\`\``;
    
              examples.push({
                title: "Authentication Information",
                content: authInfo.trim()
              });
            }
    
            // Add schema information if available
            if (entityInfo && entityInfo.logicalName) {
              const schemaInfo = `
    ## Schema Information
    
    **Entity:** ${entityInfo.logicalName} (${entityInfo.entitySetName})
    **Primary ID:** ${entityInfo.primaryIdAttribute || 'Not available'}
    **Primary Name:** ${entityInfo.primaryNameAttribute || 'Not available'}
    
    ### Available Fields:
    ${entityInfo.attributes && entityInfo.attributes.length > 0 ?
      entityInfo.attributes
        .filter((attr: any) => attr?.LogicalName)
        .slice(0, 10) // Show first 10 fields
        .map((attr: any) => `- \`${attr.LogicalName}\` (${attr.AttributeType})`)
        .join('\n') +
      (entityInfo.attributes.length > 10 ? `\n- ... and ${entityInfo.attributes.length - 10} more fields` : '') :
      'Schema information not available'}
    
    ### Lookup Navigation Properties:
    ${entityInfo.lookupNavMap && entityInfo.lookupNavMap.size > 0 ?
      Array.from(entityInfo.lookupNavMap.entries())
        .map((entry: any) => {
          const [attr, nav] = entry as [string, string];
          return `- \`${attr}\` → \`${nav}\``;
        })
        .join('\n') :
      'No lookup relationships found'}`;
    
              examples.push({
                title: "Entity Schema",
                content: schemaInfo.trim()
              });
            }
    
            const result = examples.map(example =>
              `## ${example.title}\n\n\`\`\`${example.title.includes('React') ? 'jsx' : example.title.includes('JavaScript') ? 'javascript' : example.title.includes('cURL') ? 'bash' : example.title.includes('@odata.bind') || example.title.includes('Schema') || example.title.includes('Authentication') ? 'markdown' : 'http'}\n${example.content}\n\`\`\``
            ).join('\n\n');
    
            return {
              content: [
                {
                  type: "text",
                  text: result
                }
              ]
            };
          } catch (error) {
            return {
              content: [
                {
                  type: "text",
                  text: `Error generating PowerPages WebAPI call: ${error instanceof Error ? error.message : 'Unknown error'}`
                }
              ],
              isError: true
            };
          }
        }
      );
  • Zod input schema defining parameters for the tool: operation type, entity name/ID, OData query options, request data, PowerPages-specific options like baseUrl and requestVerificationToken.
    inputSchema: {
      operation: z.enum([
        "retrieve", "retrieveMultiple", "create", "update", "delete"
      ]).describe("Type of operation to perform"),
      
      logicalEntityName: z.string().describe("Logical entity name (e.g., 'cr7ae_creditcardse', 'contact') - will be automatically suffixed with 's' for PowerPages API URLs"),
      entityId: z.string().optional().describe("Entity ID for single record operations (GUID)"),
      
      // OData query options
      select: z.array(z.string()).optional().describe("Fields to select (e.g., ['cr7ae_name', 'cr7ae_type'])"),
      filter: z.string().optional().describe("OData filter expression"),
      orderby: z.string().optional().describe("OData orderby expression"),
      top: z.number().optional().describe("Number of records to return"),
      skip: z.number().optional().describe("Number of records to skip"),
      expand: z.string().optional().describe("Related entities to expand"),
      count: z.boolean().optional().describe("Include count of records"),
      
      // Request body for create/update operations
      data: z.record(z.any()).optional().describe("Data to send in request body for create/update operations"),
      
      // PowerPages specific options
      baseUrl: z.string().optional().describe("PowerPages site base URL (e.g., 'https://yoursite.powerappsportals.com')"),
      requestVerificationToken: z.boolean().default(false).describe("Include __RequestVerificationToken placeholder for POST operations"),
      includeAuthContext: z.boolean().default(false).describe("Include authentication context information"),
      
      // Additional headers
      customHeaders: z.record(z.string()).optional().describe("Custom headers to include in the request")
    }
  • Factory function that registers the MCP tool with name 'generate_powerpages_webapi_call', title, description, input schema, and handler callback.
    export function generatePowerPagesWebAPICallTool(server: McpServer, client: DataverseClient) {
      server.registerTool(
        "generate_powerpages_webapi_call",
        {
          title: "Generate PowerPages WebAPI Call",
          description: "Generate PowerPages-specific API calls, JavaScript examples, and React components for Dataverse operations through PowerPages portals. Includes authentication context and portal-specific patterns.",
          inputSchema: {
            operation: z.enum([
              "retrieve", "retrieveMultiple", "create", "update", "delete"
            ]).describe("Type of operation to perform"),
            
            logicalEntityName: z.string().describe("Logical entity name (e.g., 'cr7ae_creditcardse', 'contact') - will be automatically suffixed with 's' for PowerPages API URLs"),
            entityId: z.string().optional().describe("Entity ID for single record operations (GUID)"),
            
            // OData query options
            select: z.array(z.string()).optional().describe("Fields to select (e.g., ['cr7ae_name', 'cr7ae_type'])"),
            filter: z.string().optional().describe("OData filter expression"),
            orderby: z.string().optional().describe("OData orderby expression"),
            top: z.number().optional().describe("Number of records to return"),
            skip: z.number().optional().describe("Number of records to skip"),
            expand: z.string().optional().describe("Related entities to expand"),
            count: z.boolean().optional().describe("Include count of records"),
            
            // Request body for create/update operations
            data: z.record(z.any()).optional().describe("Data to send in request body for create/update operations"),
            
            // PowerPages specific options
            baseUrl: z.string().optional().describe("PowerPages site base URL (e.g., 'https://yoursite.powerappsportals.com')"),
            requestVerificationToken: z.boolean().default(false).describe("Include __RequestVerificationToken placeholder for POST operations"),
            includeAuthContext: z.boolean().default(false).describe("Include authentication context information"),
            
            // Additional headers
            customHeaders: z.record(z.string()).optional().describe("Custom headers to include in the request")
          }
        },
        async (params: any) => {
          try {
            const baseUrl = params.baseUrl || 'https://yoursite.powerappsportals.com';
            
            // Resolve entity metadata for schema-aware capabilities
            let entityInfo: any = null;
            let entitySetName = '';
            let targetSetCache = new Map<string, string>();
            
            try {
              entityInfo = await resolvePowerPagesEntityInfo(client, params.logicalEntityName);
              entitySetName = entityInfo.entitySetName;
            } catch (error) {
              // Fallback to naive pluralization if metadata fails
              entitySetName = params.logicalEntityName ? 
                (params.logicalEntityName.endsWith('s') ? params.logicalEntityName : `${params.logicalEntityName}s`) : '';
            }
    
            let url = `${baseUrl}/_api/${entitySetName}`;
            let method = 'GET';
            let body: any = null;
            let headers: Record<string, string> = {
              'Accept': 'application/json',
              'Content-Type': 'application/json',
              ...params.customHeaders
            };
    
            // Add request verification token for POST operations if requested
            if (params.requestVerificationToken && ['create', 'update', 'delete'].includes(params.operation)) {
              headers['__RequestVerificationToken'] = '{{REQUEST_VERIFICATION_TOKEN}}';
            }
    
            // Helper to resolve target entity set names
            const resolveTargetSet = async (targetLogicalName: string): Promise<string> => {
              return await getPowerPagesTargetEntitySetName(client, targetSetCache, targetLogicalName);
            };
    
            switch (params.operation) {
              case 'retrieve':
                if (!params.entityId) {
                  throw new Error("entityId is required for retrieve operation");
                }
                url += `(${params.entityId})`;
                
                // Auto-select primary fields if no select specified and we have schema
                let finalSelect = params.select;
                if (!params.select && entityInfo && entityInfo.primaryIdAttribute) {
                  const autoFields = [entityInfo.primaryIdAttribute];
                  if (entityInfo.primaryNameAttribute) {
                    autoFields.push(entityInfo.primaryNameAttribute);
                  }
                  finalSelect = autoFields;
                }
                
                const queryParams = buildPowerPagesODataQuery({ select: finalSelect, expand: params.expand });
                if (queryParams) {
                  url += queryParams;
                }
                break;
    
              case 'retrieveMultiple':
                // Auto-select primary fields if no select specified and we have schema
                let finalListSelect = params.select;
                if (!params.select && entityInfo && entityInfo.primaryIdAttribute) {
                  const autoFields = [entityInfo.primaryIdAttribute];
                  if (entityInfo.primaryNameAttribute) {
                    autoFields.push(entityInfo.primaryNameAttribute);
                  }
                  finalListSelect = autoFields;
                }
                
                const listQueryParams = buildPowerPagesODataQuery({ 
                  select: finalListSelect, 
                  filter: params.filter, 
                  orderby: params.orderby, 
                  top: params.top, 
                  skip: params.skip, 
                  expand: params.expand, 
                  count: params.count 
                });
                if (listQueryParams) {
                  url += listQueryParams;
                }
                break;
    
              case 'create':
                method = 'POST';
                
                // Process @odata.bind properties and generate sample if no data provided
                if (params.data) {
                  body = processPowerPagesODataBindProperties(params.data, baseUrl, entityInfo);
                } else if (entityInfo) {
                  // Generate schema-aware sample body
                  body = await generatePowerPagesSampleBodyFromSchema(entityInfo, baseUrl, resolveTargetSet, 'create');
                } else {
                  body = {};
                }
                break;
    
              case 'update':
                if (!params.entityId) {
                  throw new Error("entityId is required for update operation");
                }
                method = 'PATCH';
                url += `(${params.entityId})`;
                
                // Process @odata.bind properties and generate sample if no data provided
                if (params.data) {
                  body = processPowerPagesODataBindProperties(params.data, baseUrl, entityInfo);
                } else if (entityInfo) {
                  // Generate schema-aware sample body
                  body = await generatePowerPagesSampleBodyFromSchema(entityInfo, baseUrl, resolveTargetSet, 'update');
                } else {
                  body = {};
                }
                break;
    
              case 'delete':
                if (!params.entityId) {
                  throw new Error("entityId is required for delete operation");
                }
                method = 'DELETE';
                url += `(${params.entityId})`;
                break;
    
              default:
                throw new Error(`Unsupported operation: ${params.operation}`);
            }
    
            // Generate examples
            const examples = [];
    
            // HTTP Request
            const httpRequest = [
              `${method} ${url} HTTP/1.1`,
              `Host: ${new URL(baseUrl).host}`,
              ...Object.entries(headers).map(([key, value]) => `${key}: ${value}`)
            ];
    
            if (body) {
              httpRequest.push('');
              httpRequest.push(JSON.stringify(body, null, 2));
            }
    
            examples.push({
              title: "HTTP Request",
              content: httpRequest.join('\n')
            });
    
            // cURL Command
            const curlParts = [`curl -X ${method}`];
            Object.entries(headers).forEach(([key, value]) => {
              curlParts.push(`-H "${key}: ${value}"`);
            });
            
            if (body) {
              curlParts.push(`-d '${JSON.stringify(body)}'`);
            }
            
            curlParts.push(`"${url}"`);
    
            examples.push({
              title: "cURL Command",
              content: curlParts.join(' \\\n  ')
            });
    
            // JavaScript Fetch
            const fetchOptions: any = {
              method,
              headers
            };
    
            if (body) {
              fetchOptions.body = JSON.stringify(body);
            }
    
            const jsCode = `
    // PowerPages WebAPI ${params.operation} operation
    fetch('${url}', ${JSON.stringify(fetchOptions, null, 2)})
      .then(response => {
        if (!response.ok) {
          throw new Error(\`HTTP error! status: \${response.status}\`);
        }
        return response.json();
      })
      .then(data => {
        console.log('Success:', data);
      })
      .catch(error => {
        console.error('Error:', error);
      });`;
    
            examples.push({
              title: "JavaScript (Fetch API)",
              content: jsCode.trim()
            });
    
            // React Component Example
            const reactCode = `
    import React, { useState, useEffect } from 'react';
    
    const ${params.operation.charAt(0).toUpperCase() + params.operation.slice(1)}Component = () => {
      const [data, setData] = useState(null);
      const [loading, setLoading] = useState(false);
      const [error, setError] = useState(null);
    
      const perform${params.operation.charAt(0).toUpperCase() + params.operation.slice(1)} = async () => {
        setLoading(true);
        setError(null);
        
        try {
          const response = await fetch('${url}', ${JSON.stringify(fetchOptions, null, 6)});
          
          if (!response.ok) {
            throw new Error(\`HTTP error! status: \${response.status}\`);
          }
          
          const result = await response.json();
          setData(result);
        } catch (err) {
          setError(err.message);
        } finally {
          setLoading(false);
        }
      };
    
      ${params.operation === 'retrieveMultiple' || params.operation === 'retrieve' ? `
      useEffect(() => {
        perform${params.operation.charAt(0).toUpperCase() + params.operation.slice(1)}();
      }, []);` : ''}
    
      return (
        <div>
          <h3>${params.operation.charAt(0).toUpperCase() + params.operation.slice(1)} ${params.logicalEntityName || 'Entity'}</h3>
          ${params.operation !== 'retrieveMultiple' && params.operation !== 'retrieve' ? `
          <button onClick={perform${params.operation.charAt(0).toUpperCase() + params.operation.slice(1)}} disabled={loading}>
            {loading ? 'Processing...' : '${params.operation.charAt(0).toUpperCase() + params.operation.slice(1)}'}
          </button>` : ''}
          
          {loading && <p>Loading...</p>}
          {error && <p style={{color: 'red'}}>Error: {error}</p>}
          {data && (
            <div>
              <h4>Result:</h4>
              <pre>{JSON.stringify(data, null, 2)}</pre>
            </div>
          )}
        </div>
      );
    };
    
    export default ${params.operation.charAt(0).toUpperCase() + params.operation.slice(1)}Component;`;
    
            examples.push({
              title: "React Component",
              content: reactCode.trim()
            });
    
            // Add @odata.bind examples if present in the body
            if (body && hasODataBindProperties(body)) {
              const bindExamples = extractNavigationPropertyExamples(body);
              if (bindExamples.length > 0) {
                const odataBindInfo = `
    ## @odata.bind Relationship Examples
    
    The request body includes relationship associations using @odata.bind:
    
    \`\`\`javascript
    ${bindExamples.join('\n')}
    \`\`\`
    
    ### @odata.bind Usage Patterns:
    
    1. **Associate with existing record:**
       \`"navigationProperty@odata.bind": "/_api/entityset(guid)"\`
    
    2. **Disassociate relationship:**
       \`"navigationProperty@odata.bind": null\`
    
    3. **PowerPages URL Format:**
       - Use relative paths: \`/_api/contacts(guid)\`
       - Entity set names are typically plural: \`contacts\`, \`accounts\`, etc.
    
    ### Navigation Property Names:
    ${entityInfo && entityInfo.lookupNavMap.size > 0 ? 
      Array.from(entityInfo.lookupNavMap.entries())
        .map((entry: any) => {
          const [attr, nav] = entry as [string, string];
          return `- Lookup attribute \`${attr}\` → Navigation property \`${nav}\``;
        })
        .join('\n') :
      '- Navigation properties are automatically resolved from table schema'}`;
    
                examples.push({
                  title: "@odata.bind Relationships",
                  content: odataBindInfo.trim()
                });
              }
            }
    
            // Authentication context if requested
            if (params.includeAuthContext) {
              const authInfo = `
    ## Authentication Context for PowerPages
    
    PowerPages uses different authentication mechanisms:
    
    1. **Anonymous Access**: No authentication required for public data
    2. **Authenticated Users**: Session-based authentication via portal login
    3. **Request Verification Token**: Anti-CSRF protection for state-changing operations
    
    ### Getting Request Verification Token (JavaScript):
    \`\`\`javascript
    // Get the token from the page (usually in a hidden input or meta tag)
    const token = document.querySelector('input[name="__RequestVerificationToken"]')?.value ||
                  document.querySelector('meta[name="__RequestVerificationToken"]')?.content;
    
    // Include in headers for POST/PATCH/DELETE operations
    headers['__RequestVerificationToken'] = token;
    \`\`\`
    
    ### User Context:
    \`\`\`javascript
    // Access current user information (if available)
    const userContext = {
      isAuthenticated: window.Shell?.user?.isAuthenticated || false,
      userId: window.Shell?.user?.id,
      userName: window.Shell?.user?.displayName
    };
    \`\`\``;
    
              examples.push({
                title: "Authentication Information",
                content: authInfo.trim()
              });
            }
    
            // Add schema information if available
            if (entityInfo && entityInfo.logicalName) {
              const schemaInfo = `
    ## Schema Information
    
    **Entity:** ${entityInfo.logicalName} (${entityInfo.entitySetName})
    **Primary ID:** ${entityInfo.primaryIdAttribute || 'Not available'}
    **Primary Name:** ${entityInfo.primaryNameAttribute || 'Not available'}
    
    ### Available Fields:
    ${entityInfo.attributes && entityInfo.attributes.length > 0 ?
      entityInfo.attributes
        .filter((attr: any) => attr?.LogicalName)
        .slice(0, 10) // Show first 10 fields
        .map((attr: any) => `- \`${attr.LogicalName}\` (${attr.AttributeType})`)
        .join('\n') +
      (entityInfo.attributes.length > 10 ? `\n- ... and ${entityInfo.attributes.length - 10} more fields` : '') :
      'Schema information not available'}
    
    ### Lookup Navigation Properties:
    ${entityInfo.lookupNavMap && entityInfo.lookupNavMap.size > 0 ?
      Array.from(entityInfo.lookupNavMap.entries())
        .map((entry: any) => {
          const [attr, nav] = entry as [string, string];
          return `- \`${attr}\` → \`${nav}\``;
        })
        .join('\n') :
      'No lookup relationships found'}`;
    
              examples.push({
                title: "Entity Schema",
                content: schemaInfo.trim()
              });
            }
    
            const result = examples.map(example =>
              `## ${example.title}\n\n\`\`\`${example.title.includes('React') ? 'jsx' : example.title.includes('JavaScript') ? 'javascript' : example.title.includes('cURL') ? 'bash' : example.title.includes('@odata.bind') || example.title.includes('Schema') || example.title.includes('Authentication') ? 'markdown' : 'http'}\n${example.content}\n\`\`\``
            ).join('\n\n');
    
            return {
              content: [
                {
                  type: "text",
                  text: result
                }
              ]
            };
          } catch (error) {
            return {
              content: [
                {
                  type: "text",
                  text: `Error generating PowerPages WebAPI call: ${error instanceof Error ? error.message : 'Unknown error'}`
                }
              ],
              isError: true
            };
          }
        }
      );
    }
  • src/index.ts:234-234 (registration)
    Invocation of the registration factory function to register the tool on the MCP server instance.
    generatePowerPagesWebAPICallTool(server, dataverseClient);
  • Key helper: Resolves entity metadata (logical/set name, primaries, attributes, lookup nav map) for schema-aware features like sample generation and @odata.bind correction.
    async function resolvePowerPagesEntityInfo(client: DataverseClient, nameOrSet?: string): Promise<{
      logicalName: string;
      entitySetName: string;
      primaryIdAttribute: string;
      primaryNameAttribute?: string;
      attributes: any[];
      lookupNavMap: Map<string, string>;
    }> {
      if (!nameOrSet) {
        return {
          logicalName: '',
          entitySetName: '',
          primaryIdAttribute: '',
          primaryNameAttribute: undefined,
          attributes: [],
          lookupNavMap: new Map()
        };
      }
    
      // Try to get by LogicalName directly with robust fallback
      const tryGetByLogicalName = async (ln: string) => {
        try {
          return await client.getMetadata(
            `EntityDefinitions(LogicalName='${ln}')?$select=EntitySetName,PrimaryIdAttribute,PrimaryNameAttribute,LogicalName`
          );
        } catch {
          // Fallback without $select for environments that don't support it on singletons
          return await client.getMetadata(
            `EntityDefinitions(LogicalName='${ln}')`
          );
        }
      };
    
      // Try to get by EntitySetName (fallback)
      const tryGetByEntitySetName = async (esn: string) => {
        const resp = await client.getMetadata(
          `EntityDefinitions?$select=EntitySetName,LogicalName,PrimaryIdAttribute,PrimaryNameAttribute&$filter=EntitySetName eq '${esn}'`
        );
        return resp?.value?.[0];
      };
    
      let def: any | null = null;
      try {
        def = await tryGetByLogicalName(nameOrSet);
      } catch {
        // If endsWith 's', try trimming 's' as a heuristic for logical name
        if (nameOrSet.endsWith('s')) {
          try {
            def = await tryGetByLogicalName(nameOrSet.slice(0, -1));
          } catch {
            // ignore
          }
        }
      }
      if (!def) {
        try {
          def = await tryGetByEntitySetName(nameOrSet);
        } catch {
          // ignore
        }
      }
      if (!def) {
        // Last resort: return minimal info using naive pluralization
        return {
          logicalName: nameOrSet,
          entitySetName: nameOrSet.endsWith('s') ? nameOrSet : `${nameOrSet}s`,
          primaryIdAttribute: '',
          primaryNameAttribute: undefined,
          attributes: [],
          lookupNavMap: new Map()
        };
      }
    
      const logicalName: string = def.LogicalName;
      const entitySetName: string = def.EntitySetName;
    
      // Fetch attributes (robust with fallback: try $select, then full set)
      let attributes: any[] = [];
      try {
        const attrsResp = await client.getMetadata(
          `EntityDefinitions(LogicalName='${logicalName}')/Attributes?$select=LogicalName,AttributeType,IsValidForCreate,IsValidForUpdate,IsPrimaryId,IsPrimaryName,RequiredLevel,Targets`
        );
        attributes = attrsResp?.value || [];
      } catch {
        try {
          const attrsRespFull = await client.getMetadata(
            `EntityDefinitions(LogicalName='${logicalName}')/Attributes`
          );
          attributes = attrsRespFull?.value || [];
        } catch {
          attributes = [];
        }
      }
    
      // Build lookup navigation property map
      const navMap: Map<string, string> = new Map();
      try {
        const relResp = await client.getMetadata(
          `EntityDefinitions(LogicalName='${logicalName}')/ManyToOneRelationships?$select=ReferencingAttribute,ReferencingEntityNavigationPropertyName`
        );
        for (const rel of relResp?.value || []) {
          if (rel?.ReferencingAttribute && rel?.ReferencingEntityNavigationPropertyName) {
            navMap.set(rel.ReferencingAttribute, rel.ReferencingEntityNavigationPropertyName);
          }
        }
      } catch {
        // ignore nav map errors
      }
    
      return {
        logicalName,
        entitySetName,
        primaryIdAttribute: def.PrimaryIdAttribute,
        primaryNameAttribute: def.PrimaryNameAttribute,
        attributes,
        lookupNavMap: navMap
      };
    }
Install Server

Other Tools

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