Skip to content
Closed
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
117 changes: 116 additions & 1 deletion enterprise/e2e/html/hurl/mcp-2025-11-25-resources.all.hurl

Large diffs are not rendered by default.

52 changes: 51 additions & 1 deletion enterprise/e2e/path/hurl/mcp-2025-11-25-resources.all.hurl

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions enterprise/index/enterprise_index.cc
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,15 @@ auto generate_mcp_resources(const std::filesystem::path &search_metapack_path,
std::string uri{configuration.origin};
uri.append(entry.path);

entries.push_back(sourcemeta::core::mcp_make_resource(
auto resource{sourcemeta::core::mcp_make_resource(
uri, name, "application/schema+json", entry.description,
static_cast<std::size_t>(entry.bytes_raw)));
static_cast<std::size_t>(entry.bytes_raw))};
auto annotations{sourcemeta::core::JSON::make_object()};
annotations.assign("priority",
sourcemeta::core::JSON{
static_cast<double>(entry.weight) / 100.0});
resource.assign("annotations", std::move(annotations));
entries.push_back(std::move(resource));
});

page.assign("resources", std::move(entries));
Expand Down
8 changes: 8 additions & 0 deletions src/build/delta.cc
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,14 @@ auto delta(const BuildPhase phase, const BuildPlan::Type build_type,
}
}
break;
case DirectoryDependencyKind::AllSchemaMetadata:
for (const auto &schema_relative : all_relative_paths) {
rule_dependencies.push_back(append_filename(
make_base_string(output_string, EXPLORER_DIRECTORY,
schema_relative.native()),
SCHEMA_METADATA_RULE.filename));
}
break;
case DirectoryDependencyKind::ChildDirectories:
for (const auto &other_directory : affected_directories) {
auto other_relative{
Expand Down
5 changes: 4 additions & 1 deletion src/build/rules.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ enum class DirectoryScope : std::uint8_t { AllDirectories, NonRoot, RootOnly };

enum class DirectoryDependencyKind : std::uint8_t {
SchemaMetadata,
AllSchemaMetadata,
ChildDirectories,
AllDirectoryListings,
SameDirectoryTarget,
Expand Down Expand Up @@ -238,8 +239,10 @@ static constexpr std::array<DirectoryRule, 6> DIRECTORY_RULES{{
.scope = DirectoryScope::RootOnly,
.only_full_rebuild = false,
.dependencies = {{{.kind = DirectoryDependencyKind::AllDirectoryListings,
.filename = nullptr},
{.kind = DirectoryDependencyKind::AllSchemaMetadata,
.filename = nullptr}}},
.dependency_count = 1},
.dependency_count = 2},

{.action = BuildPlan::Action::Type::Mcp,
.filename = "mcp.metapack",
Expand Down
10 changes: 8 additions & 2 deletions src/configuration/include/sourcemeta/one/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ struct Configuration {
-> sourcemeta::core::JSON;
static auto parse(const sourcemeta::core::JSON &data,
const std::filesystem::path &configuration_path,
const std::filesystem::path &default_base_path)
-> Configuration;
const std::filesystem::path &default_base_path,
const std::filesystem::path &self_path) -> Configuration;

sourcemeta::core::JSON::String url;
sourcemeta::core::JSON::String base_path;
Expand Down Expand Up @@ -61,6 +61,12 @@ struct Configuration {

using Collection = sourcemeta::blaze::Configuration;

[[nodiscard]] static auto is_self_collection(const Collection &collection)
-> bool {
const auto *value{collection.extra.try_at("x-sourcemeta-one:is-self")};
return value != nullptr && value->is_boolean() && value->to_boolean();
}

[[nodiscard]] auto resolve_schema(const sourcemeta::core::URI &input) const
-> std::optional<std::filesystem::path>;

Expand Down
21 changes: 16 additions & 5 deletions src/configuration/parse.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <sourcemeta/blaze/evaluator.h>
#include <sourcemeta/blaze/output.h>
#include <sourcemeta/core/io.h>
#include <sourcemeta/core/uri.h>

#include "template.h"
Expand Down Expand Up @@ -32,13 +33,14 @@ auto page_from_json(const sourcemeta::core::JSON &input)
template <typename T>
auto entries_from_json(T &result, const std::filesystem::path &location,
const sourcemeta::core::JSON &input,
const std::filesystem::path &default_base_path) -> void {
const std::filesystem::path &default_base_path,
const std::filesystem::path &self_path) -> void {
// A heuristic to check if we are at the root or not
if (input.defines("url")) {
if (input.defines("contents")) {
for (const auto &entry : input.at("contents").as_object()) {
entries_from_json<T>(result, location / entry.first, entry.second,
default_base_path);
default_base_path, self_path);
}
}
} else {
Expand All @@ -63,14 +65,22 @@ auto entries_from_json(T &result, const std::filesystem::path &location,
// This URI is guaranteed to be canonicalised by the collection parser
assert(collection.base ==
sourcemeta::core::URI::canonicalize(collection.base));
if (collection_input.defines("x-sourcemeta-one:path") &&
sourcemeta::core::is_under_path(
std::filesystem::path{
collection_input.at("x-sourcemeta-one:path").to_string()},
self_path)) {
collection.extra.assign("x-sourcemeta-one:is-self",
sourcemeta::core::JSON{true});
}
result.emplace(location, std::move(collection));
} else {
result.emplace(location, page_from_json(input));
// Only pages may have children
if (input.defines("contents")) {
for (const auto &entry : input.at("contents").as_object()) {
entries_from_json<T>(result, location / entry.first, entry.second,
default_base_path);
default_base_path, self_path);
}
}
}
Expand All @@ -83,7 +93,8 @@ namespace sourcemeta::one {

auto Configuration::parse(const sourcemeta::core::JSON &data,
const std::filesystem::path &configuration_path,
const std::filesystem::path &default_base_path)
const std::filesystem::path &default_base_path,
const std::filesystem::path &self_path)
-> Configuration {
const auto compiled_schema{sourcemeta::blaze::from_json(
sourcemeta::core::parse_json(CONFIGURATION))};
Expand Down Expand Up @@ -140,7 +151,7 @@ auto Configuration::parse(const sourcemeta::core::JSON &data,
}
}

entries_from_json(result.entries, "", data, default_base_path);
entries_from_json(result.entries, "", data, default_base_path, self_path);

return result;
}
Expand Down
79 changes: 39 additions & 40 deletions src/index/explorer.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ struct MetapackExplorerSchemaExtension {
std::int64_t bytes_bundled;
std::int64_t dependencies;
MetapackVersionInfo version;
std::uint8_t is_self;
std::uint16_t path_length;
std::uint16_t identifier_length;
std::uint16_t base_dialect_length;
Expand Down Expand Up @@ -292,11 +293,11 @@ explorer_extension_alert(const MetapackExplorerSchemaExtension *extension,
static auto make_explorer_schema_extension(
const std::int64_t health, const std::int64_t bytes,
const std::int64_t bytes_bundled, const std::int64_t dependencies,
const MetapackVersionInfo &version, const std::string_view path,
const std::string_view identifier, const std::string_view base_dialect,
const std::string_view dialect, const std::string_view title,
const std::string_view description, const std::string_view alert)
-> std::vector<std::uint8_t> {
const MetapackVersionInfo &version, const bool is_self,
const std::string_view path, const std::string_view identifier,
const std::string_view base_dialect, const std::string_view dialect,
const std::string_view title, const std::string_view description,
const std::string_view alert) -> std::vector<std::uint8_t> {
assert(path.size() <= std::numeric_limits<std::uint16_t>::max());
assert(identifier.size() <= std::numeric_limits<std::uint16_t>::max());
assert(base_dialect.size() <= std::numeric_limits<std::uint16_t>::max());
Expand All @@ -317,6 +318,7 @@ static auto make_explorer_schema_extension(
header.bytes_bundled = bytes_bundled;
header.dependencies = dependencies;
header.version = version;
header.is_self = is_self ? 1 : 0;
header.path_length = static_cast<std::uint16_t>(path.size());
header.identifier_length = static_cast<std::uint16_t>(identifier.size());
header.base_dialect_length = static_cast<std::uint16_t>(base_dialect.size());
Expand Down Expand Up @@ -454,6 +456,9 @@ struct GENERATE_EXPLORER_SCHEMA_METADATA {
result.assign("alert", sourcemeta::core::JSON{nullptr});
}

const auto is_self{
sourcemeta::one::Configuration::is_self_collection(collection)};

result.assign("breadcrumb",
make_breadcrumb(configuration.base_path,
resolver_entry.relative_path, false));
Expand All @@ -467,7 +472,8 @@ struct GENERATE_EXPLORER_SCHEMA_METADATA {
static_cast<std::int64_t>(schema_info.content_bytes),
static_cast<std::int64_t>(bundle_info.content_bytes),
result.at("dependencies").to_integer(), parse_version_info(schema_name),
result.at("path").to_string(), result.at("identifier").to_string(),
is_self, result.at("path").to_string(),
result.at("identifier").to_string(),
result.at("baseDialect").to_string(), result.at("dialect").to_string(),
result.defines("title") ? result.at("title").to_string() : "",
result.defines("description") ? result.at("description").to_string()
Expand All @@ -494,41 +500,34 @@ struct GENERATE_EXPLORER_SEARCH_INDEX {
std::vector<sourcemeta::one::SearchEntry> entries;

for (const auto &dependency : action.dependencies) {
const auto directory_option{
sourcemeta::one::metapack_read_json(dependency)};
assert(directory_option.has_value());
const auto &directory{directory_option.value()};
assert(directory.is_object());
assert(directory.defines("entries"));

for (const auto &directory_entry : directory.at("entries").as_array()) {
if (!directory_entry.defines("type") ||
directory_entry.at("type").to_string() != "schema") {
continue;
}

entries.push_back(
{directory_entry.at("path").to_string(),
directory_entry.at("identifier").to_string(),
directory_entry.defines("title")
? directory_entry.at("title").to_string()
: "",
directory_entry.defines("description")
? directory_entry.at("description").to_string()
: "",
directory_entry.defines("health")
? static_cast<std::uint8_t>(
directory_entry.at("health").to_integer())
: static_cast<std::uint8_t>(0),
directory_entry.defines("bytes")
? static_cast<std::uint64_t>(
directory_entry.at("bytes").to_integer())
: static_cast<std::uint64_t>(0),
directory_entry.defines("bytesBundled")
? static_cast<std::uint64_t>(
directory_entry.at("bytesBundled").to_integer())
: static_cast<std::uint64_t>(0)});
// We depend on every per-schema metapack under `explorer/<path>/%/`
// (AllSchemaMetadata) plus every directory listing
// (AllDirectoryListings, kept for wave ordering). Only the former
// carries schema entries.
if (dependency.filename() != "schema.metapack") {
continue;
}

sourcemeta::core::FileView schema_view{dependency};
const auto *extension{
sourcemeta::one::metapack_extension<MetapackExplorerSchemaExtension>(
schema_view)};
assert(extension != nullptr);
const auto schema_option{sourcemeta::one::metapack_read_json(dependency)};
assert(schema_option.has_value());
const auto &schema{schema_option.value()};
assert(schema.is_object());

entries.push_back(
{schema.at("path").to_string(), schema.at("identifier").to_string(),
schema.defines("title") ? schema.at("title").to_string() : "",
schema.defines("description") ? schema.at("description").to_string()
: "",
static_cast<std::uint8_t>(schema.at("health").to_integer()),
static_cast<std::uint64_t>(schema.at("bytes").to_integer()),
static_cast<std::uint64_t>(schema.at("bytesBundled").to_integer()),
extension->is_self != 0 ? static_cast<std::uint8_t>(0)
: static_cast<std::uint8_t>(100)});
}

const auto payload{sourcemeta::one::make_search(std::move(entries))};
Expand Down
3 changes: 2 additions & 1 deletion src/index/index.cc
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,8 @@ static auto index_main(const std::string_view &program,
}

auto configuration{sourcemeta::one::Configuration::parse(
raw_configuration, configuration_path, configuration_path.parent_path())};
raw_configuration, configuration_path, configuration_path.parent_path(),
SOURCEMETA_ONE_SELF)};

/////////////////////////////////////////////////////////////////////////////
// (3) Resolve a URI to a schema filesystem path
Expand Down
3 changes: 3 additions & 0 deletions src/search/include/sourcemeta/one/search.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ struct SearchEntry {
std::uint8_t health;
std::uint64_t bytes_raw;
std::uint64_t bytes_bundled;
std::uint8_t weight;
};

struct SearchListEntry {
Expand All @@ -36,6 +37,7 @@ struct SearchListEntry {
std::string_view description;
std::uint64_t bytes_raw;
std::uint64_t bytes_bundled;
std::uint8_t weight;
};

#pragma pack(push, 1)
Expand All @@ -51,6 +53,7 @@ struct SearchRecordHeader {
std::uint16_t description_length;
std::uint64_t bytes_raw;
std::uint64_t bytes_bundled;
std::uint8_t weight;
};
#pragma pack(pop)

Expand Down
9 changes: 6 additions & 3 deletions src/search/search.cc
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ auto make_search(std::vector<SearchEntry> &&entries)
.description_length =
static_cast<std::uint16_t>(entry.description.size()),
.bytes_raw = entry.bytes_raw,
.bytes_bundled = entry.bytes_bundled};
.bytes_bundled = entry.bytes_bundled,
.weight = entry.weight};
std::memcpy(payload.data() + record_position, &record_header,
sizeof(SearchRecordHeader));
record_position += sizeof(SearchRecordHeader);
Expand Down Expand Up @@ -302,7 +303,8 @@ auto SearchView::at(const std::size_t index) -> SearchListEntry {
.title = title,
.description = description,
.bytes_raw = record_header->bytes_raw,
.bytes_bundled = record_header->bytes_bundled};
.bytes_bundled = record_header->bytes_bundled,
.weight = record_header->weight};
}

auto SearchView::for_each(
Expand Down Expand Up @@ -379,7 +381,8 @@ auto SearchView::for_each(
.title = title,
.description = description,
.bytes_raw = record_header->bytes_raw,
.bytes_bundled = record_header->bytes_bundled});
.bytes_bundled = record_header->bytes_bundled,
.weight = record_header->weight});
}
}

Expand Down
21 changes: 19 additions & 2 deletions src/self/v1/schemas/mcp/resources/list/response.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@
"name": "string",
"description": "An example string schema",
"mimeType": "application/schema+json",
"size": 143
"size": 143,
"annotations": {
"priority": 1
}
},
{
"uri": "https://example.com/v1/example/string?bundle=1",
"name": "string",
"description": "An example string schema",
"mimeType": "application/schema+json",
"size": 143
"size": 143,
"annotations": {
"priority": 1
}
},
{
"uri": "https://example.com/v1/no-description",
Expand Down Expand Up @@ -77,6 +83,17 @@
"size": {
"type": "integer",
"minimum": 0
},
"annotations": {
"type": "object",
"properties": {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: I guess we can even make the priority mandatory? Defaulted to 1?

"priority": {
"type": "number",
"maximum": 1,
"minimum": 0
}
},
"additionalProperties": true
}
},
"additionalProperties": false
Expand Down
4 changes: 2 additions & 2 deletions test/cli/index/common/search-index-nested-rebuild.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ EOF
SEARCH="$TMP/output/explorer/%/search.metapack"

extract_search_paths() {
strings "$1" | grep -E '^/(left|right)/' \
strings "$1" | grep -oE '/(left|right)/[^[:space:]]*' \
| sed 's|https*://.*$||' \
| LC_ALL=C sort
| LC_ALL=C sort -u
}

# Run 1: full build with two schemas in separate directories
Expand Down
Loading
Loading