8000 feat: Add MCP server configuration parsing (e.g.: `mcp.json`) to the SDK by msabramo · Pull Request #968 · modelcontextprotocol/python-sdk · GitHub
[go: up one dir, main page]

Skip to content
8000

feat: Add MCP server configuration parsing (e.g.: mcp.json) to the SDK #968

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 42 commits into
base: main
Choose a base branch
from

Conversation

msabramo
Copy link
Contributor
@msabramo msabramo commented Jun 16, 2025

Goals

  • Add features to the Python SDK that enable Python MCP client applications to support common functionality for configuration and reduce annoying differences between applications

  • Hopefully, once the ideas are discussed and settled on, this can be ported to the TypeScript SDK, so that client applications in that ecosystem can benefit as well.

  • Add MCPServersConfig class for parsing MCP server configuration (e.g.: mcp.json) to the SDK

  • Support feature that user can specify full command in command and it will automatically be parsed into command and args (Cursor has this feature but I don't know if any other apps do)

  • Support the standard mcpServers key or the servers key, which VS Code uses

  • Support VS Code inputs key and associated string interpolation

  • Support stripping out // comments (JSONC) because a lot of VS Code examples online have these

  • Optionally support YAML for MCP server configuration (while still preserving support for the JSON format)

  • Added docs for all the above at https://github.com/msabramo/python-sdk/blob/config/docs/client-configuration.md

Related issues/discussions

Motivation and Context

Config API

Claude Desktop introduced the mcp.json file and then a lot of other applications copied the idea pretty closely, but some applications made little tweaks to their parsing code to support extensions. Examples:

  • Cursor allows one to put a full shell command into command for a stdio server which saves the user from having to split a shell command into a command string and args list, which is tedious.
  • Visual Studio Code GitHub Copilot uses servers key instead of mcpServers and supports an inputs key and allows associated interpolation of these inputs in other server fields (e.g.: https://github.com/Azure-Samples/remote-mcp-functions-python/blob/main/.vscode/mcp.json)

AFAIK, there is no standard for the mcp.json so various applications have been evolving it very informally and so applications have differing abilities and their config files aren't necessarily compatible with each other.

This PR aims to create a simple API that standardizes config parsing so applications don't need to worry about implementing it and users benefit from more consistency between its handling by applications.

from mcp.client.config.mcp_servers_config import MCPServersConfig

mcp_server = MCPServersConfig.from_file("~/.cursor/mcp.json").servers["time"]

print(f"Effective command: {mcp_server.effective_command}")
print(f"Effective args: {mcp_server.effective_args}")

Assuming that ~/.cursor/mcp.json has this:

{
  "mcpServers": {
    "time": {
      "command": "uvx mcp-server-time"
    },
    ...
}

(Note the above syntax of stuffing the entire command in command works in Cursor but I don't think it works in Claude Desktop)

then the output of this code is:

Effective command: uvx
Effective args: ['mcp-server-time']

Note that even though just command was specified, the code went ahead and parsed the command and split it into separate command and args so that the user doesn't have to.

Problems with JSON

The JSON format has some drawbacks:

  • No support for comments
  • No tolerance of commas at the end of a list, which leads to errors and noisy diffs

YAML MCP server config

Because of these problems with JSON, I have added an optional YAML variant of the server configuration. It provides a very nice, clean syntax for configuration for those who want to use it:

mcpServers:
  time:
    command: uvx mcp-server-time

  filesystem:
    command: npx -y @modelcontextprotocol/server-filesystem /Users/username/Desktop

If I have the above YAML in mcp.yaml and this Python code:

from mcp.client.config.mcp_servers_config import MCPServersConfig

mcp_config = MCPServersConfig.from_file("mcp.yaml")

filesystem_mcp_server = mcp_config.servers["filesystem"]

print(f"Effective command: {filesystem_mcp_server.effective_command}")
print(f"Effective args: {filesystem_mcp_server.effective_args}")

then the output is:

Effective command: npx
Effective args: ['-y', '@modelcontextprotocol/server-filesystem', '/Users/username/Desktop']

However, YAML is optional, and I have tried to ensure that existing mcp.json files work as expected with no changes.

This PR seeks to add parsing of the MCP servers configuration to the MCP SDK, so:

  • Applications have a stable base to rely on and don't have to duplicate work
  • Applications can get new configuration features more easily
  • MCP can evolve and it will be easier for applications to adapt to the changes

How Has This Been Tested?

Pretty extensive unit tests

Breaking Changes

No. There are lots of tests that use mcp.json files to ensure they are parsed correctly.

Types of changes

Checklist

Additional context

msabramo added 21 commits June 16, 2025 09:51
Add the ability to parse a multi-word command and extract the command
and args into `effective_command` and `effective_args` attributes.
This reverts commit 38eff49.
instead of str.split, which breaks when there are quoted arguments with
spaces in them.
@msabramo
Copy link
Contributor Author
msabramo commented Jun 16, 2025

The failing tests appears to not be from my changes. From GitHub Copilot Explain Error:


Issue Analysis

The failing job (44209986832) encountered an AssertionError in the test file tests/issues/test_188_concurrency.py on line 38. The assertion assert duration < 6 * _sleep_time_seconds failed because the duration of executing concurrent tasks exceeded the expected time limit.

Problem

The test test_messages_are_executed_concurrently attempts to execute multiple tasks concurrently and expects their combined execution duration to be less than 6 * _sleep_time_seconds. However, the actual duration exceeded this limit, indicating potential issues such as:

  1. Higher-than-expected latency in task execution.
  2. Inefficient task scheduling or resource contention.

Solution

To address this:

  1. Increase the allowed duration threshold:
    If the current threshold is too strict based on system load or other factors, you can increase the multiplier in the assertion (e.g., change 6 * _sleep_time_seconds to a higher value).

  2. Optimize task execution:
    Ensure that tasks are properly scheduled to execute concurrently without resource contention. Review the create_connected_server_and_client_session function and confirm that the anyio.create_task_group() is correctly utilized.

Code Suggestions

Modify the Test

Increase the threshold to accommodate system variability:

assert duration < 10 * _sleep_time_seconds  # Adjust multiplier

Optimize Resource Handling in create_connected_server_and_client_session

Ensure the client_session initialization does not introduce delays:

async with anyio.create_task_group() as tg:
    tg.start_soon(
        lambda: server.run(
            server_read,
            server_write,
            server.create_initialization_options(),
            raise_exceptions=raise_exceptions,
        )
    )
    async with ClientSession(
        read_stream=client_read,
        write_stream=client_write,
        read_timeout_seconds=read_timeout_seconds,
        sampling_callback=sampling_callback,
        list_roots_callback=list_roots_callback,
        logging_callback=logging_callback,
        message_handler=message_handler,
        client_info=client_info,
    ) as client_session:
        await client_session.initialize()
        yield client_session  # Ensure proper yielding without blocking

Workflow Check

The .github/workflows/pull-request-checks.yml references a shared configuration (./.github/workflows/shared.yml). Ensure this workflow doesn't impose additional constraints affecting test execution.

Summary

  • Adjust test threshold (duration < 10 * _sleep_time_seconds).
  • Optimize session creation for minimal latency.
  • Review shared workflow configurations for potential bottlenecks.

Let me know if you need further assistance or adjustments!


@msabramo msabramo changed the title feat: Add MCP server configuration (e.g.: mcp.json) to the SDK feat: Add MCP server configuration parsing (e.g.: mcp.json) to the SDK Jun 16, 2025
msabramo added a commit to msabramo/python-sdk that referenced this pull request Jun 17, 2025
because I was seeing tests failing on Windows in GitHub Actions

and GitHub Copilot
[said](modelcontextprotocol#968 (comment))
msabramo added 8 commits June 16, 2025 17:36
It seemed weird doing it in `from_file`, because the caller has to call
`from_file` to find out what the required inputs are and then has to
call `from_file` against to provide the input values.

Now, the caller calls config.get(server, input_values={...})

to provide the input values
@dsp-ant
Copy link
Member
dsp-ant commented Jun 17, 2025

Letting @ihrpr chime in here, but I believe it would be better if this is an external package that people can use. Since mcp.json is not part of the MCP standard, I don't think we want to include it for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
0