Skip to main content
Glama
Hug0x0

mcp-reunion

reunion_list_festivals

List festivals in La Réunion filtered by discipline or commune. Returns name, location, contact, and artistic discipline details from the Ministère de la Culture census.

Instructions

List festivals taking place in La Réunion: music, performing arts, cinema/audiovisual, books and literature, visual and digital arts. Returns festival name, territorial scope, host commune, postal code, address, website, email, founding year, main period of occurrence, dominant discipline, sub-categories per discipline (music genre, cinema type, etc.). Source: Ministère de la Culture festival census via data.regionreunion.com.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
disciplineNoDominant discipline prefix match. Examples: "Musique", "Spectacle vivant", "Cinéma", "Livre", "Arts visuels"
communeNoHost commune name prefix match
limitNoMax festivals to return (1-100, default 50)

Implementation Reference

  • Handler function for the reunion_list_festivals tool. Queries the 'liste-des-festivals-a-la-reunion' dataset with optional filters for discipline and commune, maps results to a structured festival response.
    server.tool(
      'reunion_list_festivals',
      'List festivals taking place in La Réunion: music, performing arts, cinema/audiovisual, books and literature, visual and digital arts. Returns festival name, territorial scope, host commune, postal code, address, website, email, founding year, main period of occurrence, dominant discipline, sub-categories per discipline (music genre, cinema type, etc.). Source: Ministère de la Culture festival census via data.regionreunion.com.',
      {
        discipline: z.string().optional().describe('Dominant discipline prefix match. Examples: "Musique", "Spectacle vivant", "Cinéma", "Livre", "Arts visuels"'),
        commune: z.string().optional().describe('Host commune name prefix match'),
        limit: z.number().int().min(1).max(100).default(50).describe('Max festivals to return (1-100, default 50)'),
      },
      async ({ discipline, commune, limit }) => {
        try {
          const data = await client.getRecords<RecordObject>(DATASET_FESTIVALS, {
            where: buildWhere([
              discipline ? `discipline_dominante LIKE ${quote(`${discipline}%`)}` : undefined,
              commune ? `commune_principale_de_deroulement LIKE ${quote(`${commune}%`)}` : undefined,
            ]),
            limit,
          });
          return jsonResult({
            total_festivals: data.total_count,
            festivals: data.results.map((row) => ({
              name: pickString(row, ['nom_du_festival']),
              scope: pickString(row, ['envergure_territoriale']),
              commune: pickString(row, ['commune_principale_de_deroulement']),
              postal_code: pickString(row, ['code_postal_de_la_commune_principale_de_deroulement']),
              address: pickString(row, ['adresse_postale']),
              website: pickString(row, ['site_internet_du_festival']),
              email: pickString(row, ['adresse_e_mail']),
              created_year: pickNumber(row, ['annee_de_creation_du_festival']),
              period: pickString(row, ['periode_principale_de_deroulement_du_festival']),
              discipline: pickString(row, ['discipline_dominante']),
              sub_category_music: pickString(row, ['sous_categorie_musique']),
              sub_category_cinema: pickString(row, ['sous_categorie_cinema_et_audiovisuel']),
              sub_category_books: pickString(row, ['sous_categorie_livre_et_litterature']),
              sub_category_visual_arts: pickString(row, ['sous_categorie_arts_visuels_et_arts_numeriques']),
            })),
          });
        } catch (error) {
          return errorResult(error instanceof Error ? error.message : 'Failed to list festivals');
        }
      }
    );
  • Input schema for reunion_list_festivals: optional discipline and commune prefix filters, and optional limit (default 50, max 100).
    {
      discipline: z.string().optional().describe('Dominant discipline prefix match. Examples: "Musique", "Spectacle vivant", "Cinéma", "Livre", "Arts visuels"'),
      commune: z.string().optional().describe('Host commune name prefix match'),
      limit: z.number().int().min(1).max(100).default(50).describe('Max festivals to return (1-100, default 50)'),
    },
  • Registration of the reunion_list_festivals tool via McpServer.server.tool() in registerCultureTools.
    server.tool(
      'reunion_list_festivals',
      'List festivals taking place in La Réunion: music, performing arts, cinema/audiovisual, books and literature, visual and digital arts. Returns festival name, territorial scope, host commune, postal code, address, website, email, founding year, main period of occurrence, dominant discipline, sub-categories per discipline (music genre, cinema type, etc.). Source: Ministère de la Culture festival census via data.regionreunion.com.',
      {
        discipline: z.string().optional().describe('Dominant discipline prefix match. Examples: "Musique", "Spectacle vivant", "Cinéma", "Livre", "Arts visuels"'),
        commune: z.string().optional().describe('Host commune name prefix match'),
        limit: z.number().int().min(1).max(100).default(50).describe('Max festivals to return (1-100, default 50)'),
      },
      async ({ discipline, commune, limit }) => {
        try {
          const data = await client.getRecords<RecordObject>(DATASET_FESTIVALS, {
            where: buildWhere([
              discipline ? `discipline_dominante LIKE ${quote(`${discipline}%`)}` : undefined,
              commune ? `commune_principale_de_deroulement LIKE ${quote(`${commune}%`)}` : undefined,
            ]),
            limit,
          });
          return jsonResult({
            total_festivals: data.total_count,
            festivals: data.results.map((row) => ({
              name: pickString(row, ['nom_du_festival']),
              scope: pickString(row, ['envergure_territoriale']),
              commune: pickString(row, ['commune_principale_de_deroulement']),
              postal_code: pickString(row, ['code_postal_de_la_commune_principale_de_deroulement']),
              address: pickString(row, ['adresse_postale']),
              website: pickString(row, ['site_internet_du_festival']),
              email: pickString(row, ['adresse_e_mail']),
              created_year: pickNumber(row, ['annee_de_creation_du_festival']),
              period: pickString(row, ['periode_principale_de_deroulement_du_festival']),
              discipline: pickString(row, ['discipline_dominante']),
              sub_category_music: pickString(row, ['sous_categorie_musique']),
              sub_category_cinema: pickString(row, ['sous_categorie_cinema_et_audiovisuel']),
              sub_category_books: pickString(row, ['sous_categorie_livre_et_litterature']),
              sub_category_visual_arts: pickString(row, ['sous_categorie_arts_visuels_et_arts_numeriques']),
            })),
          });
        } catch (error) {
          return errorResult(error instanceof Error ? error.message : 'Failed to list festivals');
        }
      }
    );
  • Tool module registration is called from registerAllTools in modules/index.ts.
    registerCultureTools(server);
  • Supporting utilities used by the reunion_list_festivals handler (buildWhere, quote, pickString, pickNumber, jsonResult, errorResult).
    // src/utils/helpers.ts
    
    import { RecordObject, ToolResult } from '../types.js';
    
    /**
     * Format data as JSON tool result
     */
    export function jsonResult(data: unknown): ToolResult {
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(data, null, 2),
          },
        ],
      };
    }
    
    /**
     * Format error as tool result
     */
    export function errorResult(message: string): ToolResult {
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({ error: message }, null, 2),
          },
        ],
      };
    }
    
    /**
     * Build ODSQL WHERE clause from conditions
     */
    export function buildWhere(
      conditions: Array<string | undefined | null | false>
    ): string | undefined {
      const valid = conditions.filter((condition): condition is string => Boolean(condition));
      return valid.length > 0 ? valid.join(' AND ') : undefined;
    }
    
    /**
     * Escape a string literal for ODSQL
     */
    export function escapeOdSqlString(value: string): string {
      return value.replace(/'/g, "''");
    }
    
    /**
     * Quote an ODSQL string literal
     */
    export function quote(value: string): string {
      return `'${escapeOdSqlString(value)}'`;
    }
    
    /**
     * Format date as YYYY-MM-DD
     */
    export function formatDate(date: Date): string {
      return date.toISOString().split('T')[0];
    }
    
    /**
     * Get today's date as YYYY-MM-DD
     */
    export function today(): string {
      return formatDate(new Date());
    }
    
    /**
     * Calculate days between two dates
     */
    export function daysBetween(date1: string, date2: string): number {
      const d1 = new Date(date1);
      const d2 = new Date(date2);
      const diffTime = d2.getTime() - d1.getTime();
      return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
    }
    
    /**
     * Pick the first defined value from a record
     */
    export function pickValue<T = unknown>(
      record: RecordObject,
      candidates: string[]
    ): T | undefined {
      for (const candidate of candidates) {
        if (candidate in record) {
          const value = record[candidate];
          if (value !== undefined && value !== null) {
            // OpenDataSoft v2.1 wraps some text fields as single-element arrays
            // (e.g. com_name → ["Saint-Denis"]). Unwrap so downstream pickers
            // see the scalar they expect.
            if (Array.isArray(value) && value.length === 1) {
              return value[0] as T;
            }
            return value as T;
          }
        }
      }
      return undefined;
    }
    
    /**
     * Pick the first string-like value from a record
     */
    export function pickString(
      record: RecordObject,
      candidates: string[]
    ): string | undefined {
      const value = pickValue(record, candidates);
      if (typeof value === 'string') {
        return value;
      }
      if (typeof value === 'number' || typeof value === 'boolean') {
        return String(value);
      }
      return undefined;
    }
    
    /**
     * Pick the first numeric value from a record
     */
    export function pickNumber(
      record: RecordObject,
      candidates: string[]
    ): number | undefined {
      const value = pickValue(record, candidates);
      if (typeof value === 'number' && Number.isFinite(value)) {
        return value;
      }
      if (typeof value === 'string' && value.trim() !== '') {
        const parsed = Number(value);
        return Number.isFinite(parsed) ? parsed : undefined;
      }
      return undefined;
    }
    
    /**
     * Pick the first boolean-like value from a record
     */
    export function pickBoolean(
      record: RecordObject,
      candidates: string[]
    ): boolean | undefined {
      const value = pickValue(record, candidates);
      if (typeof value === 'boolean') {
        return value;
      }
      if (typeof value === 'number') {
        return value !== 0;
      }
      if (typeof value === 'string') {
        const normalized = value.trim().toLowerCase();
        if (['true', '1', 'oui', 'yes'].includes(normalized)) {
          return true;
        }
        if (['false', '0', 'non', 'no'].includes(normalized)) {
          return false;
        }
      }
      return undefined;
    }
    
    /**
     * Normalize a string for case-insensitive comparisons
     */
    export function normalizeText(value: string): string {
      return value
        .normalize('NFD')
        .replace(/[\u0300-\u036f]/g, '')
        .toLowerCase()
        .trim();
    }
Behavior3/5

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

No annotations provided. Description does not disclose behavioral traits like rate limits, authentication, or side effects. For a read-only list, it is adequate but not beyond the obvious.

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

Conciseness5/5

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

Two sentences, front-loaded with main action, lists return fields efficiently. Every sentence adds value.

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

Completeness4/5

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

No output schema, but description thoroughly lists return fields. Simple list tool with 3 optional parameters, well covered.

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 coverage is 100% with descriptions. Description adds minimal new meaning beyond examples for discipline and commune. Baseline 3 is appropriate.

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

Purpose5/5

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

Description clearly states it lists festivals in La Réunion with specific disciplines and return fields. It distinguishes from sibling list tools like museums or canyons.

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

Usage Guidelines3/5

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

No explicit guidance on when to use this tool versus alternatives. Many sibling list tools exist but description does not differentiate usage context.

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/Hug0x0/mcp-reunion'

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