python-sdk icon indicating copy to clipboard operation
python-sdk copied to clipboard

Possible to launch a server and client in separate processes?

Open logan-markewich opened this issue 1 year ago • 3 comments

When looking at the examples, it seems like you always need to launch the server and client in the same script, because they share the read/write variables.

Is there a way to launch these two pieces in their own scripts? If so, is it documented? This feels like an extremely common use case that might be missing.

Trying to write an MCP server integration for tools in llama-index and realized I can't figure it out.

Thanks for any help!

logan-markewich avatar Dec 02 '24 05:12 logan-markewich

I am not sure I fully understand. MCP is a client / server architecture. Clients and Servers operate independently and speak over a transport to each other.

Implementing a client

If you want to implement a client, you likely want to connect to servers either via STDIO or HTTP+SSE. For STDIO you spawn a server executable via stdio_client(parameters). This can be done at any point. Commonly users would define a set of servers and you spawn for each one a client + session. For HTTP+SSE you can start a client + session separately and just listen on a port for a connection.

Since you mentioned 'in the same script'. If you want to have a stdio client, you need to spawn a server. However, this is not the same script. STDIO Servers are just programs that listen for JSON-RPC messages on STDIN and write JSON-RPC messages to stdout.

It might help to take https://modelcontextprotocol.io/llms-full.txt, put it into Claude and ask the model for more help to understand the concept.

Implementing a server

If you are only interested in implementing a server, you never need to start a client. I would recommend a framework like https://github.com/jlowin/fastmcp for ease of use.

I hope this helps. Let me know if you have more questions.,

dsp-ant avatar Dec 02 '24 11:12 dsp-ant

@dsp-ant yea I guess I'm just confused. The example in the readme is something like:

server_params = StdioServerParameters(
    command="python",
    args=["example_server.py"],
    env=None,
)

# launches the server
async with stdio_client(server_params) as (read, write):
    # launches the client
    async with ClientSession(read, write) as session:
        # Initialize the connection
        await session.initialize()
        ...

But in this example, it seems like the client depends on variables that you can only get from launching the server? How do you launch them separately? I may have missed where this was documented.

An example for the target usage I am looking for:

  1. user launches an MCP server somewhere
python ./mcp_server.py
  1. user runs another script that connects to that server to perform a tool call. Below is the llama-index usage I am trying to implement
# this would connect to a server and enumerate the available tools
tool = MCPTool(url="127.0.0.1:8000")

# then we can plug that into an agent
agent = FunctionCallingAgent.from_tools([tool], llm=llm)
resp = agent.chat("Hey, use your tool")

If we can achieve something similar to the above, would love to add it to llama-index, but so far I haven't found a way to achieve the above using the current mcp package/docs.

logan-markewich avatar Dec 02 '24 16:12 logan-markewich

I couldn't find an example of the SSE (Server-Sent Events) server either. However, the script below might help you get things set up.

Navigate to the directory examples/servers/simple-tool and save the code below in a file named client.py. From the same directory, run the script using the command:

uv run --script client.py

If the script hangs indefinitely, verify that the server starts successfully with the following command:

uv run mcp-simple-tool --transport sse --port 8000
# /// script
# requires-python = ">=3.10"
# dependencies = ["mcp"]
# [tool.uv.sources]
# mcp = { path = "../../..", editable = true }
# ///

import asyncio
import subprocess

from mcp.client.session import ClientSession
from mcp.client.sse import sse_client


def start_server():
    # Start the server process
    process = subprocess.Popen(
        ["uv", "run", "mcp-simple-tool", "--transport", "sse", "--port", "8000"],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
    )

    for line in process.stderr:
        if "Uvicorn running" in line:
            print(line)
            break

    return process


def main():
    # Start the server
    server_process = start_server()

    try:
        # Run the client logic
        asyncio.run(client_logic())
    finally:
        # Terminate the server process
        server_process.terminate()
        print("Server terminated.")


async def client_logic():
    async with sse_client(url="http://0.0.0.0:8000/sse") as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            # List available tools
            tools = await session.list_tools()
            print(tools)

            # Call the fetch tool
            result = await session.call_tool("fetch", {"url": "https://example.com"})
            print(result)


if __name__ == "__main__":
    main()

p13rr0m avatar Dec 04 '24 01:12 p13rr0m

Or use fastmcp to run on the server. python server.py

#server.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Echo")

@mcp.resource("echo://{message}")
def echo_resource(message: str) -> str:
    """Echo a message as a resource"""
    return f"Resource echo: {message.upper()}"

@mcp.tool()
def echo_tool(message: str) -> str:
    """Echo a message as a tool"""
    return f"Tool echo: {message}"

@mcp.prompt()
def echo_prompt(message: str) -> str:
    """Create an echo prompt"""
    return f"Please process this message: {message}"

if __name__ == "__main__":
    mcp.run(transport='sse')

thoo avatar Jan 07 '25 14:01 thoo

@logan-markewich I put up a working pattern for separate client and server processes with FastMCP still using SSE: https://github.com/sidharthrajaram/mcp-sse

Feel free to take a look.

sidharthrajaram avatar Jan 28 '25 02:01 sidharthrajaram

I have developed web servers that integrate MCP SSE functionality:

These servers can be extended with custom routes while retaining full MCP SSE capabilities.

panz2018 avatar Mar 09 '25 23:03 panz2018

当前项目中的SseServerTransport._read_stream_writers 使用了一个dict保存MemoryObjectSendStream对象,而这个对象在sse模式下,需要在不同的request handler之间共享,这是一个非常不利于扩展的实现方式。

可以参考我设计的另一种Transport抽象逻辑: https://github.com/modelcontextprotocol/python-sdk/issues/238#issuecomment-2720058579

lloydzhou avatar Mar 13 '25 08:03 lloydzhou

There has been significant changes to the repo since this issue was last active including many more examples of how to use MCP. If this is still an issue please re-open the issue :)

maxisbey avatar Sep 26 '25 14:09 maxisbey