Skip to content

tdfs4ds.agent.mcp_connection

tdfs4ds.agent.mcp_connection

Connection building for the external MCP server.

There are two Teradata MCP products, and tdfs4ds has to talk to either:

  • Community Edition — embeds its own credentials, so tdfs4ds sends none. This is the default and the historical behaviour: with none of the MCP_* auth variables set, the connection dict produced here is byte-identical to the hardcoded {"url": ..., "transport": ...} that preceded this module.
  • Enterprise — requires HTTP Basic auth supplied by the caller, and exposes a different tool set.

The edition is therefore a real distinction (tdfs4ds.MCP_SERVER_EDITION), not merely an auth switch, and it gates whether credentials are sent at all. That gating matters: TDFS4DS_TD_USERNAME/TD_PASSWORD are set in essentially every deployment, so deriving Basic auth from them unconditionally would make every existing install start sending the Teradata password to its already-configured Community endpoint on upgrade — a credential reaching somewhere it does not reach today, opted into by nobody.

This module is the single seam: the consumer agent (_skill_mcp_tools_async) and tdfs4ds.check_configuration both build their connection here, so a diagnostic that passes describes the connection the agent actually makes.

Nothing here logs or returns a credential value — only variable names.

logger module-attribute

logger = getLogger('tdfs4ds')

COMMUNITY module-attribute

COMMUNITY = 'community'

ENTERPRISE module-attribute

ENTERPRISE = 'enterprise'

_EDITIONS module-attribute

_EDITIONS = (COMMUNITY, ENTERPRISE)

AUTH_NONE module-attribute

AUTH_NONE = 'none'

AUTH_BASIC module-attribute

AUTH_BASIC = 'basic'

_AUTH_MODES module-attribute

_AUTH_MODES = (AUTH_NONE, AUTH_BASIC)

_DEFAULT_TIMEOUT_SECONDS module-attribute

_DEFAULT_TIMEOUT_SECONDS = 30

_DEFAULT_SSE_READ_SECONDS module-attribute

_DEFAULT_SSE_READ_SECONDS = 60 * 5

_TOOL_STOP_WORDS module-attribute

_TOOL_STOP_WORDS = frozenset({'a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'can', 'do', 'does', 'for', 'from', 'give', 'has', 'have', 'how', 'i', 'in', 'is', 'it', 'list', 'me', 'my', 'of', 'on', 'or', 'please', 'show', 'that', 'the', 'their', 'them', 'there', 'these', 'this', 'to', 'what', 'when', 'where', 'which', 'who', 'with', 'you', 'your'})

_CONTEXT_WINDOW_MARKERS module-attribute

_CONTEXT_WINDOW_MARKERS = ('contextwindowexceeded', 'context window', 'context length', 'maximum context', 'too many tokens', 'prompt is too long', 'exceed context limit', 'reduce the length of the messages', 'context_length_exceeded')

_SELECTION_PROMPT module-attribute

_SELECTION_PROMPT = 'You are selecting tools, not answering.\n\nBelow is every tool available, one per line, as `name — description`.\nReturn ONLY the names of the tools needed to answer the question, one per line, most important first, at most {limit}. No numbering, no prose, no explanation. If none apply, return nothing.\n\nTOOLS:\n{index}\n\nQUESTION: {question}'

_cfg

_cfg(name, default=None)

mcp_edition

mcp_edition()

Return the configured MCP edition, normalised to 'community' or 'enterprise'.

An unrecognised value falls back to 'community' (the safe side: send no credentials) with a warning naming the accepted values — never silently.

_auth_mode

_auth_mode()

Resolve the auth mechanism: explicit MCP_AUTH wins, else derived from the edition (enterprise ⇒ Basic, community ⇒ none).

_basic_credentials

_basic_credentials()

Return (username, password, user_source, password_source).

Both fall back to the Teradata connection credentials: the Teradata MCP server authenticates with the database identity, so there is nothing to restate. *_source are variable NAMES, for diagnostics.

build_mcp_headers

build_mcp_headers()

Return the HTTP headers for the MCP connection, or None.

None means no headers key at all in the connection dict — the Community Edition path, identical to the behaviour before this module existed.

Resolution order:

  1. MCP_SERVER_HEADERS — used verbatim, for any scheme Basic does not cover (Bearer, API key, ...). Setting it implies auth on its own.
  2. Basic, when the resolved mode is 'basic' (MCP_AUTH, else enterprise edition), from MCP_USERNAME/MCP_PASSWORD falling back to the Teradata credentials.
  3. None.

A half-configured Basic setup returns None with a prescriptive warning rather than sending a malformed header.

describe_mcp_auth

describe_mcp_auth()

One-line, secret-free summary of what will be sent.

Names the variable each part came from so a wrong-account failure is diagnosable without ever revealing a value.

_ca_client_factory

_ca_client_factory(ca_bundle)

Build an McpHttpClientFactory that trusts ca_bundle.

Only the trust anchor changes: verification stays on, and the MCP SDK's own client defaults (follow_redirects=True, 30 s / 300 s timeouts) are reproduced so supplying a CA bundle does not silently alter transport behaviour.

The SSL context is built here, not per request, so an unreadable or malformed bundle fails immediately with a message naming the variable — rather than as an opaque [X509] PEM lib error at connect time. A bad bundle is never downgraded to default verification: that would weaken TLS invisibly.

_tool_tokens

_tool_tokens(text)

Lower-cased word tokens of text, for relevance matching.

Splits camelCase as well as separators, and adds a crude singular form, because MCP tool names are camelCase compounds while questions are English: without both, base_databaseList never matches "databases" and every tool scores zero, silently reducing the ranking to "whatever the server listed first".

without_client_side_plot_tools

without_client_side_plot_tools(tools)

The MCP tools minus the plot_* ones.

teradata-mcp-server's plot_* tools read the rows out of the database and return Chart.js data drawn in the client. Charts are drawn in the database by TD_PLOT instead (the PLOT skill, :mod:tdfs4ds.plotting), so these tools are not bound. The list is returned unchanged when it holds none.

select_tools

select_tools(tools, question, max_tools=None)

Return at most max_tools tools, most relevant to question first.

Every tool's JSON schema is sent to the model on each turn, so the tool set is a context-window cost paid before the question is even read. The Enterprise MCP edition exposes ~132 tools where the Community one exposes a few dozen — enough to overflow a model outright (litellm.ContextWindowExceededError), which is exactly the "different tool set" difference MCP_SERVER_EDITION exists to describe.

Under the cap the list is returned untouched, so a Community-edition install binds precisely the tools it always did — the cap only engages where the alternative is failing.

Relevance is a deterministic token overlap against each tool's name and description — no LLM, no extra round trip. Ties keep the server's ordering, and unmatched tools still fill the remaining slots so an unforeseen phrasing degrades instead of emptying the toolbox.

is_context_window_error

is_context_window_error(exc)

Is exc the model refusing an over-long prompt?

Recognised across gateways because each words it differently: LiteLLM/Bedrock say ContextWindowExceededError, OpenAI maximum context length, Anthropic prompt is too long. Checked against the flattened exception text plus any code the SDK attaches, since the useful string is often nested inside a generic BadRequestError.

A false positive costs one wasted retry; a false negative leaves the caller with the hard failure it already had.

build_tool_index

build_tool_index(tools, max_chars=8000, max_description=90)

Render tools as a compact name — description text index.

This is the whole point of the two-phase fallback: a tool's JSON schema (nested parameter objects, types, enums) is an order of magnitude larger than one line naming it. ~130 tools fit in a couple of thousand tokens here, where their schemas do not fit at all.

Truncated to max_chars so a server exposing thousands of tools cannot turn the fallback into the same failure it exists to avoid.

parse_tool_selection

parse_tool_selection(reply, tools, limit=None)

Map a model's reply onto real tool objects.

Every name is verified against the actual tool set and anything unrecognised is dropped — a model asked for names will occasionally invent, number or decorate them, and binding a hallucinated name would fail at call time. Substring matching catches the decorated cases (1. base_tableList or `base_tableList`) without inventing anything.

select_tools_via_model

select_tools_via_model(tools, question, llm, limit=None)

Ask llm which of tools the question needs — the two-phase fallback.

Used only after binding the tool schemas has already failed on context size, so the extra model call is never paid on a working path. llm is injected rather than built here, keeping this module free of any dependency on the agent (and making the whole path testable without a model).

Returns [] when the model names nothing usable, which the caller should read as "give up and report", not as "bind nothing".

build_mcp_connection

build_mcp_connection(server_url=None, transport=None)

Return the connection dict for MultiServerMCPClient.

Optional keys are added only when configured, so an install using none of the MCP auth/TLS variables gets exactly {"url": ..., "transport": ...} — the shape that predates this module.

Parameters:

Name Type Description Default
server_url

Endpoint; defaults to tdfs4ds.MCP_SERVER_URL.

None
transport

'streamable_http' (default) or 'sse'; defaults to tdfs4ds.MCP_SERVER_TRANSPORT.

None