```{ "content": [ { "type": "text", "text": ...
# ask-ai
n
Copy code
{
  "content": [
    {
      "type": "text",
      "text": "Here are the results..."
    }
  ],
  "_meta": {
    "nextCursor": "eyJwYWdlIjogMn0=",
    "requestId": "req_abc123",
    "rateLimit": {
      "remaining": 42,
      "resetAt": "2026-03-27T12:00:00Z"
    }
  }
}
here when return above response of mcp tool and then meta details will be passed to LLM or not ?
✅ 1
t
Thanks for reaching out, Darshak. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m responding to your question about whether the MCP tool response metadata (like
nextCursor
,
requestId
,
rateLimit
) is passed to the LLM. Short answer: No — metadata is not passed to the LLM by default. Details: • The MCP integration serializes the tool’s
content
and that is what’s sent to the LLM by default. Meta fields such as
nextCursor
,
requestId
, and
rateLimit
are not automatically included. • The MCP integration exposes a
tool_result_resolver
you can implement. The resolver receives the raw tool
result
(including metadata) and can decide which parts to include in the string sent to the LLM. • The resolver is only invoked for successful tool calls; errors go through a
ToolError
path and do not hit the resolver. If you want the LLM to receive those meta fields, I can help you with next steps. Do you want: • an example
tool_result_resolver
that includes
nextCursor
and
rateLimit
in the prompt, or • guidance on securely filtering or redacting sensitive fields before sending them to the LLM? Tell me which option you prefer and any language/SDK you’re using (JavaScript, TypeScript, or another), and I’ll provide a concrete example. Sources: Model Context Protocol (MCP) | LiveKit Documentation, with interactive elements
n
Copy code
{'type': 'function_tools_executed', 'tools': [{'call': {'id': 'item_DNx2WSqP2qJP1IEXRh2yK', 'call_id': 'call_4RdU7bAawoyKJkHv', 'name': 'FindNowServices', 'arguments': '{  \n  "userQuery": "Now services" \n} \n'}, 'output': {'id': 'item_85260476ad0e', 'call_id': 'call_4RdU7bAawoyKJkHv', 'name': 'FindNowServices', 'output': '{"type":"text","text":"{\\"result\\":\\"Login to Now\\\\nOpen the Now app and sign in using UAE Pass.\\\\nSelect Residency Renewal\\\\nGo to Residency Visas → choose Renew Residence Visa (self or dependent).\\\\nUpload Documents\\\\nUpload required documents (passport, medical fitness if required, Emirates ID details).\\\\nPay & Track\\\\nPay the fees in-app and track the application status until approval.\\"}","annotations":null,"meta":{"stop_response":false}}', 'is_error': False}}], 'roomName': 'e_69c64291fd8ded37e9f6785e_-_e_699d77918d469e54465668ab_-_69c64291db3eff3d133166e0_-_40526_-_147'}
above output. meta will be passed to LLM or not ?
t
Thanks — I’m responding to your follow-up about whether the
meta
field in the tool output you showed will be passed to the LLM. Short answer: No — the
meta
field is not passed to the LLM by default. Why: • The MCP integration serializes the tool’s main `content`/`output` for the LLM. Fields like
meta
(for example
stop_response
) are not automatically included in what the LLM receives. • The integration gives your code access to the full raw result (including
meta
) inside the
tool_result_resolver
. That resolver is where you can decide to include, redact, or transform
meta
fields before returning the string that will be sent to the LLM. • The resolver runs only for successful tool calls; error paths do not call it. If you want to include
meta
(or just
stop_response
,
rateLimit
, etc.) in the prompt sent to the model, I can show a concrete example of a
tool_result_resolver
that: • appends selected meta fields to the tool output, and • redacts anything sensitive. Which SDK/language are you using (JavaScript/TypeScript, Python, or another)? Also, which meta fields would you like to include or redact? , with interactive elements
n
python sdk
t
Thanks — I’m responding to your request for a Python SDK example that shows how to include or redact MCP tool metadata (like
nextCursor
,
requestId
,
rateLimit
, or a tool
meta.stop_response
) before sending the tool result to the LLM. Recap: • Metadata is not passed to the LLM by default. • The Python SDK exposes a
tool_result_resolver
(it receives the raw tool
result
) where you can inspect, include, transform, or redact metadata and return the final string that will be sent to the LLM. Below is a focused Python example of a custom
tool_result_resolver
. It: • Reads the raw tool
result
. • Optionally includes selected meta fields (configured via
include_meta_fields
). • Redacts values you mark as sensitive. • Returns a clean string composed of the tool content plus included meta. Replace placeholders with your actual SDK initialization and registration details.
Copy code
# Example tool_result_resolver for Python SDK

import json
from typing import Dict, Any, List

# Configure which meta fields to include and which keys to redact
include_meta_fields: List[str] = ["nextCursor", "requestId", "rateLimit", "meta"]
redact_keys: List[str] = ["requestId"]  # example: redact requestId

def redact(obj: Any, keys_to_redact: List[str]) -> Any:
    if isinstance(obj, dict):
        out = {}
        for k, v in obj.items():
            if k in keys_to_redact:
                out[k] = "[REDACTED]"
            else:
                out[k] = redact(v, keys_to_redact)
        return out
    if isinstance(obj, list):
        return [redact(i, keys_to_redact) for i in obj]
    return obj

def tool_result_resolver(tool_name: str, arguments: Dict[str, Any], result: Dict[str, Any]) -> str:
    """
    - tool_name: name of the executed tool
    - arguments: arguments passed to the tool
    - result: the raw result returned by the tool (includes content and any metadata)
    Return: the string that the SDK will send to the LLM.
    """

    parts: List[str] = []
    parts.append(f"Tool: {tool_name}")

    # Add tool arguments (optional)
    if arguments:
        try:
            parts.append("Arguments:")
            parts.append(json.dumps(arguments, indent=2))
        except Exception:
            parts.append(str(arguments))

    # Prefer the main text content if present
    # Common pattern: result may contain {'content': [...]} or direct 'output' string
    content_text = None
    if isinstance(result, dict):
        # Try commonly used shapes
        if "content" in result:
            try:
                content_text = json.dumps(result["content"], ensure_ascii=False)
            except Exception:
                content_text = str(result["content"])
        elif "output" in result:
            # `output` may itself be a JSON string; try parsing
            try:
                parsed = json.loads(result["output"])
                # If parsed has a `text` field inside, prefer it
                if isinstance(parsed, dict) and "text" in parsed:
                    content_text = parsed["text"]
                else:
                    content_text = json.dumps(parsed, ensure_ascii=False)
            except Exception:
                content_text = str(result["output"])
        elif "text" in result:
            content_text = result["text"]

    if content_text:
        parts.append("Tool output:")
        parts.append(content_text)
    else:
        parts.append("Tool returned a result but no main text content was found.")
        parts.append(json.dumps(result, indent=2, ensure_ascii=False))

    # Include selected meta fields if present
    included_meta = {}
    for key in include_meta_fields:
        if key in result:
            included_meta[key] = result[key]

    # Sometimes metadata is nested under a field like 'meta' or inside parsed output
    # If `result` had an 'output' string that parses to an object with 'meta', include that too
    try:
        if "output" in result:
            parsed_output = None
            try:
                parsed_output = json.loads(result["output"])
            except Exception:
                parsed_output = None
            if parsed_output and isinstance(parsed_output, dict) and "meta" in parsed_output:
                included_meta.setdefault("parsed_output.meta", parsed_output["meta"])
    except Exception:
        pass

    if included_meta:
        # Redact sensitive keys
        safe_meta = redact(included_meta, redact_keys)
        parts.append("Included metadata:")
        parts.append(json.dumps(safe_meta, indent=2, ensure_ascii=False))

    # Final assembled string sent to LLM
    final = "\n\n".join(parts)
    return final

# --- Example usage when registering the resolver in your SDK setup ---
# sdk.register_tool_result_resolver(tool_result_resolver)  # pseudo-code; use actual SDK call
Notes and suggestions: • Keep the resolver minimal and focused: avoid sending large raw objects unless the LLM needs them. • Redact any request identifiers or secrets before including meta. In the example,
requestId
is redacted. • If you only need a single meta field (e.g.,
nextCursor
for pagination), include only that field to keep prompts small. • The resolver is only invoked for successful tool calls. Errors follow a different path (they raise a
ToolError
). Would you like: • a trimmed example that only appends
nextCursor
for pagination use cases, or • a sample that integrates directly with the LiveKit Python SDK init/registration call you’re using?, with interactive elements
n
don't want to pass meta field to LLM
t
Thanks — I’m responding to your final preference: you do not want any
meta
fields passed to the LLM. Short confirmation: • Metadata (fields like
nextCursor
,
requestId
,
rateLimit
, or a tool
meta
object) is not sent to the LLM by default. • To guarantee metadata is never sent, ensure your
tool_result_resolver
(or any result-handling code) explicitly omits those fields when returning the string for the LLM. Quick actionable guidance (Python): 1) Keep the resolver minimal and only return the tool’s user-facing content — do not include any
meta
keys. 2) Example minimal resolver that excludes metadata entirely:
Copy code
import json
from typing import Dict, Any

def tool_result_resolver(tool_name: str, arguments: Dict[str, Any], result: Dict[str, Any]) -> str:
    # Prefer the human-readable text in result.content or result.output
    # Do NOT include any meta fields
    content_text = None

    if isinstance(result, dict):
        if "content" in result:
            try:
                # serialize only the content items (not metadata)
                content_text = json.dumps(result["content"], ensure_ascii=False)
            except Exception:
                content_text = str(result["content"])
        elif "output" in result:
            try:
                parsed = json.loads(result["output"])
                if isinstance(parsed, dict) and "text" in parsed:
                    content_text = parsed["text"]
                else:
                    # if output is structured, include only the user-facing part
                    content_text = json.dumps(parsed, ensure_ascii=False)
            except Exception:
                content_text = str(result["output"])
        elif "text" in result:
            content_text = result["text"]

    if content_text:
        return f"Tool: {tool_name}\n\nTool output:\n{content_text}"
    # Fallback: return a short safe note without metadata
    return f"Tool: {tool_name}\n\nTool returned a result but no user-facing content was found."
3) Additional safety suggestions: • Don’t call
json.dumps(result)
or include
result
wholesale — that can accidentally include
meta
. • If you must inspect
result
in your application logs, strip or redact
meta
before logging or storing. • Test with a known tool response that contains
meta
to verify the resolver output includes no
meta
fields. If you want, I can: • Provide a slimmer resolver that extracts just a single textual field (e.g.,
result["content"][0]["text"]
) for your exact tool shape, or • Show how to register this resolver in your LiveKit Python SDK setup (share the SDK init snippet you’re using). Which would you prefer?, with interactive elements