Skip to content
MINH VO A working notebook
by an engineer in Vietnam
Foundation7 min read

Describe tools so arguments have a clear meaning

Design a calendar-search tool with explicit timezone, interval boundaries, pagination, and access rules so model-generated arguments have one meaning.

Open notebook with connected abstract shapes and olive thread linking paper swatches

For a developer exposing tools to an LLM, a good schema makes every argument’s meaning explicit enough for the application to validate. Field types are necessary, but units, boundary rules, and missing-value behavior determine whether a valid payload performs the intended operation. A string called date leaves too many decisions unresolved.

Consider a hypothetical read-only calendar search. A user asks, “Find my meetings next Friday morning.” The tool should search authorized calendars and return matching events. It should not create, move, or cancel anything. That capability boundary belongs in both the tool catalog and the executor.

Decide what the interval means

Represent a search as a start instant and an exclusive end instant. With start-based matching, an event starting exactly at a shared boundary belongs only to the next interval. With overlap matching, an event spanning that boundary can legitimately appear in both adjacent searches; an exclusive end does not deduplicate those results. Include the timezone used to interpret local phrases such as “morning,” or resolve those phrases in a separate step before invoking the tool.

{
  "name": "search_calendar_events",
  "arguments": {
    "calendar_id": "primary",
    "start": "2026-10-02T09:00:00+07:00",
    "end_exclusive": "2026-10-02T12:00:00+07:00",
    "timezone": "Asia/Ho_Chi_Minh",
    "limit": 20,
    "cursor": null
  }
}

This is an illustrative application-level call, not a complete provider API request. The interval and calendar are fixture values. The application must decide whether an event matches when it starts within the interval or whenever it overlaps the interval. An event beginning at 08:30 and ending at 09:30 exposes the difference.

For an overlap policy, an event matches when its start is before the query end and its end is after the query start, subject to the calendar’s treatment of all-day events and zero-duration entries. Document those details instead of assuming the model will infer the same policy as the backend.

Calendar event from 08:30 to 09:30 overlaps a 09:00 to noon query and demonstrates why tool schemas need matching semanticsView full-size image ↗

The figure shows one interval and an event that crosses its start boundary. A schema can require valid timestamps while still failing to explain whether that event should be returned. The description needs to carry the semantic rule.

Make the overlap rule executable

For this exercise, timed events and query windows are nonempty half-open intervals: the start belongs to the interval and the end does not. Zero-duration entries are rejected by this helper. All-day dates require a separate adapter that expands them using the calendar’s timezone rules; passing midnight strings without that decision would quietly invent semantics.

The Python function below requires timezone-aware datetimes and converts each instant to UTC before comparing. It intentionally does not resolve phrases such as “next Friday,” validate pagination cursors, or check calendar access. Those are different layers of the tool contract.

from datetime import datetime, timezone

def overlaps(event_start, event_end, query_start, query_end):
    values = [event_start, event_end, query_start, query_end]
    if any(value.utcoffset() is None for value in values):
        raise ValueError("all instants must have a timezone")
    e0, e1, q0, q1 = [value.astimezone(timezone.utc) for value in values]
    if e1 <= e0 or q1 <= q0:
        raise ValueError("intervals must have positive duration")
    return e0 < q1 and e1 > q0

parse = datetime.fromisoformat
query_start = parse("2026-10-02T09:00:00+07:00")
query_end = parse("2026-10-02T12:00:00+07:00")
print(overlaps(parse("2026-10-02T08:30:00+07:00"),
               parse("2026-10-02T09:30:00+07:00"),
               query_start, query_end))

The output is True. An event ending at 09:00 returns false because it shares no included instant with the query. One beginning at 12:00 also returns false. An event from 11:30 to 12:30 matches this query and an adjacent noon-to-15:00 query. Seeing it twice across two separate overlapping searches is consistent with the chosen rule, not evidence that the exclusive end failed.

Five calendar intervals show included overlaps, excluded touching boundaries, and one event that legitimately matches two adjacent windowsView full-size image ↗

UTC conversion avoids another subtle problem. The Python datetime documentation, retrieved September 21, 2026, explains that comparisons with the same timezone object ignore fold. During a repeated local hour, the same clock reading can denote two different instants. For example, construct 01:15 with fold=0 and 01:45 with fold=1 in America/New_York on November 1, 2026: after UTC conversion, the interval lasts 90 minutes. A query for 01:30 to 01:40 with fold=1 overlaps it. Tests must reason about those instants, not just compare displayed clock strings.

The helper accepts already resolved datetimes. It does not detect nonexistent local times created during a spring transition, choose a fold for ambiguous user input, or verify that an explicit offset agrees with a separately supplied IANA timezone. The caller must resolve or reject those conditions before execution. That separation keeps a short overlap predicate from being mistaken for a complete calendar parser.

Ken Aizawa’s September 11, 2025 tool-design article recommends making the context implicit in an engineer’s understanding explicit in tool descriptions. For this calendar, that includes overlap versus start-based matching, all-day behavior and the meaning of an unavailable result. Evaluate alternative descriptions using realistic user requests; the arithmetic fixtures establish our chosen semantics, not which wording a model interprets most reliably.

Separate schema checks from authorization

The OpenAI function calling guide, retrieved on September 20, 2026, describes defining tool arguments with schemas. Use enumerations where the choices are closed, require necessary fields, and reject unsupported extra properties if the integration supports that contract.

Then add application checks. The end must be after the start, the interval must stay within an allowed search horizon, and the limit must be within a bounded range. The requested calendar must belong to the authenticated user’s accessible set. A syntactically valid calendar identifier does not grant access to it.

Do not accept an arbitrary user_id merely because the model supplied one. Derive the principal from the authenticated session. If an administrator can search another user’s calendar, represent that capability explicitly and check it at execution time. The model should not be able to widen its access by changing an argument.

Describe errors as part of the tool

The executor can return invalid_interval, calendar_not_accessible, ambiguous_timezone, or a transient provider failure. These states suggest different next actions. An invalid interval may be repairable from the user’s request. An inaccessible calendar should not trigger repeated guessing of other identifiers.

Avoid exposing private calendar names or internal provider errors in a denial response. Give the model enough information to explain the failure without revealing records the user cannot access. The tool’s error contract is part of the same privacy boundary as its successful result.

The MCP tools specification, revision 2025-06-18, describes tool metadata, input schemas, and invocation results. It standardizes the exchange, while the application still owns the calendar semantics and access policy. State the protocol revision when implementing against it because protocol details can evolve.

Keep pagination tied to the query

A result limit protects the application from unbounded output. If more events exist, return a cursor that continues the same search. Bind the cursor to the relevant calendar, interval, ordering, and authorization scope. Otherwise, a model could accidentally reuse a cursor after changing the date range and receive confusing results.

Specify a deterministic order, such as event start followed by a stable event identifier. If calendars change during pagination, describe whether the search is a live traversal or a snapshot. The tool should not promise a complete stable result set when its provider offers only changing pages.

Return source identifiers and timestamps that the assistant can cite or display. If the user asks to reschedule an event after reading the results, the write operation should be a separate tool with its own validation and approval requirements. A search result should not silently turn into authority to modify every event it contains.

Test interpretation, not just serialization

Use fixtures for a meeting inside the interval, one ending exactly at the start, one beginning exactly at the end, and one overlapping the start. Add an all-day event and a timezone transition if the supported regions require it. Check both the tool’s returned set and the explanation the model gives the user.

Also test a request with no timezone context. The assistant should ask a necessary clarification or use a documented user preference, not invent a location. A good tool description reduces ambiguity, and the executor catches the ambiguity that remains. Together they let a valid call mean one reviewable operation.

Sources & further reading

  1. OpenAI function calling guide
  2. MCP tools specification, 2025-06-18
  3. Ken Aizawa: Writing effective tools for agents — with agents
  4. Python datetime documentation
← Back to the journal
All notes

Illustration

100%