discover-service-entities
Explore SAP service entities and their capabilities to understand available data for querying, creating, updating, or deleting operations within SAP systems.
Instructions
List all entities and their capabilities within a specific SAP service. Use this after finding a service to understand what data you can work with.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| serviceId | Yes | The SAP service ID to explore | |
| showCapabilities | No | Show CRUD capabilities for each entity |
Implementation Reference
- Input validation schema for 'discover-service-entities' tool, defining serviceId (required), entityPattern (optional regex filter), includeProperties and includeNavigations (optional booleans). Used for safe input sanitization.export const EntityDiscoverySchema = z.object({ serviceId: serviceName, entityPattern: z .string() .min(1) .max(200) .regex(/^[a-zA-Z0-9_\-*?.[\]]*$/, 'Invalid entity pattern') .optional(), includeProperties: z.boolean().optional(), includeNavigations: z.boolean().optional(), });
- ValidationSchemas object maps tool names to their Zod schemas, including 'discover-service-entities': EntityDiscoverySchema. Used by validateMCPToolInput function.export const ValidationSchemas = { // Discovery tools 'search-sap-services': ServiceDiscoverySchema, 'discover-service-entities': EntityDiscoverySchema, 'get-entity-schema': EntitySchemaSchema, // CRUD operation tools 'execute-entity-operation': EntityOperationSchema, 'sap-odata-read': EntityReadSchema, 'sap-odata-create': EntityCreateSchema, 'sap-odata-update': EntityUpdateSchema, 'sap-odata-delete': EntityDeleteSchema, // System tools 'sap-health-check': HealthCheckSchema, 'sap-system-info': SystemInfoSchema, } as const;
- getServiceMetadata fetches and parses service $metadata to extract entityTypes array, which is core logic for discovering entities in a service. Likely called by the tool handler.private async getServiceMetadata(service: ODataService): Promise<ServiceMetadata> { try { const destination = await this.sapClient.getDestination({ type: 'design-time', operation: 'discovery', }); const response = await executeHttpRequest(destination, { method: 'GET', url: service.metadataUrl, headers: { Accept: 'application/xml', }, }); return this.parseMetadata(response.data, service.odataVersion); } catch (error) { this.logger.error(`Failed to get metadata for service ${service.id}:`, error); throw error; } } private parseMetadata(metadataXml: string, odataVersion: string): ServiceMetadata {
- src/tools/sap-tool-registry.ts:32-83 (registration)Registers sap-service-metadata resource that provides entity list for serviceId via sap://service/{serviceId}/metadata URI. Provides entity discovery data, possibly used alongside or by the tool.// Workflow guide removed - now using routing-rules.json in hierarchical-tool-registry this.mcpServer.registerResource( 'sap-service-metadata', new ResourceTemplate('sap://service/{serviceId}/metadata', { list: undefined }), { title: 'SAP Service Metadata', description: 'Metadata information for SAP OData services', }, async (uri, variables) => { // variables: Record<string, unknown> from SDK const serviceId = typeof variables.serviceId === 'string' ? variables.serviceId : ''; const service = this.discoveredServices.find(s => s.id === serviceId); if (!service) { throw new Error(`Service not found: ${serviceId}`); } return { contents: [ { uri: uri.href, text: JSON.stringify( { service: { id: service.id, title: service.title, description: service.description, url: service.url, version: service.version, }, entities: service.metadata?.entityTypes?.map(entity => ({ name: entity.name, entitySet: entity.entitySet, properties: entity.properties, keys: entity.keys, operations: { creatable: entity.creatable, updatable: entity.updatable, deletable: entity.deletable, }, })) || [], }, null, 2 ), mimeType: 'application/json', }, ], }; } ); this.mcpServer.registerResource(
- Parses $metadata XML to extract detailed EntityType information (name, properties, keys, navigations), essential for the tool's output.xmlDoc: Document, entitySets: Array<{ [key: string]: string | null }> ): EntityType[] { const entityTypes: EntityType[] = []; const nodes = xmlDoc.querySelectorAll('EntityType'); nodes.forEach((node: Element) => { const entitySet = entitySets.find( entitySet => entitySet.entitytype?.split('.')[1] === node.getAttribute('Name') ); const entityType: EntityType = { name: node.getAttribute('Name') || '', namespace: node.parentElement?.getAttribute('Namespace') || '', entitySet: entitySet?.name, creatable: entitySet?.creatable?.toLowerCase() === 'true', updatable: entitySet?.updatable?.toLowerCase() === 'true', deletable: entitySet?.deletable?.toLowerCase() === 'true', addressable: entitySet?.addressable?.toLowerCase() === 'true', properties: [], navigationProperties: [], keys: [], }; // Extract properties const propNodes = node.querySelectorAll('Property'); propNodes.forEach((propNode: Element) => { entityType.properties.push({ name: propNode.getAttribute('Name') || '', type: propNode.getAttribute('Type') || '', nullable: propNode.getAttribute('Nullable') !== 'false', maxLength: propNode.getAttribute('MaxLength') ?? undefined, }); }); // Extract keys const keyNodes = node.querySelectorAll('Key PropertyRef'); keyNodes.forEach((keyNode: Element) => { entityType.keys.push(keyNode.getAttribute('Name') || ''); }); entityTypes.push(entityType); }); return entityTypes; }