mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-05-02 03:00:36 -04:00
feat: add new v20251125 version (#2303)
Add new `v20251125` specs for MCP. https://modelcontextprotocol.io/specification/2025-11-25
This commit is contained in:
@@ -20,6 +20,7 @@ The native SDKs can be combined with MCP clients in many cases.
|
|||||||
|
|
||||||
Toolbox currently supports the following versions of MCP specification:
|
Toolbox currently supports the following versions of MCP specification:
|
||||||
|
|
||||||
|
* [2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25)
|
||||||
* [2025-06-18](https://modelcontextprotocol.io/specification/2025-06-18)
|
* [2025-06-18](https://modelcontextprotocol.io/specification/2025-06-18)
|
||||||
* [2025-03-26](https://modelcontextprotocol.io/specification/2025-03-26)
|
* [2025-03-26](https://modelcontextprotocol.io/specification/2025-03-26)
|
||||||
* [2024-11-05](https://modelcontextprotocol.io/specification/2024-11-05)
|
* [2024-11-05](https://modelcontextprotocol.io/specification/2024-11-05)
|
||||||
|
|||||||
@@ -27,19 +27,21 @@ import (
|
|||||||
v20241105 "github.com/googleapis/genai-toolbox/internal/server/mcp/v20241105"
|
v20241105 "github.com/googleapis/genai-toolbox/internal/server/mcp/v20241105"
|
||||||
v20250326 "github.com/googleapis/genai-toolbox/internal/server/mcp/v20250326"
|
v20250326 "github.com/googleapis/genai-toolbox/internal/server/mcp/v20250326"
|
||||||
v20250618 "github.com/googleapis/genai-toolbox/internal/server/mcp/v20250618"
|
v20250618 "github.com/googleapis/genai-toolbox/internal/server/mcp/v20250618"
|
||||||
|
v20251125 "github.com/googleapis/genai-toolbox/internal/server/mcp/v20251125"
|
||||||
"github.com/googleapis/genai-toolbox/internal/server/resources"
|
"github.com/googleapis/genai-toolbox/internal/server/resources"
|
||||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LATEST_PROTOCOL_VERSION is the latest version of the MCP protocol supported.
|
// LATEST_PROTOCOL_VERSION is the latest version of the MCP protocol supported.
|
||||||
// Update the version used in InitializeResponse when this value is updated.
|
// Update the version used in InitializeResponse when this value is updated.
|
||||||
const LATEST_PROTOCOL_VERSION = v20250618.PROTOCOL_VERSION
|
const LATEST_PROTOCOL_VERSION = v20251125.PROTOCOL_VERSION
|
||||||
|
|
||||||
// SUPPORTED_PROTOCOL_VERSIONS is the MCP protocol versions that are supported.
|
// SUPPORTED_PROTOCOL_VERSIONS is the MCP protocol versions that are supported.
|
||||||
var SUPPORTED_PROTOCOL_VERSIONS = []string{
|
var SUPPORTED_PROTOCOL_VERSIONS = []string{
|
||||||
v20241105.PROTOCOL_VERSION,
|
v20241105.PROTOCOL_VERSION,
|
||||||
v20250326.PROTOCOL_VERSION,
|
v20250326.PROTOCOL_VERSION,
|
||||||
v20250618.PROTOCOL_VERSION,
|
v20250618.PROTOCOL_VERSION,
|
||||||
|
v20251125.PROTOCOL_VERSION,
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitializeResponse runs capability negotiation and protocol version agreement.
|
// InitializeResponse runs capability negotiation and protocol version agreement.
|
||||||
@@ -102,6 +104,8 @@ func NotificationHandler(ctx context.Context, body []byte) error {
|
|||||||
// This is the Operation phase of the lifecycle for MCP client-server connections.
|
// This is the Operation phase of the lifecycle for MCP client-server connections.
|
||||||
func ProcessMethod(ctx context.Context, mcpVersion string, id jsonrpc.RequestId, method string, toolset tools.Toolset, promptset prompts.Promptset, resourceMgr *resources.ResourceManager, body []byte, header http.Header) (any, error) {
|
func ProcessMethod(ctx context.Context, mcpVersion string, id jsonrpc.RequestId, method string, toolset tools.Toolset, promptset prompts.Promptset, resourceMgr *resources.ResourceManager, body []byte, header http.Header) (any, error) {
|
||||||
switch mcpVersion {
|
switch mcpVersion {
|
||||||
|
case v20251125.PROTOCOL_VERSION:
|
||||||
|
return v20251125.ProcessMethod(ctx, id, method, toolset, promptset, resourceMgr, body, header)
|
||||||
case v20250618.PROTOCOL_VERSION:
|
case v20250618.PROTOCOL_VERSION:
|
||||||
return v20250618.ProcessMethod(ctx, id, method, toolset, promptset, resourceMgr, body, header)
|
return v20250618.ProcessMethod(ctx, id, method, toolset, promptset, resourceMgr, body, header)
|
||||||
case v20250326.PROTOCOL_VERSION:
|
case v20250326.PROTOCOL_VERSION:
|
||||||
|
|||||||
326
internal/server/mcp/v20251125/method.go
Normal file
326
internal/server/mcp/v20251125/method.go
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package v20251125
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/prompts"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/server/resources"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessMethod returns a response for the request.
|
||||||
|
func ProcessMethod(ctx context.Context, id jsonrpc.RequestId, method string, toolset tools.Toolset, promptset prompts.Promptset, resourceMgr *resources.ResourceManager, body []byte, header http.Header) (any, error) {
|
||||||
|
switch method {
|
||||||
|
case PING:
|
||||||
|
return pingHandler(id)
|
||||||
|
case TOOLS_LIST:
|
||||||
|
return toolsListHandler(id, toolset, body)
|
||||||
|
case TOOLS_CALL:
|
||||||
|
return toolsCallHandler(ctx, id, resourceMgr, body, header)
|
||||||
|
case PROMPTS_LIST:
|
||||||
|
return promptsListHandler(ctx, id, promptset, body)
|
||||||
|
case PROMPTS_GET:
|
||||||
|
return promptsGetHandler(ctx, id, resourceMgr, body)
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("invalid method %s", method)
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.METHOD_NOT_FOUND, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pingHandler handles the "ping" method by returning an empty response.
|
||||||
|
func pingHandler(id jsonrpc.RequestId) (any, error) {
|
||||||
|
return jsonrpc.JSONRPCResponse{
|
||||||
|
Jsonrpc: jsonrpc.JSONRPC_VERSION,
|
||||||
|
Id: id,
|
||||||
|
Result: struct{}{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toolsListHandler(id jsonrpc.RequestId, toolset tools.Toolset, body []byte) (any, error) {
|
||||||
|
var req ListToolsRequest
|
||||||
|
if err := json.Unmarshal(body, &req); err != nil {
|
||||||
|
err = fmt.Errorf("invalid mcp tools list request: %w", err)
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ListToolsResult{
|
||||||
|
Tools: toolset.McpManifest,
|
||||||
|
}
|
||||||
|
return jsonrpc.JSONRPCResponse{
|
||||||
|
Jsonrpc: jsonrpc.JSONRPC_VERSION,
|
||||||
|
Id: id,
|
||||||
|
Result: result,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toolsCallHandler generate a response for tools call.
|
||||||
|
func toolsCallHandler(ctx context.Context, id jsonrpc.RequestId, resourceMgr *resources.ResourceManager, body []byte, header http.Header) (any, error) {
|
||||||
|
authServices := resourceMgr.GetAuthServiceMap()
|
||||||
|
|
||||||
|
// retrieve logger from context
|
||||||
|
logger, err := util.LoggerFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
|
||||||
|
var req CallToolRequest
|
||||||
|
if err = json.Unmarshal(body, &req); err != nil {
|
||||||
|
err = fmt.Errorf("invalid mcp tools call request: %w", err)
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
|
||||||
|
toolName := req.Params.Name
|
||||||
|
toolArgument := req.Params.Arguments
|
||||||
|
logger.DebugContext(ctx, fmt.Sprintf("tool name: %s", toolName))
|
||||||
|
tool, ok := resourceMgr.GetTool(toolName)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("invalid tool name: tool with name %q does not exist", toolName)
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INVALID_PARAMS, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get access token
|
||||||
|
authTokenHeadername, err := tool.GetAuthTokenHeaderName(resourceMgr)
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Errorf("error during invocation: %w", err)
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, errMsg.Error(), nil), errMsg
|
||||||
|
}
|
||||||
|
accessToken := tools.AccessToken(header.Get(authTokenHeadername))
|
||||||
|
|
||||||
|
// Check if this specific tool requires the standard authorization header
|
||||||
|
clientAuth, err := tool.RequiresClientAuthorization(resourceMgr)
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Errorf("error during invocation: %w", err)
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, errMsg.Error(), nil), errMsg
|
||||||
|
}
|
||||||
|
if clientAuth {
|
||||||
|
if accessToken == "" {
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, "missing access token in the 'Authorization' header", nil), util.ErrUnauthorized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshal arguments and decode it using decodeJSON instead to prevent loss between floats/int.
|
||||||
|
aMarshal, err := json.Marshal(toolArgument)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("unable to marshal tools argument: %w", err)
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
|
||||||
|
var data map[string]any
|
||||||
|
if err = util.DecodeJSON(bytes.NewBuffer(aMarshal), &data); err != nil {
|
||||||
|
err = fmt.Errorf("unable to decode tools argument: %w", err)
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tool authentication
|
||||||
|
// claimsFromAuth maps the name of the authservice to the claims retrieved from it.
|
||||||
|
claimsFromAuth := make(map[string]map[string]any)
|
||||||
|
|
||||||
|
// if using stdio, header will be nil and auth will not be supported
|
||||||
|
if header != nil {
|
||||||
|
for _, aS := range authServices {
|
||||||
|
claims, err := aS.GetClaimsFromHeader(ctx, header)
|
||||||
|
if err != nil {
|
||||||
|
logger.DebugContext(ctx, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if claims == nil {
|
||||||
|
// authService not present in header
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
claimsFromAuth[aS.GetName()] = claims
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tool authorization check
|
||||||
|
verifiedAuthServices := make([]string, len(claimsFromAuth))
|
||||||
|
i := 0
|
||||||
|
for k := range claimsFromAuth {
|
||||||
|
verifiedAuthServices[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any of the specified auth services is verified
|
||||||
|
isAuthorized := tool.Authorized(verifiedAuthServices)
|
||||||
|
if !isAuthorized {
|
||||||
|
err = fmt.Errorf("unauthorized Tool call: Please make sure your specify correct auth headers: %w", util.ErrUnauthorized)
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
logger.DebugContext(ctx, "tool invocation authorized")
|
||||||
|
|
||||||
|
params, err := tool.ParseParams(data, claimsFromAuth)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("provided parameters were invalid: %w", err)
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INVALID_PARAMS, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
logger.DebugContext(ctx, fmt.Sprintf("invocation params: %s", params))
|
||||||
|
|
||||||
|
// run tool invocation and generate response.
|
||||||
|
results, err := tool.Invoke(ctx, resourceMgr, params, accessToken)
|
||||||
|
if err != nil {
|
||||||
|
errStr := err.Error()
|
||||||
|
// Missing authService tokens.
|
||||||
|
if errors.Is(err, util.ErrUnauthorized) {
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
// Upstream auth error
|
||||||
|
if strings.Contains(errStr, "Error 401") || strings.Contains(errStr, "Error 403") {
|
||||||
|
if clientAuth {
|
||||||
|
// Error with client credentials should pass down to the client
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
// Auth error with ADC should raise internal 500 error
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
text := TextContent{
|
||||||
|
Type: "text",
|
||||||
|
Text: err.Error(),
|
||||||
|
}
|
||||||
|
return jsonrpc.JSONRPCResponse{
|
||||||
|
Jsonrpc: jsonrpc.JSONRPC_VERSION,
|
||||||
|
Id: id,
|
||||||
|
Result: CallToolResult{Content: []TextContent{text}, IsError: true},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
content := make([]TextContent, 0)
|
||||||
|
|
||||||
|
sliceRes, ok := results.([]any)
|
||||||
|
if !ok {
|
||||||
|
sliceRes = []any{results}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range sliceRes {
|
||||||
|
text := TextContent{Type: "text"}
|
||||||
|
dM, err := json.Marshal(d)
|
||||||
|
if err != nil {
|
||||||
|
text.Text = fmt.Sprintf("fail to marshal: %s, result: %s", err, d)
|
||||||
|
} else {
|
||||||
|
text.Text = string(dM)
|
||||||
|
}
|
||||||
|
content = append(content, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonrpc.JSONRPCResponse{
|
||||||
|
Jsonrpc: jsonrpc.JSONRPC_VERSION,
|
||||||
|
Id: id,
|
||||||
|
Result: CallToolResult{Content: content},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// promptsListHandler handles the "prompts/list" method.
|
||||||
|
func promptsListHandler(ctx context.Context, id jsonrpc.RequestId, promptset prompts.Promptset, body []byte) (any, error) {
|
||||||
|
// retrieve logger from context
|
||||||
|
logger, err := util.LoggerFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
logger.DebugContext(ctx, "handling prompts/list request")
|
||||||
|
|
||||||
|
var req ListPromptsRequest
|
||||||
|
if err := json.Unmarshal(body, &req); err != nil {
|
||||||
|
err = fmt.Errorf("invalid mcp prompts list request: %w", err)
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ListPromptsResult{
|
||||||
|
Prompts: promptset.McpManifest,
|
||||||
|
}
|
||||||
|
logger.DebugContext(ctx, fmt.Sprintf("returning %d prompts", len(promptset.McpManifest)))
|
||||||
|
return jsonrpc.JSONRPCResponse{
|
||||||
|
Jsonrpc: jsonrpc.JSONRPC_VERSION,
|
||||||
|
Id: id,
|
||||||
|
Result: result,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// promptsGetHandler handles the "prompts/get" method.
|
||||||
|
func promptsGetHandler(ctx context.Context, id jsonrpc.RequestId, resourceMgr *resources.ResourceManager, body []byte) (any, error) {
|
||||||
|
// retrieve logger from context
|
||||||
|
logger, err := util.LoggerFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
logger.DebugContext(ctx, "handling prompts/get request")
|
||||||
|
|
||||||
|
var req GetPromptRequest
|
||||||
|
if err := json.Unmarshal(body, &req); err != nil {
|
||||||
|
err = fmt.Errorf("invalid mcp prompts/get request: %w", err)
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
|
||||||
|
promptName := req.Params.Name
|
||||||
|
logger.DebugContext(ctx, fmt.Sprintf("prompt name: %s", promptName))
|
||||||
|
prompt, ok := resourceMgr.GetPrompt(promptName)
|
||||||
|
if !ok {
|
||||||
|
err := fmt.Errorf("prompt with name %q does not exist", promptName)
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INVALID_PARAMS, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the arguments provided in the request.
|
||||||
|
argValues, err := prompt.ParseArgs(req.Params.Arguments, nil)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("invalid arguments for prompt %q: %w", promptName, err)
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INVALID_PARAMS, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
logger.DebugContext(ctx, fmt.Sprintf("parsed args: %v", argValues))
|
||||||
|
|
||||||
|
// Substitute the argument values into the prompt's messages.
|
||||||
|
substituted, err := prompt.SubstituteParams(argValues)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("error substituting params for prompt %q: %w", promptName, err)
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast the result to the expected []prompts.Message type.
|
||||||
|
substitutedMessages, ok := substituted.([]prompts.Message)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("internal error: SubstituteParams returned unexpected type")
|
||||||
|
return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
|
||||||
|
}
|
||||||
|
logger.DebugContext(ctx, "substituted params successfully")
|
||||||
|
|
||||||
|
// Format the response messages into the required structure.
|
||||||
|
promptMessages := make([]PromptMessage, len(substitutedMessages))
|
||||||
|
for i, msg := range substitutedMessages {
|
||||||
|
promptMessages[i] = PromptMessage{
|
||||||
|
Role: msg.Role,
|
||||||
|
Content: TextContent{
|
||||||
|
Type: "text",
|
||||||
|
Text: msg.Content,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := GetPromptResult{
|
||||||
|
Description: prompt.Manifest().Description,
|
||||||
|
Messages: promptMessages,
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonrpc.JSONRPCResponse{
|
||||||
|
Jsonrpc: jsonrpc.JSONRPC_VERSION,
|
||||||
|
Id: id,
|
||||||
|
Result: result,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
219
internal/server/mcp/v20251125/types.go
Normal file
219
internal/server/mcp/v20251125/types.go
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package v20251125
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/prompts"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SERVER_NAME is the server name used in Implementation.
|
||||||
|
const SERVER_NAME = "Toolbox"
|
||||||
|
|
||||||
|
// PROTOCOL_VERSION is the version of the MCP protocol in this package.
|
||||||
|
const PROTOCOL_VERSION = "2025-11-25"
|
||||||
|
|
||||||
|
// methods that are supported.
|
||||||
|
const (
|
||||||
|
PING = "ping"
|
||||||
|
TOOLS_LIST = "tools/list"
|
||||||
|
TOOLS_CALL = "tools/call"
|
||||||
|
PROMPTS_LIST = "prompts/list"
|
||||||
|
PROMPTS_GET = "prompts/get"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Empty result */
|
||||||
|
|
||||||
|
// EmptyResult represents a response that indicates success but carries no data.
|
||||||
|
type EmptyResult jsonrpc.Result
|
||||||
|
|
||||||
|
/* Pagination */
|
||||||
|
|
||||||
|
// Cursor is an opaque token used to represent a cursor for pagination.
|
||||||
|
type Cursor string
|
||||||
|
|
||||||
|
type PaginatedRequest struct {
|
||||||
|
jsonrpc.Request
|
||||||
|
Params struct {
|
||||||
|
// An opaque token representing the current pagination position.
|
||||||
|
// If provided, the server should return results starting after this cursor.
|
||||||
|
Cursor Cursor `json:"cursor,omitempty"`
|
||||||
|
} `json:"params,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaginatedResult struct {
|
||||||
|
jsonrpc.Result
|
||||||
|
// An opaque token representing the pagination position after the last returned result.
|
||||||
|
// If present, there may be more results available.
|
||||||
|
NextCursor Cursor `json:"nextCursor,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tools */
|
||||||
|
|
||||||
|
// Sent from the client to request a list of tools the server has.
|
||||||
|
type ListToolsRequest struct {
|
||||||
|
PaginatedRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
// The server's response to a tools/list request from the client.
|
||||||
|
type ListToolsResult struct {
|
||||||
|
PaginatedResult
|
||||||
|
Tools []tools.McpManifest `json:"tools"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used by the client to invoke a tool provided by the server.
|
||||||
|
type CallToolRequest struct {
|
||||||
|
jsonrpc.Request
|
||||||
|
Params struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Arguments map[string]any `json:"arguments,omitempty"`
|
||||||
|
} `json:"params,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// The sender or recipient of messages and data in a conversation.
|
||||||
|
type Role string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoleUser Role = "user"
|
||||||
|
RoleAssistant Role = "assistant"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Base for objects that include optional annotations for the client.
|
||||||
|
// The client can use annotations to inform how objects are used or displayed
|
||||||
|
type Annotated struct {
|
||||||
|
Annotations *struct {
|
||||||
|
// Describes who the intended customer of this object or data is.
|
||||||
|
// It can include multiple entries to indicate content useful for multiple
|
||||||
|
// audiences (e.g., `["user", "assistant"]`).
|
||||||
|
Audience []Role `json:"audience,omitempty"`
|
||||||
|
// Describes how important this data is for operating the server.
|
||||||
|
//
|
||||||
|
// A value of 1 means "most important," and indicates that the data is
|
||||||
|
// effectively required, while 0 means "least important," and indicates that
|
||||||
|
// the data is entirely optional.
|
||||||
|
//
|
||||||
|
// @TJS-type number
|
||||||
|
// @minimum 0
|
||||||
|
// @maximum 1
|
||||||
|
Priority float64 `json:"priority,omitempty"`
|
||||||
|
} `json:"annotations,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextContent represents text provided to or from an LLM.
|
||||||
|
type TextContent struct {
|
||||||
|
Annotated
|
||||||
|
Type string `json:"type"`
|
||||||
|
// The text content of the message.
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// The server's response to a tool call.
|
||||||
|
//
|
||||||
|
// Any errors that originate from the tool SHOULD be reported inside the result
|
||||||
|
// object, with `isError` set to true, _not_ as an MCP protocol-level error
|
||||||
|
// response. Otherwise, the LLM would not be able to see that an error occurred
|
||||||
|
// and self-correct.
|
||||||
|
//
|
||||||
|
// However, any errors in _finding_ the tool, an error indicating that the
|
||||||
|
// server does not support tool calls, or any other exceptional conditions,
|
||||||
|
// should be reported as an MCP error response.
|
||||||
|
type CallToolResult struct {
|
||||||
|
jsonrpc.Result
|
||||||
|
// Could be either a TextContent, ImageContent, or EmbeddedResources
|
||||||
|
// For Toolbox, we will only be sending TextContent
|
||||||
|
Content []TextContent `json:"content"`
|
||||||
|
// Whether the tool call ended in an error.
|
||||||
|
// If not set, this is assumed to be false (the call was successful).
|
||||||
|
//
|
||||||
|
// Any errors that originate from the tool SHOULD be reported inside the result
|
||||||
|
// object, with `isError` set to true, _not_ as an MCP protocol-level error
|
||||||
|
// response. Otherwise, the LLM would not be able to see that an error occurred
|
||||||
|
// and self-correct.
|
||||||
|
//
|
||||||
|
// However, any errors in _finding_ the tool, an error indicating that the
|
||||||
|
// server does not support tool calls, or any other exceptional conditions,
|
||||||
|
// should be reported as an MCP error response.
|
||||||
|
IsError bool `json:"isError,omitempty"`
|
||||||
|
// An optional JSON object that represents the structured result of the tool call.
|
||||||
|
StructuredContent map[string]any `json:"structuredContent,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional properties describing a Tool to clients.
|
||||||
|
//
|
||||||
|
// NOTE: all properties in ToolAnnotations are **hints**.
|
||||||
|
// They are not guaranteed to provide a faithful description of
|
||||||
|
// tool behavior (including descriptive properties like `title`).
|
||||||
|
//
|
||||||
|
// Clients should never make tool use decisions based on ToolAnnotations
|
||||||
|
// received from untrusted servers.
|
||||||
|
type ToolAnnotations struct {
|
||||||
|
// A human-readable title for the tool.
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
// If true, the tool does not modify its environment.
|
||||||
|
// Default: false
|
||||||
|
ReadOnlyHint bool `json:"readOnlyHint,omitempty"`
|
||||||
|
// If true, the tool may perform destructive updates to its environment.
|
||||||
|
// If false, the tool performs only additive updates.
|
||||||
|
// (This property is meaningful only when `readOnlyHint == false`)
|
||||||
|
// Default: true
|
||||||
|
DestructiveHint bool `json:"destructiveHint,omitempty"`
|
||||||
|
// If true, calling the tool repeatedly with the same arguments
|
||||||
|
// will have no additional effect on the its environment.
|
||||||
|
// (This property is meaningful only when `readOnlyHint == false`)
|
||||||
|
// Default: false
|
||||||
|
IdempotentHint bool `json:"idempotentHint,omitempty"`
|
||||||
|
// If true, this tool may interact with an "open world" of external
|
||||||
|
// entities. If false, the tool's domain of interaction is closed.
|
||||||
|
// For example, the world of a web search tool is open, whereas that
|
||||||
|
// of a memory tool is not.
|
||||||
|
// Default: true
|
||||||
|
OpenWorldHint bool `json:"openWorldHint,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prompts */
|
||||||
|
|
||||||
|
// Sent from the client to request a list of prompts the server has.
|
||||||
|
type ListPromptsRequest struct {
|
||||||
|
PaginatedRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
// The server's response to a prompts/list request from the client.
|
||||||
|
type ListPromptsResult struct {
|
||||||
|
PaginatedResult
|
||||||
|
Prompts []prompts.McpManifest `json:"prompts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used by the client to get a prompt provided by the server.
|
||||||
|
type GetPromptRequest struct {
|
||||||
|
jsonrpc.Request
|
||||||
|
Params struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Arguments map[string]any `json:"arguments,omitempty"`
|
||||||
|
} `json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// The server's response to a prompts/get request from the client.
|
||||||
|
type GetPromptResult struct {
|
||||||
|
jsonrpc.Result
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Messages []PromptMessage `json:"messages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describes a message returned as part of a prompt.
|
||||||
|
type PromptMessage struct {
|
||||||
|
Role string `json:"role"`
|
||||||
|
Content TextContent `json:"content"`
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ const jsonrpcVersion = "2.0"
|
|||||||
const protocolVersion20241105 = "2024-11-05"
|
const protocolVersion20241105 = "2024-11-05"
|
||||||
const protocolVersion20250326 = "2025-03-26"
|
const protocolVersion20250326 = "2025-03-26"
|
||||||
const protocolVersion20250618 = "2025-06-18"
|
const protocolVersion20250618 = "2025-06-18"
|
||||||
|
const protocolVersion20251125 = "2025-11-25"
|
||||||
const serverName = "Toolbox"
|
const serverName = "Toolbox"
|
||||||
|
|
||||||
var basicInputSchema = map[string]any{
|
var basicInputSchema = map[string]any{
|
||||||
@@ -485,6 +486,23 @@ func TestMcpEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "version 2025-11-25",
|
||||||
|
protocol: protocolVersion20251125,
|
||||||
|
idHeader: false,
|
||||||
|
initWant: map[string]any{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": "mcp-initialize",
|
||||||
|
"result": map[string]any{
|
||||||
|
"protocolVersion": "2025-11-25",
|
||||||
|
"capabilities": map[string]any{
|
||||||
|
"tools": map[string]any{"listChanged": false},
|
||||||
|
"prompts": map[string]any{"listChanged": false},
|
||||||
|
},
|
||||||
|
"serverInfo": map[string]any{"name": serverName, "version": fakeVersionString},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, vtc := range versTestCases {
|
for _, vtc := range versTestCases {
|
||||||
t.Run(vtc.name, func(t *testing.T) {
|
t.Run(vtc.name, func(t *testing.T) {
|
||||||
@@ -494,8 +512,7 @@ func TestMcpEndpoint(t *testing.T) {
|
|||||||
if sessionId != "" {
|
if sessionId != "" {
|
||||||
header["Mcp-Session-Id"] = sessionId
|
header["Mcp-Session-Id"] = sessionId
|
||||||
}
|
}
|
||||||
|
if vtc.protocol != protocolVersion20241105 && vtc.protocol != protocolVersion20250326 {
|
||||||
if vtc.protocol == protocolVersion20250618 {
|
|
||||||
header["MCP-Protocol-Version"] = vtc.protocol
|
header["MCP-Protocol-Version"] = vtc.protocol
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user