Skip to main content
Glama
r-huijts

OpenTK Model Context Protocol Server

by r-huijts

get_overview

Retrieve a paginated overview of recent Dutch parliamentary activities, including latest documents and MPs celebrating birthdays today. Use structured data for research or further exploration.

Instructions

Provides a comprehensive overview of recent parliamentary activities, including the most recent documents and MPs celebrating birthdays today. This is the ideal starting point for any parliamentary data exploration. The response contains structured data with two main sections: 'recentDocuments' (listing the latest parliamentary documents with their IDs, titles, types, dates, and URLs) and 'birthdays' (listing MPs celebrating birthdays today). The results are paginated with 10 documents per page, and you can navigate through pages using the 'page' parameter. The tool can be used iteratively to retrieve subsequent pages of results - first call with page=1, then check the pagination.hasMoreDocuments field in the response, and if true, call again with page=2, and so on. This allows you to 'scroll' through all available documents when needed. The response includes pagination information showing the current page, whether more documents are available, and the total number of documents retrieved. Use this tool first when a user asks for general information about recent parliamentary activities or needs a starting point for research. After getting this overview, you can use other tools like 'get_document_details' to retrieve more information about specific documents, 'search_tk' to find documents on specific topics, or 'get_photo' to retrieve photos of MPs mentioned in the birthdays section.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pageNoPage number for paginated results (default: 1). Each page contains 10 documents.

Implementation Reference

  • src/index.ts:26-55 (registration)
    MCP tool registration for 'get_overview', defining the tool name, long description, input schema (optional page number), and thin async handler that validates input and calls apiService.getOverview() to execute the logic, returning formatted text content.
    mcp.tool(
      "get_overview",
      "Provides a comprehensive overview of recent parliamentary activities, including the most recent documents and MPs celebrating birthdays today. The response contains structured data with two main sections: 'recentDocuments' (listing the latest parliamentary documents with their IDs, titles, types, dates, and URLs) and 'birthdays' (listing MPs celebrating birthdays today). The results are paginated with 10 documents per page. The tool supports iterative pagination - check the pagination.hasMoreDocuments field in the response to determine if additional pages are available. The response includes pagination information showing the current page, whether more documents are available, and the total number of documents retrieved. Applicable when general information about recent parliamentary activities is needed.",
      {
        page: z.number().optional().describe("Page number for paginated results (default: 1). Each page contains 10 documents.")
      },
      async ({ page = 1 }) => {
        try {
          // Validate page number
          const validatedPage = Math.max(1, page);
    
          // Get overview data with pagination
          const overview = await apiService.getOverview(validatedPage);
    
          return {
            content: [{
              type: "text",
              text: JSON.stringify(overview, null, 2)
            }]
          };
        } catch (error: any) {
          return {
            content: [{
              type: "text",
              text: `Error fetching overview: ${error.message || 'Unknown error'}`
            }]
          };
        }
      }
    );
  • Primary handler function in ApiService class that fetches the main '/' HTML page, extracts structured overview data (recent documents and birthdays) using the html-parser utility with pagination support, and handles errors by returning empty data with error message.
    async getOverview(page: number = 1): Promise<any> {
      try {
        // Validate page number
        const validatedPage = Math.max(1, page);
    
        // Fetch the HTML of the main page
        const html = await this.fetchHtml("/");
    
        // Extract overview data from the HTML with pagination
        const overviewData = extractOverviewFromHtml(html, BASE_URL, validatedPage);
    
        return overviewData;
      } catch (error) {
        return {
          recentDocuments: [],
          birthdays: [],
          lastUpdated: new Date().toISOString(),
          pagination: {
            currentPage: page,
            hasMoreDocuments: false,
            totalDocumentsRetrieved: 0
          },
          error: (error as Error).message || 'Unknown error'
        };
      }
    }
  • Zod input schema for the get_overview tool, defining an optional 'page' parameter (number) for pagination with descriptive documentation.
    {
      page: z.number().optional().describe("Page number for paginated results (default: 1). Each page contains 10 documents.")
    },
  • Key helper utility that parses raw HTML from the main tkconv page to extract paginated recent documents (id, title, type, date, etc.) from table rows and today's birthdays from special section, returning structured OverviewData interface with pagination metadata. Used by apiService.getOverview.
    export function extractOverviewFromHtml(html: string, baseUrl: string, page: number = 1): OverviewData {
      if (!html) {
        return {
          recentDocuments: [],
          birthdays: [],
          lastUpdated: new Date().toISOString(),
          pagination: {
            currentPage: 1,
            hasMoreDocuments: false,
            totalDocumentsRetrieved: 0
          }
        };
      }
    
      const recentDocuments: RecentDocument[] = [];
      const birthdays: BirthdayPerson[] = [];
      let lastUpdated = new Date().toISOString();
    
      // Extract the table containing recent documents
      const tableRegex = /<table[^>]*>[\s\S]*?<tbody>([\s\S]*?)<\/tbody>/i;
      const tableMatch = html.match(tableRegex);
    
      if (tableMatch && tableMatch[1]) {
        const tableContent = tableMatch[1];
    
        // Extract each row (document) from the table
        const rowRegex = /<tr[^>]*>([\s\S]*?)<\/tr>/gi;
        let rowMatch;
    
        while ((rowMatch = rowRegex.exec(tableContent)) !== 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());
            }
          }
    
          // Need at least date, updated, committee, subject, and title/type
          if (cells.length < 5) continue;
    
          // Extract date
          const date = cells[0] ? cells[0].replace(/<[^>]+>/g, "").trim() : "";
          if (!date) continue;
    
          // Extract updated date
          const updated = cells[1] ? cells[1].replace(/<[^>]+>/g, "").trim() : "";
    
          // Extract committee
          const committee = cells[2] ? cells[2].replace(/<[^>]+>/g, "").trim() : undefined;
    
          // Extract subject
          const subject = cells[3] ? cells[3].replace(/<[^>]+>/g, "").trim() : undefined;
    
          // Extract title, type, and document ID from the last cell
          const titleCell = cells[4] || "";
    
          // Extract document ID and title from the link if present
          const docLinkMatch = titleCell.match(/<a href="document\.html\?nummer=([^"]+)">([\s\S]*?)<\/a>/i);
          let id = "";
          let title = "";
          let url = "";
    
          if (docLinkMatch && docLinkMatch[1] && docLinkMatch[2]) {
            id = docLinkMatch[1];
            title = docLinkMatch[2].trim();
            url = new URL(`document.html?nummer=${id}`, baseUrl).href;
          } else {
            // If no link, just use the text content
            title = titleCell.replace(/<[^>]+>/g, "").trim();
            // Generate a placeholder ID
            id = `unknown-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
            url = baseUrl;
          }
    
          // Extract document type (usually on the next line after the title)
          const typeMatch = titleCell.match(/<br\s*\/?>\s*(.*?)(?:<|$)/i);
          const type = typeMatch && typeMatch[1] ? typeMatch[1].trim() : "Unknown";
    
          // Add the document to the list
          recentDocuments.push({
            id,
            title,
            type,
            date,
            updated,
            committee,
            subject,
            url
          });
    
          // Limit to 20 documents to avoid overwhelming the response
          if (recentDocuments.length >= 20) break;
        }
      }
    
      // Extract birthdays
      const birthdayRegex = /Jarig vandaag\s*((?:<a[^>]*>[^<]*<\/a>\s*)+)/i;
      const birthdayMatch = html.match(birthdayRegex);
    
      if (birthdayMatch && birthdayMatch[1]) {
        const birthdayContent = birthdayMatch[1];
        const birthdayLinkRegex = /<a href="persoon\.html\?nummer=([^"]+)">([\s\S]*?)<\/a>/gi;
        let birthdayLinkMatch;
    
        while ((birthdayLinkMatch = birthdayLinkRegex.exec(birthdayContent)) !== null) {
          if (birthdayLinkMatch[1] && birthdayLinkMatch[2]) {
            const id = birthdayLinkMatch[1];
            const nameWithParty = birthdayLinkMatch[2].trim();
    
            // Extract name and party if in format "Name (Party)"
            const namePartyMatch = nameWithParty.match(/(.*?)\s*\((.*?)\)\s*$/);
            let name = nameWithParty;
            let party = undefined;
    
            if (namePartyMatch && namePartyMatch[1] && namePartyMatch[2]) {
              name = namePartyMatch[1].trim();
              party = namePartyMatch[2].trim();
            }
    
            const url = new URL(`persoon.html?nummer=${id}`, baseUrl).href;
    
            birthdays.push({
              id,
              name,
              party,
              url
            });
          }
        }
      }
    
      // For pagination, we would normally need to fetch different pages from the server
      // Since the tkconv site doesn't have explicit pagination, we're simulating it by
      // limiting the number of documents per page and tracking which ones we've shown
    
      const documentsPerPage = 10;
      const startIndex = (page - 1) * documentsPerPage;
      const endIndex = startIndex + documentsPerPage;
    
      // Get the documents for the current page
      const paginatedDocuments = recentDocuments.slice(startIndex, endIndex);
    
      // Check if there are more documents available
      const hasMoreDocuments = endIndex < recentDocuments.length;
    
      return {
        recentDocuments: paginatedDocuments,
        birthdays, // Birthdays are always shown regardless of page
        lastUpdated,
        pagination: {
          currentPage: page,
          hasMoreDocuments,
          totalDocumentsRetrieved: recentDocuments.length
        }
      };
    }
  • TypeScript interface defining the output structure/schema of the overview data returned by extractOverviewFromHtml and getOverview, including arrays of recent documents and birthdays with pagination info.
    interface OverviewData {
      recentDocuments: RecentDocument[];
      birthdays: BirthdayPerson[];
      lastUpdated: string;
      pagination: {
        currentPage: number;
        hasMoreDocuments: boolean;
        totalDocumentsRetrieved: number;
      };
    }
Behavior4/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It effectively describes the tool's behavior: pagination mechanics (10 documents per page, hasMoreDocuments field), response structure (two main sections), and the iterative usage pattern. It doesn't mention rate limits or authentication requirements, but covers the core operational behavior well.

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

Conciseness3/5

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

The description is comprehensive but somewhat verbose. While all information is relevant, it could be more front-loaded with the core purpose. The explanation of pagination mechanics and sibling tool relationships, while valuable, extends the description length. Every sentence earns its place, but the structure could be tighter.

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 complexity (pagination, multiple data sections, iterative usage) and lack of annotations/output schema, the description does an excellent job of providing context. It explains the response structure, pagination behavior, and workflow. The main gap is the absence of output schema details, but the description compensates well by describing the response format and fields.

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?

The schema description coverage is 100%, so the baseline is 3. The description adds significant value by explaining the pagination context ('first call with page=1, then check...'), the relationship between the page parameter and document retrieval, and the iterative scrolling workflow. This provides practical usage semantics beyond the schema's basic parameter documentation.

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 tool provides a comprehensive overview of recent parliamentary activities with two specific sections (recent documents and MP birthdays). It distinguishes itself from siblings by being the 'ideal starting point' and explicitly mentions alternative tools like get_document_details and search_tk for follow-up actions.

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?

The description provides explicit guidance on when to use this tool ('first when a user asks for general information about recent parliamentary activities or needs a starting point for research') and when to use alternatives ('After getting this overview, you can use other tools like...'). It also explains the iterative pagination workflow for scrolling through documents.

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