Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,7 @@ private static String inputSchema(
final JsonObjectBuilder properties = Json.createObjectBuilder();
final List<String> required = new LinkedList<>();
final List<String> propertyNames = new LinkedList<>();
JsonValue additionalProperties = null;

if (operation.parameters != null)
{
Expand Down Expand Up @@ -1161,6 +1162,11 @@ private static String inputSchema(
}
}
}
if (schema != null && schema.model.additionalProperties != null &&
!JsonValue.FALSE.equals(schema.model.additionalProperties))
{
additionalProperties = schema.model.additionalProperties;
}
break;
}
}
Expand All @@ -1174,6 +1180,10 @@ private static String inputSchema(
required.forEach(requiredArray::add);
object.add("required", requiredArray);
}
if (additionalProperties != null)
{
object.add("additionalProperties", additionalProperties);
}

return object.build().toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,86 @@ public void shouldDenyPathParameterInOpenBodySchema()
assertThat(bodySchemaObject.getBoolean("additionalProperties"), equalTo(true));
}

@Test
public void shouldAdvertiseAdditionalPropertiesInInputSchema()
{
String spec =
"""
{
"openapi": "3.0.1",
"info": { "title": "things", "version": "1.0" },
"servers": [ { "url": "https://api.example.com" } ],
"paths": {
"/things/{id}": {
"put": {
"operationId": "update_thing",
"parameters": [
{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
],
"requestBody": {
"required": true,
"content": { "application/json": { "schema": {
"type": "object",
"properties": { "name": { "type": "string" } },
"additionalProperties": true } } }
},
"responses": { "200": { "description": "ok" } }
}
}
}
}
""";
lenient().when(catalog.resolve(eq("things-api"), eq("latest"))).thenReturn(77);
lenient().when(catalog.resolve(eq(77))).thenReturn(spec);

BindingConfig binding = GenericBindingConfig.builder()
.namespace("test")
.name("mcp-openapi0")
.type("mcp-openapi")
.kind(CLIENT)
.options(McpOpenapiOptionsConfig.builder()
.spec()
.label("openapi_things0")
.server("https://api.example.com")
.catalog()
.name("catalog0")
.subject("things-api")
.version("latest")
.build()
.build()
.build())
.route()
.when(McpOpenapiConditionConfig.builder()
.tool("update_thing")
.build())
.with(McpOpenapiWithConfig.builder()
.spec("openapi_things0")
.operation("update_thing")
.build())
.build()
.build();
binding.resolveId = resolveId;

McpOpenapiCompositeConfig composite = generator.generate(new McpOpenapiBindingConfig(context, binding));

NamespaceConfig namespace = composite.namespaces.get(0);
String inputSchema = namespace.catalogs.stream()
.map(c -> c.options)
.filter(InlineOptionsConfig.class::isInstance)
.map(InlineOptionsConfig.class::cast)
.flatMap(o -> o.subjects.stream())
.filter(s -> "update_thing-input".equals(s.subject))
.map(s -> s.schema)
.findFirst()
.orElse(null);

assertThat(inputSchema, notNullValue());
JsonObject inputSchemaObject = Json.createReader(new StringReader(inputSchema)).readObject();
assertThat(inputSchemaObject.getJsonObject("properties").containsKey("id"), equalTo(true));
assertThat(inputSchemaObject.getJsonObject("properties").containsKey("name"), equalTo(true));
assertThat(inputSchemaObject.getBoolean("additionalProperties"), equalTo(true));
}

@Test
public void shouldOverrideOutputSchema()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ read '{"tools":[{"name":"list_connectors","title":"List Connectors","descriptio
'{"name":"describe_connector","title":"Describe Connector","description":"Read the configuration and task list of a connector.","inputSchema":{"type":"object","properties":{"connector":{"type":"string"}},"required":["connector"]},"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},'
'{"name":"delete_connector","title":"Delete Connector","description":"Delete a connector.","inputSchema":{"type":"object","properties":{"connector":{"type":"string"}},"required":["connector"]},"annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false}},'
'{"name":"describe_connector_config","title":"Describe Connector Config","description":"Read the effective configuration of a connector.","inputSchema":{"type":"object","properties":{"connector":{"type":"string"}},"required":["connector"]},"outputSchema":{"type":"object"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},'
'{"name":"update_connector_config","title":"Update Connector Config","description":"Create or update a connector by setting its full configuration.","inputSchema":{"type":"object","properties":{"connector":{"type":"string"},"connector.class":{"type":"string"},"tasks.max":{"type":"string"}},"required":["connector"]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},'
'{"name":"validate_connector_config","title":"Validate Connector Config","description":"Validate a connector configuration against a plugin configuration definition, without creating or updating any connector.","inputSchema":{"type":"object","properties":{"pluginName":{"type":"string"},"connector.class":{"type":"string"},"tasks.max":{"type":"string"}},"required":["pluginName"]},"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},'
'{"name":"update_connector_config","title":"Update Connector Config","description":"Create or update a connector by setting its full configuration.","inputSchema":{"type":"object","properties":{"connector":{"type":"string"},"connector.class":{"type":"string"},"tasks.max":{"type":"string"}},"required":["connector"],"additionalProperties":true},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},'
'{"name":"validate_connector_config","title":"Validate Connector Config","description":"Validate a connector configuration against a plugin configuration definition, without creating or updating any connector.","inputSchema":{"type":"object","properties":{"pluginName":{"type":"string"},"connector.class":{"type":"string"},"tasks.max":{"type":"string"}},"required":["pluginName"],"additionalProperties":true},"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},'
'{"name":"describe_connector_status","title":"Describe Connector Status","description":"Read the current state of a connector and the state of each of its tasks.","inputSchema":{"type":"object","properties":{"connector":{"type":"string"}},"required":["connector"]},"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},'
'{"name":"restart_connector","title":"Restart Connector","description":"Restart a connector.","inputSchema":{"type":"object","properties":{"connector":{"type":"string"}},"required":["connector"]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false}},'
'{"name":"pause_connector","title":"Pause Connector","description":"Pause a connector and all of its tasks.","inputSchema":{"type":"object","properties":{"connector":{"type":"string"}},"required":["connector"]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},'
Expand Down
Loading