Skip to content

Commit 3142063

Browse files
feat(mcp): add output schema toggle config (#24308)
* feat(mcp): add output schema toggle config * Update changelog/unreleased/pr-24308.toml Co-authored-by: Dennis Oelkers <[email protected]>
1 parent 588747b commit 3142063

File tree

16 files changed

+214
-92
lines changed

16 files changed

+214
-92
lines changed

changelog/unreleased/pr-24308.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type = "added" # One of: a(dded), c(hanged), d(eprecated), r(emoved), f(ixed), s(ecurity)
2+
message = "Add option in MCP config for disabling schema output in responses."
3+
4+
issues = ["23980"]
5+
pulls = ["24308"]

graylog2-server/src/main/java/org/graylog/mcp/config/McpConfiguration.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,22 @@
2424
@JsonAutoDetect
2525
@AutoValue
2626
public abstract class McpConfiguration {
27-
public static final McpConfiguration DEFAULT_VALUES = create(false);
27+
public static final McpConfiguration DEFAULT_VALUES = create(
28+
false,
29+
false
30+
);
2831

2932
@JsonProperty("enable_remote_access")
3033
public abstract boolean enableRemoteAccess();
3134

35+
@JsonProperty("enable_output_schema")
36+
public abstract boolean enableOutputSchema();
37+
3238
@JsonCreator
33-
public static McpConfiguration create(@JsonProperty("enable_remote_access") boolean enableRemoteAccess) {
34-
return new AutoValue_McpConfiguration(enableRemoteAccess);
39+
public static McpConfiguration create(
40+
@JsonProperty("enable_remote_access") boolean enableRemoteAccess,
41+
@JsonProperty("enable_output_schema") boolean enableOutputSchema
42+
) {
43+
return new AutoValue_McpConfiguration(enableRemoteAccess, enableOutputSchema);
3544
}
3645
}

graylog2-server/src/main/java/org/graylog/mcp/server/McpService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,9 @@ public Optional<McpSchema.Result> handle(PermissionHelper permissionHelper, McpS
164164
.title(tool.title())
165165
.description(tool.description());
166166
tool.inputSchema().ifPresent(builder::inputSchema);
167-
tool.outputSchema().ifPresent(builder::outputSchema);
167+
if (tool.isOutputSchemaEnabled()) {
168+
tool.outputSchema().ifPresent(builder::outputSchema);
169+
}
168170
return builder.build();
169171
}).toList();
170172
auditEventSender.success(auditActor, AuditEventType.create(MCP_TOOL_LIST), auditContext);

graylog2-server/src/main/java/org/graylog/mcp/server/Tool.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@
2121
import com.fasterxml.jackson.databind.ObjectMapper;
2222
import com.github.victools.jsonschema.generator.SchemaGenerator;
2323
import io.modelcontextprotocol.spec.McpSchema;
24+
import jakarta.inject.Inject;
25+
import org.graylog.mcp.config.McpConfiguration;
2426
import org.graylog.mcp.tools.PermissionHelper;
27+
import org.graylog2.plugin.cluster.ClusterConfigService;
28+
import org.graylog2.web.customization.CustomizationConfig;
2529

2630
import java.util.Map;
2731
import java.util.Optional;
@@ -33,29 +37,56 @@
3337
* @param <O> Output type
3438
*/
3539
public abstract class Tool<P, O> {
36-
3740
private final ObjectMapper objectMapper;
41+
private final ClusterConfigService clusterConfigService;
42+
3843
private final TypeReference<P> parameterType;
3944
private final String name;
4045
private final String title;
4146
private final String description;
4247
private final McpSchema.JsonSchema inputSchema;
4348
private final Map<String, Object> outputSchema;
4449

50+
@Deprecated
4551
protected Tool(
4652
ObjectMapper objectMapper,
4753
SchemaGeneratorProvider schemaGeneratorProvider,
4854
TypeReference<P> parameterType,
4955
TypeReference<O> outputType,
5056
String name,
5157
String title,
52-
String description) {
53-
this.objectMapper = objectMapper;
58+
String description
59+
) {
60+
this(
61+
parameterType,
62+
outputType,
63+
name,
64+
title,
65+
description,
66+
objectMapper,
67+
null,
68+
schemaGeneratorProvider
69+
);
70+
}
71+
72+
protected Tool(
73+
TypeReference<P> parameterType,
74+
TypeReference<O> outputType,
75+
String name,
76+
String title,
77+
String description,
78+
ObjectMapper objectMapper,
79+
ClusterConfigService clusterConfigService,
80+
SchemaGeneratorProvider schemaGeneratorProvider
81+
) {
5482
this.parameterType = parameterType;
5583
this.name = name;
5684
this.title = title;
5785
this.description = description;
5886

87+
this.objectMapper = objectMapper;
88+
this.clusterConfigService = clusterConfigService;
89+
5990
// Get the schema generator with all contributed modules
6091
SchemaGenerator generator = schemaGeneratorProvider.get();
6192

@@ -74,6 +105,11 @@ protected Tool(
74105
}
75106
}
76107

108+
protected boolean isOutputSchemaEnabled() {
109+
return clusterConfigService != null && clusterConfigService.getOrDefault(McpConfiguration.class, McpConfiguration.DEFAULT_VALUES)
110+
.enableOutputSchema();
111+
}
112+
77113
protected ObjectMapper getObjectMapper() {
78114
return objectMapper;
79115
}

graylog2-server/src/main/java/org/graylog/mcp/tools/AggregateMessagesTool.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.graylog.plugins.views.search.rest.scriptingapi.request.Grouping;
3636
import org.graylog.plugins.views.search.rest.scriptingapi.request.Metric;
3737
import org.graylog.plugins.views.search.rest.scriptingapi.response.TabularResponse;
38+
import org.graylog2.plugin.cluster.ClusterConfigService;
3839
import org.graylog2.plugin.indexer.searches.timeranges.RelativeRange;
3940
import org.graylog2.web.customization.CustomizationConfig;
4041
import org.slf4j.Logger;
@@ -52,12 +53,12 @@ public class AggregateMessagesTool extends Tool<AggregateMessagesTool.Parameters
5253
private final ScriptingApiService scriptingApiService;
5354

5455
@Inject
55-
public AggregateMessagesTool(ObjectMapper objectMapper,
56-
CustomizationConfig customizationConfig,
57-
SchemaGeneratorProvider schemaGeneratorProvider,
58-
ScriptingApiService scriptingApiService) {
59-
super(objectMapper,
60-
schemaGeneratorProvider,
56+
public AggregateMessagesTool(ScriptingApiService scriptingApiService,
57+
final CustomizationConfig customizationConfig,
58+
final ObjectMapper objectMapper,
59+
final ClusterConfigService clusterConfigService,
60+
final SchemaGeneratorProvider schemaGeneratorProvider) {
61+
super(
6162
new TypeReference<>() {},
6263
new TypeReference<>() {},
6364
NAME,
@@ -70,7 +71,11 @@ You can scope the search to streams (by passing their IDs) or stream categories,
7071
You need to provide at least one grouping, a field name and limit, as well as one metric to calculate for the group by buckets.
7172
For example, to count the top 10 number of messages per source, you can send {"groupings": [{"field":"source", "limit": 10}], "metrics": {"function":"count"}}
7273
The query string supports Lucene query language, but be careful about leading wildcards, %1$s might not have them enabled.
73-
""".formatted(customizationConfig.productName()));
74+
""".formatted(customizationConfig.productName()),
75+
objectMapper,
76+
clusterConfigService,
77+
schemaGeneratorProvider
78+
);
7479
this.scriptingApiService = scriptingApiService;
7580
}
7681

graylog2-server/src/main/java/org/graylog/mcp/tools/CurrentTimeTool.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.graylog.mcp.server.SchemaGeneratorProvider;
2323
import org.graylog.mcp.server.Tool;
2424
import org.graylog2.plugin.Tools;
25+
import org.graylog2.plugin.cluster.ClusterConfigService;
2526
import org.graylog2.web.customization.CustomizationConfig;
2627
import org.joda.time.DateTime;
2728
import org.joda.time.DateTimeZone;
@@ -40,16 +41,23 @@ public class CurrentTimeTool extends Tool<CurrentTimeTool.Parameters, String> {
4041
public static String NAME = "get_current_time";
4142

4243
@Inject
43-
public CurrentTimeTool(ObjectMapper objectMapper,
44-
CustomizationConfig customizationConfig,
45-
SchemaGeneratorProvider schemaGeneratorProvider) {
46-
super(objectMapper,
47-
schemaGeneratorProvider,
44+
public CurrentTimeTool(final CustomizationConfig customizationConfig,
45+
final ObjectMapper objectMapper,
46+
final ClusterConfigService clusterConfigService,
47+
final SchemaGeneratorProvider schemaGeneratorProvider) {
48+
super(
4849
new TypeReference<>() {},
4950
new TypeReference<>() {},
5051
NAME,
5152
"Get current time",
52-
f("Return the current time from the %s server in ISO‑8601 UTC format.", customizationConfig.productName()));
53+
f(
54+
"Return the current time from the %s server in ISO‑8601 UTC format.",
55+
customizationConfig.productName()
56+
),
57+
objectMapper,
58+
clusterConfigService,
59+
schemaGeneratorProvider
60+
);
5361
}
5462

5563
@Override

graylog2-server/src/main/java/org/graylog/mcp/tools/ListFieldsTool.java

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@
2525
import com.google.auto.value.AutoValue;
2626
import jakarta.annotation.Nullable;
2727
import jakarta.inject.Inject;
28-
import org.graylog.mcp.server.Tool;
2928
import org.graylog.mcp.server.SchemaGeneratorProvider;
29+
import org.graylog.mcp.server.Tool;
3030
import org.graylog.plugins.views.search.rest.MappedFieldTypeDTO;
3131
import org.graylog2.indexer.fieldtypes.MappedFieldTypesService;
32+
import org.graylog2.plugin.cluster.ClusterConfigService;
3233
import org.graylog2.plugin.indexer.searches.timeranges.RelativeRange;
3334

3435
import java.util.Set;
@@ -40,22 +41,28 @@ public class ListFieldsTool extends Tool<ListFieldsTool.Parameters, ListFieldsTo
4041
private final MappedFieldTypesService mappedFieldTypesService;
4142

4243
@Inject
43-
protected ListFieldsTool(final ObjectMapper objectMapper,
44-
SchemaGeneratorProvider schemaGeneratorProvider, MappedFieldTypesService mappedFieldTypesService) {
45-
super(objectMapper,
46-
schemaGeneratorProvider,
47-
new TypeReference<>() {},
48-
new TypeReference<>() {},
49-
NAME,
44+
protected ListFieldsTool(MappedFieldTypesService mappedFieldTypesService,
45+
final ObjectMapper objectMapper,
46+
final ClusterConfigService clusterConfigService,
47+
final SchemaGeneratorProvider schemaGeneratorProvider) {
48+
super(
49+
new TypeReference<>() {},
50+
new TypeReference<>() {},
51+
NAME,
5052
"List available fields",
51-
"""
52-
Retrieve the available field names and metadata about them, for example their datatype and capabilities.
53-
Field names must be used to properly create search queries and request specific fields from tools like
54-
search_messages and aggregate_messages.
55-
Fields can have different meanings in different streams (and also per source), so that needs to be taken into account.
56-
This tool can be scoped to streams to cut down on the noise.
57-
Fields can have various properties, such as "enumerable" which means you can aggregate on them, they can be "numeric",
58-
making them suitable to use in aggregation metrics, or "full-text-search", which means their content is tokenized""");
53+
"""
54+
Retrieve the available field names and metadata about them, for example their datatype and capabilities.
55+
Field names must be used to properly create search queries and request specific fields from tools like
56+
search_messages and aggregate_messages.
57+
Fields can have different meanings in different streams (and also per source), so that needs to be taken into account.
58+
This tool can be scoped to streams to cut down on the noise.
59+
Fields can have various properties, such as "enumerable" which means you can aggregate on them, they can be "numeric",
60+
making them suitable to use in aggregation metrics, or "full-text-search", which means their content is tokenized
61+
""",
62+
objectMapper,
63+
clusterConfigService,
64+
schemaGeneratorProvider
65+
);
5966
this.mappedFieldTypesService = mappedFieldTypesService;
6067
}
6168

graylog2-server/src/main/java/org/graylog/mcp/tools/ListIndexSetsTool.java

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.graylog2.indexer.rotation.strategies.SizeBasedRotationStrategyConfig;
2727
import org.graylog2.indexer.rotation.strategies.TimeBasedRotationStrategyConfig;
2828
import org.graylog2.indexer.rotation.strategies.TimeBasedSizeOptimizingStrategyConfig;
29+
import org.graylog2.plugin.cluster.ClusterConfigService;
2930
import org.graylog2.plugin.indexer.rotation.RotationStrategyConfig;
3031
import org.graylog2.rest.resources.system.indexer.responses.IndexSetResponse;
3132
import org.graylog2.shared.security.RestPermissions;
@@ -40,16 +41,16 @@
4041
public class ListIndexSetsTool extends Tool<ListIndexSetsTool.Parameters, String> {
4142
public static String NAME = "list_index_sets";
4243

43-
private final String productName;
4444
private final IndexSetService indexSetService;
45+
private final CustomizationConfig customizationConfig;
4546

4647
@Inject
47-
public ListIndexSetsTool(ObjectMapper objectMapper,
48-
SchemaGeneratorProvider schemaGeneratorProvider,
49-
CustomizationConfig customizationConfig,
50-
IndexSetService indexSetService) {
51-
super(objectMapper,
52-
schemaGeneratorProvider,
48+
public ListIndexSetsTool(IndexSetService indexSetService,
49+
final CustomizationConfig customizationConfig,
50+
final ObjectMapper objectMapper,
51+
final ClusterConfigService clusterConfigService,
52+
final SchemaGeneratorProvider schemaGeneratorProvider) {
53+
super(
5354
new TypeReference<>() {},
5455
new TypeReference<>() {},
5556
NAME,
@@ -59,9 +60,13 @@ public ListIndexSetsTool(ObjectMapper objectMapper,
5960
retention settings (how long data is kept), index prefix patterns, and current state. Use this to understand data lifecycle management,
6061
troubleshoot retention issues, or plan storage capacity. Essential for understanding how your log data is organized and managed over time.
6162
No parameters required. Returns index set details.
62-
""".formatted(customizationConfig.productName()));
63-
this.productName = customizationConfig.productName();
63+
""".formatted(customizationConfig.productName()),
64+
objectMapper,
65+
clusterConfigService,
66+
schemaGeneratorProvider
67+
);
6468
this.indexSetService = indexSetService;
69+
this.customizationConfig = customizationConfig;
6570
}
6671

6772
@Override
@@ -70,7 +75,7 @@ public String apply(PermissionHelper permissionHelper, ListIndexSetsTool.Paramet
7075

7176
final var indexSets = indexSetService.findAll();
7277
if (indexSets.isEmpty()) {
73-
return f("No index sets found in the %s server.", productName);
78+
return f("No index sets found in the %s server.", customizationConfig.productName());
7479
}
7580

7681
final var defaultIndexSet = indexSetService.getDefault();
@@ -83,7 +88,7 @@ public String apply(PermissionHelper permissionHelper, ListIndexSetsTool.Paramet
8388
final var sw = new StringWriter();
8489
final var pw = new PrintWriter(sw);
8590

86-
pw.println(f("%s Index Sets:", productName));
91+
pw.println(f("%s Index Sets:", customizationConfig.productName()));
8792
indexSetConfigStream.forEach(pw::println);
8893

8994
return sw.toString();

graylog2-server/src/main/java/org/graylog/mcp/tools/ListIndicesTool.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.graylog2.indexer.indices.Indices;
2929
import org.graylog2.indexer.indices.stats.IndexStatistics;
3030
import org.graylog2.indexer.indices.util.NumberBasedIndexNameComparator;
31+
import org.graylog2.plugin.cluster.ClusterConfigService;
3132
import org.graylog2.rest.models.system.indexer.responses.AllIndices;
3233
import org.graylog2.rest.models.system.indexer.responses.ClosedIndices;
3334
import org.graylog2.rest.models.system.indexer.responses.IndexInfo;
@@ -56,14 +57,14 @@ public class ListIndicesTool extends Tool<ListIndicesTool.Parameters, String> {
5657
private final IndexSetRegistry indexSetRegistry;
5758

5859
@Inject
59-
public ListIndicesTool(ObjectMapper objectMapper,
60-
SchemaGeneratorProvider schemaGeneratorProvider,
61-
Indices indices,
60+
public ListIndicesTool(Indices indices,
6261
NodeInfoCache nodeInfoCache,
6362
IndexSetRegistry indexSetRegistry,
64-
CustomizationConfig customizationConfig) {
65-
super(objectMapper,
66-
schemaGeneratorProvider,
63+
final CustomizationConfig customizationConfig,
64+
final ObjectMapper objectMapper,
65+
final ClusterConfigService clusterConfigService,
66+
final SchemaGeneratorProvider schemaGeneratorProvider) {
67+
super(
6768
new TypeReference<>() {},
6869
new TypeReference<>() {},
6970
NAME,
@@ -72,7 +73,13 @@ public ListIndicesTool(ObjectMapper objectMapper,
7273
List all %s indices from the cluster. Returns comprehensive index information including status (open/closed),
7374
document counts, storage size, and health metrics. Use this to understand data distribution, identify problematic indices,
7475
or before performing queries to understand available data sources. No parameters required.
75-
""", customizationConfig.productName()));
76+
""",
77+
customizationConfig.productName()
78+
),
79+
objectMapper,
80+
clusterConfigService,
81+
schemaGeneratorProvider
82+
);
7683
this.indices = indices;
7784
this.nodeInfoCache = nodeInfoCache;
7885
this.indexSetRegistry = indexSetRegistry;

0 commit comments

Comments
 (0)