diff --git a/README.md b/README.md index dfade1d..6786ea9 100644 --- a/README.md +++ b/README.md @@ -1028,6 +1028,26 @@ foreach (var run in runs?.Runs ?? []) } ``` +## Marketplace MCP connections — tool catalog + +An MCP connection (a Marketplace connection whose listing uses the MCP +transport) stores its server's tool catalog when it is validated. +`Code.GetMarketplaceIntegrationToolsAsync` reads it +(`GET /{version}/code/marketplace/integrations/{IntegrationViewId}/tools`): +each tool has `Name`, `Title`, `Description`, `InputSchema` (JSON Schema as +text), `ReadOnlyHint` and `DestructiveHint`; the response also carries +`ListedAt`, `CatalogHash` and `Truncated`. A connection that was never +validated answers `CM-ERRORS-MARKETPLACE-020`. A function on a tool is saved +with `SaveMarketplaceFunction` (`Function.FunctionKey` = the tool name). + +```csharp +var catalog = await hub.Code.GetMarketplaceIntegrationToolsAsync( + new GetMarketplaceIntegrationTools { IntegrationViewId = "int_…" }); + +foreach (var tool in catalog?.Tools ?? []) + Console.WriteLine($"{tool.Name} {(tool.ReadOnlyHint == true ? "read-only" : "writes")}"); +``` + ## Coverage Notes Generated coverage tracks the API and Hub DTO contract files. Some flows are not generated automatically: diff --git a/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs b/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs index 0e921cc..c456187 100644 --- a/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs +++ b/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs @@ -4128,6 +4128,28 @@ public partial class MarketplaceListingProjection public virtual string? VideoUrl { get; set; } } + [DataContract] + public partial class MarketplaceMcpToolDto + { + [DataMember] + public virtual string Name { get; set; } + + [DataMember] + public virtual string? Title { get; set; } + + [DataMember] + public virtual string? Description { get; set; } + + [DataMember] + public virtual string InputSchema { get; set; } + + [DataMember] + public virtual bool? ReadOnlyHint { get; set; } + + [DataMember] + public virtual bool? DestructiveHint { get; set; } + } + public enum MarketplaceParameterLocation { Body, @@ -11598,6 +11620,25 @@ public partial class GetMarketplaceIntegrationsResponse public virtual PaginatedResponse? List { get; set; } } + [NorbixRoute("/{version}/code/marketplace/integrations/{IntegrationViewId}/tools", "GET")] + public partial class GetMarketplaceIntegrationTools + : CodeMashRequestBase, INorbixRequest + { + /// + ///Integration view id, from get_marketplace_integrations. + /// + public virtual string IntegrationViewId { get; set; } + } + + public partial class GetMarketplaceIntegrationToolsResponse + : ResponseBase + { + public virtual IReadOnlyList Tools { get; set; } + public virtual DateTimeOffset? ListedAt { get; set; } + public virtual string? CatalogHash { get; set; } + public virtual bool Truncated { get; set; } + } + [NorbixRoute("/{version}/code/marketplace/integrations/{IntegrationViewId}/secrets", "PUT")] [DataContract] public partial class ReplaceMarketplaceIntegrationSecretsRequest diff --git a/tests/Norbix.Hub.Tests/MarketplaceMcpToolsTests.cs b/tests/Norbix.Hub.Tests/MarketplaceMcpToolsTests.cs new file mode 100644 index 0000000..661103a --- /dev/null +++ b/tests/Norbix.Hub.Tests/MarketplaceMcpToolsTests.cs @@ -0,0 +1,57 @@ +using Norbix.Sdk.Tests.Helpers; +using Norbix.Sdk.Types.Hub; + +using NUnit.Framework; + +using VerifyNUnit; + +namespace Norbix.Hub.Tests; + +/// +/// GET /{version}/code/marketplace/integrations/{IntegrationViewId}/tools returns +/// the stored tool catalog of an MCP connection. The coverage snapshot sends a +/// placeholder id; this case pins a real connection id in the path, no query +/// value for it, no body, and the catalog read back from the generated response. +/// Nothing leaves the process. +/// +[TestFixture] +public sealed class MarketplaceMcpToolsTests +{ + private static NorbixTestFixture NewFixture() => + NorbixTestFixture.Create(o => + { + o.AccountId = "test-account"; + o.BearerToken = "test-bearer"; + }); + + [Test] + public async Task Tools_put_the_connection_id_in_the_path_with_no_body() + { + using var fixture = NewFixture(); + fixture.RespondNext(new + { + tools = new[] + { + new + { + name = "create_issue", + title = "Create issue", + description = "Open a new issue in a repository", + inputSchema = "{\"type\":\"object\",\"properties\":{\"title\":{\"type\":\"string\"}},\"required\":[\"title\"]}", + readOnlyHint = false, + destructiveHint = false, + }, + }, + listedAt = "2026-10-13T08:00:00Z", + catalogHash = "4ce4259bec02f283", + truncated = false, + }); + + var response = await fixture.Client.Code.GetMarketplaceIntegrationToolsAsync( + new GetMarketplaceIntegrationTools { IntegrationViewId = "int_7Hq2mcpServer" }); + + await Verifier.Verify( + new { Sent = fixture.LastRequest, response!.Tools, response.CatalogHash, response.Truncated }, + VerifyConfig.VerifySettings); + } +} diff --git a/tests/Norbix.Hub.Tests/test_results/EndpointCoverageTests.Hub.Code.verified.txt b/tests/Norbix.Hub.Tests/test_results/EndpointCoverageTests.Hub.Code.verified.txt index d18ee68..1a9d872 100644 --- a/tests/Norbix.Hub.Tests/test_results/EndpointCoverageTests.Hub.Code.verified.txt +++ b/tests/Norbix.Hub.Tests/test_results/EndpointCoverageTests.Hub.Code.verified.txt @@ -1,7 +1,7 @@ { Target: Hub, Group: code, - Count: 38, + Count: 39, Endpoints: [ { Target: Hub, @@ -1060,6 +1060,41 @@ } } }, + { + Target: Hub, + Group: code, + MethodName: GetMarketplaceIntegrationToolsAsync, + RequestType: GetMarketplaceIntegrationTools, + HttpMethod: GET, + Path: /{version}/code/marketplace/integrations/{IntegrationViewId}/tools, + PathParams: [ + IntegrationViewId + ], + IsAccountScoped: false, + IsUnauthenticated: false, + Sent: { + Method: GET, + Path: /v2/code/marketplace/integrations/{guid}/tools, + Query: ?env=test-env, + Headers: { + Authorization: Bearer test-bearer, + norbix-account-id: test-account, + norbix-project-id: test-project + } + }, + ResponseType: GetMarketplaceIntegrationToolsResponse, + Response: { + Tools: [ + {} + ], + ListedAt: DateTimeOffset_1, + CatalogHash: test-catalogHash, + Truncated: true, + ResponseStatus: { + IsSuccess: true + } + } + }, { Target: Hub, Group: code, diff --git a/tests/Norbix.Hub.Tests/test_results/MarketplaceMcpToolsTests.Tools_put_the_connection_id_in_the_path_with_no_body.verified.txt b/tests/Norbix.Hub.Tests/test_results/MarketplaceMcpToolsTests.Tools_put_the_connection_id_in_the_path_with_no_body.verified.txt new file mode 100644 index 0000000..b460bff --- /dev/null +++ b/tests/Norbix.Hub.Tests/test_results/MarketplaceMcpToolsTests.Tools_put_the_connection_id_in_the_path_with_no_body.verified.txt @@ -0,0 +1,23 @@ +{ + Sent: { + Method: GET, + Path: /v2/code/marketplace/integrations/int_7Hq2mcpServer/tools, + Headers: { + Authorization: Bearer test-bearer, + norbix-account-id: test-account, + norbix-project-id: test-project + } + }, + Tools: [ + { + Name: create_issue, + Title: Create issue, + Description: Open a new issue in a repository, + InputSchema: {"type":"object","properties":{"title":{"type":"string"}},"required":["title"]}, + ReadOnlyHint: false, + DestructiveHint: false + } + ], + CatalogHash: 4ce4259bec02f283, + Truncated: false +} \ No newline at end of file