# Server event examples

Code to send an event for each call to your service, from curl, Python, Node or an MCP server.

Each sends one call’s event with the token from the `FIVEBAR_TOKEN` environment variable, and gives up after 2 seconds. Send it once your service has answered, never waiting for it. [Server events](https://fiveb.ar/docs/server-events.md) says how to make the token, what each field holds and what each answer means.

## curl

```
curl https://fiveb.ar/api/events \
  -H "Authorization: Bearer $FIVEBAR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"site": "example.com", "name": "/v1/postcodes/:code", "ip": "203.0.113.7"}' \
  --max-time 2 -w '%{http_code}\n'
```

## Python

With the standard library alone, in a thread of its own:

```
import json, os, threading, urllib.request

def send_event(name, **caller):
    req = urllib.request.Request(
        'https://fiveb.ar/api/events',
        data=json.dumps({'site': 'example.com', 'name': name, **caller}).encode(),
        headers={
            'Authorization': 'Bearer ' + os.environ['FIVEBAR_TOKEN'],
            'Content-Type': 'application/json',
        },
    )

    def send():
        try:
            urllib.request.urlopen(req, timeout=2).close()
        except OSError:
            pass

    threading.Thread(target=send).start()

send_event('/v1/postcodes/:code', ip='203.0.113.7')
```

## Node

For Node 18 or later. `sendEvent()` doesn’t wait for fivebar’s answer. With Express, `req.route.path` is the route as written, `req.ip` is the caller’s address only where Express’s `trust proxy` setting names the proxies in front of your service, as a proxy’s own address is refused, `bad ip`, and `lookUp()` stands for your own code:

```
export function sendEvent(name, caller = {}) {
  fetch('https://fiveb.ar/api/events', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + process.env.FIVEBAR_TOKEN,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ site: 'example.com', name, ...caller }),
    signal: AbortSignal.timeout(2000),
  }).catch(() => {});
}

app.get('/v1/postcodes/:code', async (req, res) => {
  res.json(await lookUp(req.params.code));
  sendEvent(req.route.path, { ip: req.ip, user_agent: req.get('User-Agent') });
});
```

## An MCP server

With version 1 of the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk/tree/v1.x), `@modelcontextprotocol/sdk`, and `sendEvent()` above, each `tools/call` is counted once it has its answer: the tool as its name, the session as its caller and the app’s `clientInfo.name` as its source, read as [What is counted](https://fiveb.ar/docs/server-events.md#what-is-counted) says. `server` is the SDK’s `Server`, and `callTool()` your own:

```
import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';

server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
  const answer = await callTool(request.params.name, request.params.arguments);
  sendEvent(request.params.name, {
    id: extra.sessionId,
    client: server.getClientVersion()?.name,
  });
  return answer;
});
```

A session has an id only on a server that keeps sessions, over Streamable HTTP with a `sessionIdGenerator`. A stateless server, or one over stdio, has none, and an event with neither `id` nor `ip` is refused: send another `id` there, such as the account calling, as fivebar’s own MCP server does.

---

fivebar is web analytics without cookies, at https://fiveb.ar. Every page of its docs is listed at https://fiveb.ar/docs/index.md.
