Skip to content

Commit e325bd6

Browse files
HavenDVclaude
andcommitted
fix: resolve build failures from breaking API changes in dependencies
Migrate to System.CommandLine 2.0.5 stable API (ICommandHandler → AsynchronousCommandLineAction, AddOption → Add, Option constructor changes, IConsole removal, CommandLineParser.Parse for invocation). Migrate to ModelContextProtocol 1.1.0 API (McpClientFactory/McpServerConfig → StdioClientTransport + McpClient.CreateAsync). Fix Npgsql 10.x DbColumn.PostgresType access (cast to NpgsqlDbColumn). Fix Testcontainers 4.11 UntilPortIsAvailable → UntilInternalTcpPortIsAvailable. Fix pre-existing AsChatClient → GetChatClient().AsIChatClient() for MEAI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2787441 commit e325bd6

8 files changed

Lines changed: 165 additions & 211 deletions

File tree

src/Cli/src/Commands/DoCommand.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ internal sealed class DoCommand : Command
88
{
99
var handler = new DoCommandHandler();
1010

11-
AddOption(handler.InputOption);
12-
AddOption(handler.InputFileOption);
13-
AddOption(handler.OutputFileOption);
14-
AddOption(handler.ToolsOption);
15-
AddOption(handler.DirectoriesOption);
16-
AddOption(handler.FormatOption);
17-
AddOption(handler.DebugOption);
18-
AddOption(handler.ModelOption);
19-
AddOption(handler.ProviderOption);
11+
Add(handler.InputOption);
12+
Add(handler.InputFileOption);
13+
Add(handler.OutputFileOption);
14+
Add(handler.ToolsOption);
15+
Add(handler.DirectoriesOption);
16+
Add(handler.FormatOption);
17+
Add(handler.DebugOption);
18+
Add(handler.ModelOption);
19+
Add(handler.ProviderOption);
2020

21-
Handler = handler;
21+
Action = handler;
2222
}
23-
}
23+
}

src/Cli/src/Commands/DoCommandHandler.cs

Lines changed: 104 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -8,55 +8,49 @@
88
using System.Text.Json.Schema;
99
using LangChain.Cli.Models;
1010
using Microsoft.Extensions.AI;
11-
using ModelContextProtocol;
1211
using ModelContextProtocol.Client;
13-
using ModelContextProtocol.Protocol.Transport;
14-
using ModelContextProtocol.Protocol.Types;
12+
using ModelContextProtocol.Protocol;
1513
using Octokit;
1614
using Tool = LangChain.Cli.Models.Tool;
1715

1816
namespace LangChain.Cli.Commands;
1917

20-
internal sealed class DoCommandHandler : ICommandHandler
18+
internal sealed class DoCommandHandler : AsynchronousCommandLineAction
2119
{
2220
public Option<string> InputOption { get; } = CommonOptions.Input;
2321
public Option<FileInfo?> InputFileOption { get; } = CommonOptions.InputFile;
2422
public Option<FileInfo?> OutputFileOption { get; } = CommonOptions.OutputFile;
2523
public Option<bool> DebugOption { get; } = CommonOptions.Debug;
2624
public Option<string> ModelOption { get; } = CommonOptions.Model;
2725
public Option<Provider> ProviderOption { get; } = CommonOptions.Provider;
28-
public Option<string[]> ToolsOption { get; } = new(
29-
aliases: ["--tools", "-t"],
30-
description: $"Tools you want to use - {string.Join(", ", Enum.GetNames<Tool>())}. " +
31-
"You can specify toolsets using square brackets, e.g., github[issues].")
26+
public Option<string[]> ToolsOption { get; } = new("--tools", "-t")
3227
{
28+
Description = $"Tools you want to use - {string.Join(", ", Enum.GetNames<Tool>())}. " +
29+
"You can specify toolsets using square brackets, e.g., github[issues].",
3330
AllowMultipleArgumentsPerToken = true,
3431
};
35-
public Option<DirectoryInfo[]> DirectoriesOption { get; } = new(
36-
aliases: ["--directories", "-d"],
37-
getDefaultValue: () => [new DirectoryInfo(".")],
38-
description: "Directories you want to use for filesystem.");
39-
public Option<Format> FormatOption { get; } = new(
40-
aliases: ["--format", "-f"],
41-
getDefaultValue: () => Format.Text,
42-
description: "Format of answer.");
43-
44-
public int Invoke(InvocationContext context)
32+
public Option<DirectoryInfo[]> DirectoriesOption { get; } = new("--directories", "-d")
4533
{
46-
throw new NotImplementedException();
47-
}
34+
Description = "Directories you want to use for filesystem.",
35+
DefaultValueFactory = _ => [new DirectoryInfo(".")],
36+
};
37+
public Option<Format> FormatOption { get; } = new("--format", "-f")
38+
{
39+
Description = "Format of answer.",
40+
DefaultValueFactory = _ => Format.Text,
41+
};
4842

49-
public async Task<int> InvokeAsync(InvocationContext context)
43+
public override async Task<int> InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken = default)
5044
{
51-
var input = context.ParseResult.GetValueForOption(InputOption) ?? string.Empty;
52-
var inputPath = context.ParseResult.GetValueForOption(InputFileOption);
53-
var outputPath = context.ParseResult.GetValueForOption(OutputFileOption);
54-
var debug = context.ParseResult.GetValueForOption(DebugOption);
55-
var model = context.ParseResult.GetValueForOption(ModelOption);
56-
var provider = context.ParseResult.GetValueForOption(ProviderOption);
57-
var toolStrings = context.ParseResult.GetValueForOption(ToolsOption) ?? [];
58-
var directories = context.ParseResult.GetValueForOption(DirectoriesOption) ?? [];
59-
var format = context.ParseResult.GetValueForOption(FormatOption);
45+
var input = parseResult.GetValue(InputOption) ?? string.Empty;
46+
var inputPath = parseResult.GetValue(InputFileOption);
47+
var outputPath = parseResult.GetValue(OutputFileOption);
48+
var debug = parseResult.GetValue(DebugOption);
49+
var model = parseResult.GetValue(ModelOption);
50+
var provider = parseResult.GetValue(ProviderOption);
51+
var toolStrings = parseResult.GetValue(ToolsOption) ?? [];
52+
var directories = parseResult.GetValue(DirectoriesOption) ?? [];
53+
var format = parseResult.GetValue(FormatOption);
6054

6155
// Parse tool strings into tools and toolsets
6256
var toolsWithToolsets = toolStrings.Select(ToolExtensions.ParseTool).ToArray();
@@ -76,116 +70,84 @@ public async Task<int> InvokeAsync(InvocationContext context)
7670
// Get toolsets for this tool if any
7771
var toolsets = toolsetsByTool.GetValueOrDefault(tool) ?? [];
7872

79-
return await McpClientFactory.CreateAsync(
80-
tool switch
73+
var transport = tool switch
74+
{
75+
Tool.Filesystem => new StdioClientTransport(new StdioClientTransportOptions
8176
{
82-
Tool.Filesystem => new McpServerConfig
83-
{
84-
Id = Tool.Filesystem.ToString(),
85-
Name = Tool.Filesystem.ToString(),
86-
TransportType = TransportTypes.StdIo,
87-
TransportOptions = new Dictionary<string, string>
88-
{
89-
["command"] = "npx",
90-
["arguments"] = $"-y @modelcontextprotocol/server-filesystem {string.Join(' ', directories.Select(x => x.FullName))}",
91-
},
92-
},
93-
Tool.Fetch => new McpServerConfig
94-
{
95-
Id = Tool.Fetch.ToString(),
96-
Name = Tool.Fetch.ToString(),
97-
TransportType = TransportTypes.StdIo,
98-
TransportOptions = new Dictionary<string, string>
99-
{
100-
["command"] = "docker",
101-
["arguments"] = "run -i --rm mcp/fetch",
102-
},
103-
},
104-
Tool.GitHub => new McpServerConfig
105-
{
106-
Id = Tool.GitHub.ToString(),
107-
Name = Tool.GitHub.ToString(),
108-
TransportType = TransportTypes.StdIo,
109-
TransportOptions = new Dictionary<string, string>
110-
{
111-
["command"] = "docker",
112-
["arguments"] = $"run -i --rm " +
113-
$"-e GITHUB_PERSONAL_ACCESS_TOKEN={Environment.GetEnvironmentVariable("GITHUB_TOKEN")} " +
114-
// $"-e GITHUB_DYNAMIC_TOOLSETS=1 " +
115-
(toolsets.Length != 0 ?
116-
$"-e GITHUB_TOOLSETS={string.Join(',', toolsets)} "
117-
: string.Empty) +
118-
"ghcr.io/github/github-mcp-server",
119-
},
120-
},
121-
Tool.Git => new McpServerConfig
122-
{
123-
Id = Tool.Git.ToString(),
124-
Name = Tool.Git.ToString(),
125-
TransportType = TransportTypes.StdIo,
126-
TransportOptions = new Dictionary<string, string>
127-
{
128-
["command"] = "docker",
129-
["arguments"] = $"run -i --rm {string.Join(' ', directories.Select(x => $"--mount type=bind,src={x},dst={x} "))} mcp/git",
130-
},
131-
},
132-
Tool.Puppeteer => new McpServerConfig
133-
{
134-
Id = Tool.Puppeteer.ToString(),
135-
Name = Tool.Puppeteer.ToString(),
136-
TransportType = TransportTypes.StdIo,
137-
TransportOptions = new Dictionary<string, string>
138-
{
139-
["command"] = "docker",
140-
["arguments"] = "run -i --rm --init -e DOCKER_CONTAINER=true mcp/puppeteer",
141-
},
142-
},
143-
Tool.SequentialThinking => new McpServerConfig
144-
{
145-
Id = Tool.SequentialThinking.ToString(),
146-
Name = Tool.SequentialThinking.ToString(),
147-
TransportType = TransportTypes.StdIo,
148-
TransportOptions = new Dictionary<string, string>
149-
{
150-
["command"] = "docker",
151-
["arguments"] = "run -i --rm mcp/sequentialthinking",
152-
},
153-
},
154-
Tool.Slack => new McpServerConfig
155-
{
156-
Id = Tool.Slack.ToString(),
157-
Name = Tool.Slack.ToString(),
158-
TransportType = TransportTypes.StdIo,
159-
TransportOptions = new Dictionary<string, string>
160-
{
161-
["command"] = "docker",
162-
["arguments"] = $"run -i --rm -e SLACK_BOT_TOKEN={Environment.GetEnvironmentVariable("SLACK_BOT_TOKEN")} -e SLACK_TEAM_ID={Environment.GetEnvironmentVariable("SLACK_TEAM_ID")} -e SLACK_CHANNEL_IDS={Environment.GetEnvironmentVariable("SLACK_CHANNEL_IDS")} mcp/slack",
163-
},
164-
},
165-
Tool.Figma => new McpServerConfig
166-
{
167-
Id = Tool.Figma.ToString(),
168-
Name = Tool.Figma.ToString(),
169-
TransportType = TransportTypes.StdIo,
170-
TransportOptions = new Dictionary<string, string>
171-
{
172-
["command"] = "npx",
173-
["arguments"] = $"-y figma-developer-mcp --figma-api-key={Environment.GetEnvironmentVariable("FIGMA_API_KEY")} --stdio",
174-
},
175-
},
176-
Tool.DocumentConversion => new McpServerConfig
177-
{
178-
Id = Tool.DocumentConversion.ToString(),
179-
Name = Tool.DocumentConversion.ToString(),
180-
TransportType = TransportTypes.StdIo,
181-
TransportOptions = new Dictionary<string, string>
182-
{
183-
["command"] = "uvx",
184-
["arguments"] = "mcp-pandoc",
185-
},
186-
},
187-
_ => throw new ArgumentException($"Unknown tool: {tool}"),
188-
},
77+
Name = Tool.Filesystem.ToString(),
78+
Command = "npx",
79+
Arguments = ["-y", "@modelcontextprotocol/server-filesystem", .. directories.Select(x => x.FullName)],
80+
}),
81+
Tool.Fetch => new StdioClientTransport(new StdioClientTransportOptions
82+
{
83+
Name = Tool.Fetch.ToString(),
84+
Command = "docker",
85+
Arguments = ["run", "-i", "--rm", "mcp/fetch"],
86+
}),
87+
Tool.GitHub => new StdioClientTransport(new StdioClientTransportOptions
88+
{
89+
Name = Tool.GitHub.ToString(),
90+
Command = "docker",
91+
Arguments = [
92+
"run", "-i", "--rm",
93+
"-e", $"GITHUB_PERSONAL_ACCESS_TOKEN={Environment.GetEnvironmentVariable("GITHUB_TOKEN")}",
94+
.. (toolsets.Length != 0
95+
? new[] { "-e", $"GITHUB_TOOLSETS={string.Join(',', toolsets)}" }
96+
: Array.Empty<string>()),
97+
"ghcr.io/github/github-mcp-server",
98+
],
99+
}),
100+
Tool.Git => new StdioClientTransport(new StdioClientTransportOptions
101+
{
102+
Name = Tool.Git.ToString(),
103+
Command = "docker",
104+
Arguments = [
105+
"run", "-i", "--rm",
106+
.. directories.SelectMany(x => new[] { "--mount", $"type=bind,src={x},dst={x}" }),
107+
"mcp/git",
108+
],
109+
}),
110+
Tool.Puppeteer => new StdioClientTransport(new StdioClientTransportOptions
111+
{
112+
Name = Tool.Puppeteer.ToString(),
113+
Command = "docker",
114+
Arguments = ["run", "-i", "--rm", "--init", "-e", "DOCKER_CONTAINER=true", "mcp/puppeteer"],
115+
}),
116+
Tool.SequentialThinking => new StdioClientTransport(new StdioClientTransportOptions
117+
{
118+
Name = Tool.SequentialThinking.ToString(),
119+
Command = "docker",
120+
Arguments = ["run", "-i", "--rm", "mcp/sequentialthinking"],
121+
}),
122+
Tool.Slack => new StdioClientTransport(new StdioClientTransportOptions
123+
{
124+
Name = Tool.Slack.ToString(),
125+
Command = "docker",
126+
Arguments = [
127+
"run", "-i", "--rm",
128+
"-e", $"SLACK_BOT_TOKEN={Environment.GetEnvironmentVariable("SLACK_BOT_TOKEN")}",
129+
"-e", $"SLACK_TEAM_ID={Environment.GetEnvironmentVariable("SLACK_TEAM_ID")}",
130+
"-e", $"SLACK_CHANNEL_IDS={Environment.GetEnvironmentVariable("SLACK_CHANNEL_IDS")}",
131+
"mcp/slack",
132+
],
133+
}),
134+
Tool.Figma => new StdioClientTransport(new StdioClientTransportOptions
135+
{
136+
Name = Tool.Figma.ToString(),
137+
Command = "npx",
138+
Arguments = ["-y", "figma-developer-mcp", $"--figma-api-key={Environment.GetEnvironmentVariable("FIGMA_API_KEY")}", "--stdio"],
139+
}),
140+
Tool.DocumentConversion => new StdioClientTransport(new StdioClientTransportOptions
141+
{
142+
Name = Tool.DocumentConversion.ToString(),
143+
Command = "uvx",
144+
Arguments = ["mcp-pandoc"],
145+
}),
146+
_ => throw new ArgumentException($"Unknown tool: {tool}"),
147+
};
148+
149+
return await McpClient.CreateAsync(
150+
transport,
189151
new McpClientOptions
190152
{
191153
ClientInfo = new Implementation
@@ -194,7 +156,8 @@ public async Task<int> InvokeAsync(InvocationContext context)
194156
Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "0.0.0",
195157
},
196158
InitializationTimeout = TimeSpan.FromMinutes(10),
197-
}).ConfigureAwait(false);
159+
},
160+
cancellationToken: cancellationToken).ConfigureAwait(false);
198161
})).ConfigureAwait(false);
199162

200163
var aiTools = await Task.WhenAll(clients
@@ -262,7 +225,7 @@ .. tools.Contains(Tool.GitHub)
262225
output = value?.ToString() ?? string.Empty;
263226
}
264227

265-
await Helpers.WriteOutputAsync(output, outputPath, context.Console).ConfigureAwait(false);
228+
await Helpers.WriteOutputAsync(output, outputPath).ConfigureAwait(false);
266229

267230
return 0;
268231

src/Cli/src/CommonOptions.cs

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,39 @@ namespace LangChain.Cli;
55

66
internal static class CommonOptions
77
{
8-
public static Option<string> Input => new(
9-
aliases: ["--input", "-i"],
10-
getDefaultValue: () => string.Empty,
11-
description: "Input text");
8+
public static Option<string> Input => new("--input", "-i")
9+
{
10+
Description = "Input text",
11+
DefaultValueFactory = _ => string.Empty,
12+
};
1213

13-
public static Option<FileInfo?> InputFile => new(
14-
aliases: ["--input-file"],
15-
getDefaultValue: () => null,
16-
description: "Input file path");
14+
public static Option<FileInfo?> InputFile => new("--input-file")
15+
{
16+
Description = "Input file path",
17+
DefaultValueFactory = _ => null,
18+
};
1719

18-
public static Option<FileInfo?> OutputFile => new(
19-
aliases: ["--output-file"],
20-
getDefaultValue: () => null,
21-
description: "Output file path");
20+
public static Option<FileInfo?> OutputFile => new("--output-file")
21+
{
22+
Description = "Output file path",
23+
DefaultValueFactory = _ => null,
24+
};
2225

23-
public static Option<bool> Debug => new(
24-
aliases: ["--debug"],
25-
getDefaultValue: () => false,
26-
description: "Show Debug Information");
26+
public static Option<bool> Debug => new("--debug")
27+
{
28+
Description = "Show Debug Information",
29+
DefaultValueFactory = _ => false,
30+
};
2731

28-
public static Option<string> Model => new(
29-
aliases: ["--model"],
30-
getDefaultValue: () => "o3-mini",
31-
description: "Model to use for commands.");
32+
public static Option<string> Model => new("--model")
33+
{
34+
Description = "Model to use for commands.",
35+
DefaultValueFactory = _ => "o3-mini",
36+
};
3237

33-
public static Option<Provider> Provider => new(
34-
aliases: ["--provider"],
35-
getDefaultValue: () => default,
36-
description: $"Provider to use for commands.");
37-
}
38+
public static Option<Provider> Provider => new("--provider")
39+
{
40+
Description = "Provider to use for commands.",
41+
DefaultValueFactory = _ => default,
42+
};
43+
}

0 commit comments

Comments
 (0)