Skip to main content

Documentation Index

Fetch the complete documentation index at: https://sedataai.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

The auto-instrumentation gives you per-tool spans for free. For everything else — cache lookups, upstream HTTP calls, business outcomes — use the helpers on the ObservabilityInstance returned by instrumentServer.

Setup once, reuse everywhere

Create your instruments once at module scope so you’re not reallocating histograms or counters on the hot path.
instruments.ts
import { instrumentServer } from '@sedata-ai/mcp'

export const telemetry = instrumentServer(server, telemetryConfig)

export const recordCacheLookup = telemetry.getHistogram('cache.lookup.duration', {
  description: 'Cache lookup duration',
  unit: 'ms',
})

export const incCacheOutcome = telemetry.getIncrementCounter('cache.outcome.count', {
  description: 'Cache hit/miss outcome',
})
Then import where you need them:
import { telemetry, recordCacheLookup, incCacheOutcome } from './instruments'

Pattern: time + count an operation

async function fetchUser(id: string) {
  return telemetry.startActiveSpan(
    'user.fetch',
    { 'user.id': id },
    async (span) => {
      const t0 = Date.now()
      try {
        const user = await db.user.findUnique({ where: { id } })
        span.setAttribute('user.found', !!user)
        return user
      } catch (err) {
        span.recordException(err as Error)
        span.setStatus({ code: 2, message: (err as Error).message })
        throw err
      } finally {
        span.setAttribute('user.fetch.duration_ms', Date.now() - t0)
        span.end()
      }
    },
  )
}
The new span becomes a child of the auto-generated tools/call <toolName> span if you call this from inside a tool handler.

Pattern: record a histogram with tags

const t0 = Date.now()
const value = cache.get(key)
recordCacheLookup(Date.now() - t0, {
  'cache.hit': !!value,
  'cache.key.kind': 'user',
})
incCacheOutcome(1, { outcome: value ? 'hit' : 'miss' })

Pattern: business outcomes

Counters are great for outcome tracking even when nothing is timed:
const incPaymentOutcome = telemetry.getIncrementCounter('payment.outcome.count', {
  description: 'Payment outcome',
})

incPaymentOutcome(1, { result: 'authorized', method: 'card' })
incPaymentOutcome(1, { result: 'declined',  method: 'card', code: 'insufficient_funds' })

Pattern: distributed trace context

If a tool calls an upstream HTTP service, propagate the active context so the upstream’s spans hang under your trace:
import { context, propagation } from '@opentelemetry/api'

await telemetry.startActiveSpan(
  'github.repos.get',
  { 'http.method': 'GET', 'http.target': '/repos/x/y' },
  async (span) => {
    const headers: Record<string, string> = {}
    propagation.inject(context.active(), headers)

    const res = await fetch('https://api.github.com/repos/x/y', { headers })
    span.setAttribute('http.status_code', res.status)
    span.end()
  },
)
The upstream service (if it’s also instrumented with OTel) will continue your trace.

Pattern: feature-flag the SDK

If you want to gate instrumentation:
const enabled = process.env.ENABLE_SEDATA === 'true'
const telemetry = enabled
  ? instrumentServer(server, config)
  : noopTelemetry()

function noopTelemetry() {
  const noop = () => {}
  return {
    startActiveSpan: (_n: string, _a: any, fn: any) => fn({ end: noop, setAttribute: noop, setStatus: noop, recordException: noop }),
    getHistogram: () => noop,
    getIncrementCounter: () => noop,
    processTelemetryAttributes: (d: any) => d,
    shutdown: async () => {},
  }
}

Pattern: async iterators

If your tool streams events, record one span per batch + a counter per event:
const incEvent = telemetry.getIncrementCounter('stream.event.count', {
  description: 'Stream event count',
})

for await (const evt of stream) {
  incEvent(1, { type: evt.type })
}
For a span-per-event you’d lose more in overhead than you’d gain in visibility — counters are usually the right shape for high-frequency events.

Pattern: shut down on signal

const stop = async (code = 0) => {
  await telemetry.shutdown()
  process.exit(code)
}

process.on('SIGINT', () => stop(0))
process.on('SIGTERM', () => stop(0))
process.on('uncaughtException', (err) => {
  console.error(err)
  stop(1)
})

See also

Automatic metrics

What the package records without any code.

Data processors

Mutate every attribute set in one place.