MCP server: generate and decode ULIDs (sortable, 128-bit, base32 identifiers).
Two simple, focused tools with clear names and basic descriptions. Both tools have input schemas with type definitions. However, parameter descriptions are minimal or absent, output schemas are undocumented, and error handling lacks recovery guidance. The 'generate' tool's optional timestamp_ms parameter has a description, but 'decode' tool's required 'ulid' parameter lacks any description. No tool annotations (readOnlyHint) despite both being read-only. Descriptions are adequate (60-80 chars) but could be more LLM-optimized with usage context.
Decode a ULID into timestamp (ms + ISO) and randomness parts.
Generate a fresh ULID. 26-char Crockford-base32: 10 chars timestamp + 16 chars random.
Missing parameter descriptions: 'decode' tool's required 'ulid' parameter has no description explaining what format is expected or what happens on invalid input.
Output schemas are not documented. LLMs cannot infer the structure of 'generate' (returns {ulid: string}) or 'decode' (returns {ulid, timestamp_ms, iso, randomness}). Agents need explicit output schemas to plan downstream calls.
Error messages lack recovery guidance. When decode() throws 'ULID must be 26 chars' or 'invalid base32 character', the LLM receives no hint on what to do next (e.g., 'Ensure the ULID is exactly 26 characters using Crockford base32 alphabet').
Tool annotations missing. Both tools are read-only (no side effects), but neither declares readOnlyHint. This prevents agents from optimizing caching and retry strategies.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | D | 55 | 2026-07-28+ | v2 |
Minimal parameter constraints. 'timestamp_ms' accepts any integer but should document the valid range (0 to 281474976710655 ms, ~8921 years). 'ulid' should specify format (26-char Crockford base32) in the parameter description, not just the tool description.