Skip to main content
Glama

mcp-google-sheets

drupal-entities.ts16.2 kB
import { HttpMethod, } from '@activepieces/pieces-common'; import { PiecePropValueSchema, DynamicPropsValue, Property, } from '@activepieces/pieces-framework'; import { drupalAuth } from '../../'; import { makeJsonApiRequest } from './jsonapi'; type DrupalAuthType = PiecePropValueSchema<typeof drupalAuth>; // ============================================================================= // ENTITY TYPE DISCOVERY // Functions for discovering what entity types are available in Drupal // ============================================================================= /** * Discovers available entity types from Drupal's JSON:API endpoint * * This function queries the main JSON:API endpoint to see what entity types * and bundles are available, then filters them to only show content entities * that users would typically want to work with (not config entities). * * @param auth - Drupal authentication credentials * @param context - Whether entities will be used for 'reading' or 'editing' * @returns Dropdown options for entity type selection */ async function fetchEntityTypes(auth: DrupalAuthType, context: 'reading' | 'editing') { if (!auth || !auth.website_url) { return { disabled: true, options: [], placeholder: 'Please configure authentication first', }; } // Get the list of entity types that are allowed for this context const type = context === 'editing' ? 'form' : 'view'; const response = await makeJsonApiRequest(auth, `${auth.website_url}/jsonapi/entity_${type}_display/entity_${type}_display`, HttpMethod.GET); const data = (response.body as any).data || []; const allowedEntityTypes: string[] = []; data.forEach((entityType: any) => { allowedEntityTypes.push(entityType.attributes.targetEntityType + '--' + entityType.attributes.bundle); }); try { const response = await makeJsonApiRequest(auth, `${auth.website_url}/jsonapi`, HttpMethod.GET); if (response.status === 200) { const entityTypes: Array<{label: string; value: any}> = []; const data = response.body as any; if (data.links) { for (const [key, value] of Object.entries(data.links)) { if (key !== 'self' && typeof value === 'object' && (value as any).href) { const parts = key.split('--'); if (parts.length === 2) { const [entityType, bundle] = parts; if (allowedEntityTypes.includes(key)) { const bundleName = bundle.charAt(0).toUpperCase() + bundle.slice(1).replace(/_/g, ' '); const entityTypeName = entityType.charAt(0).toUpperCase() + entityType.slice(1).replace(/_/g, ' '); const label = `${bundleName} (${entityTypeName})`; entityTypes.push({ label, value: { id: key, entity_type: entityType, bundle, } }); } } } } } return { disabled: false, options: entityTypes.sort((a, b) => a.label.localeCompare(b.label)), }; } } catch (e) { console.error('Failed to fetch entity types', e); } return { disabled: true, options: [], placeholder: 'Error loading entity types', }; } export async function fetchEntityTypesForReading(auth: DrupalAuthType) { return await fetchEntityTypes(auth, 'reading'); } export async function fetchEntityTypesForEditing(auth: DrupalAuthType) { return await fetchEntityTypes(auth, 'editing'); } // ============================================================================= // FORM DISPLAY DISCOVERY // Functions for discovering how Drupal displays forms for entity editing // ============================================================================= /** * Fetches the form display configuration for an entity bundle * * In Drupal, administrators configure which fields appear on edit forms, * their order, and how they're displayed. This function retrieves that * configuration so we can show the same fields in the same order. * * Without this, we might show fields that admins have intentionally hidden * or fields that are read-only and shouldn't be edited. * * @param auth - Drupal authentication credentials * @param entityType - The entity type (e.g., 'node', 'user') * @param bundle - The bundle (e.g., 'article', 'page') * @returns Form display configuration object */ export async function fetchEntityFormDisplay(auth: DrupalAuthType, entityType: string, bundle: string) { try { const formDisplayId = `${entityType}.${bundle}.default`; const response = await makeJsonApiRequest( auth, `${auth.website_url}/jsonapi/entity_form_display/entity_form_display?filter[drupal_internal__id]=${encodeURIComponent(formDisplayId)}`, HttpMethod.GET ); if (response.status === 200 && response.body) { const data = (response.body as any).data; if (data && data.length > 0) { return data[0].attributes.content || {}; } } } catch (e) { console.error('Failed to fetch form display', e); } return {}; } /** * Fetches available text formats for rich text fields * * Drupal allows different text formats (like 'basic_html', 'full_html') * for rich text fields. This function gets the available formats so users * can choose how their content should be processed. */ export async function fetchTextFormats(auth: DrupalAuthType) { try { const response = await makeJsonApiRequest( auth, `${auth.website_url}/jsonapi/filter_format/filter_format`, HttpMethod.GET ); if (response.status === 200 && response.body) { const formats = (response.body as any).data || []; return formats.reduce((acc: Record<string, string>, format: any) => { const formatId = format.attributes.drupal_internal__format; const formatName = format.attributes.name; if (formatId && formatName) { acc[formatId] = formatName; } return acc; }, {}); } } catch (e) { console.error('Failed to fetch text formats', e); } return {}; } // ============================================================================= // FIELD CONFIGURATION DISCOVERY // Functions for discovering field metadata and configuration // ============================================================================= /** * Fetches detailed field configuration including labels and requirements * * This gets the actual field definitions including human-readable labels, * whether fields are required, field types, etc. This metadata is used * to create appropriate form inputs with proper validation. * * @param auth - Drupal authentication credentials * @param entityType - The entity type (e.g., 'node', 'user') * @param bundle - The bundle (e.g., 'article', 'page') * @returns Object mapping field names to their configuration */ export async function fetchEntityFieldConfig(auth: DrupalAuthType, entityType: string, bundle: string) { try { const response = await makeJsonApiRequest( auth, `${auth.website_url}/jsonapi/field_config/field_config?filter[entity_type]=${entityType}&filter[bundle]=${bundle}`, HttpMethod.GET ); if (response.status === 200 && response.body) { const fields = (response.body as any).data || []; const fieldConfig: Record<string, any> = {}; fields.forEach((field: any) => { const fieldName = field.attributes.field_name; fieldConfig[fieldName] = { label: field.attributes.label, required: field.attributes.required, fieldType: field.attributes.field_type, }; }); return fieldConfig; } } catch (e) { console.error('Failed to fetch field config', e); } return {}; } /** * Checks if the entity type and bundle are supported by the workflow * * This gets the actual workflow configuration on the site and determines if the * given entity type and bundle is supported by the workflow. * * @param auth - Drupal authentication credentials * @param entityType - The entity type (e.g., 'node', 'user') * @param bundle - The bundle (e.g., 'article', 'page') * @returns True if the entity type and bundle are supported by the workflow, * false otherwise */ export async function isEntitySupportingWorkflow(auth: DrupalAuthType, entityType: string, bundle: string): Promise<boolean> { try { const response = await makeJsonApiRequest( auth, `${auth.website_url}/jsonapi/workflow/workflow`, HttpMethod.GET ); if (response.status === 200 && response.body) { const workflows = (response.body as any).data || []; let found = false; workflows.forEach((workflow: any) => { const attrs = workflow?.attributes ?? {}; const typeSettings = attrs?.type_settings ?? {}; const entityTypes = typeSettings?.entity_types ?? {}; if (!entityTypes) { return; } if (entityTypes[entityType].includes(bundle)) { found = true; } }); return found; } } catch (e) { // Ignore this as the workflow may not be supported or the endpoint missing. } return false; } /** * Checks if a field type can be edited with simple form inputs * * Some Drupal field types are too complex for simple text/checkbox inputs * (like entity references, file uploads). This function determines which * field types we can reasonably handle in a workflow interface. */ export function isEditableFieldType(fieldType: string): boolean { const editableTypes = [ 'string', 'string_long', 'text', 'text_long', 'text_with_summary', 'integer', 'decimal', 'float', 'boolean', 'email', 'telephone', 'uri' ]; return editableTypes.includes(fieldType); } /** * Gets human-readable labels for Drupal base fields * * TODO: This should be fetched from the form display instead of hardcoded. * Base fields like 'title', 'status' have standard labels, but these could * be customized by site administrators. We should get the actual labels * from the form display configuration. * * @param fieldName - The machine name of the field * @returns Human-readable label for the field */ export function getBaseFieldLabel(fieldName: string): string { const baseFieldLabels: Record<string, string> = { 'title': 'Title', 'status': 'Published', 'created': 'Authored on', 'changed': 'Changed', 'promote': 'Promoted to front page', 'sticky': 'Sticky at top of lists', 'name': 'Name', 'mail': 'Email address', }; return baseFieldLabels[fieldName] || fieldName; } // ============================================================================= // FIELD PROCESSING & FORM GENERATION // Functions that combine the above data to generate form properties // ============================================================================= /** * Extracts editable fields from form display configuration * * This combines form display configuration (what fields to show) with * field configuration (labels, types, requirements) to create a list * of fields that should be editable in the workflow interface. * * @param auth - Drupal authentication credentials * @param entityType - The entity type (e.g., 'node', 'user') * @param bundle - The bundle (e.g., 'article', 'page') * @param formDisplayContent - Form display configuration from fetchEntityFormDisplay * @returns Array of field objects with name, type, label, required, weight */ export async function getEditableFieldsWithLabels( auth: DrupalAuthType, entityType: string, bundle: string, formDisplayContent: Record<string, any> ) { const fieldConfig = await fetchEntityFieldConfig(auth, entityType, bundle); const fields: Array<{ name: string; type: string; label: string; required: boolean; weight: number; }> = []; const baseFields = ['title', 'name']; if (!await isEntitySupportingWorkflow(auth, entityType, bundle)) { baseFields.push('status'); } for (const [fieldName, config] of Object.entries(formDisplayContent)) { if (config && typeof config === 'object' && config.type) { const configInfo = fieldConfig[fieldName]; if (configInfo) { // Custom field with configuration if (isEditableFieldType(configInfo.fieldType)) { fields.push({ name: fieldName, type: configInfo.fieldType, label: configInfo.label, required: configInfo.required, weight: config.weight || 0 }); } } else { // Base field - check if it's editable if (baseFields.includes(fieldName)) { fields.push({ name: fieldName, type: fieldName === 'status' ? 'boolean' : 'string', label: getBaseFieldLabel(fieldName), required: ['title', 'name'].includes(fieldName), weight: config.weight || 0 }); } } } } // Sort by weight (form display order), then by label return fields.sort((a, b) => { if (a.weight !== b.weight) return a.weight - b.weight; return a.label.localeCompare(b.label); }); } /** * Builds Activepieces Property objects for dynamic form generation * * This is the main function that combines all the field discovery and * configuration to create the actual form properties that users will * see in the Activepieces interface. * * @param auth - Drupal authentication credentials * @param entityType - Selected entity type from dropdown * @param isCreateAction - Whether this is for creating (true) or updating (false) * @returns Dynamic properties object for Activepieces form */ export async function buildFieldProperties( auth: DrupalAuthType, entityType: any, isCreateAction = false ): Promise<DynamicPropsValue> { const properties: DynamicPropsValue = {}; if (!entityType) { return properties; } try { const formDisplay = await fetchEntityFormDisplay(auth, entityType.entity_type, entityType.bundle); const textFormats = await fetchTextFormats(auth); const availableFields = await getEditableFieldsWithLabels( auth, entityType.entity_type, entityType.bundle, formDisplay ); if (availableFields.length === 0) { properties['no_fields'] = Property.MarkDown({ value: 'No editable fields found for this entity type.' }); return properties; } // Generate properties for all editable fields for (const field of availableFields) { const displayName = field.label; const description = undefined; const isRequired = field.required && isCreateAction; if (field.type === 'text_with_summary' || field.type === 'text_long') { properties[field.name] = Property.LongText({ displayName, description, required: isRequired, }); // Add text format selection if formats are available if (Object.keys(textFormats).length > 0) { properties[`${field.name}_format`] = Property.StaticDropdown({ displayName: `${displayName} Format`, required: false, options: { options: Object.entries(textFormats).map(([key, name]) => ({ label: String(name), value: key, })), }, }); } } else if (field.type === 'boolean') { properties[field.name] = Property.Checkbox({ displayName, description, required: isRequired, }); } else { // Default to text input for most field types properties[field.name] = Property.ShortText({ displayName, description, required: isRequired, }); } } } catch (e) { console.error('Failed to generate field properties', e); properties['error'] = Property.MarkDown({ value: 'Failed to load fields. Please check your authentication and entity type selection.' }); } return properties; }

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/activepieces/activepieces'

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