mirror of https://github.com/alibaba/nacos.git
adapt to new registry api (#13849)
This commit is contained in:
parent
2b9bfd4c07
commit
a8e9f19de8
|
@ -398,26 +398,12 @@ public class McpServerTransformService {
|
|||
out.setName(registryServer.getName());
|
||||
out.setDescription(registryServer.getDescription());
|
||||
// Map status from registry to nacos model (default to active)
|
||||
out.setStatus(normalizeStatus(registryServer.getStatus()));
|
||||
out.setStatus(registryServer.getStatus());
|
||||
if (registryServer.getRepository() != null) {
|
||||
out.setRepository(registryServer.getRepository());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize registry status to nacos supported values.
|
||||
*/
|
||||
private String normalizeStatus(String status) {
|
||||
if (status == null) {
|
||||
return AiConstants.Mcp.MCP_STATUS_ACTIVE;
|
||||
}
|
||||
String s = status.trim().toLowerCase();
|
||||
if (AiConstants.Mcp.MCP_STATUS_ACTIVE.equals(s) || AiConstants.Mcp.MCP_STATUS_DEPRECATED.equals(s)) {
|
||||
return s;
|
||||
}
|
||||
return AiConstants.Mcp.MCP_STATUS_ACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive registry id from _meta.official.id or repository.id.
|
||||
*/
|
||||
|
@ -426,9 +412,9 @@ public class McpServerTransformService {
|
|||
McpRegistryServerDetail detail = (McpRegistryServerDetail) registryServer;
|
||||
Meta meta = detail.getMeta();
|
||||
if (meta != null && meta.getOfficial() != null) {
|
||||
String id = meta.getOfficial().getId();
|
||||
if (StringUtils.isNotBlank(id)) {
|
||||
return id;
|
||||
String serverId = meta.getOfficial().getServerId();
|
||||
if (StringUtils.isNotBlank(serverId)) {
|
||||
return serverId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -451,10 +437,7 @@ public class McpServerTransformService {
|
|||
if (v == null) {
|
||||
v = new ServerVersionDetail();
|
||||
}
|
||||
String release = detail.getPublishedAt();
|
||||
if (StringUtils.isBlank(release) && official != null) {
|
||||
release = official.getPublishedAt();
|
||||
}
|
||||
String release = official != null ? official.getPublishedAt() : null;
|
||||
if (StringUtils.isNotBlank(release)) {
|
||||
v.setRelease_date(release);
|
||||
}
|
||||
|
@ -521,7 +504,7 @@ public class McpServerTransformService {
|
|||
// Without packages, try transportType from the first remote
|
||||
if (detail.getRemotes() != null && !detail.getRemotes().isEmpty()) {
|
||||
Remote first = detail.getRemotes().get(0);
|
||||
String tt = first != null ? first.getTransportType() : null;
|
||||
String tt = first != null ? first.getType() : null;
|
||||
if (tt != null) {
|
||||
String lower = tt.trim().toLowerCase();
|
||||
if (AiConstants.Mcp.OFFICIAL_TRANSPORT_SSE.equals(lower)) {
|
||||
|
@ -591,7 +574,7 @@ public class McpServerTransformService {
|
|||
FrontEndpointConfig cfg = new FrontEndpointConfig();
|
||||
cfg.setEndpointData(endpointData);
|
||||
cfg.setPath(StringUtils.isNotBlank(path) ? path : "/");
|
||||
cfg.setType(remote.getTransportType());
|
||||
cfg.setType(remote.getType());
|
||||
cfg.setProtocol(isHttps ? "https" : AiConstants.Mcp.MCP_PROTOCOL_HTTP);
|
||||
cfg.setEndpointType(AiConstants.Mcp.MCP_ENDPOINT_TYPE_DIRECT);
|
||||
cfg.setHeaders(remote.getHeaders());
|
||||
|
|
|
@ -45,7 +45,7 @@ class McpServerTransformServiceTest {
|
|||
@Test
|
||||
void testTransformMcpRegistryServerList() throws Exception {
|
||||
String registryJson = "{\"servers\":[{\"_meta\":{\"io.modelcontextprotocol.registry/official\":"
|
||||
+ "{\"id\":\"4e9cf4cf-71f6-4aca-bae8-2d10a29ca2e0\"}},"
|
||||
+ "{\"serverId\":\"4e9cf4cf-71f6-4aca-bae8-2d10a29ca2e0\"}},"
|
||||
+ "\"name\":\"io.github.21st-dev/magic-mcp\","
|
||||
+ "\"description\":\"It's like v0 but in your Cursor/WindSurf/Cline. 21st dev Magic MCP server\","
|
||||
+ "\"repository\":{\"url\":\"https://github.com/21st-dev/magic-mcp\",\"source\":\"github\",\"id\":\"935450522\"},"
|
||||
|
@ -73,7 +73,7 @@ class McpServerTransformServiceTest {
|
|||
|
||||
@Test
|
||||
void testTransformSingleMcpRegistryServer() throws Exception {
|
||||
String registryJson = "{\"_meta\":{\"io.modelcontextprotocol.registry/official\":{\"id\":\"d3669201-252f-403c-944b-c3ec0845782b\"}},"
|
||||
String registryJson = "{\"_meta\":{\"io.modelcontextprotocol.registry/official\":{\"serverId\":\"d3669201-252f-403c-944b-c3ec0845782b\"}},"
|
||||
+ "\"name\":\"io.github.adfin-engineering/mcp-server-adfin\","
|
||||
+ "\"description\":\"A Model Context Protocol Server for connecting with Adfin APIs\","
|
||||
+ "\"repository\":{\"url\":\"https://github.com/Adfin-Engineering/mcp-server-adfin\",\"source\":\"github\",\"id\":\"951338147\"},"
|
||||
|
@ -107,7 +107,7 @@ class McpServerTransformServiceTest {
|
|||
|
||||
@Test
|
||||
void testTransformLegacyFormat() throws Exception {
|
||||
String legacyJson = "{\"servers\":[{\"_meta\":{\"io.modelcontextprotocol.registry/official\":{\"id\":\"legacy-server\"}}"
|
||||
String legacyJson = "{\"servers\":[{\"_meta\":{\"io.modelcontextprotocol.registry/official\":{\"serverId\":\"legacy-server\"}}"
|
||||
+ ",\"name\":\"Legacy MCP Server\","
|
||||
+ "\"description\":\"A legacy format server\"}]}";
|
||||
|
||||
|
@ -191,7 +191,7 @@ class McpServerTransformServiceTest {
|
|||
@Test
|
||||
void testUrlValidationWithValidPackage() throws Exception {
|
||||
// Test with valid package format that doesn't trigger URL validation issues
|
||||
String jsonWithValidPackage = "{\"_meta\":{\"io.modelcontextprotocol.registry/official\":{\"id\":\"valid-server\"}},"
|
||||
String jsonWithValidPackage = "{\"_meta\":{\"io.modelcontextprotocol.registry/official\":{\"serverId\":\"valid-server\"}},"
|
||||
+ "\"name\":\"Valid Server\","
|
||||
+ "\"repository\":{\"url\":\"https://github.com/test/valid-server\",\"source\":\"github\",\"id\":\"123\"},"
|
||||
+ "\"version\":\"1.0.0\","
|
||||
|
|
|
@ -31,14 +31,12 @@ public class Input {
|
|||
|
||||
private String description;
|
||||
|
||||
@JsonProperty("is_required")
|
||||
private Boolean isRequired;
|
||||
|
||||
private String format;
|
||||
|
||||
private String value;
|
||||
|
||||
@JsonProperty("is_secret")
|
||||
private Boolean isSecret;
|
||||
|
||||
@JsonProperty("default")
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
package com.alibaba.nacos.api.ai.model.mcp.registry;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* KeyValueInput used for headers / env vars.
|
||||
*
|
||||
|
@ -25,7 +23,6 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
*/
|
||||
public class KeyValueInput extends InputWithVariables {
|
||||
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package com.alibaba.nacos.api.ai.model.mcp.registry;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* McpRegistryServer (renamed from Server) to align with registry package naming.
|
||||
|
@ -37,15 +36,8 @@ public class McpRegistryServer {
|
|||
|
||||
private String version;
|
||||
|
||||
@JsonProperty("website_url")
|
||||
private String websiteUrl;
|
||||
|
||||
@JsonProperty("created_at")
|
||||
private String createdAt;
|
||||
|
||||
@JsonProperty("updated_at")
|
||||
private String updatedAt;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
@ -93,20 +85,4 @@ public class McpRegistryServer {
|
|||
public void setWebsiteUrl(String websiteUrl) {
|
||||
this.websiteUrl = websiteUrl;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(String createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(String updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,9 +39,6 @@ public class McpRegistryServerDetail extends McpRegistryServer {
|
|||
@JsonProperty("_meta")
|
||||
private Meta meta;
|
||||
|
||||
@JsonProperty("published_at")
|
||||
private String publishedAt;
|
||||
|
||||
public String getSchema() {
|
||||
return schema;
|
||||
}
|
||||
|
@ -74,11 +71,4 @@ public class McpRegistryServerDetail extends McpRegistryServer {
|
|||
this.meta = meta;
|
||||
}
|
||||
|
||||
public String getPublishedAt() {
|
||||
return publishedAt;
|
||||
}
|
||||
|
||||
public void setPublishedAt(String publishedAt) {
|
||||
this.publishedAt = publishedAt;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,9 @@
|
|||
|
||||
package com.alibaba.nacos.api.ai.model.mcp.registry;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
/**
|
||||
* NamedArgument per components.schemas.NamedArgument.
|
||||
|
@ -36,15 +34,8 @@ public class NamedArgument extends InputWithVariables implements Argument {
|
|||
|
||||
private String name;
|
||||
|
||||
@JsonProperty("is_repeated")
|
||||
private Boolean isRepeated;
|
||||
|
||||
/**
|
||||
* Optional UI/UX hint for value input; accept any JSON type to be forward-compatible.
|
||||
*/
|
||||
@JsonProperty("value_hint")
|
||||
private JsonNode valueHint;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
@ -69,11 +60,4 @@ public class NamedArgument extends InputWithVariables implements Argument {
|
|||
this.isRepeated = isRepeated;
|
||||
}
|
||||
|
||||
public JsonNode getValueHint() {
|
||||
return valueHint;
|
||||
}
|
||||
|
||||
public void setValueHint(JsonNode valueHint) {
|
||||
this.valueHint = valueHint;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package com.alibaba.nacos.api.ai.model.mcp.registry;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Official metadata inside _meta.
|
||||
|
@ -27,23 +26,30 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class OfficialMeta {
|
||||
|
||||
private String id;
|
||||
private String serverId;
|
||||
|
||||
private String versionId;
|
||||
|
||||
@JsonProperty("published_at")
|
||||
private String publishedAt;
|
||||
|
||||
@JsonProperty("updated_at")
|
||||
private String updatedAt;
|
||||
|
||||
@JsonProperty("is_latest")
|
||||
private Boolean isLatest;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
public String getServerId() {
|
||||
return serverId;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
public void setServerId(String serverId) {
|
||||
this.serverId = serverId;
|
||||
}
|
||||
|
||||
public String getVersionId() {
|
||||
return versionId;
|
||||
}
|
||||
|
||||
public void setVersionId(String versionId) {
|
||||
this.versionId = versionId;
|
||||
}
|
||||
|
||||
public String getPublishedAt() {
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package com.alibaba.nacos.api.ai.model.mcp.registry;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -29,29 +28,22 @@ import java.util.List;
|
|||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Package {
|
||||
|
||||
@JsonProperty("registry_type")
|
||||
private String registryType;
|
||||
|
||||
@JsonProperty("registry_base_url")
|
||||
private String registryBaseUrl;
|
||||
|
||||
private String identifier;
|
||||
|
||||
private String version;
|
||||
|
||||
@JsonProperty("file_sha256")
|
||||
private String fileSha256;
|
||||
|
||||
@JsonProperty("runtime_hint")
|
||||
private String runtimeHint;
|
||||
|
||||
@JsonProperty("runtime_arguments")
|
||||
private List<Argument> runtimeArguments;
|
||||
|
||||
@JsonProperty("package_arguments")
|
||||
private List<Argument> packageArguments;
|
||||
|
||||
@JsonProperty("environment_variables")
|
||||
private List<KeyValueInput> environmentVariables;
|
||||
|
||||
public String getRegistryType() {
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package com.alibaba.nacos.api.ai.model.mcp.registry;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
|
||||
/**
|
||||
|
@ -31,10 +30,8 @@ public class PositionalArgument extends InputWithVariables implements Argument {
|
|||
|
||||
private String type = "positional";
|
||||
|
||||
@JsonProperty("value_hint")
|
||||
private String valueHint;
|
||||
|
||||
@JsonProperty("is_repeated")
|
||||
private Boolean isRepeated;
|
||||
|
||||
public String getType() {
|
||||
|
|
|
@ -30,20 +30,20 @@ import java.util.List;
|
|||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class Remote {
|
||||
|
||||
@JsonProperty("transport_type")
|
||||
@JsonAlias("type")
|
||||
private String transportType;
|
||||
@JsonProperty("type")
|
||||
@JsonAlias({"transport_type"})
|
||||
private String type;
|
||||
|
||||
private String url;
|
||||
|
||||
private List<KeyValueInput> headers;
|
||||
|
||||
public String getTransportType() {
|
||||
return transportType;
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setTransportType(String transportType) {
|
||||
this.transportType = transportType;
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
|
|
|
@ -38,39 +38,43 @@ class NacosMcpRegistryServerDetailTest extends BasicRequestTest {
|
|||
mcpRegistryServerDetail.setDescription("test mcp registry server object");
|
||||
mcpRegistryServerDetail.setRepository(new Repository());
|
||||
mcpRegistryServerDetail.setVersion("1.0.0");
|
||||
mcpRegistryServerDetail.setCreatedAt("2022-01-01T00:00:00Z");
|
||||
mcpRegistryServerDetail.setUpdatedAt("2022-01-01T00:00:00Z");
|
||||
mcpRegistryServerDetail.setPublishedAt("2022-01-01T00:00:00Z");
|
||||
Meta meta = new Meta();
|
||||
OfficialMeta official = new OfficialMeta();
|
||||
official.setPublishedAt("2022-01-01T00:00:00Z");
|
||||
meta.setOfficial(official);
|
||||
mcpRegistryServerDetail.setMeta(meta);
|
||||
mcpRegistryServerDetail.setRemotes(Collections.singletonList(new Remote()));
|
||||
mcpRegistryServerDetail.getRemotes().get(0).setUrl("127.0.0.1:8848/sse");
|
||||
mcpRegistryServerDetail.getRemotes().get(0).setTransportType("https");
|
||||
mcpRegistryServerDetail.getRemotes().get(0).setType("https");
|
||||
String json = mapper.writeValueAsString(mcpRegistryServerDetail);
|
||||
assertNotNull(json);
|
||||
assertTrue(json.contains("\"name\":\"testRegistryServer\""));
|
||||
assertTrue(json.contains("\"description\":\"test mcp registry server object\""));
|
||||
assertTrue(json.contains("\"repository\":{}"));
|
||||
assertTrue(json.contains("\"version\":\"1.0.0\""));
|
||||
assertTrue(json.contains("\"published_at\":\"2022-01-01T00:00:00Z\""));
|
||||
assertTrue(json.contains("\"io.modelcontextprotocol.registry/official\""));
|
||||
assertTrue(json.contains("\"publishedAt\":\"2022-01-01T00:00:00Z\""));
|
||||
assertTrue(json.contains("\"remotes\":[{"));
|
||||
assertTrue(json.contains("\"url\":\"127.0.0.1:8848/sse\""));
|
||||
assertTrue(json.contains("\"transport_type\":\"https\""));
|
||||
assertTrue(json.contains("\"type\":\"https\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeserialize() throws JsonProcessingException {
|
||||
String json = "{\"name\":\"testRegistryServer\",\"description\":\"test mcp registry server object\","
|
||||
+ "\"repository\":{},\"version\":\"1.0.0\",\"remotes\":[{\"transport_type\":\"https\","
|
||||
+ "\"url\":\"127.0.0.1:8848/sse\"}],\"published_at\":\"2022-01-01T00:00:00Z\"}";
|
||||
+ "\"repository\":{},\"version\":\"1.0.0\",\"remotes\":[{\"type\":\"https\","
|
||||
+ "\"url\":\"127.0.0.1:8848/sse\"}],\"_meta\":{\"io.modelcontextprotocol.registry/official\":"
|
||||
+ "{\"publishedAt\":\"2022-01-01T00:00:00Z\"}}}";
|
||||
McpRegistryServerDetail mcpRegistryServerDetail = mapper.readValue(json, McpRegistryServerDetail.class);
|
||||
assertNotNull(mcpRegistryServerDetail);
|
||||
assertEquals("testRegistryServer", mcpRegistryServerDetail.getName());
|
||||
assertEquals("test mcp registry server object", mcpRegistryServerDetail.getDescription());
|
||||
assertNotNull(mcpRegistryServerDetail.getRepository());
|
||||
assertEquals("1.0.0", mcpRegistryServerDetail.getVersion());
|
||||
assertEquals("2022-01-01T00:00:00Z", mcpRegistryServerDetail.getPublishedAt());
|
||||
assertEquals("2022-01-01T00:00:00Z", mcpRegistryServerDetail.getMeta().getOfficial().getPublishedAt());
|
||||
assertNotNull(mcpRegistryServerDetail.getRemotes());
|
||||
assertEquals(1, mcpRegistryServerDetail.getRemotes().size());
|
||||
assertEquals("https", mcpRegistryServerDetail.getRemotes().get(0).getTransportType());
|
||||
assertEquals("https", mcpRegistryServerDetail.getRemotes().get(0).getType());
|
||||
assertEquals("127.0.0.1:8848/sse", mcpRegistryServerDetail.getRemotes().get(0).getUrl());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -412,8 +412,9 @@ class McpDetail extends React.Component {
|
|||
serverName = 'mcp-server';
|
||||
}
|
||||
const serverConfig = {};
|
||||
if (packageDef.runtime_hint) {
|
||||
serverConfig.command = packageDef.runtime_hint;
|
||||
const runtimeHint = packageDef.runtimeHint || packageDef.runtime_hint;
|
||||
if (runtimeHint) {
|
||||
serverConfig.command = runtimeHint;
|
||||
} else if (this.getRegistryType(packageDef) === 'npm') {
|
||||
serverConfig.command = 'npx';
|
||||
} else {
|
||||
|
@ -433,8 +434,9 @@ class McpDetail extends React.Component {
|
|||
|
||||
// 检查是否已经有runtime_arguments包含了包名
|
||||
let hasPackageInRuntimeArgs = false;
|
||||
if (packageDef.runtime_arguments && Array.isArray(packageDef.runtime_arguments)) {
|
||||
for (const arg of packageDef.runtime_arguments) {
|
||||
const runtimeArguments = packageDef.runtimeArguments || packageDef.runtime_arguments || [];
|
||||
if (runtimeArguments && Array.isArray(runtimeArguments)) {
|
||||
for (const arg of runtimeArguments) {
|
||||
if (arg.value && arg.value.includes(pkgName)) {
|
||||
hasPackageInRuntimeArgs = true;
|
||||
break;
|
||||
|
@ -443,8 +445,8 @@ class McpDetail extends React.Component {
|
|||
}
|
||||
|
||||
// 先添加运行时参数
|
||||
if (packageDef.runtime_arguments && Array.isArray(packageDef.runtime_arguments)) {
|
||||
packageDef.runtime_arguments.forEach(arg => {
|
||||
if (runtimeArguments && Array.isArray(runtimeArguments)) {
|
||||
runtimeArguments.forEach(arg => {
|
||||
args.push(...this.processArgument(arg));
|
||||
});
|
||||
}
|
||||
|
@ -484,8 +486,9 @@ class McpDetail extends React.Component {
|
|||
}
|
||||
|
||||
// 添加包参数
|
||||
if (packageDef.package_arguments && Array.isArray(packageDef.package_arguments)) {
|
||||
packageDef.package_arguments.forEach(arg => {
|
||||
const packageArguments = packageDef.packageArguments || packageDef.package_arguments || [];
|
||||
if (packageArguments && Array.isArray(packageArguments)) {
|
||||
packageArguments.forEach(arg => {
|
||||
args.push(...this.processArgument(arg));
|
||||
});
|
||||
}
|
||||
|
@ -493,9 +496,11 @@ class McpDetail extends React.Component {
|
|||
serverConfig.args = args;
|
||||
|
||||
// 处理环境变量
|
||||
if (packageDef.environment_variables && Array.isArray(packageDef.environment_variables)) {
|
||||
const environmentVariables =
|
||||
packageDef.environmentVariables || packageDef.environment_variables || [];
|
||||
if (environmentVariables && Array.isArray(environmentVariables)) {
|
||||
const env = {};
|
||||
packageDef.environment_variables.forEach(envVar => {
|
||||
environmentVariables.forEach(envVar => {
|
||||
if (envVar.name) {
|
||||
let value = envVar.value || envVar.default;
|
||||
if (!value) {
|
||||
|
@ -542,8 +547,8 @@ class McpDetail extends React.Component {
|
|||
case 'positional':
|
||||
if (arg.value) {
|
||||
result.push(this.replaceVariables(arg.value, arg.variables));
|
||||
} else if (arg.value_hint) {
|
||||
result.push(`<${arg.value_hint}>`);
|
||||
} else if (arg.value_hint || arg.valueHint) {
|
||||
result.push(`<${arg.value_hint || arg.valueHint}>`);
|
||||
} else if (arg.default) {
|
||||
result.push(this.replaceVariables(arg.default, arg.variables));
|
||||
}
|
||||
|
@ -607,9 +612,13 @@ class McpDetail extends React.Component {
|
|||
const isTabsExpanded = this.state.packageTabsExpanded[index];
|
||||
|
||||
// 统计各类参数数量
|
||||
const runtimeArgsCount = packageDef.runtime_arguments?.length || 0;
|
||||
const packageArgsCount = packageDef.package_arguments?.length || 0;
|
||||
const envVarsCount = packageDef.environment_variables?.length || 0;
|
||||
const runtimeArguments = packageDef.runtimeArguments || packageDef.runtime_arguments || [];
|
||||
const packageArguments = packageDef.packageArguments || packageDef.package_arguments || [];
|
||||
const environmentVariables =
|
||||
packageDef.environmentVariables || packageDef.environment_variables || [];
|
||||
const runtimeArgsCount = runtimeArguments?.length || 0;
|
||||
const packageArgsCount = packageArguments?.length || 0;
|
||||
const envVarsCount = environmentVariables?.length || 0;
|
||||
const totalParamsCount = runtimeArgsCount + packageArgsCount + envVarsCount;
|
||||
|
||||
return (
|
||||
|
@ -731,7 +740,7 @@ class McpDetail extends React.Component {
|
|||
{this.getRegistryType(packageDef)}
|
||||
</p>
|
||||
</Col>
|
||||
{packageDef.runtime_hint && (
|
||||
{(packageDef.runtimeHint || packageDef.runtime_hint) && (
|
||||
<Col span={24} style={{ display: 'flex', marginBottom: '8px' }}>
|
||||
<p style={{ minWidth: 120, fontWeight: 'bold', color: '#000' }}>
|
||||
{locale.runtimeHint || '运行时提示'}:
|
||||
|
@ -745,7 +754,7 @@ class McpDetail extends React.Component {
|
|||
color: '#000',
|
||||
}}
|
||||
>
|
||||
{packageDef.runtime_hint}
|
||||
{packageDef.runtimeHint || packageDef.runtime_hint}
|
||||
</p>
|
||||
</Col>
|
||||
)}
|
||||
|
@ -831,14 +840,14 @@ class McpDetail extends React.Component {
|
|||
</div>
|
||||
{this.state.parameterContainersExpanded[index]?.runtime && (
|
||||
<div style={{ padding: '8px 16px' }}>
|
||||
{packageDef.runtime_arguments.map((arg, argIndex) => (
|
||||
{runtimeArguments.map((arg, argIndex) => (
|
||||
<div
|
||||
key={argIndex}
|
||||
style={{
|
||||
marginBottom: '8px',
|
||||
paddingBottom: '8px',
|
||||
borderBottom:
|
||||
argIndex < packageDef.runtime_arguments.length - 1
|
||||
argIndex < runtimeArguments.length - 1
|
||||
? '1px solid #e6e6e6'
|
||||
: 'none',
|
||||
}}
|
||||
|
@ -916,14 +925,14 @@ class McpDetail extends React.Component {
|
|||
</div>
|
||||
{this.state.parameterContainersExpanded[index]?.package && (
|
||||
<div style={{ padding: '8px 16px' }}>
|
||||
{packageDef.package_arguments.map((arg, argIndex) => (
|
||||
{packageArguments.map((arg, argIndex) => (
|
||||
<div
|
||||
key={argIndex}
|
||||
style={{
|
||||
marginBottom: '8px',
|
||||
paddingBottom: '8px',
|
||||
borderBottom:
|
||||
argIndex < packageDef.package_arguments.length - 1
|
||||
argIndex < packageArguments.length - 1
|
||||
? '1px solid #e6e6e6'
|
||||
: 'none',
|
||||
}}
|
||||
|
@ -999,14 +1008,14 @@ class McpDetail extends React.Component {
|
|||
</div>
|
||||
{this.state.parameterContainersExpanded[index]?.env && (
|
||||
<div style={{ padding: '8px 16px' }}>
|
||||
{packageDef.environment_variables.map((envVar, envIndex) => (
|
||||
{environmentVariables.map((envVar, envIndex) => (
|
||||
<div
|
||||
key={envIndex}
|
||||
style={{
|
||||
marginBottom: '8px',
|
||||
paddingBottom: '8px',
|
||||
borderBottom:
|
||||
envIndex < packageDef.environment_variables.length - 1
|
||||
envIndex < environmentVariables.length - 1
|
||||
? '1px solid #e6e6e6'
|
||||
: 'none',
|
||||
}}
|
||||
|
@ -1047,7 +1056,7 @@ class McpDetail extends React.Component {
|
|||
{envVar.value || envVar.default || '<未设置>'}
|
||||
</span>
|
||||
<div style={{ display: 'flex', gap: '6px' }}>
|
||||
{envVar.is_required && (
|
||||
{(envVar.isRequired || envVar.is_required) && (
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: '#ff4d4f',
|
||||
|
@ -1061,7 +1070,7 @@ class McpDetail extends React.Component {
|
|||
必填
|
||||
</span>
|
||||
)}
|
||||
{envVar.is_secret && (
|
||||
{(envVar.isSecret || envVar.is_secret) && (
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: '#faad14',
|
||||
|
@ -1120,7 +1129,7 @@ class McpDetail extends React.Component {
|
|||
// 注册表类型:优先 registry_type,兼容旧 registry_name
|
||||
getRegistryType = packageDef => {
|
||||
if (!packageDef) return '';
|
||||
return packageDef.registry_type || packageDef.registry_name || '';
|
||||
return packageDef.registryType || packageDef.registry_type || packageDef.registry_name || '';
|
||||
};
|
||||
|
||||
// 包名显示与链接用:优先 identifier,兼容旧 name
|
||||
|
@ -1131,10 +1140,10 @@ class McpDetail extends React.Component {
|
|||
|
||||
// 获取包名对应的仓库链接
|
||||
getPackageRepositoryUrl = packageDef => {
|
||||
const registry_name = this.getRegistryType(packageDef);
|
||||
const registryType = this.getRegistryType(packageDef);
|
||||
const name = (packageDef && (packageDef.identifier || packageDef.name)) || '';
|
||||
|
||||
switch (registry_name) {
|
||||
switch (registryType) {
|
||||
case 'npm':
|
||||
return `https://www.npmjs.com/package/${name}`;
|
||||
case 'docker':
|
||||
|
@ -1247,7 +1256,7 @@ class McpDetail extends React.Component {
|
|||
>
|
||||
{header.name}
|
||||
</span>
|
||||
{header.is_required && (
|
||||
{(header.isRequired || header.is_required) && (
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: '#ff4d4f',
|
||||
|
@ -1262,7 +1271,7 @@ class McpDetail extends React.Component {
|
|||
必填
|
||||
</span>
|
||||
)}
|
||||
{header.is_secret && (
|
||||
{(header.isSecret || header.is_secret) && (
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: '#faad14',
|
||||
|
@ -1468,7 +1477,7 @@ class McpDetail extends React.Component {
|
|||
const versionSelections = [];
|
||||
for (let i = 0; i < versions.length; i++) {
|
||||
const item = versions[i];
|
||||
if (item.is_latest) {
|
||||
if (item.isLatest || item.is_latest) {
|
||||
versionSelections.push({
|
||||
label: item.version + ` (` + locale.versionIsPublished + ')',
|
||||
value: item.version,
|
||||
|
|
|
@ -132,7 +132,10 @@ class NewMcpServer extends React.Component {
|
|||
|
||||
const allPublishedVersions = [];
|
||||
for (let i = 0; i < allVersions.length; i++) {
|
||||
if (i === allVersions.length - 1 && !allVersions[i].is_latest) {
|
||||
if (
|
||||
i === allVersions.length - 1 &&
|
||||
!(allVersions[i].isLatest || allVersions[i].is_latest)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
allPublishedVersions.push(allVersions[i].version);
|
||||
|
@ -140,7 +143,7 @@ class NewMcpServer extends React.Component {
|
|||
|
||||
this.setState({
|
||||
currentVersion: versionDetail.version,
|
||||
isLatestVersion: versionDetail.is_latest,
|
||||
isLatestVersion: versionDetail.isLatest || versionDetail.is_latest,
|
||||
versionsList: allPublishedVersions,
|
||||
});
|
||||
|
||||
|
@ -250,21 +253,21 @@ class NewMcpServer extends React.Component {
|
|||
}
|
||||
|
||||
const pkg = {
|
||||
registry_type: this.inferRegistryType(parsedCommand),
|
||||
registryType: this.inferRegistryType(parsedCommand),
|
||||
identifier: this.extractPackageNameFromArgs(parsedArgs, parsedCommand),
|
||||
version: this.extractPackageVersionFromArgs(parsedArgs),
|
||||
};
|
||||
|
||||
// 处理 runtime hint 和 runtime arguments
|
||||
if (parsedCommand && parsedCommand !== pkg.identifier) {
|
||||
pkg.runtime_hint = parsedCommand;
|
||||
pkg.runtimeHint = parsedCommand;
|
||||
|
||||
// 从 args 中提取 runtime_arguments 和 package_arguments
|
||||
if (parsedArgs && Array.isArray(parsedArgs)) {
|
||||
const { runtimeArgs, packageArgs } = this.separateArguments(parsedArgs, pkg.identifier);
|
||||
|
||||
if (runtimeArgs.length > 0) {
|
||||
pkg.runtime_arguments = runtimeArgs.map(arg => ({
|
||||
pkg.runtimeArguments = runtimeArgs.map(arg => ({
|
||||
type: 'positional',
|
||||
value: arg,
|
||||
format: 'string',
|
||||
|
@ -272,7 +275,7 @@ class NewMcpServer extends React.Component {
|
|||
}
|
||||
|
||||
if (packageArgs.length > 0) {
|
||||
pkg.package_arguments = packageArgs.map(arg => ({
|
||||
pkg.packageArguments = packageArgs.map(arg => ({
|
||||
type: 'positional',
|
||||
value: arg,
|
||||
format: 'string',
|
||||
|
@ -281,7 +284,7 @@ class NewMcpServer extends React.Component {
|
|||
}
|
||||
} else if (parsedArgs && Array.isArray(parsedArgs)) {
|
||||
// 如果 command 就是包名,所有 args 都是 package_arguments
|
||||
pkg.package_arguments = parsedArgs.map(arg => ({
|
||||
pkg.packageArguments = parsedArgs.map(arg => ({
|
||||
type: 'positional',
|
||||
value: arg,
|
||||
format: 'string',
|
||||
|
@ -290,7 +293,7 @@ class NewMcpServer extends React.Component {
|
|||
|
||||
// 处理环境变量
|
||||
if (config.env && typeof config.env === 'object') {
|
||||
pkg.environment_variables = Object.entries(config.env).map(([name, value]) => ({
|
||||
pkg.environmentVariables = Object.entries(config.env).map(([name, value]) => ({
|
||||
name: name,
|
||||
value: value,
|
||||
format: 'string',
|
||||
|
@ -1088,7 +1091,9 @@ class NewMcpServer extends React.Component {
|
|||
|
||||
let hasDraftVersion = false;
|
||||
if (versions.length > 0) {
|
||||
hasDraftVersion = !versions[versions.length - 1].is_latest;
|
||||
hasDraftVersion = !(
|
||||
versions[versions.length - 1].isLatest || versions[versions.length - 1].is_latest
|
||||
);
|
||||
}
|
||||
|
||||
let currentVersionExist = versions
|
||||
|
|
|
@ -217,7 +217,8 @@ public class NacosMcpRegistryService {
|
|||
if (detail == null) {
|
||||
return;
|
||||
}
|
||||
detail.setStatus(AiConstants.Mcp.MCP_STATUS_ACTIVE);
|
||||
// Align with enum-based status in registry model
|
||||
detail.setStatus("active");
|
||||
detail.setSchema("https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json");
|
||||
Meta meta = detail.getMeta();
|
||||
if (meta == null) {
|
||||
|
@ -227,10 +228,19 @@ public class NacosMcpRegistryService {
|
|||
if (official == null) {
|
||||
official = new OfficialMeta();
|
||||
}
|
||||
official.setId(id);
|
||||
official.setPublishedAt(detail.getPublishedAt());
|
||||
official.setUpdatedAt(detail.getUpdatedAt());
|
||||
official.setIsLatest(detail.getUpdatedAt() != null && detail.getUpdatedAt().equals(detail.getPublishedAt()));
|
||||
official.setServerId(id);
|
||||
// published/updated timestamps should be carried in official meta rather than top-level
|
||||
// Keep existing values if already set
|
||||
if (official.getPublishedAt() == null && detail.getMeta() != null && detail.getMeta().getOfficial() != null) {
|
||||
official.setPublishedAt(detail.getMeta().getOfficial().getPublishedAt());
|
||||
}
|
||||
if (official.getUpdatedAt() == null && detail.getMeta() != null && detail.getMeta().getOfficial() != null) {
|
||||
official.setUpdatedAt(detail.getMeta().getOfficial().getUpdatedAt());
|
||||
}
|
||||
// If still null, do not synthesize values here
|
||||
if (official.getIsLatest() == null && detail.getMeta() != null && detail.getMeta().getOfficial() != null) {
|
||||
official.setIsLatest(detail.getMeta().getOfficial().getIsLatest());
|
||||
}
|
||||
meta.setOfficial(official);
|
||||
detail.setMeta(meta);
|
||||
}
|
||||
|
@ -266,7 +276,7 @@ public class NacosMcpRegistryService {
|
|||
}
|
||||
return endpoints.stream().map((item) -> {
|
||||
Remote remote = new Remote();
|
||||
remote.setTransportType(transport);
|
||||
remote.setType(transport);
|
||||
remote.setUrl(String.format("%s://%s:%d%s", Constants.PROTOCOL_TYPE_HTTP, item.getAddress(),
|
||||
item.getPort(), item.getPath()));
|
||||
KeyValueInput headerAuth = new KeyValueInput();
|
||||
|
@ -310,9 +320,20 @@ public class NacosMcpRegistryService {
|
|||
if (mcpServerDetail.getVersionDetail() != null) {
|
||||
result.setVersion(mcpServerDetail.getVersionDetail().getVersion());
|
||||
String iso = toRfc3339(mcpServerDetail.getVersionDetail().getRelease_date());
|
||||
result.setCreatedAt(iso);
|
||||
result.setUpdatedAt(iso);
|
||||
result.setPublishedAt(iso);
|
||||
Meta meta = result.getMeta();
|
||||
if (meta == null) {
|
||||
meta = new Meta();
|
||||
}
|
||||
OfficialMeta official = meta.getOfficial();
|
||||
if (official == null) {
|
||||
official = new OfficialMeta();
|
||||
}
|
||||
official.setPublishedAt(iso);
|
||||
official.setUpdatedAt(iso);
|
||||
// mark latest when release equals update in our simple synthesis
|
||||
official.setIsLatest(Boolean.TRUE);
|
||||
meta.setOfficial(official);
|
||||
result.setMeta(meta);
|
||||
}
|
||||
enrich(result, id, mcpServerDetail.getFrontProtocol(),
|
||||
mcpServerDetail.getFrontendEndpoints(), mcpServerDetail.getBackendEndpoints());
|
||||
|
|
|
@ -18,6 +18,8 @@ package com.alibaba.nacos.mcpregistry.controller;
|
|||
|
||||
import com.alibaba.nacos.api.ai.model.mcp.registry.McpRegistryServerDetail;
|
||||
import com.alibaba.nacos.api.ai.model.mcp.registry.McpRegistryServerList;
|
||||
import com.alibaba.nacos.api.ai.model.mcp.registry.Meta;
|
||||
import com.alibaba.nacos.api.ai.model.mcp.registry.OfficialMeta;
|
||||
import com.alibaba.nacos.api.exception.api.NacosApiException;
|
||||
import com.alibaba.nacos.mcpregistry.form.GetServerForm;
|
||||
import com.alibaba.nacos.mcpregistry.service.NacosMcpRegistryService;
|
||||
|
@ -150,9 +152,12 @@ class McpRegistryControllerTest {
|
|||
McpRegistryServerDetail d = new McpRegistryServerDetail();
|
||||
d.setName(id + "-name");
|
||||
d.setDescription("desc-" + id);
|
||||
d.setPublishedAt(publishedAt);
|
||||
d.setCreatedAt(publishedAt);
|
||||
d.setUpdatedAt(updatedAt);
|
||||
Meta meta = new Meta();
|
||||
OfficialMeta official = new OfficialMeta();
|
||||
official.setPublishedAt(publishedAt);
|
||||
official.setUpdatedAt(updatedAt);
|
||||
meta.setOfficial(official);
|
||||
d.setMeta(meta);
|
||||
return d;
|
||||
}
|
||||
|
||||
|
|
|
@ -236,7 +236,9 @@ class NacosMcpRegistryServiceTest {
|
|||
assertEquals("Description:" + RANDOM_NAMESPACE_ID, result.getDescription());
|
||||
assertNull(result.getRepository());
|
||||
assertEquals("1.0.0", result.getVersion());
|
||||
assertEquals("2025-06-10T02:29:17Z", result.getPublishedAt());
|
||||
assertNotNull(result.getMeta());
|
||||
assertNotNull(result.getMeta().getOfficial());
|
||||
assertEquals("2025-06-10T02:29:17Z", result.getMeta().getOfficial().getPublishedAt());
|
||||
assertNull(result.getRemotes());
|
||||
}
|
||||
|
||||
|
@ -254,10 +256,12 @@ class NacosMcpRegistryServiceTest {
|
|||
assertEquals("Description:" + RANDOM_NAMESPACE_ID, result.getDescription());
|
||||
assertNull(result.getRepository());
|
||||
assertEquals("1.0.0", result.getVersion());
|
||||
assertEquals("2025-06-10T02:29:17Z", result.getPublishedAt());
|
||||
assertNotNull(result.getMeta());
|
||||
assertNotNull(result.getMeta().getOfficial());
|
||||
assertEquals("2025-06-10T02:29:17Z", result.getMeta().getOfficial().getPublishedAt());
|
||||
assertNotNull(result.getRemotes());
|
||||
assertEquals(1, result.getRemotes().size());
|
||||
assertEquals("sse", result.getRemotes().get(0).getTransportType());
|
||||
assertEquals("sse", result.getRemotes().get(0).getType());
|
||||
assertEquals("http://127.0.0.1:8080/api/path", result.getRemotes().get(0).getUrl());
|
||||
}
|
||||
|
||||
|
@ -296,14 +300,16 @@ class NacosMcpRegistryServiceTest {
|
|||
for (int i = 0; i < actualSize; i++) {
|
||||
McpServerBasicInfo basicInfo = mockMcpServerBasicInfo(i, namespaceId);
|
||||
mockPage.getPageItems().add(basicInfo);
|
||||
// ensure getServer won't return null by mocking index and detail lookup for each generated id
|
||||
// ensure getServer won't return null by mocking index and detail lookup for
|
||||
// each generated id
|
||||
McpServerIndexData indexData = new McpServerIndexData();
|
||||
indexData.setId(basicInfo.getId());
|
||||
indexData.setNamespaceId(namespaceId);
|
||||
Mockito.lenient().when(mcpServerIndex.getMcpServerById(basicInfo.getId())).thenReturn(indexData);
|
||||
// default detail mocks without backend endpoints or tools
|
||||
try {
|
||||
Mockito.lenient().when(mcpServerOperationService.getMcpServerDetail(namespaceId, basicInfo.getId(), null, null))
|
||||
Mockito.lenient()
|
||||
.when(mcpServerOperationService.getMcpServerDetail(namespaceId, basicInfo.getId(), null, null))
|
||||
.thenReturn(mockMcpServerDetailInfo(basicInfo.getId(), namespaceId, false, false));
|
||||
} catch (NacosException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
|
Loading…
Reference in New Issue