Skip to content

Commit 2055367

Browse files
committed
Add MCP tests to LangChain
1 parent 9f89dad commit 2055367

3 files changed

Lines changed: 268 additions & 7 deletions

File tree

tests/mlmodel_langchain/cassette.yaml

Lines changed: 155 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,12 @@ interactions:
110110
body:
111111
string: '{"id": "chatcmpl-DyjwCWB6oPS6gcxdy4b9JW4Ey6Lv5", "object": "chat.completion",
112112
"created": 1783368636, "model": "gpt-3.5-turbo-0125", "choices": [{"index":
113-
0, "message": {"role": "assistant", "content": "Hello!",
114-
"refusal": null, "annotations": []}, "logprobs": null, "finish_reason": "stop"}],
115-
"usage": {"prompt_tokens": 107, "completion_tokens": 2, "total_tokens": 109,
116-
"prompt_tokens_details": {"cached_tokens": 0, "audio_tokens": 0}, "completion_tokens_details":
117-
{"reasoning_tokens": 0, "audio_tokens": 0, "accepted_prediction_tokens": 0,
118-
"rejected_prediction_tokens": 0}}, "service_tier": "default", "system_fingerprint":
119-
null}'
113+
0, "message": {"role": "assistant", "content": "Hello!", "refusal": null,
114+
"annotations": []}, "logprobs": null, "finish_reason": "stop"}], "usage":
115+
{"prompt_tokens": 107, "completion_tokens": 2, "total_tokens": 109, "prompt_tokens_details":
116+
{"cached_tokens": 0, "audio_tokens": 0}, "completion_tokens_details": {"reasoning_tokens":
117+
0, "audio_tokens": 0, "accepted_prediction_tokens": 0, "rejected_prediction_tokens":
118+
0}}, "service_tier": "default", "system_fingerprint": null}'
120119
headers:
121120
access-control-expose-headers:
122121
- X-Request-ID
@@ -1015,4 +1014,153 @@ interactions:
10151014
status:
10161015
code: 200
10171016
message: OK
1017+
- request:
1018+
body: '{"messages":[{"content":"You are a text manipulation algorithm.","role":"system"},{"content":"Call
1019+
the add_exclamation_mcp tool with message=\"Hello\". Reply with only the tool
1020+
output, no other text.","role":"user"}],"model":"gpt-3.5-turbo","stream":false,"temperature":0.7,"tools":[{"type":"function","function":{"name":"add_exclamation_mcp","description":"Adds
1021+
an exclamation mark to the input message.","parameters":{"properties":{"message":{"type":"string"}},"required":["message"],"type":"object"}}}]}'
1022+
headers:
1023+
accept:
1024+
- application/json
1025+
accept-encoding:
1026+
- gzip, deflate, zstd
1027+
authorization:
1028+
- XXXXXX
1029+
connection:
1030+
- keep-alive
1031+
content-type:
1032+
- application/json
1033+
method: POST
1034+
uri: https://api.openai.com/v1/chat/completions
1035+
response:
1036+
body:
1037+
string: '{"id": "chatcmpl-DzVrTIy5YWxOo2JjHyNNLCEoZeIQ5", "object": "chat.completion",
1038+
"created": 1783552855, "model": "gpt-3.5-turbo-0125", "choices": [{"index":
1039+
0, "message": {"role": "assistant", "content": null, "tool_calls": [{"id":
1040+
"call_FI6Wqecvc3Pm1sIJIWbfiPqn", "type": "function", "function": {"name":
1041+
"add_exclamation_mcp", "arguments": "{\"message\":\"Hello\"}"}}], "refusal":
1042+
null, "annotations": []}, "logprobs": null, "finish_reason": "tool_calls"}],
1043+
"usage": {"prompt_tokens": 82, "completion_tokens": 17, "total_tokens": 99,
1044+
"prompt_tokens_details": {"cached_tokens": 0, "audio_tokens": 0}, "completion_tokens_details":
1045+
{"reasoning_tokens": 0, "audio_tokens": 0, "accepted_prediction_tokens": 0,
1046+
"rejected_prediction_tokens": 0}}, "service_tier": "default", "system_fingerprint":
1047+
null}'
1048+
headers:
1049+
access-control-expose-headers:
1050+
- X-Request-ID
1051+
- CF-Ray
1052+
- CF-Ray
1053+
cf-cache-status:
1054+
- DYNAMIC
1055+
cf-ray:
1056+
- a182d27a8acc5ee6-PDX
1057+
connection:
1058+
- keep-alive
1059+
content-type:
1060+
- application/json
1061+
date:
1062+
- Wed, 08 Jul 2026 23:20:56 GMT
1063+
openai-organization:
1064+
- nr-test-org
1065+
openai-processing-ms:
1066+
- '1537'
1067+
openai-project:
1068+
- nr-test-project
1069+
openai-version:
1070+
- '2020-10-01'
1071+
server:
1072+
- cloudflare
1073+
transfer-encoding:
1074+
- chunked
1075+
x-ratelimit-limit-requests:
1076+
- '10000'
1077+
x-ratelimit-limit-tokens:
1078+
- '50000000'
1079+
x-ratelimit-remaining-requests:
1080+
- '9999'
1081+
x-ratelimit-remaining-tokens:
1082+
- '49999975'
1083+
x-ratelimit-reset-requests:
1084+
- 6ms
1085+
x-ratelimit-reset-tokens:
1086+
- 0s
1087+
x-request-id:
1088+
- req_769ac9ab9bce44929dff73538cc803d1
1089+
status:
1090+
code: 200
1091+
message: OK
1092+
- request:
1093+
body: '{"messages":[{"content":"You are a text manipulation algorithm.","role":"system"},{"content":"Call
1094+
the add_exclamation_mcp tool with message=\"Hello\". Reply with only the tool
1095+
output, no other text.","role":"user"},{"content":null,"name":"my_agent","role":"assistant","tool_calls":[{"type":"function","id":"call_FI6Wqecvc3Pm1sIJIWbfiPqn","function":{"name":"add_exclamation_mcp","arguments":"{\"message\":
1096+
\"Hello\"}"}}]},{"content":[{"type":"text","text":"Hello!"}],"role":"tool","tool_call_id":"call_FI6Wqecvc3Pm1sIJIWbfiPqn"}],"model":"gpt-3.5-turbo","stream":false,"temperature":0.7,"tools":[{"type":"function","function":{"name":"add_exclamation_mcp","description":"Adds
1097+
an exclamation mark to the input message.","parameters":{"properties":{"message":{"type":"string"}},"required":["message"],"type":"object"}}}]}'
1098+
headers:
1099+
accept:
1100+
- application/json
1101+
accept-encoding:
1102+
- gzip, deflate, zstd
1103+
authorization:
1104+
- XXXXXX
1105+
connection:
1106+
- keep-alive
1107+
content-type:
1108+
- application/json
1109+
method: POST
1110+
uri: https://api.openai.com/v1/chat/completions
1111+
response:
1112+
body:
1113+
string: '{"id": "chatcmpl-DzVrUApwqEqiJqmsGYi0xyFRiHOG5", "object": "chat.completion",
1114+
"created": 1783552856, "model": "gpt-3.5-turbo-0125", "choices": [{"index":
1115+
0, "message": {"role": "assistant", "content": "{\"message\":\"Hello!\"}",
1116+
"refusal": null, "annotations": []}, "logprobs": null, "finish_reason": "stop"}],
1117+
"usage": {"prompt_tokens": 115, "completion_tokens": 7, "total_tokens": 122,
1118+
"prompt_tokens_details": {"cached_tokens": 0, "audio_tokens": 0}, "completion_tokens_details":
1119+
{"reasoning_tokens": 0, "audio_tokens": 0, "accepted_prediction_tokens": 0,
1120+
"rejected_prediction_tokens": 0}}, "service_tier": "default", "system_fingerprint":
1121+
null}'
1122+
headers:
1123+
access-control-expose-headers:
1124+
- X-Request-ID
1125+
- CF-Ray
1126+
- CF-Ray
1127+
cf-cache-status:
1128+
- DYNAMIC
1129+
cf-ray:
1130+
- a182d2894ae25ee6-PDX
1131+
connection:
1132+
- keep-alive
1133+
content-type:
1134+
- application/json
1135+
date:
1136+
- Wed, 08 Jul 2026 23:20:57 GMT
1137+
openai-organization:
1138+
- nr-test-org
1139+
openai-processing-ms:
1140+
- '409'
1141+
openai-project:
1142+
- nr-test-project
1143+
openai-version:
1144+
- '2020-10-01'
1145+
server:
1146+
- cloudflare
1147+
transfer-encoding:
1148+
- chunked
1149+
x-ratelimit-limit-requests:
1150+
- '10000'
1151+
x-ratelimit-limit-tokens:
1152+
- '50000000'
1153+
x-ratelimit-remaining-requests:
1154+
- '9999'
1155+
x-ratelimit-remaining-tokens:
1156+
- '49999975'
1157+
x-ratelimit-reset-requests:
1158+
- 6ms
1159+
x-ratelimit-reset-tokens:
1160+
- 0s
1161+
x-request-id:
1162+
- req_3a560a188af14f67890e2b1ad837bd71
1163+
status:
1164+
code: 200
1165+
message: OK
10181166
version: 1
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Copyright 2010 New Relic, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import pytest
16+
from langchain.messages import HumanMessage
17+
from mcp.server.fastmcp import FastMCP
18+
from mcp.shared.memory import create_connected_server_and_client_session
19+
from testing_support.fixtures import dt_enabled, reset_core_stats_engine, validate_attributes
20+
from testing_support.validators.validate_custom_events import validate_custom_events
21+
from testing_support.validators.validate_span_events import validate_span_events
22+
from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics
23+
24+
from newrelic.api.background_task import background_task
25+
from newrelic.hooks.mlmodel_langchain import _is_local_tool
26+
27+
PROMPT = {
28+
"messages": [
29+
HumanMessage(
30+
'Call the add_exclamation_mcp tool with message="Hello". Reply with only the tool output, no other text.'
31+
)
32+
]
33+
}
34+
35+
tool_recorded_event = [
36+
(
37+
{"type": "LlmTool"},
38+
{
39+
"id": None,
40+
"run_id": None,
41+
# MCP adapters return the result as a content block carrying a random uuid, so
42+
# the exact output string is non-deterministic. We can only assert the key
43+
# is present, not its value.
44+
"output": None,
45+
"name": "add_exclamation_mcp",
46+
"agent_name": "my_agent",
47+
"span_id": None,
48+
"trace_id": "trace-id",
49+
"input": "{'message': 'Hello'}",
50+
"vendor": "langchain",
51+
"ingest_source": "Python",
52+
"duration": None,
53+
},
54+
)
55+
]
56+
57+
58+
@pytest.fixture
59+
def mcp_server():
60+
server = FastMCP("Test Tools")
61+
62+
@server.tool()
63+
def add_exclamation_mcp(message: str) -> str:
64+
"""Adds an exclamation mark to the input message."""
65+
return f"{message}!"
66+
67+
return server
68+
69+
70+
@dt_enabled
71+
@reset_core_stats_engine()
72+
def test_remote_mcp_tool_no_subcomponent(loop, set_trace_info, chat_openai_client, mcp_server):
73+
@validate_custom_events(tool_recorded_event)
74+
@validate_transaction_metrics(
75+
"test_remote_mcp_tool_no_subcomponent",
76+
scoped_metrics=[("Llm/tool/LangChain/arun/add_exclamation_mcp", 1)],
77+
rollup_metrics=[("Llm/tool/LangChain/arun/add_exclamation_mcp", 1)],
78+
background_task=True,
79+
)
80+
@validate_attributes("agent", ["llm"])
81+
# Agent span still has a subcomponent attribute as the agent is executing locally
82+
@validate_span_events(count=1, exact_agents={"subcomponent": '{"type": "APM-AI_AGENT", "name": "my_agent"}'})
83+
# Tool span must NOT have a subcomponent attribute as it will be captured on the server side
84+
@validate_span_events(
85+
count=1,
86+
exact_intrinsics={"name": "Llm/tool/LangChain/arun/add_exclamation_mcp"},
87+
unexpected_agents=["subcomponent"],
88+
)
89+
@background_task(name="test_remote_mcp_tool_no_subcomponent")
90+
def _test():
91+
set_trace_info()
92+
93+
async def _run():
94+
from langchain.agents import create_agent
95+
from langchain_mcp_adapters.tools import load_mcp_tools
96+
97+
async with create_connected_server_and_client_session(mcp_server) as session:
98+
tools = await load_mcp_tools(session)
99+
# The loaded tool is a genuine remote proxy, so the fix must treat it as non-local.
100+
assert _is_local_tool(tools[0]) is False
101+
102+
agent = create_agent(
103+
model=chat_openai_client.with_config(model="gpt-5.1", timeout=30),
104+
tools=tools,
105+
system_prompt="You are a text manipulation algorithm.",
106+
name="my_agent",
107+
)
108+
return await agent.ainvoke(PROMPT)
109+
110+
loop.run_until_complete(_run())
111+
112+
_test()

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ deps =
475475
mlmodel_langchain: langchain-openai
476476
mlmodel_langchain: pytest-recording
477477
mlmodel_langchain: langgraph
478+
mlmodel_langchain: langchain-mcp-adapters
478479
mlmodel_langchain: pypdf
479480
mlmodel_langchain: tiktoken
480481
mlmodel_langchain: faiss-cpu

0 commit comments

Comments
 (0)