modelcontextprotocol / modelcontextprotocol/csharp-sdk

Support for dynamic tools

Open
#237 12 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement needs confirmation
Dominant language
C#
Stars
4.5k
Forks
814
Avg merge
9d 19h
Merged PRs (30d)
4

Description

Is your feature request related to a problem? Please describe.
I would like to map a collection of dynamic tools to a single class handler (see example below).

Describe the solution you'd like
I would like a single handler to be mapped to multiple tools. These tools will be dynamically mapped and available at runtime and may be on other servers. The core of this would be a base class DynamicTools

builder.Services.AddMcpServer()
    .WithStdioServerTransport()
    .WithTools<EchoTools>()  // support with static tools side by side
    .WithTools<MyDynamicTools>(
    [
        new Tool { Name = "echo_a", Description = "Echoes the input back to the user.", InputSchema = "..." },
        new Tool { Name = "echo_b", Description = "Echoes the input back to the user.", InputSchema = "..." },
    ]);

public class MyDynamicTools : DynamicTools
{
    private readonly HttpClient _client;

    public MyDynamicTools(HttpClient client) // also other scoped services should be able to be injected here
    {
        _client = client;
    }

    public override async Task<IMcpActionResult> Invoke(CallToolRequestParams requestParams)
    {
        // just an example other routing logic may apply here
        var result = await _client.PostAsJsonAsync($"/service/{requestParams.Name}", requestParams.Arguments);
        // could also return error
        return Ok(result);
    }
}

The rest would be optional, only the Invoke(CallToolRequestParams params) is important.
Maybe make it similar to MVC.

public interface IMcpActionResult
{
    Task Invoke(ITransport transport, CancellationToken cancellationToken);
}

public class OkObjectResult : IMcpActionResult
{
    private readonly object _value;
    public OkObjectResult(object value)
    {
        _value = value;
    }
    public Task Invoke(ITransport transport, CancellationToken cancellationToken)
    {
        return transport.SendMessageAsync(new JsonRpcResponse { Result = _value}, cancellationToken);
    }
}

public abstract class DynamicTools
{
    public abstract async Task<IMcpActionResult> Invoke(CallToolRequestParams requestParams);

    protected IMcpActionResult Ok(object value)
    {
        return new OkObjectResult(value);
    }
}

Describe alternatives you've considered
Create a custom a CallToolHandler

Additional context
Add any other context or screenshots about the feature request here.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the existing tool registration and call handling behind AddMcpServer, WithTools, and the custom CallToolHandler alternative. Compare static tool registration with the proposed DynamicTools and Invoke entry points. Done should include runtime registration of multiple tools to one handler, service injection, and a defined result path, with tests covering the behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
api, backend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.