MCP session auditing
This page answers the practical question: when a tool call happens on an MCP server that is running on some host you don't control, what does the HANA side see, and how does an auditor with only HANA access query it?
Every field this page describes is populated automatically on every tool
call via setclientinfo. No coordination with the MCP host is required;
an auditor with SELECT on M_SQL_PLAN_CACHE — or, with an audit
policy enabled, AUDIT_LOG — has the full picture.
What lands on the HANA side automatically
Every tool call sets five setclientinfo channels on the toolkit's HANA
connection before executing SQL:
setclientinfo channel |
Carried value |
HANA-side visibility |
|---|---|---|
|
MCP client's declared name |
|
|
MCP client's declared end-user / id |
|
|
client version / env tag |
|
|
|
|
|
Pipe-delimited pack of MCP identity + per-call correlation ids + optional |
|
The APPLICATIONSOURCE pack is the key mechanism: HANA gives us five
setclientinfo slots, but we have ~10 fields to surface, so all the MCP-only
identity + invocation ids ride on one channel in a compact key=value form.
Pack layout:
mcp=hana-ai/<ver>|sess=<mcp_session_id>|agent=<agent_name>|model=<model_name>
|cli=<client_name>|mcp_ip=<client_ip>|tool=<tool_name>|inv=<invocation_id>
|corr=<hana_correlation_id>[|resp=<response_size>]
Real example captured from a live MCP HTTP session:
mcp=hana-ai/1.1.26072000|sess=d71dfba1752941aab632db0f6c2adb01
|agent=probe-agent|model=claude-opus-4-8|cli=appsource-probe-535c0807
|mcp_ip=127.0.0.1|tool=list_models
|inv=inv-b98880b44944408286382131b7e2f92c
|corr=hana-corr-d4ba732436c8422085147bf45e81b8ae
|resp=42
Pack contract:
ASCII-only, ≤ 254 bytes — HANA silently truncates
APPLICATIONSOURCEpast 256 wire bytes, so the builder enforces a hard 254-byte cap and refuses non-ASCII input.Field order is fixed so simple
SUBSTR_REGEXPRon the HANA side works.resp is optional and last — it is only emitted on the post-completion "beacon" write (see below); the started-path pack has no
resp=segment. When identity plusresp=would overflow the 254-byte cap,resp=is dropped first (tail-first truncation on a|boundary).Sanitised — every field value is passed through a
[A-Za-z0-9._:/@-]whitelist so a malicious tool name cannot inject fake|sess=segments.Plan-cache safe — verified against HANA Cloud 4.00 that
setclientinfodoes not participate inSTATEMENT_HASH, so a freshinv=per call does not fragmentM_SQL_PLAN_CACHE.
Surfacing RESPONSE_SIZE on the HANA side
RESPONSE_SIZE is only known after the tool body runs, so it cannot
travel in the started-path pack that HANA freezes onto the tool's own
plan-cache rows (M_SQL_PLAN_CACHE.APPLICATION_SOURCE follows
first-execution-wins semantics). Instead, after each successful tool call
the MCP host rewrites APPLICATIONSOURCE with a beacon pack carrying
resp=<N> and executes one uniquely-tagged synthetic statement:
SELECT /* mcp-audit-beacon inv=<invocation_id> */ CURRENT_UTCTIMESTAMP FROM DUMMY
The comment embeds the invocation id so each beacon hashes to a distinct
plan-cache entry (no cross-invocation reuse of a cached beacon plan).
fetch_hana_mcp_audit_view() reads back the plan cache, extracts
resp= per invocation, left-joins it onto every row of the same
INVOCATION_ID, and drops the beacon rows themselves (identified by
STATEMENT_STRING containing mcp-audit-beacon). Rows without a
beacon — failed tool calls, or plan cache evicted before the beacon fired
— show RESPONSE_SIZE = None.
Querying HANA-side without an audit policy
If your DBA cannot or will not enable HANA audit policies, an auditor with
SELECT on M_SQL_PLAN_CACHE alone can already reconstruct which MCP
session / agent / tool / invocation produced which SQL:
SELECT DISTINCT
STATEMENT_HASH,
SUBSTR_REGEXPR('sess=([^|]*)' IN APPLICATION_SOURCE GROUP 1) AS MCP_SESSION_ID,
SUBSTR_REGEXPR('agent=([^|]*)' IN APPLICATION_SOURCE GROUP 1) AS AGENT_NAME,
SUBSTR_REGEXPR('model=([^|]*)' IN APPLICATION_SOURCE GROUP 1) AS MODEL_NAME,
SUBSTR_REGEXPR('tool=([^|]*)' IN APPLICATION_SOURCE GROUP 1) AS TOOL_NAME,
SUBSTR_REGEXPR('inv=([^|]*)' IN APPLICATION_SOURCE GROUP 1) AS INVOCATION_ID,
SUBSTR_REGEXPR('resp=([0-9]+)' IN APPLICATION_SOURCE GROUP 1) AS RESPONSE_SIZE,
APPLICATION_USER_NAME, -- MCP-declared end user
APPLICATION_NAME, -- MCP-declared client name
USER_NAME, -- HANA-authenticated DB user
EXECUTION_COUNT,
LAST_EXECUTION_TIMESTAMP
FROM M_SQL_PLAN_CACHE
WHERE APPLICATION_SOURCE LIKE 'mcp=hana-ai/%'
ORDER BY LAST_EXECUTION_TIMESTAMP DESC;
Or, via the Python convenience wrapper that also fans out resp= per
invocation and hides the beacon rows:
from hana_ai.tools.hana_ml_tools.utility import fetch_hana_mcp_audit_view
audit_df = fetch_hana_mcp_audit_view(
cc.connection,
application_name='context-agent-notebook',
)
audit_df[[
'LAST_EXECUTION_TIMESTAMP', 'TOOL_NAME', 'RESPONSE_SIZE',
'AGENT_NAME', 'MODEL_NAME',
'MCP_SESSION_ID', 'INVOCATION_ID',
'HANA_AUTHENTICATED_USER', 'EXECUTION_COUNT',
]].head(20)
Limits of this view:
First-execution-wins semantics. HANA writes
M_SQL_PLAN_CACHE.APPLICATION_SOURCEat plan-caching time and does not update it on subsequent executions. Different tools produce different SQL, so each tool's rows carry that tool's own pack; but repeated calls to the same tool from different MCP sessions all appear under the first session's pack.Plan cache ages out — this is a near-term audit view, not long-term retention. Once a plan is evicted the row is gone. For permanent retention, enable audit policies (next section).
``RESPONSE_SIZE`` is best-effort — populated for successful tool calls whose beacon plan-cache row is still resident. Failed tool calls do not emit a beacon and evicted beacons drop the value.
Querying HANA-side with an audit policy
For long-term retention on HANA Cloud, the DBA enables audit policies on
the relevant schemas. On HANA Cloud, global_auditing_state is enabled
by default at the tenant level, so no ALTER SYSTEM ... CONFIGURATION
call is required (and the on-prem 'SYSTEM' layer is not exposed to
tenants anyway). Only TRAIL TYPE TABLE is supported on HANA Cloud:
-- One-time DBA setup on HANA Cloud
CREATE AUDIT POLICY p_mcp_tools
AUDITING SUCCESSFUL EXECUTE ON SCHEMA "MY_MCP_SCHEMA"
LEVEL INFO TRAIL TYPE TABLE; -- TABLE is the only trail type on HANA Cloud
ALTER AUDIT POLICY p_mcp_tools ENABLE;
Note
maximum_statement_string_length is a platform-managed setting on
HANA Cloud and cannot be adjusted by the tenant. If STATEMENT_STRING
in AUDIT_LOG is truncated for your workload, fall back to the
M_SQL_PLAN_CACHE path (previous section), whose full SQL text is
not subject to this cap.
Once policies are on, the same pack lands in AUDIT_LOG alongside every
audited SQL, permanently:
SELECT TIMESTAMP,
USER_NAME,
APPLICATION_USER_NAME,
APPLICATION_NAME,
SUBSTR_REGEXPR('sess=([^|]*)' IN CLIENT_APPLICATION_SOURCE GROUP 1) AS MCP_SESSION_ID,
SUBSTR_REGEXPR('inv=([^|]*)' IN CLIENT_APPLICATION_SOURCE GROUP 1) AS INVOCATION_ID,
SUBSTR_REGEXPR('tool=([^|]*)' IN CLIENT_APPLICATION_SOURCE GROUP 1) AS TOOL_NAME,
SUBSTR_REGEXPR('resp=([0-9]+)' IN CLIENT_APPLICATION_SOURCE GROUP 1) AS RESPONSE_SIZE,
STATEMENT_STRING
FROM AUDIT_LOG
WHERE CLIENT_APPLICATION_SOURCE LIKE 'mcp=hana-ai/%'
AND TIMESTAMP > ADD_SECONDS(CURRENT_TIMESTAMP, -86400)
ORDER BY TIMESTAMP DESC;
Note
The audit-log column that mirrors setclientinfo('APPLICATIONSOURCE')
is exposed as CLIENT_APPLICATION_SOURCE on HANA Cloud.
HANA-side field reference
The columns surfaced by fetch_hana_mcp_audit_view() and their
positions in the HANA views:
Field |
HANA-side location |
|---|---|
|
Pack |
|
Pack |
|
|
|
Pack |
|
Pack |
|
Pack |
|
|
|
|
|
|
|
Pack |
|
Pack |
|
Pack |
|
Pack |
|
|
|
|
|
|
|
The raw pack string, kept for troubleshooting when a field decodes unexpectedly |
Inspecting a live MCP connection's pack
For interactive debugging you can decode the pack from the currently active HANA connection:
from hana_ai.tools.hana_ml_tools.utility import fetch_hana_appsource_pack
fetch_hana_appsource_pack(cc.connection)
# -> {
# 'mcp': 'hana-ai/1.1.26072000',
# 'sess': 'd71dfba1752941aab632db0f6c2adb01',
# 'agent': 'probe-agent',
# 'model': 'claude-opus-4-8',
# 'cli': 'appsource-probe-535c0807',
# 'mcp_ip': '127.0.0.1',
# 'tool': 'list_models',
# 'inv': 'inv-b98880b44944408286382131b7e2f92c',
# 'corr': 'hana-corr-d4ba732436c8422085147bf45e81b8ae',
# 'response_size': 42, # only after the beacon has fired
# }