Skip to content

MCP Tools writen by rust sdk can not use by LangGraph's MultiServerMCPClient #251

Open
@yexuanyang

Description

@yexuanyang

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:

  1. follow the tutorial in https://langchain-ai.github.io/langgraph/agents/mcp/
  2. change a little bit, add the counter stdio mcp server example we implement in rust-sdk. e.g.:
    Add these lines to the arguments of MultiServerMCPClient 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",
        },
  1. 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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-bugBug fixes and error corrections

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions