Skip to content
Merged
Show file tree
Hide file tree
Changes from 67 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
6ff1e04
chore: Add Transport interface and Toolbox Transport
dishaprakash Dec 3, 2025
f3a3380
increase code coverage
dishaprakash Dec 3, 2025
5b0f494
add tests for types
dishaprakash Dec 3, 2025
cd44f53
linter fix
dishaprakash Dec 3, 2025
b9631d3
add build tags
dishaprakash Dec 3, 2025
14f6932
add static check
dishaprakash Dec 17, 2025
a597df7
review changes
dishaprakash Jan 1, 2026
cc4d679
update header to 2026
dishaprakash Jan 1, 2026
c09566b
fix test
dishaprakash Jan 1, 2026
6e32016
fix tests
dishaprakash Jan 1, 2026
4b3358a
change tokensources to resolved headers
dishaprakash Jan 6, 2026
e43bc0e
minor fix
dishaprakash Jan 6, 2026
792848b
feat: Add MCP Transport version 2024-11-05
dishaprakash Dec 16, 2025
2c5c6ea
add headers
dishaprakash Dec 16, 2025
b5ff80c
increase code coverage
dishaprakash Dec 16, 2025
ecf2755
seperate constants
dishaprakash Dec 17, 2025
74374ee
minor fix
dishaprakash Dec 17, 2025
40c9849
Add strict validation and increase code coverage
dishaprakash Dec 17, 2025
22e4ac5
allow 202/204 status code for notifications
dishaprakash Dec 17, 2025
c81aa0d
standardize naming convention and docstring
dishaprakash Dec 18, 2025
749689a
use version.txt
dishaprakash Dec 18, 2025
440c88c
add version.go in mcp package
dishaprakash Dec 18, 2025
4a8cceb
fix tests
dishaprakash Dec 18, 2025
6e51794
add headers
dishaprakash Dec 18, 2025
c556994
review changes
dishaprakash Dec 18, 2025
e571c83
add build tag
dishaprakash Dec 18, 2025
5c13444
review changes and change headers to 2026
dishaprakash Jan 1, 2026
6459ebf
fix tests
dishaprakash Jan 1, 2026
90bacf6
better error
dishaprakash Jan 1, 2026
d7a5322
fix tests
dishaprakash Jan 1, 2026
bf36813
change tokensources into resolved strings
dishaprakash Jan 6, 2026
2f063eb
minor fix
dishaprakash Jan 6, 2026
20195db
chore(deps): update all non-major dependencies (#127)
renovate-bot Dec 16, 2025
632544e
feat: Add MCP Transport version 2025-03-26
dishaprakash Dec 17, 2025
eb7237b
undo
dishaprakash Dec 17, 2025
bed9222
allow 202/204 status codes for notifications
dishaprakash Dec 17, 2025
7d6a515
fetch mcp session id from header
dishaprakash Dec 17, 2025
cdd649a
refactor
dishaprakash Dec 18, 2025
fd56504
minor fix
dishaprakash Dec 18, 2025
e2196f4
fix tests
dishaprakash Dec 18, 2025
638c855
code comment
dishaprakash Dec 18, 2025
f915b63
better error
dishaprakash Jan 1, 2026
086be8d
better error
dishaprakash Jan 1, 2026
19182c1
fix tests
dishaprakash Jan 1, 2026
3b0ed14
fetch session id through header
dishaprakash Jan 5, 2026
d9a1d1e
change tokensources into resolved strings
dishaprakash Jan 6, 2026
3a82314
chore(deps): update module google.golang.org/adk to v0.3.0 (#130)
renovate-bot Dec 17, 2025
fe8d822
feat: Add MCP Transport version 2025-06-18
dishaprakash Dec 18, 2025
6a2b8e2
code comment
dishaprakash Dec 18, 2025
e4e8e7a
better error
dishaprakash Jan 1, 2026
eb23431
fix tests
dishaprakash Jan 1, 2026
91939a2
add accept header
dishaprakash Jan 5, 2026
028b426
change tokensources into resolved strings
dishaprakash Jan 6, 2026
dcab6b2
feat: Add support for MCP protocol in core SDK
dishaprakash Dec 18, 2025
60bf503
fix tests
dishaprakash Dec 18, 2025
fe43aab
fix tests
dishaprakash Dec 18, 2025
99b2ea7
fix error msg
dishaprakash Dec 18, 2025
3d062b0
add release-please auto update to sdk version
dishaprakash Dec 18, 2025
3bd4b92
fix tests
dishaprakash Dec 18, 2025
8cf91f1
fix tests
dishaprakash Dec 18, 2025
433c436
fix tests
dishaprakash Dec 20, 2025
e0ddb88
fix tests
dishaprakash Dec 23, 2025
54d02e3
fix tests
dishaprakash Dec 23, 2025
0696603
fix error handling
dishaprakash Jan 1, 2026
361d09e
add headers check in e2e test
dishaprakash Jan 5, 2026
c9e4b5c
change tokensources into resolved strings
dishaprakash Jan 6, 2026
156604a
test default mcp
dishaprakash Jan 6, 2026
08f60be
fix test failure
dishaprakash Jan 12, 2026
ab13b6a
fix error
dishaprakash Jan 12, 2026
85cb43d
fix error
dishaprakash Jan 12, 2026
e7ba658
Merge branch 'main' into integrate-transport
dishaprakash Jan 12, 2026
9360dd7
Merge branch 'main' into integrate-transport
dishaprakash Jan 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@
handleGHRelease: true
packageName: mcp-toolbox-sdk-go
releaseType: go
extraFiles: [
"core/transport/mcp/version.go",
]
66 changes: 45 additions & 21 deletions core/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,24 @@ package core
import (
"context"
"fmt"
"log"
"net/http"
"strings"

"github.com/googleapis/mcp-toolbox-sdk-go/core/transport"
mcp20241105 "github.com/googleapis/mcp-toolbox-sdk-go/core/transport/mcp/v20241105"
mcp20250326 "github.com/googleapis/mcp-toolbox-sdk-go/core/transport/mcp/v20250326"
mcp20250618 "github.com/googleapis/mcp-toolbox-sdk-go/core/transport/mcp/v20250618"
"github.com/googleapis/mcp-toolbox-sdk-go/core/transport/toolboxtransport"
"golang.org/x/oauth2"
)

// The synchronous interface for a Toolbox service client.
type ToolboxClient struct {
baseURL string
httpClient *http.Client
protocol Protocol
protocolSet bool
transport transport.Transport
clientHeaderSources map[string]oauth2.TokenSource
defaultToolOptions []ToolOption
defaultOptionsSet bool
Expand All @@ -39,17 +46,19 @@ type ToolboxClient struct {
// Inputs:
// - url: The base URL of the Toolbox server.
// - opts: A variadic list of ClientOption functions to configure the client,
// such as setting a custom http.Client or default headers.
// such as setting a custom http.Client, default headers, or the underlying protocol.
//
// Returns:
//
// A configured *ToolboxClient and a nil error on success, or a nil client
// and an error if configuration fails.
func NewToolboxClient(url string, opts ...ClientOption) (*ToolboxClient, error) {
// Initialize the client with default values.
// We default to ProtocolMCP (the newest version alias) if not overridden.
tc := &ToolboxClient{
baseURL: url,
httpClient: &http.Client{},
protocol: MCP, // Default
clientHeaderSources: make(map[string]oauth2.TokenSource),
defaultToolOptions: []ToolOption{},
}
Expand All @@ -64,7 +73,22 @@ func NewToolboxClient(url string, opts ...ClientOption) (*ToolboxClient, error)
}
}

return tc, nil
// Initialize the Transport based on the selected Protocol.
var transportErr error = nil
switch tc.protocol {
case MCPv20250618:
tc.transport, transportErr = mcp20250618.New(tc.baseURL, tc.httpClient)
case MCPv20250326:
tc.transport, transportErr = mcp20250326.New(tc.baseURL, tc.httpClient)
case MCPv20241105:
tc.transport, transportErr = mcp20241105.New(tc.baseURL, tc.httpClient)
case Toolbox:
tc.transport = toolboxtransport.New(tc.baseURL, tc.httpClient)
default:
return nil, fmt.Errorf("unsupported protocol version: %s", tc.protocol)
}

return tc, transportErr
}

// newToolboxTool is an internal factory method that constructs a
Expand All @@ -87,6 +111,7 @@ func (tc *ToolboxClient) newToolboxTool(
schema ToolSchema,
finalConfig *ToolConfig,
isStrict bool,
tr transport.Transport,
) (*ToolboxTool, []string, []string, error) {

// These will be the parameters that the end-user must provide at invocation time.
Expand Down Expand Up @@ -151,17 +176,12 @@ func (tc *ToolboxClient) newToolboxTool(
finalConfig.AuthTokenSources,
)

if (len(remainingAuthnParams) > 0 || len(remainingAuthzTokens) > 0 || len(tc.clientHeaderSources) > 0) && !strings.HasPrefix(tc.baseURL, "https://") {
log.Println("WARNING: Sending ID token over HTTP. User data may be exposed. Use HTTPS for secure communication.")
}

// Construct the final tool object.
tt := &ToolboxTool{
name: name,
description: schema.Description,
parameters: finalParameters,
invocationURL: fmt.Sprintf("%s/api/tool/%s%s", tc.baseURL, name, toolInvokeSuffix),
httpClient: tc.httpClient,
transport: tr,
authTokenSources: finalConfig.AuthTokenSources,
boundParams: localBoundParams,
requiredAuthnParams: remainingAuthnParams,
Expand Down Expand Up @@ -204,9 +224,14 @@ func (tc *ToolboxClient) LoadTool(name string, ctx context.Context, opts ...Tool
}
}

resolvedHeaders, err := resolveClientHeaders(tc.clientHeaderSources)
if err != nil {
return nil, err
}

// Fetch the manifest for the specified tool.
url := fmt.Sprintf("%s/api/tool/%s", tc.baseURL, name)
manifest, err := loadManifest(ctx, url, tc.httpClient, tc.clientHeaderSources)
manifest, err := tc.transport.GetTool(ctx, name, resolvedHeaders)

if err != nil {
return nil, fmt.Errorf("failed to load tool manifest for '%s': %w", name, err)
}
Expand All @@ -219,7 +244,7 @@ func (tc *ToolboxClient) LoadTool(name string, ctx context.Context, opts ...Tool
}

// Construct the tool from its schema and the final configuration.
tool, usedAuthKeys, usedBoundKeys, err := tc.newToolboxTool(name, schema, finalConfig, true)
tool, usedAuthKeys, usedBoundKeys, err := tc.newToolboxTool(name, schema, finalConfig, true, tc.transport)
if err != nil {
return nil, fmt.Errorf("failed to create toolbox tool from schema for '%s': %w", name, err)
}
Expand Down Expand Up @@ -291,15 +316,14 @@ func (tc *ToolboxClient) LoadToolset(name string, ctx context.Context, opts ...T
}
}

// Determine the manifest URL based on whether a specific toolset name was provided.
var url string
if name == "" {
url = fmt.Sprintf("%s/api/toolset/", tc.baseURL)
} else {
url = fmt.Sprintf("%s/api/toolset/%s", tc.baseURL, name)
}
// Fetch the manifest for the toolset.
manifest, err := loadManifest(ctx, url, tc.httpClient, tc.clientHeaderSources)
resolvedHeaders, err := resolveClientHeaders(tc.clientHeaderSources)
if err != nil {
return nil, err
}

// Fetch Manifest via Transport
manifest, err := tc.transport.ListTools(ctx, name, resolvedHeaders)
if err != nil {
return nil, fmt.Errorf("failed to load toolset manifest for '%s': %w", name, err)
}
Expand All @@ -322,7 +346,7 @@ func (tc *ToolboxClient) LoadToolset(name string, ctx context.Context, opts ...T

for toolName, schema := range manifest.Tools {
// Construct each tool from its schema and the shared configuration.
tool, usedAuthKeys, usedBoundKeys, err := tc.newToolboxTool(toolName, schema, finalConfig, finalConfig.Strict)
tool, usedAuthKeys, usedBoundKeys, err := tc.newToolboxTool(toolName, schema, finalConfig, finalConfig.Strict, tc.transport)
if err != nil {
return nil, fmt.Errorf("failed to create tool '%s': %w", toolName, err)
}
Expand Down
47 changes: 19 additions & 28 deletions core/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ func TestNewToolboxClient(t *testing.T) {
if client.httpClient.Timeout != 0 {
t.Errorf("expected no timeout, got %v", client.httpClient.Timeout)
}

if client.protocol != ProtocolMCP {
t.Errorf("expected default protocol to be ProtocolMCP, got %v", client.protocol)
}

})

t.Run("Returns error when a nil option is provided", func(t *testing.T) {
Expand Down Expand Up @@ -259,7 +264,7 @@ func TestLoadToolAndLoadToolset(t *testing.T) {
defer server.Close()

t.Run("LoadTool - Success", func(t *testing.T) {
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()))
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()), WithProtocol(Toolbox))
tool, err := client.LoadTool("toolA",
context.Background(),
WithBindParamString("param1", "value1"),
Expand All @@ -274,7 +279,7 @@ func TestLoadToolAndLoadToolset(t *testing.T) {
})

t.Run("LoadTool - Negative Test - Unused bound parameter", func(t *testing.T) {
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()))
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()), WithProtocol(Toolbox))
_, err := client.LoadTool("toolA",
context.Background(),
WithBindParamString("param1", "value1"),
Expand All @@ -289,7 +294,7 @@ func TestLoadToolAndLoadToolset(t *testing.T) {
})

t.Run("LoadToolset - Success with non-strict mode", func(t *testing.T) {
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()))
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()), WithProtocol(Toolbox))
tools, err := client.LoadToolset(
"",
context.Background(),
Expand All @@ -306,7 +311,7 @@ func TestLoadToolAndLoadToolset(t *testing.T) {
})

t.Run("LoadToolset - Negative Test - Unused parameter in non-strict mode", func(t *testing.T) {
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()))
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()), WithProtocol(Toolbox))
_, err := client.LoadToolset(
"",
context.Background(),
Expand All @@ -322,7 +327,7 @@ func TestLoadToolAndLoadToolset(t *testing.T) {
})

t.Run("LoadToolset - Negative Test - Unused parameter in strict mode", func(t *testing.T) {
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()))
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()), WithProtocol(Toolbox))
_, err := client.LoadToolset(
"",
context.Background(),
Expand Down Expand Up @@ -434,7 +439,7 @@ func TestNegativeAndEdgeCases(t *testing.T) {

t.Run("LoadTool fails when a nil ToolOption is provided", func(t *testing.T) {

client, _ := NewToolboxClient(server.URL)
client, _ := NewToolboxClient(server.URL, WithProtocol(Toolbox))
_, err := client.LoadTool("any-tool", context.Background(), nil)
if err == nil {
t.Fatal("Expected an error when a nil option is passed to LoadTool, but got nil")
Expand Down Expand Up @@ -474,7 +479,7 @@ func TestNegativeAndEdgeCases(t *testing.T) {
}))
defer serverWithNoTools.Close()

client, _ := NewToolboxClient(serverWithNoTools.URL, WithHTTPClient(serverWithNoTools.Client()))
client, _ := NewToolboxClient(serverWithNoTools.URL, WithHTTPClient(serverWithNoTools.Client()), WithProtocol(Toolbox))

// This call would panic if the code doesn't check for a nil map.
_, err := client.LoadTool("any-tool", context.Background())
Expand Down Expand Up @@ -567,25 +572,11 @@ func TestLoadToolAndLoadToolset_ErrorPaths(t *testing.T) {
log.SetOutput(&buf)
defer log.SetOutput(originalOutput)

t.Run("logs warning for HTTP with headers", func(t *testing.T) {
buf.Reset()

client, _ := NewToolboxClient(server.URL,
WithHTTPClient(server.Client()),
)

_, _ = client.LoadTool("toolA", context.Background())

expectedLog := "WARNING: Sending ID token over HTTP"
if !strings.Contains(buf.String(), expectedLog) {
t.Errorf("expected log message '%s' not found in output: '%s'", expectedLog, buf.String())
}
})

t.Run("LoadTool fails when a default option is invalid", func(t *testing.T) {
// Setup client with duplicate default options
client, _ := NewToolboxClient(server.URL,
WithHTTPClient(server.Client()),
WithProtocol(Toolbox),
WithDefaultToolOptions(
WithStrict(true),
WithStrict(false),
Expand All @@ -605,7 +596,7 @@ func TestLoadToolAndLoadToolset_ErrorPaths(t *testing.T) {
})

t.Run("LoadTool fails when tool is not in the manifest", func(t *testing.T) {
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()))
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()), WithProtocol(Toolbox))
_, err := client.LoadTool("tool-that-does-not-exist", context.Background())

if err == nil {
Expand All @@ -621,7 +612,7 @@ func TestLoadToolAndLoadToolset_ErrorPaths(t *testing.T) {
errorServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
errorServer.Close()

client, _ := NewToolboxClient(errorServer.URL, WithHTTPClient(errorServer.Client()))
client, _ := NewToolboxClient(errorServer.URL, WithHTTPClient(errorServer.Client()), WithProtocol(Toolbox))
_, err := client.LoadTool("any-tool", context.Background())

if err == nil {
Expand All @@ -633,7 +624,7 @@ func TestLoadToolAndLoadToolset_ErrorPaths(t *testing.T) {
})

t.Run("LoadTool fails with unused auth tokens", func(t *testing.T) {
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()))
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()), WithProtocol(Toolbox))
_, err := client.LoadTool("toolA", context.Background(),
WithAuthTokenString("unused-auth", "token"), // This auth is not needed by toolA
)
Expand All @@ -646,7 +637,7 @@ func TestLoadToolAndLoadToolset_ErrorPaths(t *testing.T) {
})

t.Run("LoadTool fails with unused bound parameters", func(t *testing.T) {
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()))
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()), WithProtocol(Toolbox))
_, err := client.LoadTool("toolA", context.Background(),
WithBindParamString("unused-param", "value"), // This param is not defined on toolA
)
Expand All @@ -661,7 +652,7 @@ func TestLoadToolAndLoadToolset_ErrorPaths(t *testing.T) {
})

t.Run("LoadToolset fails with unused parameters in strict mode", func(t *testing.T) {
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()))
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()), WithProtocol(Toolbox))
_, err := client.LoadToolset(
"",
context.Background(),
Expand All @@ -679,7 +670,7 @@ func TestLoadToolAndLoadToolset_ErrorPaths(t *testing.T) {
})

t.Run("LoadToolset fails with unused parameters in non-strict mode", func(t *testing.T) {
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()))
client, _ := NewToolboxClient(server.URL, WithHTTPClient(server.Client()), WithProtocol(Toolbox))
_, err := client.LoadToolset(
"",
context.Background(),
Expand Down
Loading
Loading