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 @@ -23,13 +23,22 @@ public final class McpConditionConfig extends ConditionConfig
{
public final String toolkit;
public final List<String> capability;
public final List<String> tools;
public final List<String> prompts;
public final List<String> resources;

McpConditionConfig(
String toolkit,
List<String> capability)
List<String> capability,
List<String> tools,
List<String> prompts,
List<String> resources)
{
this.toolkit = toolkit;
this.capability = capability;
this.tools = tools;
this.prompts = prompts;
this.resources = resources;
}

public static McpConditionConfigBuilder<McpConditionConfig> builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public final class McpConditionConfigBuilder<T> extends ConfigBuilder<T, McpCond

private String toolkit;
private List<String> capability;
private List<String> tools;
private List<String> prompts;
private List<String> resources;

public McpConditionConfigBuilder(
Function<ConditionConfig, T> mapper)
Expand All @@ -47,6 +50,27 @@ public McpConditionConfigBuilder<T> capability(
return this;
}

public McpConditionConfigBuilder<T> tools(
List<String> tools)
{
this.tools = tools;
return this;
}

public McpConditionConfigBuilder<T> prompts(
List<String> prompts)
{
this.prompts = prompts;
return this;
}

public McpConditionConfigBuilder<T> resources(
List<String> resources)
{
this.resources = resources;
return this;
}

@Override
@SuppressWarnings("unchecked")
protected Class<McpConditionConfigBuilder<T>> thisType()
Expand All @@ -57,6 +81,6 @@ protected Class<McpConditionConfigBuilder<T>> thisType()
@Override
public T build()
{
return mapper.apply(new McpConditionConfig(toolkit, capability));
return mapper.apply(new McpConditionConfig(toolkit, capability, tools, prompts, resources));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public List<McpRoutePrefix> resolveAll(
{
if (route.authorized(authorization) && route.serves(capability))
{
result.add(new McpRoutePrefix(route.id, new String8FW(route.prefix(kind))));
result.add(new McpRoutePrefix(route.id, new String8FW(route.prefix(kind)), route));
}
}
result.sort(Comparator.comparing(p -> p.prefix().asString()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public final class McpConditionConfigAdapter implements ConditionConfigAdapterSp
{
private static final String TOOLKIT_NAME = "toolkit";
private static final String CAPABILITY_NAME = "capability";
private static final String TOOLS_NAME = "tools";
private static final String PROMPTS_NAME = "prompts";
private static final String RESOURCES_NAME = "resources";

@Override
public String type()
Expand Down Expand Up @@ -62,6 +65,27 @@ public JsonObject adaptToJson(
object.add(CAPABILITY_NAME, array);
}

if (mcpCondition.tools != null)
{
JsonArrayBuilder array = Json.createArrayBuilder();
mcpCondition.tools.forEach(array::add);
object.add(TOOLS_NAME, array);
}

if (mcpCondition.prompts != null)
{
JsonArrayBuilder array = Json.createArrayBuilder();
mcpCondition.prompts.forEach(array::add);
object.add(PROMPTS_NAME, array);
}

if (mcpCondition.resources != null)
{
JsonArrayBuilder array = Json.createArrayBuilder();
mcpCondition.resources.forEach(array::add);
object.add(RESOURCES_NAME, array);
}

return object.build();
}

Expand All @@ -73,19 +97,28 @@ public ConditionConfig adaptFromJson(
? object.getString(TOOLKIT_NAME)
: null;

List<String> capability = null;
if (object.containsKey(CAPABILITY_NAME))
return McpConditionConfig.builder()
.toolkit(toolkit)
.capability(asStringList(object, CAPABILITY_NAME))
.tools(asStringList(object, TOOLS_NAME))
.prompts(asStringList(object, PROMPTS_NAME))
.resources(asStringList(object, RESOURCES_NAME))
.build();
}

private static List<String> asStringList(
JsonObject object,
String name)
{
List<String> result = null;
if (object.containsKey(name))
{
JsonArray array = object.getJsonArray(CAPABILITY_NAME);
capability = array.stream()
JsonArray array = object.getJsonArray(name);
result = array.stream()
.map(JsonString.class::cast)
.map(JsonString::getString)
.collect(toList());
}

return McpConditionConfig.builder()
.toolkit(toolkit)
.capability(capability)
.build();
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/*
* Copyright 2021-2024 Aklivity Inc
*
* Licensed under the Aklivity Community License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the
* License at
*
* https://www.aklivity.io/aklivity-community-license/
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package io.aklivity.zilla.runtime.binding.mcp.internal.config;

import static io.aklivity.zilla.runtime.binding.mcp.internal.config.McpRouteConfig.CAPABILITY_PROMPTS;
import static io.aklivity.zilla.runtime.binding.mcp.internal.config.McpRouteConfig.CAPABILITY_RESOURCES;
import static io.aklivity.zilla.runtime.binding.mcp.internal.config.McpRouteConfig.CAPABILITY_TOOLS;
import static io.aklivity.zilla.runtime.binding.mcp.internal.types.McpCapabilities.SERVER_PROMPTS;
import static io.aklivity.zilla.runtime.binding.mcp.internal.types.McpCapabilities.SERVER_RESOURCES;
import static io.aklivity.zilla.runtime.binding.mcp.internal.types.McpCapabilities.SERVER_TOOLS;

import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import io.aklivity.zilla.runtime.binding.mcp.config.McpConditionConfig;

final class McpConditionMatcher
{
private static final String DELIMITER_NAME = "__";
private static final String DELIMITER_URI = "+";

final String toolkit;

private final String toolsPrefix;
private final String promptsPrefix;
private final String resourcesPrefix;
private final List<Pattern> toolsAllow;
private final List<Pattern> promptsAllow;
private final List<Pattern> resourcesAllow;

McpConditionMatcher(
McpConditionConfig condition)
{
final List<String> capabilities = condition.capability;
final String toolkit = condition.toolkit;
this.toolkit = toolkit;

final boolean anyCapability = capabilities == null;
final boolean tools = anyCapability || capabilities.contains(CAPABILITY_TOOLS);
final boolean prompts = anyCapability || capabilities.contains(CAPABILITY_PROMPTS);
final boolean resources = anyCapability || capabilities.contains(CAPABILITY_RESOURCES);

this.toolsPrefix = tools ? (toolkit != null ? toolkit + DELIMITER_NAME : "") : null;
this.promptsPrefix = prompts ? (toolkit != null ? toolkit + DELIMITER_NAME : "") : null;
this.resourcesPrefix = resources ? (toolkit != null ? toolkit + DELIMITER_URI : "") : null;

this.toolsAllow = compile(condition.tools);
this.promptsAllow = compile(condition.prompts);
this.resourcesAllow = compile(condition.resources);
}

int serverCapabilities()
{
int bits = 0;
if (toolsPrefix != null)
{
bits |= SERVER_TOOLS.value();
}
if (promptsPrefix != null)
{
bits |= SERVER_PROMPTS.value();
}
if (resourcesPrefix != null)
{
bits |= SERVER_RESOURCES.value();
}
return bits;
}

String match(
String capability,
String identifier)
{
final String prefix = prefix(capability);
String result = null;

if (prefix != null && identifier != null && identifier.startsWith(prefix))
{
final String stripped = identifier.substring(prefix.length());
if (admits(allow(capability), stripped))
{
result = stripped;
}
}

return result;
}

boolean serves(
String capability)
{
return prefix(capability) != null;
}

boolean admits(
String capability,
String name)
{
return serves(capability) && admits(allow(capability), name);
}

boolean filters(
String capability)
{
return serves(capability) && allow(capability) != null;
}

String prefix(
String capability)
{
return switch (capability)
{
case CAPABILITY_TOOLS -> toolsPrefix;
case CAPABILITY_PROMPTS -> promptsPrefix;
case CAPABILITY_RESOURCES -> resourcesPrefix;
default -> null;
};
}

private List<Pattern> allow(
String capability)
{
return switch (capability)
{
case CAPABILITY_TOOLS -> toolsAllow;
case CAPABILITY_PROMPTS -> promptsAllow;
case CAPABILITY_RESOURCES -> resourcesAllow;
default -> null;
};
}

private static boolean admits(
List<Pattern> allow,
String name)
{
boolean result = allow == null;

if (!result)
{
for (Pattern pattern : allow)
{
if (pattern.matcher(name).matches())
{
result = true;
break;
}
}
}

return result;
}

private static List<Pattern> compile(
List<String> globs)
{
return globs == null
? null
: globs.stream()
.map(McpConditionMatcher::compile)
.collect(Collectors.toList());
}

private static Pattern compile(
String glob)
{
final StringBuilder regex = new StringBuilder();
final String[] literals = glob.split("\\*", -1);
for (int index = 0; index < literals.length; index++)
{
if (index > 0)
{
regex.append(".*");
}
if (!literals[index].isEmpty())
{
regex.append(Pattern.quote(literals[index]));
}
}
return Pattern.compile(regex.toString());
}
}
Loading
Loading