Skip to main content
Glama
edlovesjava

mcp-api-bridge

by edlovesjava

mcp-api-bridge

An MCP server that puts your existing in-house catalog search APIs in front of any MCP client, with Amazon Bedrock doing the natural-language → structured query translation.

Two ideas carry the whole thing:

  1. Catalogs are configuration, not code. A YAML file describes each API's URL, auth, request shape, and response shape. Adding a catalog means adding a config block.

  2. The filter vocabulary is the prompt. The description, type, and values you write for each filter are handed to Bedrock verbatim as the vocabulary it maps language onto. Describe a filter well and query understanding works for it — there is no separate prompt to maintain.

MCP client ──▶ mcp-api-bridge ──▶ Bedrock (Claude)   plan the query
                     │
                     └──────────▶ your catalog REST API   execute the search

Tools exposed

Tool

What it does

list_catalogs

Describes every configured catalog: its filters, allowed values, and sorts. Call this first to learn the vocabulary.

catalog_search

Runs a search with explicit filters / sort / paging. No model in the loop.

catalog_get_item

Fetches one item by id, for catalogs that configure a get_item endpoint.

understand_query

Bedrock turns a natural-language query into a QueryPlan — keywords, filters, sort, synonyms — without searching.

smart_search

understand_query then catalog_search, returning both the plan and the results.

catalog_search is deliberately usable on its own: when the client is already an LLM that knows the filter vocabulary from list_catalogs, the Bedrock hop is redundant latency. Reach for smart_search when a raw end-user string needs interpreting.

Related MCP server: Amplify Data API MCP Server

Quick start

uv venv && uv pip install -e ".[dev]"

cp config/catalog.example.yaml config/catalog.yaml   # then edit
cp .env.example .env                                 # then fill in

export MCP_API_BRIDGE_CONFIG=./config/catalog.yaml
export CATALOG_TOKEN=...        # whatever your config's auth blocks name
export AWS_REGION=us-east-1     # plus standard AWS credentials

.venv/bin/mcp-api-bridge        # speaks MCP over stdio

Register it with an MCP client:

{
  "mcpServers": {
    "catalog": {
      "command": "/path/to/mcp-api-bridge/.venv/bin/mcp-api-bridge",
      "env": {
        "MCP_API_BRIDGE_CONFIG": "/path/to/config/catalog.yaml",
        "CATALOG_TOKEN": "...",
        "AWS_REGION": "us-east-1"
      }
    }
  }
}

Importing from OpenAPI

Hand-writing config stops being viable fast: the bundled catalog-search-service-api.json declares 49 query parameters on one operation across three sibling endpoints. import-openapi reads the spec and emits config, so the wire contract comes from the API team's own document.

# See what's in the spec
mcp-api-bridge import-openapi catalog-search-service-api.json --list

# Generate, curating the filters that reach the model
mcp-api-bridge import-openapi catalog-search-service-api.json \
  --operation productions=searchProductions \
  --include productions=startDate,endDate,city,stateCode,months,minListingPriceFloor \
  --operation performers=getPerformers \
  --include performers=activeFilter,minProductionCount \
  -o config/catalog.yaml

config/vividseats.example.yaml was scaffolded this way, then hand-edited for the parts below that no spec can supply.

It takes from the spec: base URL, method, path, parameter names, types, enums, descriptions, paging defaults, the response envelope, and the auth scheme. It strips HTML out of descriptions first — 81 of the 150 parameter descriptions in that spec contain <br/> or <b>, and those strings become the model's vocabulary.

It cannot take the semantic roles, because OpenAPI does not carry them: which parameter is the free-text query, which response field is the title, when each sort applies. Those are guessed by name and every guess is reported — to stderr as it runs, and as a comment block at the top of the generated file:

note: roles matched by name: query=query, page=page, page_size=pageSize, sortBy=sort — verify
note: first_page=1 taken from the 'page' default
TODO: base_url ...vividseats-staging.com looks non-production — confirm before deploying
TODO: sort descriptions are blank — the spec has only raw enum values

--include matters more than it looks. Every filter enters the query-understanding prompt, so importing all 45 makes the prompt expensive and gives the model a wide surface to invent against. The importer nags when an uncurated import exceeds 15 filters.

One thing the spec cannot fix. That spec declares no securitySchemes at all, so the importer writes auth: none; if a gateway fronts the API, that is invisible here. (The opaque-id problem it also surfaces is handled — see the next section.)

The importer needs no dependencies beyond PyYAML — $refs are resolved on demand with cycle guards, because real specs are cyclic (Production → Venue → Production).

Opaque ids: names in, ids out

The best filters on a real catalog are keyed by ids — regionId, performerId, venueId, categoryId. No model turns "Taylor Swift in Chicago" into performerId=9134&regionId=5 from a description, so a filter like that is dead weight in the vocabulary. Declaring how a filter's values resolve brings it back to life. Two strategies, because there are two shapes of id:

lookup — closed sets (regions, categories). The bridge fetches the table once, publishes the names as the filter's vocabulary, and translates back to the id on the way out. This is not an optimization: GET /v1/regions filters by IP and lat/long only, so holding the list is the only way to match "Chicago".

region:
  param: regionId
  type: integer
  description: Metro area the event's venue sits in.
  lookup:
    path: /v1/regions
    id_field: id
    name_field: name
    alias_fields: [listName]     # accept "Chicago, IL" too

resolve — open sets (performers, venues). No table can be preloaded, so the model supplies the name it read and the bridge searches a sibling catalog for it. Pointing this at the Algolia-backed search service is deliberate — it handles misspellings and partial names, which is exactly what resolution needs.

performer:
  param: performerId
  type: integer
  description: Artist, team, or touring show.
  resolve:
    api: performers              # another configured catalog

Either way the caller passes a name and gets told what it became:

"resolutions": {
  "performer": {"name": "Taylor Swift", "id": "9134"},
  "region":    {"name": "Chicago",      "id": "5"}
}

That readback matters because resolution is a guess where names collide — "Chicago" is a city, a band, and a musical. list_catalogs marks these filters accepts_name, listing allowed_values for closed sets and leaving it null for open ones. An id passed directly still works and skips the lookup entirely; an unrecognised name gets a did-you-mean.

config/vividseats.example.yaml wires both services together this way: catalog-service for authoritative data and the reference tables, catalog-search-service for name resolution.

Configuring a catalog

config/catalog.example.yaml is a commented walkthrough of both common shapes: a GET search API with a nested response envelope, and a POST search API with body filters and zero-indexed paging. Generated config uses the same format, so anything below applies to imported catalogs too. The pieces:

Request templating. Any value under query: or body: may contain {query}, {page}, {page_size}, or {sort}. A value that is exactly one placeholder keeps its type (size: "{page_size}" sends an integer). A placeholder with nothing to fill it drops the whole parameter — that is how optional params disappear when the caller omits them. Values with no placeholder are sent as-is, which covers static params like channel: web.

Response mapping. items_path and total_path locate the result list and the count inside whatever envelope the API uses. Paths are dotted with optional indices — data.items, media[0].url, variants[*].sku. fields maps the five normalized fields (id, title, description, url, image); attributes carries anything else through untouched. With no fields mapping at all, the raw upstream item passes through as attributes.

Auth. none, bearer, api_key (header or query param), or basic. Every variant names an environment variable; no credential is ever read from the config file.

Paging. first_page: 0 for APIs that page from zero. Callers always pass 1-based page; the bridge translates.

The Bedrock layer

understand_query constrains Claude to the QueryPlan schema via structured outputs, so the response is always parseable — no regex over prose, no retry loop for malformed JSON. The plan carries:

  • keywords — search terms with filter-like phrases removed, so the keyword match is not fighting the filters

  • filters — only names the catalog actually exposes

  • sort — only sorts the catalog actually exposes

  • expansions — domain synonyms to retry with if results are thin

  • intent and ambiguities — for the trace, and for a client deciding whether to ask a clarifying question

Filters and sorts the model invents anyway are dropped before they reach the catalog, so a hallucination degrades to a slightly broader search rather than a failed request.

Defaults are anthropic.claude-opus-5 at effort: low — query understanding is a short hop in front of the real work, and low effort keeps it cheap without changing the answers on typical queries. Override per deployment:

bedrock:
  model_id: anthropic.claude-opus-5
  effort: medium
  guidance: |
    Domain shorthand the model should know about.

or with $BEDROCK_MODEL_ID / $BEDROCK_EFFORT / $BEDROCK_REGION.

Bedrock is only ever on the understand_query and smart_search paths. If it is unreachable, catalog_search, catalog_get_item, and list_catalogs keep working — and smart_search says so in its error rather than failing silently.

Tests

.venv/bin/pytest

The suite mocks the catalog HTTP layer with respx and stubs the Bedrock client, so it runs with no network and no AWS credentials. The OpenAPI tests run against the real catalog-search-service-api.json rather than a tidy fixture — its cyclic $refs, HTML descriptions, 3.1 type unions, and missing security scheme each broke a first draft of the importer.

Available Tools

5 tools
catalog_get_itemA

Fetch a single catalog item by its id.

Args: item_id: The catalog's own identifier, as returned in a search result's id field. api: Which catalog to read from. Optional when only one is configured.

ParametersJSON Schema
NameRequiredDescriptionDefault
apiNo
item_idYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.2/5.0
Behavior3/5

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

There are no annotations, so the description carries the behavioral burden. 'Fetch a single catalog item' conveys a read-only, non-destructive operation. However, it does not disclose behavior for missing/unknown ids, whether api is required when multiple catalogs exist, or any auth requirements.

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 tight and effective: one clear purpose sentence plus two parameter bullets with no filler. The most important information is front-loaded, and every line earns its place.

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

Completeness4/5

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

For a simple two-parameter fetch with an output schema available, the description covers both parameters and the main usage path. The only notable gap is the lack of explicit behavior when api is omitted while multiple catalogs are configured.

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?

Schema description coverage is 0%, but the description documents both parameters meaningfully: item_id is tied to a search result's `id` field, and api selects the catalog with an optionality condition. This adds real value beyond the bare schema types.

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 opens with a specific verb and resource: 'Fetch a single catalog item by its id.' It clearly distinguishes itself from sibling search tools by targeting a single item via the catalog's own identifier rather than a query.

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 gives clear context: item_id is the identifier returned in a search result's `id` field, which implies this tool is used after catalog_search or smart_search. It does not explicitly name alternatives or state when not to use it, so it falls just short of a 5.

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

list_catalogsA

List the configured catalogs and the vocabulary each one accepts.

Returns every catalog's filter names, types, allowed values, and sort options. Call this before catalog_search so you filter with names the catalog actually exposes.

Filters marked accepts_name are keyed by an opaque id upstream but take a human name here — pass "Chicago", not a region id. Where the value set is closed it is listed in allowed_values; where it is open (performers, venues) any name is accepted and resolved on the way through.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
catalogsYes
default_catalogNoThe catalog used when a tool's `api` argument is omitted.

TDQS

A4.7/5.0
Behavior4/5

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

With no annotations, the description carries the full burden of behavioral disclosure. It explains the accepts_name nuance (opaque ids upstream vs human names here) and distinguishes closed vs open value sets, which is valuable beyond a simple listing. It doesn't cover potential error conditions or pagination, but for a read-only list tool this is adequate.

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 concise and logically structured: it states the core purpose, adds a usage directive, and then explains key data semantics. Every sentence earns its place, and the critical usage instruction is front-loaded before the detailed nuances.

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

Completeness5/5

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

Given there are no parameters and an output schema exists, the description provides sufficient context for correct usage: it tells when to use it, what it returns, and how to interpret the returned data. It doesn't need to explain return structure since the output schema covers that, and it fully prepares the agent to interact with catalog_search.

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 tool has zero parameters, so the baseline per guidelines is 4. The description adds no parameter-related meaning because there are none, and the schema coverage is complete (empty properties). No deduction is needed.

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 lists configured catalogs and the vocabulary each accepts, including specific details like filter names, types, allowed values, and sort options. It explicitly references the sibling catalog_search, distinguishing itself as a precursor rather than a search or retrieval tool.

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

Usage Guidelines5/5

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

The description gives explicit guidance: 'Call this before `catalog_search`' and explains why, so an agent knows exactly when to invoke it. It also clarifies how to interpret results for filtering, which is actionable and context-specific.

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

understand_queryA

Turn a natural-language query into a structured search plan, without searching.

Uses Bedrock to split a shopper's phrasing into keywords, structured filters, a sort, and synonyms — restricted to the vocabulary the target catalog actually exposes. Use this when you want to inspect or adjust the plan before running it; use smart_search to do both at once.

Args: query: The user's request, in their own words. api: Which catalog's vocabulary to plan against. Optional when only one is configured.

ParametersJSON Schema
NameRequiredDescriptionDefault
apiNo
queryYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
apiYes
planYes
model_idYes
original_queryYes

TDQS

A4.7/5.0
Behavior4/5

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

With no annotations, the description carries the behavioral burden itself. It discloses that the tool does not search, that it uses Bedrock, and that it restricts output to the target catalog's exposed vocabulary by splitting into keywords, filters, sort, and synonyms. It does not mention potential failure modes or permissions, but for a planning-only tool the behavior is transparent enough to set correct expectations.

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 compact and front-loaded: the core purpose and key non-behavior ('without searching') appear in the first sentence. The usage note, behavior summary, and parameter explanations each add distinct value with no redundant filler.

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

Completeness4/5

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

For a two-parameter planning tool with an output schema, the description covers purpose, usage, behavior, and parameters sufficiently. It could add how to know valid `api` values when multiple catalogs are configured, but the sibling `list_catalogs` likely covers that, and the description already notes the optional case.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must define both parameters itself. It does so clearly: `query` is 'The user's request, in their own words,' and `api` is 'Which catalog's vocabulary to plan against. Optional when only one is configured.' This adds real meaning beyond the bare schema types and even explains the optionality condition.

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 opens with a specific verb-plus-resource statement: 'Turn a natural-language query into a structured search plan, without searching.' It also distinguishes itself from the sibling `smart_search` by noting the split between planning and executing, so an agent can clearly tell when this tool is the right one.

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

Usage Guidelines5/5

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

Explicit usage guidance is provided: 'Use this when you want to inspect or adjust the plan before running it; use `smart_search` to do both at once.' This names the alternative and gives the conditional that selects this tool over it, leaving no ambiguity about when to call it.

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

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.1.0
    • First observedcatalog_get_item
    • First observedcatalog_search
    • First observedlist_catalogs
    • First observedsmart_search
    • First observedunderstand_query

TDQS

A4.4/5.0

Scored across 5 tools

Disambiguation5/5

Each tool has a clearly distinct role: list_catalogs exposes metadata, catalog_search executes structured searches, catalog_get_item fetches a single item, understand_query only plans from natural language, and smart_search both plans and searches. The descriptions explicitly contrast the NL tools and the structured-search tool, eliminating ambiguity.

Naming Consistency3/5

Tool names mix conventions: list_catalogs and understand_query are verb-first, while catalog_search and catalog_get_item are noun-first, and smart_search is an adjective-noun compound. The inconsistency is noticeable but not chaotic, with a 'catalog_' prefix for search/get actions providing some order.

Tool Count5/5

Five tools is a well-scoped size for a catalog search bridge: metadata discovery, structured search, item retrieval, NL interpretation, and NL search. Each tool earns its place with no redundant coverage, making the set feel appropriately lean.

Completeness5/5

The surface covers the full query lifecycle: discover catalogs, run structured searches, retrieve individual items, and process natural language either as a plan or as a plan-plus-search. As a read-only bridge, missing create/update/delete operations are not gaps; no dead ends remain for the stated purpose.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    This MCP server enables users to interact with AWS Amplify Gen2 application data through natural language, allowing AI assistants like Claude to perform operations on Amplify data models using conversational language instead of complex code.
    4
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    An MCP server that enables large language models to directly access and analyze Amazon product information, including product details, variants, and reviews.
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    An MCP server that enables natural language interaction with Google's Discovery Engine API, allowing users to search, recommend, and manage data through conversational interfaces.
    -