Skip to main content
Glama

Smart EHR MCP Server

by jmandel
process-brands.ts10.5 kB
#!/usr/bin/env node import * as fs from 'fs'; import * as process from 'process'; import * as path from 'path'; // For potential future use or resolving paths // --- Interfaces for FHIR Resources (Simplified) --- interface Identifier { system?: string; value?: string; } interface Coding { system?: string; code?: string; display?: string; } interface CodeableConcept { coding?: Coding[]; text?: string; } interface Address { type?: 'postal' | 'physical' | 'both'; text?: string; line?: string[]; city?: string; state?: string; postalCode?: string; country?: string; } interface Reference { reference?: string; type?: string; identifier?: Identifier; display?: string; } interface BaseResource { resourceType: string; id?: string; } interface Endpoint extends BaseResource { resourceType: 'Endpoint'; status?: string; connectionType: Coding; // Required pattern: hl7-fhir-rest name?: string; managingOrganization?: Reference; contact?: { system?: string; value?: string }[]; address: string; // The FHIR base URL (required) extension?: any[]; // To capture fhir-version etc. if needed later } interface Organization extends BaseResource { resourceType: 'Organization'; identifier?: Identifier[]; active?: boolean; type?: CodeableConcept[]; name?: string; alias?: string[]; telecom?: { system?: string; value?: string }[]; address?: Address[]; partOf?: Reference; endpoint?: Reference[]; extension?: any[]; // To capture brand extensions etc. if needed later } interface BundleEntry { fullUrl?: string; resource?: Endpoint | Organization; // Add other types if necessary } interface Bundle { resourceType: 'Bundle'; id?: string; type: 'collection' | string; // Expect 'collection' timestamp?: string; entry?: BundleEntry[]; } // --- Interfaces for Processed Output --- interface ProcessedEndpoint { url: string; name?: string; // Optional: Endpoint name for context } interface SearchableItem { // Information about the item itself (Brand or Facility) searchName: string; // Name used primarily for searching (lower case?) - could be same as displayName displayName: string; // Name for display (Brand or Facility name) itemType: 'brand' | 'facility'; city?: string | null; // Facility city state?: string | null; // Facility state postalCode?: string | null;// Facility postal code // Note: We could add more searchable fields like address line if desired // Direct access to the associated primary brand details brandName: string; // Name of the associated primary brand brandId?: string; // Optional: ID of the brand for potential client-side grouping // brandLogoUrl?: string; // Optional: If we extract from extensions // brandWebsiteUrl?: string; // Optional: If we extract from telecom endpoints: ProcessedEndpoint[]; // Endpoints associated with the primary brand } interface ProcessedData { items: SearchableItem[]; // We could add metadata here, like processing date processedTimestamp: string; } // --- Helper Functions --- function parseArgs(): string { const args = process.argv.slice(2); let brandsFile: string | null = null; for (let i = 0; i < args.length; i++) { if (args[i] === '--brands' && i + 1 < args.length) { brandsFile = args[i + 1]; break; } } if (!brandsFile) { console.error('Usage: ts-node process-brands.ts --brands <path-to-brands-bundle.json>'); process.exit(1); } return brandsFile; } function extractIdFromReference(reference?: string): string | null { if (!reference) return null; // Handles relative URLs (e.g., "Endpoint/id", "Organization/id") or urn:uuid:id const parts = reference.split(/[\/:]/); return parts.pop() || null; } // --- Main Processing Logic --- function processBrandsBundle(bundle: Bundle): ProcessedData { if (!bundle || !Array.isArray(bundle.entry)) { throw new Error("Invalid bundle structure: 'entry' array not found."); } // Intermediate storage const endpointsMap = new Map<string, ProcessedEndpoint>(); // Endpoint ID -> { url, name } const primaryBrandsMap = new Map<string, { // Brand ID -> Intermediate data resource: Organization; resolvedEndpoints: ProcessedEndpoint[]; // Pre-resolve endpoints here }>(); const facilityEntries: BundleEntry[] = []; // Store entries containing facility Orgs // --- First Pass: Categorize and Extract Endpoints/Brands --- for (const entry of bundle.entry) { if (!entry?.resource?.id) continue; // Need ID for mapping const resource = entry.resource; const id = resource.id; try { if (resource.resourceType === 'Endpoint') { // Basic validation: check for required fields for our use case if (resource.address && resource.connectionType?.code === 'hl7-fhir-rest') { endpointsMap.set(id, { url: resource.address, name: resource.name }); } else { // console.warn(`Skipping endpoint ${id}: missing address or not hl7-fhir-rest.`); } } else if (resource.resourceType === 'Organization') { if (!resource.partOf) { // Primary Brand primaryBrandsMap.set(id, { resource: resource, resolvedEndpoints: [] }); } else { // Care Facility facilityEntries.push(entry); } } } catch (error) { console.warn(`Error processing resource ID ${id}: ${error instanceof Error ? error.message : String(error)}`); } } // --- Second Pass: Resolve Endpoints for Brands --- for (const [brandId, brandData] of primaryBrandsMap.entries()) { const endpointRefs = brandData.resource.endpoint || []; for (const epRef of endpointRefs) { const endpointId = extractIdFromReference(epRef.reference); if (endpointId && endpointsMap.has(endpointId)) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion brandData.resolvedEndpoints.push(endpointsMap.get(endpointId)!); } else { // console.warn(`Endpoint reference "${epRef.reference}" for brand "${brandData.resource.name || brandId}" (ID: ${brandId}) not found or invalid.`); } } // Sort endpoints by name or URL for consistent output? Optional. brandData.resolvedEndpoints.sort((a, b) => a.url.localeCompare(b.url)); } // --- Third Pass: Create Searchable Items --- const searchableItems: SearchableItem[] = []; // Create items for Primary Brands for (const [brandId, brandData] of primaryBrandsMap.entries()) { const brandResource = brandData.resource; const brandName = brandResource.name || 'Unknown Brand'; searchableItems.push({ searchName: brandName.toLowerCase(), // Use lowercase for easier search matching displayName: brandName, itemType: 'brand', city: null, // Brands themselves don't have a single city in this model state: brandResource.address?.[0]?.state || null, // Maybe take first state? Or aggregate later? Null seems safest. postalCode: null, brandName: brandName, // It is its own brand brandId: brandId, endpoints: brandData.resolvedEndpoints, }); } // Create items for Facilities for (const entry of facilityEntries) { const facilityResource = entry.resource as Organization; // We know these are Orgs const facilityName = facilityResource.name || 'Unknown Facility'; const parentBrandId = extractIdFromReference(facilityResource.partOf?.reference); if (!parentBrandId || !primaryBrandsMap.has(parentBrandId)) { // console.warn(`Facility "${facilityName}" (ID: ${facilityResource.id}) references unknown parent brand "${facilityResource.partOf?.reference}". Skipping.`); continue; } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const parentBrandData = primaryBrandsMap.get(parentBrandId)!; const parentBrandName = parentBrandData.resource.name || 'Unknown Parent Brand'; let city: string | null = null; let state: string | null = null; let postalCode: string | null = null; if (Array.isArray(facilityResource.address) && facilityResource.address.length > 0) { // Take the first address as representative for the facility location const addr = facilityResource.address[0]; city = addr.city || null; state = addr.state || null; postalCode = addr.postalCode || null; } searchableItems.push({ searchName: facilityName.toLowerCase(), displayName: facilityName, itemType: 'facility', city: city, state: state, postalCode: postalCode, brandName: parentBrandName, brandId: parentBrandId, // Include parent brand ID endpoints: parentBrandData.resolvedEndpoints, // Use the pre-resolved endpoints }); } // Optional: Sort all items for consistent output? By displayName? searchableItems.sort((a, b) => a.displayName.localeCompare(b.displayName)); return { items: searchableItems, processedTimestamp: new Date().toISOString(), }; } // --- Main Execution --- try { const brandsFilePath = parseArgs(); if (!fs.existsSync(brandsFilePath)) { throw new Error(`Brands file not found: ${brandsFilePath}`); } const fileContent = fs.readFileSync(brandsFilePath, 'utf-8'); const bundle: Bundle = JSON.parse(fileContent); const processedData = processBrandsBundle(bundle); // Output processed JSON to stdout - use standard indentation for readability // Gzip will handle the compression effectively. process.stdout.write(JSON.stringify(processedData, null, 2)); } catch (error) { console.error(`Error: ${error instanceof Error ? error.message : String(error)}`); process.exit(1); }

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/jmandel/health-record-mcp'

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