mirror of
https://gitea.com/gitea/gitea-mcp.git
synced 2026-08-03 15:49:23 +02:00
fix: accept null tool arguments and bound HTTP resource use
Review follow-ups on the SDK migration. An "arguments": null is what clients send for parameterless tools like get_me, and what mcp-go accepted by returning a nil map. The new adapter rejected it with InvalidParams, which broke those calls outright. The /mcp endpoint took unlimited request bodies and never expired idle sessions, so a peer that goes away without DELETE kept its session for the process lifetime. Both are reachable before any token check, so neither can stay unbounded; the body cap sits above the SDK default to leave room for the base64 content create_or_update_file accepts. Required() smuggled a bool through the property schema map and deleted it again, colliding with the JSON Schema keyword of the same name. It now sets a field on Property, so an object property can carry its own required list. The tool contract fixture cost a manual regeneration step and four hand-maintained counts on every tool change, and a snapshot freezes defects rather than reporting them. Property assertions cover the same surface and reject a duplicate tool name, a readOnlyHint that disagrees with the register call, and a default that contradicts its own type or enum. Co-Authored-By: Claude (Opus 5) <noreply@anthropic.com>
This commit is contained in:
+24
-28
@@ -10,7 +10,7 @@ type Property struct {
|
||||
}
|
||||
|
||||
// PropertyOption configures one property in a tool's input schema.
|
||||
type PropertyOption func(map[string]any)
|
||||
type PropertyOption func(*Property)
|
||||
|
||||
// NewDefinition builds a tool definition without enabling SDK-side validation.
|
||||
func NewDefinition(name, description string, annotations *mcp.ToolAnnotations, properties ...Property) *mcp.Tool {
|
||||
@@ -40,71 +40,67 @@ func NewDefinition(name, description string, annotations *mcp.ToolAnnotations, p
|
||||
}
|
||||
|
||||
func String(name string, options ...PropertyOption) Property {
|
||||
return newProperty(name, "string", false, options...)
|
||||
return newProperty(name, map[string]any{"type": "string"}, options...)
|
||||
}
|
||||
|
||||
func Number(name string, options ...PropertyOption) Property {
|
||||
return newProperty(name, "number", false, options...)
|
||||
return newProperty(name, map[string]any{"type": "number"}, options...)
|
||||
}
|
||||
|
||||
func Boolean(name string, options ...PropertyOption) Property {
|
||||
return newProperty(name, "boolean", false, options...)
|
||||
return newProperty(name, map[string]any{"type": "boolean"}, options...)
|
||||
}
|
||||
|
||||
func Array(name string, options ...PropertyOption) Property {
|
||||
return newProperty(name, "array", false, options...)
|
||||
return newProperty(name, map[string]any{"type": "array"}, options...)
|
||||
}
|
||||
|
||||
func Object(name string, options ...PropertyOption) Property {
|
||||
return newProperty(name, "object", true, options...)
|
||||
return newProperty(name, map[string]any{"type": "object", "properties": map[string]any{}}, options...)
|
||||
}
|
||||
|
||||
func newProperty(name, propertyType string, object bool, options ...PropertyOption) Property {
|
||||
schema := map[string]any{"type": propertyType}
|
||||
if object {
|
||||
schema["properties"] = map[string]any{}
|
||||
}
|
||||
func newProperty(name string, schema map[string]any, options ...PropertyOption) Property {
|
||||
property := Property{name: name, schema: schema}
|
||||
for _, option := range options {
|
||||
option(schema)
|
||||
option(&property)
|
||||
}
|
||||
|
||||
required, _ := schema["required"].(bool)
|
||||
delete(schema, "required")
|
||||
return Property{name: name, schema: schema, required: required}
|
||||
return property
|
||||
}
|
||||
|
||||
// Required marks the property as required on the parent schema. It is not a
|
||||
// property-level keyword, so it never touches the emitted property schema.
|
||||
func Required() PropertyOption {
|
||||
return func(schema map[string]any) {
|
||||
schema["required"] = true
|
||||
return func(property *Property) {
|
||||
property.required = true
|
||||
}
|
||||
}
|
||||
|
||||
func Description(description string) PropertyOption {
|
||||
return func(schema map[string]any) {
|
||||
schema["description"] = description
|
||||
return func(property *Property) {
|
||||
property.schema["description"] = description
|
||||
}
|
||||
}
|
||||
|
||||
func Enum(values ...string) PropertyOption {
|
||||
return func(schema map[string]any) {
|
||||
schema["enum"] = values
|
||||
return func(property *Property) {
|
||||
property.schema["enum"] = values
|
||||
}
|
||||
}
|
||||
|
||||
func Default(value any) PropertyOption {
|
||||
return func(schema map[string]any) {
|
||||
schema["default"] = value
|
||||
return func(property *Property) {
|
||||
property.schema["default"] = value
|
||||
}
|
||||
}
|
||||
|
||||
func Minimum(value float64) PropertyOption {
|
||||
return func(schema map[string]any) {
|
||||
schema["minimum"] = value
|
||||
return func(property *Property) {
|
||||
property.schema["minimum"] = value
|
||||
}
|
||||
}
|
||||
|
||||
func Items(schema any) PropertyOption {
|
||||
return func(propertySchema map[string]any) {
|
||||
propertySchema["items"] = schema
|
||||
return func(property *Property) {
|
||||
property.schema["items"] = schema
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user