Skip to main content
Glama
metadata.ts6.64 kB
import { z } from 'zod'; import type { ToolRegistrationContext } from '../context.js'; import { normalizeActivityType, normalizeContactFieldType, normalizeCountry, normalizeGender, normalizeRelationshipType, normalizeCurrency, normalizeOccupation } from '../../utils/formatters.js'; const metadataResourceSchema = z.enum([ 'genders', 'countries', 'contactFieldTypes', 'activityTypes', 'relationshipTypes', 'currencies', 'occupations' ]); type MetadataResource = z.infer<typeof metadataResourceSchema>; export function registerMetadataTools(context: ToolRegistrationContext): void { const { server, client } = context; server.registerTool( 'monica_browse_metadata', { title: 'Browse Monica metadata catalogs', description: 'Inspect Monica lookup catalogs (genders, countries, contact field types, activity types, relationship types). Helpful when you need to confirm the exact name/ID before performing other actions.', inputSchema: { resource: metadataResourceSchema, search: z.string().min(1).max(255).optional(), limit: z.number().int().min(1).max(250).optional(), page: z.number().int().min(1).optional() } }, async ({ resource, search, limit, page }) => { const query = search?.trim().toLowerCase(); const { items, meta } = await fetchResource({ client, resource, limit, page }); const filteredItems = query ? filterByQuery(resource, items, query) : items; const label = resourceLabels[resource]; const summary = buildSummary(label, filteredItems.length, query, meta.currentPage, meta.lastPage); return { content: [ { type: 'text' as const, text: summary } ], structuredContent: { resource, search: search ?? null, items: filteredItems, pagination: meta } }; } ); } interface FetchResourceArgs { client: ToolRegistrationContext['client']; resource: MetadataResource; limit?: number; page?: number; } async function fetchResource({ client, resource, limit, page }: FetchResourceArgs) { switch (resource) { case 'genders': { const response = await client.listGenders(limit, page); return { items: response.data.map(normalizeGender), meta: normalizeMeta(response.meta) }; } case 'countries': { const response = await client.listCountries(limit, page); return { items: Object.values(response.data).map(normalizeCountry), meta: normalizeMeta(response.meta) }; } case 'contactFieldTypes': { const response = await client.listContactFieldTypes({ limit, page }); return { items: response.data.map(normalizeContactFieldType), meta: normalizeMeta(response.meta) }; } case 'activityTypes': { const response = await client.listActivityTypes({ limit, page }); return { items: response.data.map(normalizeActivityType), meta: normalizeMeta(response.meta) }; } case 'relationshipTypes': { const response = await client.listRelationshipTypes({ limit, page }); return { items: response.data.map(normalizeRelationshipType), meta: normalizeMeta(response.meta) }; } case 'currencies': { const response = await client.listCurrencies({ limit, page }); return { items: response.data.map(normalizeCurrency), meta: normalizeMeta(response.meta) }; } case 'occupations': { const response = await client.listOccupations({ limit, page }); return { items: response.data.map(normalizeOccupation), meta: normalizeMeta(response.meta) }; } default: throw new Error(`Unsupported resource: ${resource satisfies never}`); } } function filterByQuery(resource: MetadataResource, items: unknown[], query: string) { switch (resource) { case 'genders': return (items as ReturnType<typeof normalizeGender>[]).filter((gender) => gender.name.toLowerCase().includes(query) ); case 'countries': return (items as ReturnType<typeof normalizeCountry>[]).filter((country) => country.name.toLowerCase().includes(query) || country.iso?.toLowerCase().includes(query) ); case 'contactFieldTypes': return (items as ReturnType<typeof normalizeContactFieldType>[]).filter((type) => type.name.toLowerCase().includes(query) || type.kind?.toLowerCase().includes(query) ); case 'activityTypes': return (items as ReturnType<typeof normalizeActivityType>[]).filter((activityType) => activityType.name.toLowerCase().includes(query) || activityType.category?.name?.toLowerCase().includes(query) ); case 'relationshipTypes': return (items as ReturnType<typeof normalizeRelationshipType>[]).filter((relationshipType) => relationshipType.name.toLowerCase().includes(query) || relationshipType.reverseName.toLowerCase().includes(query) ); case 'currencies': return (items as ReturnType<typeof normalizeCurrency>[]).filter((currency) => currency.iso.toLowerCase().includes(query) || currency.name.toLowerCase().includes(query) ); case 'occupations': return (items as ReturnType<typeof normalizeOccupation>[]).filter((occupation) => occupation.title.toLowerCase().includes(query) || (occupation.company?.name?.toLowerCase().includes(query) ?? false) ); default: return items; } } const resourceLabels: Record<MetadataResource, string> = { genders: 'gender', countries: 'country', contactFieldTypes: 'contact field type', activityTypes: 'activity type', relationshipTypes: 'relationship type', currencies: 'currency', occupations: 'occupation' }; function buildSummary( label: string, count: number, query: string | undefined, currentPage: number, lastPage: number ): string { const plural = count === 1 ? label : `${label}s`; if (count === 0) { return query ? `No ${label}s matched "${query}".` : `No ${label}s found on page ${currentPage}.`; } const base = `Found ${count} ${plural}${query ? ` matching "${query}"` : ''}.`; return `${base} Showing page ${currentPage} of ${lastPage}.`; } function normalizeMeta(meta: { current_page: number; last_page: number; per_page: number; total: number }) { return { currentPage: meta.current_page, lastPage: meta.last_page, perPage: meta.per_page, total: meta.total }; }

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/Jacob-Stokes/monica-mcp'

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