feat(files): update save_file tool description and enforce size limit for base64 payloads
This commit is contained in:
@@ -2,6 +2,7 @@ package mcpserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -12,6 +13,8 @@ import (
|
|||||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const maxLoggedArgBytes = 512
|
||||||
|
|
||||||
var toolSchemaOptions = &jsonschema.ForOptions{
|
var toolSchemaOptions = &jsonschema.ForOptions{
|
||||||
TypeSchemas: map[reflect.Type]*jsonschema.Schema{
|
TypeSchemas: map[reflect.Type]*jsonschema.Schema{
|
||||||
reflect.TypeFor[uuid.UUID](): {
|
reflect.TypeFor[uuid.UUID](): {
|
||||||
@@ -37,7 +40,7 @@ func logToolCall[In any, Out any](logger *slog.Logger, toolName string, handler
|
|||||||
start := time.Now()
|
start := time.Now()
|
||||||
attrs := []any{slog.String("tool", toolName)}
|
attrs := []any{slog.String("tool", toolName)}
|
||||||
if req != nil && req.Params != nil {
|
if req != nil && req.Params != nil {
|
||||||
attrs = append(attrs, slog.Any("arguments", req.Params.Arguments))
|
attrs = append(attrs, slog.String("arguments", truncateArgs(req.Params.Arguments)))
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("mcp tool started", attrs...)
|
logger.Info("mcp tool started", attrs...)
|
||||||
@@ -56,6 +59,17 @@ func logToolCall[In any, Out any](logger *slog.Logger, toolName string, handler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func truncateArgs(args any) string {
|
||||||
|
b, err := json.Marshal(args)
|
||||||
|
if err != nil {
|
||||||
|
return "<unserializable>"
|
||||||
|
}
|
||||||
|
if len(b) <= maxLoggedArgBytes {
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
return string(b[:maxLoggedArgBytes]) + fmt.Sprintf("… (%d bytes total)", len(b))
|
||||||
|
}
|
||||||
|
|
||||||
func setToolSchemas[In any, Out any](tool *mcp.Tool) error {
|
func setToolSchemas[In any, Out any](tool *mcp.Tool) error {
|
||||||
if tool.InputSchema == nil {
|
if tool.InputSchema == nil {
|
||||||
inputSchema, err := jsonschema.For[In](toolSchemaOptions)
|
inputSchema, err := jsonschema.For[In](toolSchemaOptions)
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ func New(cfg config.MCPConfig, logger *slog.Logger, toolSet ToolSet) http.Handle
|
|||||||
|
|
||||||
addTool(server, logger, &mcp.Tool{
|
addTool(server, logger, &mcp.Tool{
|
||||||
Name: "save_file",
|
Name: "save_file",
|
||||||
Description: "Store a base64-encoded file such as an image, document, or audio clip, optionally linking it to a thought.",
|
Description: "Store a file and optionally link it to a thought. Supply either content_base64 (≤10 MB) or content_uri (amcs://files/{id} from a prior POST /files upload). For files larger than 10 MB, upload via POST /files first and pass the returned URI as content_uri.",
|
||||||
}, toolSet.Files.Save)
|
}, toolSet.Files.Save)
|
||||||
|
|
||||||
addTool(server, logger, &mcp.Tool{
|
addTool(server, logger, &mcp.Tool{
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ import (
|
|||||||
thoughttypes "git.warky.dev/wdevs/amcs/internal/types"
|
thoughttypes "git.warky.dev/wdevs/amcs/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// maxBase64ToolBytes is the maximum base64 payload accepted by save_file via
|
||||||
|
// the MCP tool interface. For larger files use POST /files (binary) and pass
|
||||||
|
// the returned amcs://files/{id} URI as content_uri instead.
|
||||||
|
const maxBase64ToolBytes = 10 << 20 // 10 MB of base64 ≈ 7.5 MB decoded
|
||||||
|
|
||||||
type FilesTool struct {
|
type FilesTool struct {
|
||||||
store *store.DB
|
store *store.DB
|
||||||
sessions *session.ActiveProjects
|
sessions *session.ActiveProjects
|
||||||
@@ -75,6 +80,11 @@ func (t *FilesTool) Save(ctx context.Context, req *mcp.CallToolRequest, in SaveF
|
|||||||
if uri != "" && b64 != "" {
|
if uri != "" && b64 != "" {
|
||||||
return nil, SaveFileOutput{}, errInvalidInput("provide content_uri or content_base64, not both")
|
return nil, SaveFileOutput{}, errInvalidInput("provide content_uri or content_base64, not both")
|
||||||
}
|
}
|
||||||
|
if len(b64) > maxBase64ToolBytes {
|
||||||
|
return nil, SaveFileOutput{}, errInvalidInput(
|
||||||
|
"content_base64 exceeds the 10 MB MCP tool limit; upload the file via POST /files and pass the returned amcs://files/{id} URI as content_uri instead",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
var content []byte
|
var content []byte
|
||||||
var mediaTypeFromSource string
|
var mediaTypeFromSource string
|
||||||
|
|||||||
Reference in New Issue
Block a user