The server provides 8 tools with schemas and descriptions, but exhibits significant architectural and design issues. All tools have descriptions and Zod-based schemas, but the implementation suffers from: (1) severe tool composition redundancy, 7 of 8 tools are single-filter variants of the same list operation; (2) incomplete parameter documentation missing constraints, ranges, and format guidance; (3) undocumented output schema structure; (4) generic error responses without recovery guidance; (5) no pagination or result limiting despite potentially returning large datasets. The rubric baseline for average tools is 194 chars for descriptions (p10=34, p90=392); most tools here range 50-85 chars, placing them in the minimal adequate range. All tools use proper action verbs (get_, list_) and have Zod schemas, earning base competence. However, the design violates the single-responsibility principle (pattern:tool) and lacks the LLM-friendly output structure expected of production tools.
Severe tool composition failure: 7 of 8 tools are single-filter variants (get_visas_by_country_code, get_visas_by_status, etc.) that delegate to the same fetchAndFilterVisas() function. The generic list_visas tool accepts all filters. This violates the single-responsibility principle and forces LLMs to reason about choosing between nearly-identical tools, wasting tokens and increasing hallucination risk.
No output schema documented for any tool. The fetchAndFilterVisas() function returns { content: [{ type: 'text', text: string }], isError?: boolean }, but this structure is not declared or described in tool registrations. LLMs cannot infer the return format or plan downstream operations. Production tools must document all output fields with types and descriptions.
Collapse 7 single-filter tools into the generic list_visas tool. Remove get_visas_by_country_code, get_visas_by_status, get_visas_by_mission_code, get_visas_by_visa_category, get_visas_by_visa_type, and get_visas_by_center. Keep only list_visas (with all optional filters) and get_visas_by_country_and_mission (the only multi-parameter composition). This eliminates cognitive load for LLMs and matches the pattern:tool single-responsibility principle.
Document the output schema for all tools. Add a schema description like: 'Returns an array of visa objects, each with fields: id (number), tracking_count (number), country_code (string), mission_code (string), visa_category (string), visa_type (string), center (string), status (string), last_checked_at (ISO 8601 timestamp), last_open_at (optional ISO 8601), last_available_date (optional ISO 8601). Total count and pagination cursor included if result is truncated.'
Add enum constraints for parameters. For country_code and mission_code, add: .enum(['tur', 'gbr', 'deu', ...]) with a description: 'ISO 3166-1 alpha-3 country code (e.g., 'tur' for Turkey, 'gbr' for UK). Valid values: [list common codes].' For status, add: .enum(['open', 'closed']) with description 'Application status: open (accepting submissions) or closed (currently not accepting).' Fetch valid values from the external API on startup or hardcode if stable.
Add pagination parameters to list_visas: limit (integer, 1 - 100, default 20) and offset (integer, ≥0, default 0). Modify fetchAndFilterVisas() to return { visas: [...], total: number, offset: number, limit: number, hasMore: boolean }. Update the tool response to include these fields so LLMs know when to request the next page.
Score history
Overall score trend
↑ 6 points across a rubric change (v1 → v2)
48/100
Scored
Grade
Overall
Spec posture
Rubric
2026-09-22
F
48
2026-07-28+
v2
2026-03-09
F
42
-
v1
source verified
77/100
List all visas with optional filtering by country code, mission code, visa category, visa type, center, or status
Parameter values are exemplified in descriptions (e.g., 'country code (e.g., 'tur', 'gbr')' and 'status (e.g., 'open', 'closed')') rather than constrained as enums. Rubric guideline: 'Replace 'e.g. red, blue, green' with an enum constraint.' LLMs tend to reuse example values literally. Without formal enums, agents may hallucinate invalid country codes or statuses.
No pagination or result limiting. The fetchAndFilterVisas() function retrieves and returns all matching visas as JSON in a single text field. The rubric baseline states: 'Even if the API allows returning thousands of items, cap results at a reasonable limit (e.g. 20-50) and offer pagination.' Large result sets will blow the context window and degrade LLM reasoning. list_visas and all filter tools need limit and offset/cursor parameters.
Error responses are generic and non-actionable. src/utils/index.ts returns error messages like 'Error fetching visa data: {statusText}' and 'Error parsing visa data.' with no guidance on recovery steps. Rubric: 'Error responses must tell the LLM what to do next.' Example: 'Error parsing visa data. The API response did not match the expected schema. Try again or contact support.'
Several parameter descriptions are below the rubric baseline (34-392 chars, mean 72 chars for params). Examples: 'The visa category to filter by' (33 chars), 'The visa type to filter by' (26 chars), 'The application center to filter by' (35 chars). These are minimal and lack context about what values are valid, how the filtering works, or when to use this filter vs others.
Enhance error messages with recovery guidance. In src/utils/index.ts, replace generic errors like 'Error parsing visa data' with: 'Error parsing visa data from the external API. The response format may have changed. Try again in a few moments, or contact the server administrator if the error persists.' For HTTP errors, add: 'The visa API is unavailable (HTTP {statusCode}). Try again later.'
Expand parameter descriptions for visa_category, visa_type, and center to at least 50 chars and include examples or enum options. E.g., 'The visa category (e.g., short-stay, long-stay, transit). Use list_visas with no filters to discover available categories.' This guides LLMs on what values are valid.
Add a discovery tool or document valid filter values. Since the server fetches from an external API (https://api.visasbot.com/api/visa/list), the set of valid countries, missions, categories, types, and centers may change. Consider a tool like 'get_visa_metadata()' that returns available filter values, or document them in the server README.
Return structured objects, not JSON strings. Currently, fetchAndFilterVisas() returns { content: [{ type: 'text', text: JSON.stringify(visas) }] }. Change this to return the Visa objects directly in a structured response: { type: 'text', visas: [...], total: number, offset: number }. This reduces token overhead and makes parsing unambiguous.