158 lines
5.9 KiB
C++
158 lines
5.9 KiB
C++
// ============================================================================
|
|
// MCP Apps Extension - Tool Metadata Tests
|
|
// PBI-083: MCP Apps Support - Phase 1
|
|
//
|
|
// Test IDs: mcp-apps-tool-01 through mcp-apps-tool-07
|
|
// ============================================================================
|
|
|
|
#include <nlohmann/json.hpp>
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <yams/mcp/mcp_server.h>
|
|
|
|
using namespace yams::mcp;
|
|
using json = nlohmann::json;
|
|
|
|
namespace {
|
|
|
|
class NullTransport : public yams::mcp::ITransport {
|
|
public:
|
|
void send(const json&) override {}
|
|
yams::mcp::MessageResult receive() override {
|
|
return yams::Error{yams::ErrorCode::NotImplemented, "Null transport"};
|
|
}
|
|
bool isConnected() const override { return false; }
|
|
void close() override {}
|
|
yams::mcp::TransportState getState() const override {
|
|
return yams::mcp::TransportState::Disconnected;
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("MCP Apps Tool - UI Linkage With Support",
|
|
"[mcp][apps][tool][mcp-apps-tool-01][phase1]") {
|
|
auto transport = std::make_unique<NullTransport>();
|
|
auto server = std::make_shared<yams::mcp::MCPServer>(std::move(transport));
|
|
|
|
json initRequest = {{"jsonrpc", "2.0"},
|
|
{"id", 1},
|
|
{"method", "initialize"},
|
|
{"params",
|
|
{{"protocolVersion", "2025-11-25"},
|
|
{"clientInfo", {{"name", "test"}, {"version", "1.0"}}},
|
|
{"capabilities",
|
|
{{"extensions",
|
|
{{"io.modelcontextprotocol/ui",
|
|
{{"mimeTypes", json::array({"text/html;profile=mcp-app"})}}}}}}}}}};
|
|
|
|
auto initResponse = server->handleRequestPublic(initRequest);
|
|
REQUIRE(initResponse.has_value());
|
|
|
|
json toolsRequest = {
|
|
{"jsonrpc", "2.0"}, {"id", 2}, {"method", "tools/list"}, {"params", json::object()}};
|
|
|
|
auto toolsResponse = server->handleRequestPublic(toolsRequest);
|
|
REQUIRE(toolsResponse.has_value());
|
|
|
|
json result = toolsResponse.value();
|
|
REQUIRE(result.contains("result"));
|
|
REQUIRE(result["result"].contains("tools"));
|
|
|
|
// Check that at least one tool has UI metadata
|
|
bool foundUITool = false;
|
|
for (const auto& tool : result["result"]["tools"]) {
|
|
if (tool.contains("_meta") && tool["_meta"].contains("ui")) {
|
|
foundUITool = true;
|
|
CHECK(tool["_meta"]["ui"].contains("resourceUri"));
|
|
std::string uri = tool["_meta"]["ui"]["resourceUri"].get<std::string>();
|
|
CHECK(uri.find("ui://") == 0);
|
|
}
|
|
}
|
|
|
|
CHECK(foundUITool);
|
|
}
|
|
|
|
TEST_CASE("MCP Apps Tool - UI Linkage Without Support",
|
|
"[mcp][apps][tool][mcp-apps-tool-02][phase1]") {
|
|
auto transport = std::make_unique<NullTransport>();
|
|
auto server = std::make_shared<yams::mcp::MCPServer>(std::move(transport));
|
|
|
|
json initRequest = {{"jsonrpc", "2.0"},
|
|
{"id", 1},
|
|
{"method", "initialize"},
|
|
{"params",
|
|
{{"protocolVersion", "2025-11-25"},
|
|
{"clientInfo", {{"name", "test"}, {"version", "1.0"}}},
|
|
{"capabilities", json::object()}}}};
|
|
|
|
auto initResponse = server->handleRequestPublic(initRequest);
|
|
REQUIRE(initResponse.has_value());
|
|
|
|
json toolsRequest = {
|
|
{"jsonrpc", "2.0"}, {"id", 2}, {"method", "tools/list"}, {"params", json::object()}};
|
|
|
|
auto toolsResponse = server->handleRequestPublic(toolsRequest);
|
|
REQUIRE(toolsResponse.has_value());
|
|
|
|
json result = toolsResponse.value();
|
|
REQUIRE(result.contains("result"));
|
|
REQUIRE(result["result"].contains("tools"));
|
|
|
|
// Check that no tools have UI metadata
|
|
for (const auto& tool : result["result"]["tools"]) {
|
|
if (tool.contains("_meta")) {
|
|
CHECK_FALSE(tool["_meta"].contains("ui"));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("MCP Apps Tool - Visibility Model and App",
|
|
"[mcp][apps][tool][mcp-apps-tool-03][phase1]") {
|
|
auto transport = std::make_unique<NullTransport>();
|
|
auto server = std::make_shared<yams::mcp::MCPServer>(std::move(transport));
|
|
|
|
json initRequest = {{"jsonrpc", "2.0"},
|
|
{"id", 1},
|
|
{"method", "initialize"},
|
|
{"params",
|
|
{{"protocolVersion", "2025-11-25"},
|
|
{"clientInfo", {{"name", "test"}, {"version", "1.0"}}},
|
|
{"capabilities",
|
|
{{"extensions",
|
|
{{"io.modelcontextprotocol/ui",
|
|
{{"mimeTypes", json::array({"text/html;profile=mcp-app"})}}}}}}}}}};
|
|
|
|
auto initResponse = server->handleRequestPublic(initRequest);
|
|
REQUIRE(initResponse.has_value());
|
|
|
|
json toolsRequest = {
|
|
{"jsonrpc", "2.0"}, {"id", 2}, {"method", "tools/list"}, {"params", json::object()}};
|
|
|
|
auto toolsResponse = server->handleRequestPublic(toolsRequest);
|
|
REQUIRE(toolsResponse.has_value());
|
|
|
|
json result = toolsResponse.value();
|
|
|
|
// Look for tool with visibility ["model", "app"]
|
|
for (const auto& tool : result["result"]["tools"]) {
|
|
if (tool.contains("_meta") && tool["_meta"].contains("ui")) {
|
|
if (tool["_meta"]["ui"].contains("visibility")) {
|
|
auto visibility = tool["_meta"]["ui"]["visibility"];
|
|
bool hasModel = false;
|
|
bool hasApp = false;
|
|
for (const auto& v : visibility) {
|
|
if (v == "model")
|
|
hasModel = true;
|
|
if (v == "app")
|
|
hasApp = true;
|
|
}
|
|
|
|
if (hasModel && hasApp) {
|
|
// Tool should be visible and callable
|
|
CHECK(true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|