Skip to main content
Glama
SoapyRED

FreightUtils MCP Server

adr_lookup

Read-onlyIdempotent

Search the ADR 2025 database for hazardous materials by UN number or substance name to get hazard class, packing group, labels, tunnel codes, and transport categories.

Instructions

Look up dangerous goods (hazmat) information from the ADR 2025 database.

ADR is the European agreement for international carriage of dangerous goods by road. This tool searches 2,939 entries covering all 9 hazard classes.

Use this tool when you need to:

  • Find the hazard class, packing group, and labels for a UN number

  • Search dangerous goods by name (e.g., "petrol", "lithium batteries")

  • Get tunnel restriction codes and transport categories

  • Check limited quantity allowances

Provide a UN number for exact lookup, or a search term for name-based search.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
un_numberNoUN number (e.g., "1203", "UN1203")
searchNoSearch by substance name (min 2 characters)
hazard_classNoFilter by hazard class (e.g., "3" for flammable liquids)

Implementation Reference

  • The handler function for 'adr_lookup' — calls the FreightUtils API endpoint 'adr' with un_number, search term, and optional hazard_class filter.
      handler: async (args) =>
        apiGet('adr', { un: args.un_number, q: args.search, class: args.hazard_class }),
    };
  • Zod schema defining the input parameters: un_number (UN + 4 digits), search (min 2 chars), and hazard_class (optional string).
    schema: z.object({
      un_number: z.string().regex(/^(UN)?\d{4}$/i, 'UN number must be 4 digits, optionally prefixed with "UN" (e.g., "1203" or "UN1203")').optional().describe('UN number (e.g., "1203", "UN1203")'),
      search: z.string().min(2, 'Search term must be at least 2 characters').optional().describe('Search by substance name (min 2 characters)'),
      hazard_class: z.string().optional().describe('Filter by hazard class (e.g., "3" for flammable liquids)'),
    }).strict(),
  • src/tools.ts:717-717 (registration)
    The adrLookup ToolDef is exported in the ALL_TOOLS array, which is iterated over in server.ts to register the tool with the MCP server.
    adrLookup,
  • src/server.ts:19-42 (registration)
    Server-side loop that registers every tool, including adr_lookup, with the MCP SDK server.
    for (const tool of ALL_TOOLS) {
      server.tool(
        tool.name,
        tool.description,
        tool.schema.shape,
        tool.annotations,
        async (args: Record<string, unknown>) => {
          try {
            const result = await tool.handler(args);
            return {
              content: [
                { type: 'text' as const, text: JSON.stringify(result, null, 2) },
              ],
            };
          } catch (err: unknown) {
            const message = err instanceof Error ? err.message : String(err);
            return {
              content: [{ type: 'text' as const, text: `Error: ${message}` }],
              isError: true,
            };
          }
        },
      );
    }
  • API helper function that builds the request URL and calls the FreightUtils API. The adr_lookup handler calls apiGet('adr', ...).
    export async function apiGet(endpoint: string, params: Record<string, unknown>): Promise<unknown> {
      const url = new URL(`${BASE_URL}/${endpoint}`);
      for (const [k, v] of Object.entries(params)) {
        if (v === undefined || v === null || v === '') continue;
        url.searchParams.set(k, String(v));
      }
    
      const res = await fetch(url.toString(), {
        headers: { 'Accept': 'application/json' },
      });
    
      if (!res.ok) {
        const body = await res.text();
        throw new Error(`FreightUtils API error ${res.status}: ${body}`);
      }
    
      return res.json();
    }
    
    export async function apiPost(endpoint: string, body: unknown): Promise<unknown> {
      const res = await fetch(`${BASE_URL}/${endpoint}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
        body: JSON.stringify(body),
      });
    
      if (!res.ok) {
        const text = await res.text();
        throw new Error(`FreightUtils API error ${res.status}: ${text}`);
      }
    
      return res.json();
    }
Behavior3/5

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

Annotations already declare readOnlyHint=true, destructiveHint=false, idempotentHint=true, and openWorldHint=false, which adequately cover the tool's behavioral profile. The description adds minimal extra behavioral context (e.g., 2,939 entries, 9 hazard classes) but does not disclose potential limitations or return format details, so the added value beyond annotations is modest.

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 well-structured and concise: a one-sentence purpose, background on ADR, a bulleted list of use cases, and parameter instructions. Every part serves a purpose with no redundancy.

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

Completeness3/5

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

The description covers the core functionality and parameter usage well, and annotations provide behavioral context. However, it lacks explanation of the output format or return values, which could be critical for an agent to interpret results correctly. This is a gap given there is no output schema.

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?

The input schema has 100% description coverage for all three parameters. The description adds value by explaining the two usage modes: 'Provide a UN number for exact lookup, or a search term for name-based search,' and mentions filtering by hazard class, which aligns with the schema. The added context clarifies how to interact with the parameters.

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?

The description clearly states the tool's purpose: 'Look up dangerous goods (hazmat) information from the ADR 2025 database.' It specifies the resource (ADR database) and verb (look up), and distinguishes it from sibling lookup tools (e.g., airline_lookup, hs_code_lookup) by focusing on dangerous goods.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description lists four specific use cases (e.g., 'Find the hazard class, packing group, and labels for a UN number'), providing clear guidance on when to use the tool. However, it does not explicitly mention when not to use it or point to alternative tools like adr_exemption_calculator or adr_lq_eq_check.

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

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/SoapyRED/freightutils-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server