Skip to content

Commit 39f0cf4

Browse files
committed
Add tools.list SDK support and bump CLI to 0.0.407
Add listTools() / list_tools() / ListTools() / ListToolsAsync() methods across all four SDK languages (TypeScript, Python, Go, .NET) to expose the new tools.list RPC from the CLI runtime. Includes ToolInfo types, client methods, exports, and E2E tests for each language. Bumps @github/copilot from ^0.0.405 to ^0.0.407 which includes the tools.list RPC handler.
1 parent 4dc5629 commit 39f0cf4

File tree

16 files changed

+347
-29
lines changed

16 files changed

+347
-29
lines changed

dotnet/src/Client.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,23 @@ public async Task<List<ModelInfo>> ListModelsAsync(CancellationToken cancellatio
593593
}
594594
}
595595

596+
/// <summary>
597+
/// Lists available built-in tools with their metadata.
598+
/// </summary>
599+
/// <param name="model">Optional model ID to get model-specific tool overrides.</param>
600+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
601+
/// <returns>A task that resolves with a list of available tools.</returns>
602+
/// <exception cref="InvalidOperationException">Thrown when the client is not connected.</exception>
603+
public async Task<List<ToolInfoItem>> ListToolsAsync(string? model = null, CancellationToken cancellationToken = default)
604+
{
605+
var connection = await EnsureConnectedAsync(cancellationToken);
606+
607+
var response = await InvokeRpcAsync<GetToolsResponse>(
608+
connection.Rpc, "tools.list", [new ListToolsRequest { Model = model }], cancellationToken);
609+
610+
return response.Tools;
611+
}
612+
596613
/// <summary>
597614
/// Gets the ID of the most recently used session.
598615
/// </summary>
@@ -1385,6 +1402,11 @@ internal record UserInputRequestResponse(
13851402
internal record HooksInvokeResponse(
13861403
object? Output);
13871404

1405+
internal record ListToolsRequest
1406+
{
1407+
public string? Model { get; init; }
1408+
}
1409+
13881410
/// <summary>Trace source that forwards all logs to the ILogger.</summary>
13891411
internal sealed class LoggerTraceSource : TraceSource
13901412
{
@@ -1439,6 +1461,7 @@ public override void WriteLine(string? message) =>
14391461
[JsonSerializable(typeof(GetLastSessionIdResponse))]
14401462
[JsonSerializable(typeof(HooksInvokeResponse))]
14411463
[JsonSerializable(typeof(ListSessionsResponse))]
1464+
[JsonSerializable(typeof(ListToolsRequest))]
14421465
[JsonSerializable(typeof(PermissionRequestResponse))]
14431466
[JsonSerializable(typeof(PermissionRequestResult))]
14441467
[JsonSerializable(typeof(ProviderConfig))]

dotnet/src/Types.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,41 @@ public class GetModelsResponse
10631063
public List<ModelInfo> Models { get; set; } = new();
10641064
}
10651065

1066+
/// <summary>
1067+
/// Information about an available built-in tool
1068+
/// </summary>
1069+
public class ToolInfoItem
1070+
{
1071+
/// <summary>Tool identifier (e.g., "bash", "grep", "str_replace_editor")</summary>
1072+
[JsonPropertyName("name")]
1073+
public string Name { get; set; } = string.Empty;
1074+
1075+
/// <summary>Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP tools)</summary>
1076+
[JsonPropertyName("namespacedName")]
1077+
public string? NamespacedName { get; set; }
1078+
1079+
/// <summary>Description of what the tool does</summary>
1080+
[JsonPropertyName("description")]
1081+
public string Description { get; set; } = string.Empty;
1082+
1083+
/// <summary>JSON Schema for the tool's input parameters</summary>
1084+
[JsonPropertyName("parameters")]
1085+
public JsonElement? Parameters { get; set; }
1086+
1087+
/// <summary>Optional instructions for how to use this tool effectively</summary>
1088+
[JsonPropertyName("instructions")]
1089+
public string? Instructions { get; set; }
1090+
}
1091+
1092+
/// <summary>
1093+
/// Response from tools.list
1094+
/// </summary>
1095+
public class GetToolsResponse
1096+
{
1097+
[JsonPropertyName("tools")]
1098+
public List<ToolInfoItem> Tools { get; set; } = new();
1099+
}
1100+
10661101
// ============================================================================
10671102
// Session Lifecycle Types (for TUI+server mode)
10681103
// ============================================================================
@@ -1143,6 +1178,7 @@ public class SetForegroundSessionResponse
11431178
[JsonSerializable(typeof(GetAuthStatusResponse))]
11441179
[JsonSerializable(typeof(GetForegroundSessionResponse))]
11451180
[JsonSerializable(typeof(GetModelsResponse))]
1181+
[JsonSerializable(typeof(GetToolsResponse))]
11461182
[JsonSerializable(typeof(GetStatusResponse))]
11471183
[JsonSerializable(typeof(McpLocalServerConfig))]
11481184
[JsonSerializable(typeof(McpRemoteServerConfig))]
@@ -1165,6 +1201,7 @@ public class SetForegroundSessionResponse
11651201
[JsonSerializable(typeof(SetForegroundSessionResponse))]
11661202
[JsonSerializable(typeof(SystemMessageConfig))]
11671203
[JsonSerializable(typeof(ToolBinaryResult))]
1204+
[JsonSerializable(typeof(ToolInfoItem))]
11681205
[JsonSerializable(typeof(ToolInvocation))]
11691206
[JsonSerializable(typeof(ToolResultObject))]
11701207
[JsonSerializable(typeof(JsonElement))]

dotnet/test/ClientTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,35 @@ public async Task Should_List_Models_When_Authenticated()
148148
}
149149
}
150150

151+
[Fact]
152+
public async Task Should_List_Tools()
153+
{
154+
using var client = new CopilotClient(new CopilotClientOptions { UseStdio = true });
155+
156+
try
157+
{
158+
await client.StartAsync();
159+
160+
var tools = await client.ListToolsAsync();
161+
Assert.NotNull(tools);
162+
Assert.True(tools.Count > 0, "Expected at least one tool");
163+
if (tools.Count > 0)
164+
{
165+
var tool = tools[0];
166+
Assert.NotNull(tool.Name);
167+
Assert.NotEmpty(tool.Name);
168+
Assert.NotNull(tool.Description);
169+
Assert.NotEmpty(tool.Description);
170+
}
171+
172+
await client.StopAsync();
173+
}
174+
finally
175+
{
176+
await client.ForceStopAsync();
177+
}
178+
}
179+
151180
[Fact]
152181
public void Should_Accept_GithubToken_Option()
153182
{

go/client.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,27 @@ func (c *Client) ListModels(ctx context.Context) ([]ModelInfo, error) {
970970
return models, nil
971971
}
972972

973+
// ListTools returns available built-in tools with their metadata.
974+
//
975+
// When a model is provided, the returned tool list reflects model-specific overrides.
976+
func (c *Client) ListTools(ctx context.Context, model string) ([]ToolInfo, error) {
977+
if c.client == nil {
978+
return nil, fmt.Errorf("client not connected")
979+
}
980+
981+
result, err := c.client.Request("tools.list", listToolsRequest{Model: model})
982+
if err != nil {
983+
return nil, err
984+
}
985+
986+
var response listToolsResponse
987+
if err := json.Unmarshal(result, &response); err != nil {
988+
return nil, fmt.Errorf("failed to unmarshal tools response: %w", err)
989+
}
990+
991+
return response.Tools, nil
992+
}
993+
973994
// verifyProtocolVersion verifies that the server's protocol version matches the SDK's expected version
974995
func (c *Client) verifyProtocolVersion(ctx context.Context) error {
975996
expectedVersion := GetSdkProtocolVersion()

go/internal/e2e/client_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,4 +225,37 @@ func TestClient(t *testing.T) {
225225

226226
client.Stop()
227227
})
228+
229+
t.Run("should list tools", func(t *testing.T) {
230+
client := copilot.NewClient(&copilot.ClientOptions{
231+
CLIPath: cliPath,
232+
UseStdio: copilot.Bool(true),
233+
})
234+
t.Cleanup(func() { client.ForceStop() })
235+
236+
if err := client.Start(t.Context()); err != nil {
237+
t.Fatalf("Failed to start client: %v", err)
238+
}
239+
240+
tools, err := client.ListTools(t.Context(), "")
241+
if err != nil {
242+
t.Fatalf("Failed to list tools: %v", err)
243+
}
244+
245+
if len(tools) == 0 {
246+
t.Error("Expected at least one tool")
247+
}
248+
249+
if len(tools) > 0 {
250+
tool := tools[0]
251+
if tool.Name == "" {
252+
t.Error("Expected tool.Name to be non-empty")
253+
}
254+
if tool.Description == "" {
255+
t.Error("Expected tool.Description to be non-empty")
256+
}
257+
}
258+
259+
client.Stop()
260+
})
228261
}

go/types.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,15 @@ type ModelInfo struct {
541541
DefaultReasoningEffort string `json:"defaultReasoningEffort,omitempty"`
542542
}
543543

544+
// ToolInfo contains information about an available built-in tool
545+
type ToolInfo struct {
546+
Name string `json:"name"`
547+
NamespacedName string `json:"namespacedName,omitempty"`
548+
Description string `json:"description"`
549+
Parameters map[string]interface{} `json:"parameters,omitempty"`
550+
Instructions string `json:"instructions,omitempty"`
551+
}
552+
544553
// SessionMetadata contains metadata about a session
545554
type SessionMetadata struct {
546555
SessionID string `json:"sessionId"`
@@ -733,6 +742,16 @@ type listModelsResponse struct {
733742
Models []ModelInfo `json:"models"`
734743
}
735744

745+
// listToolsRequest is the request for tools.list
746+
type listToolsRequest struct {
747+
Model string `json:"model,omitempty"`
748+
}
749+
750+
// listToolsResponse is the response from tools.list
751+
type listToolsResponse struct {
752+
Tools []ToolInfo `json:"tools"`
753+
}
754+
736755
// sessionGetMessagesRequest is the request for session.getMessages
737756
type sessionGetMessagesRequest struct {
738757
SessionID string `json:"sessionId"`

nodejs/package-lock.json

Lines changed: 28 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nodejs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"author": "GitHub",
4141
"license": "MIT",
4242
"dependencies": {
43-
"@github/copilot": "^0.0.405",
43+
"@github/copilot": "^0.0.407",
4444
"vscode-jsonrpc": "^8.2.1",
4545
"zod": "^4.3.6"
4646
},

nodejs/src/client.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import type {
4242
ToolCallRequestPayload,
4343
ToolCallResponsePayload,
4444
ToolHandler,
45+
ToolInfo,
4546
ToolResult,
4647
ToolResultObject,
4748
TypedSessionLifecycleHandler,
@@ -721,6 +722,24 @@ export class CopilotClient {
721722
}
722723
}
723724

725+
/**
726+
* List available built-in tools with their metadata.
727+
*
728+
* Returns the list of tools available in the runtime, optionally filtered
729+
* by model-specific overrides when a model ID is provided.
730+
*
731+
* @param model - Optional model ID to get model-specific tool overrides
732+
*/
733+
async listTools(model?: string): Promise<ToolInfo[]> {
734+
if (!this.connection) {
735+
throw new Error("Client not connected");
736+
}
737+
738+
const result = await this.connection.sendRequest("tools.list", { model });
739+
const response = result as { tools: ToolInfo[] };
740+
return response.tools;
741+
}
742+
724743
/**
725744
* Verify that the server's protocol version matches the SDK's expected version
726745
*/

nodejs/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export type {
4545
SystemMessageReplaceConfig,
4646
Tool,
4747
ToolHandler,
48+
ToolInfo,
4849
ToolInvocation,
4950
ToolResultObject,
5051
TypedSessionEventHandler,

0 commit comments

Comments
 (0)