Skip to main content
Glama

SFomo: Bay Area events

Create a custom calendar

create_custom_calendar
Read-onlyIdempotent

Build a subscribable calendar of upcoming Bay Area events from the user's interests. Returns a link that opens the calendar on SF Events (open_on_sf_events; give the user this first), subscribe links (Google Calendar, Apple/Outlook webcal, plain .ics) and a preview. The calendar refreshes itself every few hours, so new matching events keep appearing. An event is included if it matches ANY interest and no exclude term. Nothing is stored by SF Events: the interests are encoded in the link itself. Interview the user first (see the custom_calendar prompt), check matches with search_events, and call this again to adjust. For a one-off answer rather than a standing calendar, use search_events. No sign-in; each client gets 30 calls a minute and 2,500 event rows an hour.

Input Schema

NameRequiredDescriptionDefault
nameNoCalendar name shown in their calendar app, e.g. 'Salsa + AI nights'
excludeNoWords; any event containing one is left out
interestsYes1-12 interests; each needs a query, category or topic
include_kidsNoInclude kids'/teens'-only events (default false)
include_seniorsNoInclude events only for older adults (senior-center programs, 55+ groups; default false)

Output Schema

NameRequiredDescriptionDefault
nameNo
sampleNo
subscribeYesSubscribe links: Google Calendar, Apple/Outlook webcal, plain .ics
place_noteNo
attributionNo
how_it_worksNo
total_upcomingNo
events_next_7_daysNo
events_next_30_daysNo

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • addedOutput schema / properties / sample / items / properties / venue_status
      Added value: +{
      +  "description": "Present only when location is empty, and says why (we never guess a venue): rsvp = the address is shared after RSVP/registration; tba = the venue isn't decided; hidden = the source shows the venue to its members only (Meetup stubs; /mcp leaves these out, so you won't see it here); title = the title names the place; text = the description names a place; unset = the host left it blank. Open the link for the venue",
      +  "enum": [
      +    "rsvp",
      +    "tba",
      +    "hidden",
      +    "title",
      +    "text",
      +    "unset"
      +  ],
      +  "type": "string"
      +}
  2. Changed1 schema field changed
    • changedInput schema / properties / interests / items / properties / topic / enum
      Previous value: -[
      -  "arts",
      -  "books",
      -  "civic",
      -  "classes",
      -  "comedy",
      -  "community",
      -  "conferences",
      -  "culture",
      -  "faith",
      -  "family",
      -  "film",
      -  "food_drink",
      -  "games",
      -  "markets",
      -  "networking",
      -  "nightlife",
      -  "nonprofit",
      -  "outdoors",
      -  "science",
      -  "sports",
      -  "startup",
      -  "talks",
      -  "theater",
      -  "wellness",
      -  "wine",
      -  "business",
      -  "ai",
      -  "tech"
      -]New value: +[
      +  "arts",
      +  "books",
      +  "civic",
      +  "classes",
      +  "comedy",
      +  "community",
      +  "conferences",
      +  "culture",
      +  "faith",
      +  "family",
      +  "film",
      +  "food_drink",
      +  "games",
      +  "markets",
      +  "networking",
      +  "nightlife",
      +  "nonprofit",
      +  "outdoors",
      +  "science",
      +  "sports",
      +  "startup",
      +  "talks",
      +  "theater",
      +  "wellness",
      +  "wine",
      +  "business",
      +  "ai",
      +  "tech",
      +  "service_orgs"
      +]
  3. Changed4 schema fields changed
    • changedInput schema / properties / interests / items / properties / topic / enum
      Previous value: -[
      -  "arts",
      -  "books",
      -  "civic",
      -  "classes",
      -  "comedy",
      -  "community",
      -  "conferences",
      -  "culture",
      -  "faith",
      -  "family",
      -  "film",
      -  "food_drink",
      -  "games",
      -  "markets",
      -  "networking",
      -  "nightlife",
      -  "nonprofit",
      -  "outdoors",
      -  "science",
      -  "sports",
      -  "startup",
      -  "talks",
      -  "wellness",
      -  "wine",
      -  "business"
      -]New value: +[
      +  "arts",
      +  "books",
      +  "civic",
      +  "classes",
      +  "comedy",
      +  "community",
      +  "conferences",
      +  "culture",
      +  "faith",
      +  "family",
      +  "film",
      +  "food_drink",
      +  "games",
      +  "markets",
      +  "networking",
      +  "nightlife",
      +  "nonprofit",
      +  "outdoors",
      +  "science",
      +  "sports",
      +  "startup",
      +  "talks",
      +  "theater",
      +  "wellness",
      +  "wine",
      +  "business",
      +  "ai",
      +  "tech"
      +]
    • addedOutput schema / properties / sample / items / properties / changed
      Added value: +{
      +  "description": "When we last saw the title, date or time, venue, link, price or status change (local ISO time); the first-seen day for older events",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / sample / items / properties / event_status
      Added value: +{
      +  "description": "What a source says: postponed (no new date yet; listed on its original date), rescheduled (this is the new date), moved (to another venue; location is the new one), sold_out. Absent = scheduled as listed; cancelled events are left out",
      +  "enum": [
      +    "sold_out",
      +    "postponed",
      +    "rescheduled",
      +    "moved"
      +  ],
      +  "type": "string"
      +}
    • addedOutput schema / properties / sample / items / properties / status_note
      Added value: +{
      +  "description": "The source's own wording of event_status, e.g. \"Moved to The Warfield\"",
      +  "type": "string"
      +}
  4. Changed1 schema field changed
    • addedOutput schema / properties / sample / items / properties / checked_live
      Added value: +{
      +  "description": "When we last opened the original listing and found it still matches (time, venue, not cancelled); local ISO time. Absent = not checked in the last two days",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
  5. Changed3 schema fields changed
    • addedInput schema / properties / include_seniors
      Added value: +{
      +  "description": "Include events only for older adults (senior-center programs, 55+ groups; default false)",
      +  "type": "boolean"
      +}
    • changedInput schema / properties / interests / items / properties / topic / enum
      Previous value: -[
      -  "arts",
      -  "books",
      -  "civic",
      -  "classes",
      -  "comedy",
      -  "community",
      -  "conferences",
      -  "culture",
      -  "faith",
      -  "family",
      -  "film",
      -  "food_drink",
      -  "games",
      -  "markets",
      -  "networking",
      -  "nightlife",
      -  "nonprofit",
      -  "outdoors",
      -  "science",
      -  "sports",
      -  "startup",
      -  "talks",
      -  "wellness",
      -  "wine"
      -]New value: +[
      +  "arts",
      +  "books",
      +  "civic",
      +  "classes",
      +  "comedy",
      +  "community",
      +  "conferences",
      +  "culture",
      +  "faith",
      +  "family",
      +  "film",
      +  "food_drink",
      +  "games",
      +  "markets",
      +  "networking",
      +  "nightlife",
      +  "nonprofit",
      +  "outdoors",
      +  "science",
      +  "sports",
      +  "startup",
      +  "talks",
      +  "wellness",
      +  "wine",
      +  "business"
      +]
    • changedOutput schema / (root)
      Previous value: -nullNew value: +{
      +  "properties": {
      +    "attribution": {
      +      "type": "string"
      +    },
      +    "events_next_30_days": {
      +      "type": "number"
      +    },
      +    "events_next_7_days": {
      +      "type": "number"
      +    },
      +    "how_it_works": {
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "name": {
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "place_note": {
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "sample": {
      +      "items": {
      +        "properties": {
      +          "audience": {
      +            "description": "kids, teens or seniors when the event is only for them",
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "biz_formats": {
      +            "items": {
      +              "type": "string"
      +            },
      +            "type": "array"
      +          },
      +          "categories": {
      +            "items": {
      +              "type": "string"
      +            },
      +            "type": "array"
      +          },
      +          "city": {
      +            "description": "Town or city within the metro",
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "date": {
      +            "description": "Local start date, YYYY-MM-DD",
      +            "type": "string"
      +          },
      +          "distance_km": {
      +            "description": "With near: distance from the place",
      +            "type": "number"
      +          },
      +          "end": {
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "first_seen": {
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "free": {
      +            "type": "boolean"
      +          },
      +          "id": {
      +            "description": "Stable 12-character id; pass it to get_event",
      +            "type": "string"
      +          },
      +          "last_fetched": {
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "last_seen": {
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "link": {
      +            "description": "The original listing; keep it and credit What's Up In",
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "location": {
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "neighborhood": {
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "performers": {
      +            "items": {
      +              "type": "string"
      +            },
      +            "type": "array"
      +          },
      +          "possibly_free": {
      +            "type": "boolean"
      +          },
      +          "posted_by_sources": {
      +            "type": "number"
      +          },
      +          "price": {
      +            "description": "All-in price as the source states it, \"Free\", or \"See tickets\" when the source doesn't state the all-in total",
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "recurring": {
      +            "description": "weekly, biweekly or monthly when the event is one date of a series",
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "region": {
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "rv": {
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "sectors": {
      +            "items": {
      +              "type": "string"
      +            },
      +            "type": "array"
      +          },
      +          "sold_out": {
      +            "type": "boolean"
      +          },
      +          "sources": {
      +            "items": {
      +              "type": "string"
      +            },
      +            "type": "array"
      +          },
      +          "start": {
      +            "description": "Local start, ISO 8601, when the source gives a time",
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "title": {
      +            "type": "string"
      +          },
      +          "topics": {
      +            "items": {
      +              "type": "string"
      +            },
      +            "type": "array"
      +          }
      +        },
      +        "required": [
      +          "id",
      +          "title",
      +          "date"
      +        ],
      +        "type": "object"
      +      },
      +      "type": "array"
      +    },
      +    "subscribe": {
      +      "description": "Subscribe links: Google Calendar, Apple/Outlook webcal, plain .ics",
      +      "type": "object"
      +    },
      +    "total_upcoming": {
      +      "type": "number"
      +    }
      +  },
      +  "required": [
      +    "subscribe"
      +  ],
      +  "type": "object"
      +}
  6. Changed3 schema fields changed
    • addedInput schema / properties / interests / items / properties / near
      Added value: +{
      +  "description": "Events within radius_km of a place: a neighborhood, a town, a venue name, or \"lat,lon\". Results get distance_km, nearest first within each day. Use this for \"near X\"; use neighborhood for \"in X\". Unmapped venues are left out (counted in place.unplaced_events).",
      +  "type": "string"
      +}
    • addedInput schema / properties / interests / items / properties / neighborhood
      Added value: +{
      +  "description": "Only events in this San Francisco neighborhood (or town): Bayview, Bernal Heights, Castro, Chinatown, Excelsior, FiDi / South Beach, Glen Park, Golden Gate Park, Haight, Hayes Valley, Ingleside / Oceanview, Inner Richmond, Inner Sunset, Japantown, Lakeshore, Lincoln Park, Lone Mountain / USF, Marina, McLaren Park, Mission, Mission Bay, Nob Hill, Noe Valley, North Beach, Outer Mission, Outer Richmond, Pacific Heights, Portola, Potrero Hill, Presidio, Presidio Heights, Russian Hill, Seacliff, SoMa, Sunset / Parkside, Tenderloin, Treasure Island, Twin Peaks, Visitacion Valley, West of Twin Peaks, Western Addition. Sub-neighborhood and common names work too. Add radius_km to also take events within that distance of its centre. Events whose venue isn't mapped can't be placed and are left out; the response's place.unplaced_events counts them.",
      +  "type": "string"
      +}
    • addedInput schema / properties / interests / items / properties / radius_km
      Added value: +{
      +  "description": "Distance for near (default 1.5, max 50), or how far neighborhood reaches past its boundary (default 0)",
      +  "type": "number"
      +}
  7. Changed3 schema fields changed
    • changedInput schema / properties / interests / items / properties / city / description
      Previous value: -"One city outside SF, e.g. 'Oakland'"New value: +"One city outside SF (case-insensitive)"
    • changedInput schema / properties / interests / items / properties / times / description
      Previous value: -"Start times, Pacific: morning 5-12, afternoon 12-17, evening 17-21, late 21+; allday = no set time (fairs, exhibits). Default all"New value: +"Start times, local: morning 5-12, afternoon 12-17, evening 17-21, late 21+; allday = no set time (fairs, exhibits). Default all"
    • changedInput schema / properties / interests / items / properties / topic / enum
      Previous value: -[
      -  "arts",
      -  "books",
      -  "civic",
      -  "classes",
      -  "comedy",
      -  "community",
      -  "conferences",
      -  "culture",
      -  "faith",
      -  "family",
      -  "film",
      -  "food_drink",
      -  "games",
      -  "markets",
      -  "networking",
      -  "nightlife",
      -  "nonprofit",
      -  "outdoors",
      -  "science",
      -  "sports",
      -  "startup",
      -  "talks",
      -  "wellness"
      -]New value: +[
      +  "arts",
      +  "books",
      +  "civic",
      +  "classes",
      +  "comedy",
      +  "community",
      +  "conferences",
      +  "culture",
      +  "faith",
      +  "family",
      +  "film",
      +  "food_drink",
      +  "games",
      +  "markets",
      +  "networking",
      +  "nightlife",
      +  "nonprofit",
      +  "outdoors",
      +  "science",
      +  "sports",
      +  "startup",
      +  "talks",
      +  "wellness",
      +  "wine"
      +]
  8. Added

TDQS

A4.9/5.0
Behavior5/5

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

Adds substantial behavior beyond annotations: returns open_on_sf_events, subscribe links, and a preview; refreshes every few hours; includes events matching ANY interest and no exclude term; and stores nothing because interests are encoded in the link. This also clarifies why readOnlyHint=true is consistent despite the create-style name.

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?

The description is dense but front-loaded, starting with what the tool builds and what it returns. Every sentence adds operational value: workflow, matching logic, storage behavior, alternatives, and rate limits.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the annotations, complete schema coverage, and an output schema, the description is complete for correct invocation. It supplies the workflow and behavioral context an agent needs without redundantly explaining return shapes.

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%, so the schema already carries detailed parameter meaning. The description adds useful cross-parameter semantics: interests combine with ANY logic and exclude terms act as a veto, which is not fully captured by the schema.

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?

States a specific verb and resource: build a subscribable calendar of upcoming Bay Area events from the user's interests. It clearly distinguishes this from search_events by contrasting a standing calendar with a one-off answer.

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 tells the agent to interview the user first, check matches with search_events, and call this again to adjust. It also names the alternative for one-off answers and states auth/rate-limit constraints, leaving little to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources