88using System . Text . Json . Schema ;
99using LangChain . Cli . Models ;
1010using Microsoft . Extensions . AI ;
11- using ModelContextProtocol ;
1211using ModelContextProtocol . Client ;
13- using ModelContextProtocol . Protocol . Transport ;
14- using ModelContextProtocol . Protocol . Types ;
12+ using ModelContextProtocol . Protocol ;
1513using Octokit ;
1614using Tool = LangChain . Cli . Models . Tool ;
1715
1816namespace 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
0 commit comments