Description
Describe the bug
While implementing an agent with multiple MCP server tools following the LangGraph MCP integration documentation, I discovered that the tools implemented by the Rust SDK cannot be used when they have no parameters. This is because Pydantic checks whether the inputSchema necessarily contains a properties field. Due to the implementation of RMCP, when a method without parameters is converted into a tool, the response returned by the MCP client calling tools/list looks like the following:
"5": {
"name": "increment"
"description": "Increment the counter by 1"
"inputSchema": {
"type": "object"
"title": "EmptyObject"
}
}
Here, the properties field is absent.
To Reproduce
Steps to reproduce the behavior:
- follow the tutorial in https://langchain-ai.github.io/langgraph/agents/mcp/
- change a little bit, add the counter stdio mcp server example we implement in rust-sdk. e.g.:
Add these lines to the arguments ofMultiServerMCPClient
creation, change the path to your exe or binary.
"bash": {
"command": "D:\\Documents\\Code\\rust-sdk\\target\\debug\\examples\\servers_std_io.exe",
"args": [],
"transport": "stdio",
},
- Then we can just run uv run to boot the client, then we will find the error like this:
(base) PS D:\Documents\Code\test-langchain> uv run client.py
type 'quit' to exit the chat
Query: list the mcp tools you have
Agent:
[chat_loop] Error: Error code: 400 - {'error': {'message': 'Provider returned error', 'code': 400, 'metadata': {'raw': '{\n "error": {\n "message": "Invalid schema for function \'get_value\': In context=(), object schema missing properties.",\n "type": "invalid_request_error",\n "param": "tools[0].function.parameters",\n "code": "invalid_function_parameters"\n }\n}', 'provider_name': 'OpenAI'}}, 'user_id': 'user_2w1Xd51MmtujqPoZL8XtpAuyES7'}
Query:
Well, it says "object schema missing properties". Then I checked the mcp tools writen by python-sdk, the tools without argument have schema like this:
"2": {
"name": "print_info"
"description": "print Info"
"inputSchema": {
"type":"object"
"properties":{}
"title":"print_infoArguments"
}
}
It does have properties.
Expected behavior
Maybe we should add a properties
field when we schemaize a EmptyObject
?
Logs
Sorry, no logs. If you need I can leave other useful things. This is my client.py
using the LangGraph, check it if you need:
import asyncio
from contextlib import AsyncExitStack
from typing import Optional
from dotenv import load_dotenv
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from mcp import ClientSession
load_dotenv()
client = MultiServerMCPClient(
{
# "math": {
# "command": "uv",
# "args": [
# "run",
# "--directory",
# "D:\\Documents\\Code\\test-langchain",
# "math_server.py",
# ],
# "transport": "stdio",
# },
"bash": {
"command": "D:\\Documents\\Code\\rust-sdk\\target\\debug\\examples\\servers_std_io.exe",
"args": [],
"transport": "stdio",
},
}
)
class MyAgent:
def __init__(self):
self.session: Optional[ClientSession] = None
self.exit_stack = AsyncExitStack()
self.agent = None
def initialize(self, tools):
model = ChatOpenAI(
model="openai/gpt-4.1-mini", base_url="https://openrouter.ai/api/v1"
)
self.agent = create_react_agent(
model=model,
tools=tools,
)
async def process_query_stream(self, query: str):
messages = {"messages": [{"role": "user", "content": query}]}
async for chunk in self.agent.astream(messages, stream_mode="updates"):
# print(chunk)
# print("\n")
if "agent" in chunk.keys():
for msg in chunk["agent"]["messages"]:
print(msg.content)
async def chat_loop(self):
print("type 'quit' to exit the chat")
while True:
try:
query = input("\n\033[34mQuery\033[0m: ").strip()
if query == "quit":
return
print("\033[1;32mAgent\033[0m: ")
await self.process_query_stream(query=query)
except Exception as e:
print(f"[chat_loop] Error: {e}")
async def clean_up(self):
await self.exit_stack.aclose()
async def main():
tools = await client.get_tools()
agent = MyAgent()
agent.initialize(tools)
try:
await agent.chat_loop()
except Exception as e:
print(f"[main] Error: {e}")
finally:
await agent.clean_up()
if __name__ == "__main__":
asyncio.run(main())
Additional context
EmptyObject
is defined at https://github.com/yexuanyang/rust-sdk/blob/db03f63e76b5b32f65d34a1bd08ae56dab595f60/crates/rmcp/src/model.rs#L48-L50
Maybe we can implement the JsonSchema
for this object, like this:
use schemars::{JsonSchema, json_schema};
impl JsonSchema for EmptyObject {
fn schema_name() -> Cow<'static, str> {
"EmptyObject".into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
json_schema!({
"properties": {},
"type": "object"
})
}
}
Thanks!