Skip to main content
Glama
mwhesse

Dataverse MCP Server

by mwhesse

Generate PowerPages WebAPI Call

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
      };
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions 'authentication context' and 'portal-specific patterns,' which adds some context about security and environment, but it fails to describe critical behaviors such as whether this tool performs actual API calls (likely not, as it 'generates' examples), what the output format is (e.g., code snippets, documentation), or any rate limits or permissions required. For a tool with 15 parameters and no annotation coverage, this is a significant gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core purpose ('Generate PowerPages-specific API calls...') and adds supplementary details ('Includes authentication context...'). There is no wasted text, and it's appropriately sized for the tool's complexity, though it could benefit from more structured guidance.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's high complexity (15 parameters, no annotations, no output schema), the description is incomplete. It lacks information on output format (critical for a generation tool), behavioral details like whether it executes calls or just generates code, and differentiation from siblings. The 100% schema coverage helps with parameters, but overall context for effective agent use is insufficient.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, meaning all parameters are documented in the input schema itself. The description adds no specific parameter details beyond what the schema provides (e.g., it doesn't explain how 'logicalEntityName' interacts with 'operation' or clarify the 'includeAuthContext' usage). With high schema coverage, the baseline score is 3, as the description doesn't compensate with additional semantic insights.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool generates 'PowerPages-specific API calls, JavaScript examples, and React components for Dataverse operations through PowerPages portals,' which is specific about the verb (generate) and resources (API calls, examples, components). However, it doesn't explicitly differentiate from the sibling tool 'generate_webapi_call,' which appears to be a more general version, leaving some ambiguity about when to choose this PowerPages-specific variant.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description mentions 'Includes authentication context and portal-specific patterns,' which implies usage in PowerPages contexts, but it provides no explicit guidance on when to use this tool versus alternatives like 'generate_webapi_call' or other sibling tools. There are no when-not-to-use statements or prerequisites, leaving the agent to infer context from the tool name alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

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