Cookbook / Agent Weather

Agent Weather

agent

Agent-surface async fetch — prop city and action fetchForecast carry expose/describe so MCP agents can read and invoke them.

id
agent-weather
since
0.5.0
file
agent-weather.aihu
propprop.exposeprop.describestateactionaction.exposeaction.describegroupifelseifelseon:clickinterpolation

Run it

live · runnable

The real, compiled agent-weather.aihu component, hydrated as an island right here — fully interactive.

Source

agent-weather.aihu
governedagent-weather.aihuaihu1.9 kB
// agent-weather — agent-surface pattern: prop city + action fetchForecast with expose/describe

@state {
  let city = prop({
    default: 'London',
    describe: 'City name to retrieve weather forecast for',
    expose: 'read',
  })

  let forecast = state('')
  let loading = state(false)
  let errorMsg = state('')

  const fetchForecast = action(
    { describe: 'Fetch the latest weather forecast for the current city',
      expose: 'read write' },
    async () => {
        loading = true
        errorMsg = ''
        try {
          const res = await fetch(`/api/weather?city=${encodeURIComponent(city)}`)
          if (!res.ok) throw new Error(`HTTP ${res.status}`)
          forecast = await res.text()
        } catch (e) {
          errorMsg = (e as Error).message
        } finally {
          loading = false
        }
      })
}

@template {
  <div class="weather-agent">
    <h2 class="title">Weather — {city}</h2>
    <group if={loading}>
      <p class="loading">Fetching forecast…</p>
    </group><group elseif={errorMsg}>
      <p class="error">{errorMsg}</p>
    </group><group elseif={forecast}>
      <p class="forecast">{forecast}</p>
    </group><group else>
      <p class="prompt">Press Refresh to load forecast.</p>
    </group>
    <button on:click={fetchForecast} class="refresh-btn" disabled={loading}>
      Refresh
    </button>
  </div>
}

@style {
  .weather-agent { padding: 1.5rem; max-width: 22rem; border: 1px solid var(--border, #ccc); border-radius: 6px; }
  .title { margin: 0 0 1rem; font-size: 1.2rem; }
  .loading { color: var(--info, #036); }
  .error { color: var(--error, #c00); }
  .forecast { font-size: 1rem; line-height: 1.5; }
  .prompt { color: var(--muted, #888); font-style: italic; }
  .refresh-btn { margin-top: 0.75rem; padding: 0.4rem 1rem; cursor: pointer; border: 1px solid var(--border, #ccc); border-radius: 4px; }
  .refresh-btn:disabled { opacity: 0.5; cursor: not-allowed; }
}

Requires

concerns
stateeventsgovernance

Anti-patterns

  • Do not mark a mutating action expose: 'read' — the expose tier must match what the action does; fetchForecast writes state, so it is 'read write'.
  • Do not throw from an exposed action for ordinary failures — set an error state the agent can read back.

Related recipes