Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.sedata-ai.tech/llms.txt

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

Most tools that accept user prose are good candidates for safetyCheck. This example shows the wrapper in isolation — same shape as the weather server’s summarizer tool.

summarizer.ts

import { z } from 'zod'
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { instrumentServer, safetyCheck } from '@sedata-ai/mcp'
import type { TelemetryConfig } from '@sedata-ai/mcp'

const NAME = 'summarizer-mcp'
const VERSION = '0.1.0'

const server = new McpServer({ name: NAME, version: VERSION })

const telemetryConfig: TelemetryConfig = {
  serverName: NAME,
  serverVersion: VERSION,
  exporterEndpoint: 'https://otel.sedata-ai.tech/v1',
  exporterAuth: { type: 'bearer', token: process.env.SEDATA_TOKEN! },
}

const telemetry = instrumentServer(server, telemetryConfig)

server.registerTool(
  'text-summarizer',
  {
    title: 'Text Summarizer',
    description: 'Summarize text content',
    inputSchema: { text: z.string() },
    outputSchema: { summary: z.string() },
  },
  safetyCheck(
    async ({ text }) => {
      // toy summarizer — replace with your real implementation
      const summary = text.length > 120 ? text.slice(0, 120) + '...' : text
      return {
        content: [{ type: 'text', text: JSON.stringify({ summary }) }],
        structuredContent: { summary },
      }
    },
    {
      parameterName: 'text',     // which input field to check
      output_screen: true,       // log a 🚨 line if flagged
    },
  ),
)

const stop = async (code = 0) => { await telemetry.shutdown(); process.exit(code) }
process.on('SIGINT', () => stop(0))
process.on('SIGTERM', () => stop(0))

server.connect(new StdioServerTransport())

What happens on a flagged input

If the safety API flags the text parameter:
  1. The wrapper doesn’t call your handler.
  2. It returns a structured blocked response:
    {
      "content": [
        { "type": "text", "text": "{ \"summary\": \"🚫 CONTENT BLOCKED: ...\", \"blocked\": true, \"reason\": \"Content flagged by safety check\", \"timestamp\": \"...\" }" }
      ],
      "structuredContent": { "summary": "🚫 CONTENT BLOCKED: ..." }
    }
    
  3. With output_screen: true, your terminal also prints:
    🚨 Safety Check Alert: Content "..." was flagged as malicious (API latency: 42ms)
    
  4. The active span gets mcp.safety_check.flagged = true plus latency and content attributes.

What happens on a clean input

The wrapper writes safety attributes (flagged: false, latency, success) to the span, then calls your handler with the original params unchanged.

Customizing the blocked response

The blocked response uses structuredContent.summary as the summary key. If your tool’s outputSchema uses a different key, you’ll either:
  • match the wrapper’s expectation (call your output field summary), or
  • fork the wrapper for that tool.
// shape your outputSchema to match
outputSchema: { summary: z.string() }

Performance

The wrapper adds one round-trip to api.sedata-ai.tech per call. Typical latency: tens of milliseconds. The wrapper records this latency on the span as mcp.safety_check.latency_ms so you can monitor it. If the safety API is unreachable, the wrapper fails open — your handler runs and the span attribute mcp.safety_check.success is false. Set up an alert on mcp.safety_check.success = false rate to catch outages.

See also

Safety checks concept

Detailed flow and failure modes.

safetyCheck reference

Full function signature.