Skip to main content
Glama
r-huijts

OpenTK Model Context Protocol Server

by r-huijts

get_committee_details

Retrieve comprehensive details about a parliamentary committee, including members, leadership roles, and recent activities, based on the committee's unique ID, to understand its structure and work.

Instructions

Retrieves detailed information about a specific parliamentary committee, including its members, recent activities, and description. This provides deeper insight into the committee's composition, leadership roles, and recent work. Use this when you need comprehensive information about a particular committee's structure and activities.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
committeeIdYesCommittee ID - the unique identifier for the parliamentary committee you want information about

Implementation Reference

  • src/index.ts:467-516 (registration)
    Registration of the get_committee_details tool using mcp.tool(), including description, Zod input schema for committeeId, and inline handler function that fetches committee HTML and extracts details.
    /** Get committee details */
    mcp.tool(
      "get_committee_details",
      "Retrieves detailed information about a specific parliamentary committee, including its members, recent activities, and description. This provides deeper insight into the committee's composition, leadership roles, and recent work. Use this when you need comprehensive information about a particular committee's structure and activities.",
      {
        committeeId: z.string().describe("Committee ID - the unique identifier for the parliamentary committee you want information about")
      },
      async ({ committeeId }) => {
        try {
          const html = await apiService.fetchHtml(`/commissie.html?id=${encodeURIComponent(committeeId)}`);
          const committeeDetails = extractCommitteeDetailsFromHtml(html, BASE_URL, committeeId);
    
          if (!committeeDetails) {
            // If we couldn't extract details from the HTML, return a simplified response with just the name and ID
            const titleRegex = /<title>([^<]+)<\/title>/i;
            const titleMatch = html.match(titleRegex);
            const name = titleMatch?.[1]?.trim() || "Unknown Committee";
    
            return {
              content: [{
                type: "text",
                text: JSON.stringify({
                  id: committeeId,
                  name: name,
                  url: `${BASE_URL}/commissie.html?id=${encodeURIComponent(committeeId)}`,
                  note: "This committee uses dynamic content rendering. Only basic information is available."
                }, null, 2)
              }]
            };
          }
    
          return {
            content: [{
              type: "text",
              text: JSON.stringify(committeeDetails, null, 2)
            }]
          };
        } catch (error: any) {
          return {
            content: [{
              type: "text",
              text: JSON.stringify({
                error: `Error fetching committee details: ${error.message || 'Unknown error'}`
              })
            }]
          };
        }
      }
    );
  • Handler function that executes the tool: fetches HTML from /commissie.html?id={committeeId} using apiService, extracts details using helper function, handles fallback and errors, returns JSON response.
      async ({ committeeId }) => {
        try {
          const html = await apiService.fetchHtml(`/commissie.html?id=${encodeURIComponent(committeeId)}`);
          const committeeDetails = extractCommitteeDetailsFromHtml(html, BASE_URL, committeeId);
    
          if (!committeeDetails) {
            // If we couldn't extract details from the HTML, return a simplified response with just the name and ID
            const titleRegex = /<title>([^<]+)<\/title>/i;
            const titleMatch = html.match(titleRegex);
            const name = titleMatch?.[1]?.trim() || "Unknown Committee";
    
            return {
              content: [{
                type: "text",
                text: JSON.stringify({
                  id: committeeId,
                  name: name,
                  url: `${BASE_URL}/commissie.html?id=${encodeURIComponent(committeeId)}`,
                  note: "This committee uses dynamic content rendering. Only basic information is available."
                }, null, 2)
              }]
            };
          }
    
          return {
            content: [{
              type: "text",
              text: JSON.stringify(committeeDetails, null, 2)
            }]
          };
        } catch (error: any) {
          return {
            content: [{
              type: "text",
              text: JSON.stringify({
                error: `Error fetching committee details: ${error.message || 'Unknown error'}`
              })
            }]
          };
        }
      }
    );
  • Zod schema for tool input parameters: committeeId as string.
    {
      committeeId: z.string().describe("Committee ID - the unique identifier for the parliamentary committee you want information about")
    },
  • Primary helper function implementing the parsing logic: extracts committee name, description, members (with ids, roles, parties), and recent activities from the committee page HTML using regex patterns on tables.
    export function extractCommitteeDetailsFromHtml(html: string, baseUrl: string, committeeId: string): CommitteeDetails | null {
      if (!html) {
        return null;
      }
    
      // Extract the committee name from the title tag or h2 tag
      let name = "";
    
      // First try to get the name from the h2 tag
      const h2Regex = /<h2>([^<]+)<\/h2>/i;
      const h2Match = html.match(h2Regex);
      if (h2Match && h2Match[1]) {
        name = h2Match[1].trim();
      }
    
      // If not found, try to get it from the title tag
      if (!name) {
        const titleRegex = /<title>([^<]+)<\/title>/i;
        const titleMatch = html.match(titleRegex);
        if (titleMatch && titleMatch[1]) {
          name = titleMatch[1].trim();
        }
      }
    
      if (!name) {
        return null;
      }
    
      const details: CommitteeDetails = {
        id: committeeId,
        name,
        url: `${baseUrl}/commissie.html?id=${encodeURIComponent(committeeId)}`,
        members: [],
        recentActivities: []
      };
    
      // Extract description if available
      const descriptionRegex = /<p class="description">([^<]+)<\/p>/i;
      const descriptionMatch = html.match(descriptionRegex);
      details.description = descriptionMatch?.[1]?.trim() || null;
    
      // Extract members from the first table
      const membersTableRegex = /<table[^>]*>[\s\S]*?<thead>[\s\S]*?<\/thead>[\s\S]*?<tbody>([\s\S]*?)<\/tbody>/i;
      const membersTableMatch = html.match(membersTableRegex);
    
      if (membersTableMatch && membersTableMatch[1]) {
        const membersTableContent = membersTableMatch[1];
        const rowRegex = /<tr[^>]*>([\s\S]*?)<\/tr>/gi;
        let rowMatch;
    
        while ((rowMatch = rowRegex.exec(membersTableContent)) !== null) {
          if (!rowMatch[1]) continue;
    
          const rowContent = rowMatch[1];
    
          // Extract cells
          const cellRegex = /<td[^>]*>([\s\S]*?)<\/td>/gi;
          const cells: string[] = [];
          let cellMatch;
    
          while ((cellMatch = cellRegex.exec(rowContent)) !== null) {
            if (cellMatch[1]) {
              cells.push(cellMatch[1].trim());
            }
          }
    
          if (cells.length < 3) continue;
    
          // Extract role, name, and party
          const role = cells[1] ? cells[1].replace(/<[^>]+>/g, "").trim() : "";
    
          // Extract name and ID from the link
          const nameCell = cells[2] || "";
          const nameMatch = nameCell.match(/<a href="persoon\.html\?nummer=([^"]+)">([^<]+)<\/a>/);
    
          if (!nameMatch || !nameMatch[1] || !nameMatch[2]) continue;
    
          const id = nameMatch[1];
          const name = nameMatch[2].trim();
    
          // Extract party if available (might be in the same cell as the name)
          let party = "";
          const partyMatch = nameCell.match(/>([^<]+)<\/a>\s*\(([^)]+)\)/);
          if (partyMatch && partyMatch[2]) {
            party = partyMatch[2].trim();
          }
    
          details.members?.push({
            id: id,
            name: name,
            role: role || undefined,
            party: party || undefined
          });
        }
      }
    
      // Extract recent activities from the second table
      const tablesRegex = /<table[^>]*>[\s\S]*?<thead>[\s\S]*?<\/thead>[\s\S]*?<tbody>([\s\S]*?)<\/tbody>/gi;
      let tableMatch;
      let tableCount = 0;
      let activitiesTableContent = "";
    
      // Find the second table (activities)
      while ((tableMatch = tablesRegex.exec(html)) !== null) {
        tableCount++;
        if (tableCount === 2 && tableMatch[1]) {
          activitiesTableContent = tableMatch[1];
          break;
        }
      }
    
      if (activitiesTableContent) {
        const rowRegex = /<tr[^>]*>([\s\S]*?)<\/tr>/gi;
        let rowMatch;
    
        while ((rowMatch = rowRegex.exec(activitiesTableContent)) !== null) {
          if (!rowMatch[1]) continue;
    
          const rowContent = rowMatch[1];
    
          // Extract cells
          const cellRegex = /<td[^>]*>([\s\S]*?)<\/td>/gi;
          const cells: string[] = [];
          let cellMatch;
    
          while ((cellMatch = cellRegex.exec(rowContent)) !== null) {
            if (cellMatch[1]) {
              cells.push(cellMatch[1].trim());
            }
          }
    
          if (cells.length < 2) continue;
    
          // Extract date
          const date = cells[0] ? cells[0].replace(/<[^>]+>/g, "").trim() : "";
          if (!date) continue;
    
          // Extract title and link
          const titleCell = cells[1] || "";
          const titleMatch = titleCell.match(/<a href="(activiteit\.html\?nummer=([^"]+))">([^<]+)<\/a>/);
    
          if (!titleMatch || !titleMatch[1] || !titleMatch[3]) continue;
    
          const url = new URL(titleMatch[1], baseUrl).href;
          const title = titleMatch[3].trim();
    
          details.recentActivities?.push({
            title,
            date,
            url
          });
        }
      }
    
      return details;
    }
  • TypeScript interfaces defining the structure of Committee and output CommitteeDetails (name, description, members, recentActivities).
    interface Committee {
      id: string;
      name: string;
      url: string;
    }
    
    interface CommitteeDetails extends Committee {
      description?: string | null;
      members?: Array<{
        name: string;
        id: string;
        party?: string;
        role?: string;
      }>;
      recentActivities?: Array<{
        title: string;
        date: string;
        url: string;
      }>;
    }
Behavior3/5

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

No annotations are provided, so the description carries the full burden. It discloses that the tool retrieves information (implying read-only, non-destructive behavior) and specifies the scope of data returned. However, it lacks details on error handling, rate limits, or authentication needs, leaving behavioral gaps for a tool with no annotation coverage.

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 that are front-loaded with the core purpose, followed by usage guidance. Every phrase adds value without redundancy, making it efficiently structured and appropriately sized for the tool's complexity.

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?

Given the tool's low complexity (1 parameter, no output schema, no annotations), the description is mostly complete—it covers purpose, usage, and data scope. However, without annotations or output schema, it could benefit from more behavioral details (e.g., response format), slightly reducing completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, with the parameter 'committeeId' well-documented in the schema. The description adds context by implying this ID is for a 'specific parliamentary committee,' reinforcing its purpose without repeating schema details. With only one parameter, this minimal addition is sufficient for a high score.

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?

The description clearly states the verb ('Retrieves') and resource ('detailed information about a specific parliamentary committee'), specifying what information is included (members, recent activities, description, composition, leadership roles, recent work). It distinguishes from sibling tools like 'get_committees' by focusing on details for a specific committee rather than listing committees.

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

Usage Guidelines5/5

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

Explicitly states 'Use this when you need comprehensive information about a particular committee's structure and activities,' providing clear context for when to use this tool. It implies alternatives like 'get_committees' for broader lists, though not explicitly named, the specificity suffices for high guidance.

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

Related 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/r-huijts/opentk-mcp'

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