Repository navigation
Conversation
🦋 Changeset detectedLatest commit: f0af20f The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
5 issues found across 24 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="server/internal/agentmanagement/delegable_test.go">
<violation number="1" location="server/internal/agentmanagement/delegable_test.go:225">
P3: These `require.Contains` checks would still pass if discovery returned extra or wrongly scoped grants alongside the expected two. Assert the exact set with `require.ElementsMatch`, as the neighboring exclusion test does.</violation>
</file>
<file name="client/dashboard/src/pages/agents/agent-key-discovery.test.ts">
<violation number="1" location="client/dashboard/src/pages/agents/agent-key-discovery.test.ts:248">
P2: The third test passes `[]` (organization-wide reach) but its mock has no wildcard, so it never checks that wildcards from any project are kept. Add a case with `[]` and a `wildcard("project_two")` in the mock, and expect it in the result. That covers the organization-wide branch the wizard uses.</violation>
</file>
<file name="server/internal/mcp/serve_agent_gateway_test.go">
<violation number="1" location="server/internal/mcp/serve_agent_gateway_test.go:537">
P2: This test rebuilds the wizard's issuance by hand (grant filtering, policy version, containment check, direct `CreateAgentAPIKey`) instead of going through the key creation service. If the wizard's wildcard selection or the service's validation regresses, this test still passes. Drive issuance through `keys.Service.createAgentKey` (or the delegable-grants endpoint) so the test covers the code that ships.</violation>
</file>
<file name="server/design/agents/design.go">
<violation number="1" location="server/design/agents/design.go:313">
P3: The new `carry_exclusions` description says "Requesting both on a key", but "both" has no referent, and this endpoint takes no version. Name the pair and say that issuance enforces the version: "Storing the wildcard with its exclusion on one key requires delegated_grants_version 4 or later, enforced at key issuance."</violation>
</file>
<file name="client/dashboard/src/pages/agents/credentials/CredentialsWizard.tsx">
<violation number="1" location="client/dashboard/src/pages/agents/credentials/CredentialsWizard.tsx:1096">
P1: This treats any delegable wildcard as covering the full “All servers” reach. If one project has no delegable wildcard, `listed` is empty and the gap check is skipped, so the key silently omits that project; verify coverage for every project before skipping the per-server check.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| })), | ||
| controller.signal, | ||
| ); | ||
| const listed = wildcard.length > 0 ? [] : reach; |
There was a problem hiding this comment.
P1: This treats any delegable wildcard as covering the full “All servers” reach. If one project has no delegable wildcard, listed is empty and the gap check is skipped, so the key silently omits that project; verify coverage for every project before skipping the per-server check.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At client/dashboard/src/pages/agents/credentials/CredentialsWizard.tsx, line 1096:
<comment>This treats any delegable wildcard as covering the full “All servers” reach. If one project has no delegable wildcard, `listed` is empty and the gap check is skipped, so the key silently omits that project; verify coverage for every project before skipping the per-server check.</comment>
<file context>
@@ -1068,26 +1071,42 @@ export function CredentialsWizard({
- })),
- controller.signal,
- );
+ const listed = wildcard.length > 0 ? [] : reach;
+ const delegable =
+ wildcard.length > 0
</file context>
| expect(grants).toEqual([wildcard("project_one"), exclusion]); | ||
| }); | ||
|
|
||
| it("is empty when no wildcard can be delegated, so the caller names servers", async () => { |
There was a problem hiding this comment.
P2: The third test passes [] (organization-wide reach) but its mock has no wildcard, so it never checks that wildcards from any project are kept. Add a case with [] and a wildcard("project_two") in the mock, and expect it in the result. That covers the organization-wide branch the wizard uses.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At client/dashboard/src/pages/agents/agent-key-discovery.test.ts, line 248:
<comment>The third test passes `[]` (organization-wide reach) but its mock has no wildcard, so it never checks that wildcards from any project are kept. Add a case with `[]` and a `wildcard("project_two")` in the mock, and expect it in the result. That covers the organization-wide branch the wizard uses.</comment>
<file context>
@@ -190,3 +191,70 @@ describe("scoped credential discovery", () => {
+ expect(grants).toEqual([wildcard("project_one"), exclusion]);
+ });
+
+ it("is empty when no wildcard can be delegated, so the caller names servers", async () => {
+ const listDelegableGrants = vi
+ .fn()
</file context>
| } | ||
| } | ||
| version := runtimepolicy.IssuedDelegatedPolicyVersion | ||
| policy, err := runtimepolicy.NewDelegatedPolicy(version, requested) |
There was a problem hiding this comment.
P2: This test rebuilds the wizard's issuance by hand (grant filtering, policy version, containment check, direct CreateAgentAPIKey) instead of going through the key creation service. If the wizard's wildcard selection or the service's validation regresses, this test still passes. Drive issuance through keys.Service.createAgentKey (or the delegable-grants endpoint) so the test covers the code that ships.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At server/internal/mcp/serve_agent_gateway_test.go, line 537:
<comment>This test rebuilds the wizard's issuance by hand (grant filtering, policy version, containment check, direct `CreateAgentAPIKey`) instead of going through the key creation service. If the wizard's wildcard selection or the service's validation regresses, this test still passes. Drive issuance through `keys.Service.createAgentKey` (or the delegable-grants endpoint) so the test covers the code that ships.</comment>
<file context>
@@ -475,6 +477,104 @@ func TestServeAgentGateway_ListsPrivateOnlyMembersOnPrivateIngress(t *testing.T)
+ }
+ }
+ version := runtimepolicy.IssuedDelegatedPolicyVersion
+ policy, err := runtimepolicy.NewDelegatedPolicy(version, requested)
+ require.NoError(t, err)
+ contained, err := runtimepolicy.DelegationContainedFor(authCtx.UserID, policy, agentPolicy, ownerPolicy, ownerPolicy)
</file context>
| require.Contains(t, result, &gen.AgentPolicyGrantForm{Scope: string(authz.ScopeMCPConnect), Effect: "allow", Selector: &gen.AgentPolicySelector{ResourceKind: authz.ResourceKindMCP, ResourceID: "*", ProjectID: &projectOne}}) | ||
| require.Contains(t, result, &gen.AgentPolicyGrantForm{Scope: string(authz.ScopeMCPBlockedConnect), Effect: "allow", Selector: &gen.AgentPolicySelector{ResourceKind: authz.ResourceKindMCP, ResourceID: "blocked-server", ProjectID: &projectOne}}) |
There was a problem hiding this comment.
P3: These require.Contains checks would still pass if discovery returned extra or wrongly scoped grants alongside the expected two. Assert the exact set with require.ElementsMatch, as the neighboring exclusion test does.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At server/internal/agentmanagement/delegable_test.go, line 225:
<comment>These `require.Contains` checks would still pass if discovery returned extra or wrongly scoped grants alongside the expected two. Assert the exact set with `require.ElementsMatch`, as the neighboring exclusion test does.</comment>
<file context>
@@ -205,6 +205,27 @@ func TestListDelegableGrantsConservativelyOmitsOverlappingExclusions(t *testing.
+ result, err := f.service.ListDelegableGrants(validatedHumanContext(t, "org-delegable", "owner"), &gen.ListDelegableGrantsPayload{AgentID: f.agentID.String(), CarryExclusions: true})
+ require.NoError(t, err)
+ projectOne := "project-one"
+ require.Contains(t, result, &gen.AgentPolicyGrantForm{Scope: string(authz.ScopeMCPConnect), Effect: "allow", Selector: &gen.AgentPolicySelector{ResourceKind: authz.ResourceKindMCP, ResourceID: "*", ProjectID: &projectOne}})
+ require.Contains(t, result, &gen.AgentPolicyGrantForm{Scope: string(authz.ScopeMCPBlockedConnect), Effect: "allow", Selector: &gen.AgentPolicySelector{ResourceKind: authz.ResourceKindMCP, ResourceID: "blocked-server", ProjectID: &projectOne}})
+}
</file context>
| require.Contains(t, result, &gen.AgentPolicyGrantForm{Scope: string(authz.ScopeMCPConnect), Effect: "allow", Selector: &gen.AgentPolicySelector{ResourceKind: authz.ResourceKindMCP, ResourceID: "*", ProjectID: &projectOne}}) | |
| require.Contains(t, result, &gen.AgentPolicyGrantForm{Scope: string(authz.ScopeMCPBlockedConnect), Effect: "allow", Selector: &gen.AgentPolicySelector{ResourceKind: authz.ResourceKindMCP, ResourceID: "blocked-server", ProjectID: &projectOne}}) | |
| require.ElementsMatch(t, []*gen.AgentPolicyGrantForm{ | |
| {Scope: string(authz.ScopeMCPConnect), Effect: "allow", Selector: &gen.AgentPolicySelector{ResourceKind: authz.ResourceKindMCP, ResourceID: "*", ProjectID: &projectOne}}, | |
| {Scope: string(authz.ScopeMCPBlockedConnect), Effect: "allow", Selector: &gen.AgentPolicySelector{ResourceKind: authz.ResourceKindMCP, ResourceID: "blocked-server", ProjectID: &projectOne}}, | |
| }, result) |
| Extend(AgentIDForm) | ||
| Attribute("toolset_id", String, "Optional MCP authorization resource identifier: the toolset ID when present, otherwise the MCP server ID. Narrows discovery to this server and its project before evaluating exclusions.", func() { Format(FormatUUID) }) | ||
| Attribute("toolset_ids", ArrayOf(String, func() { Format(FormatUUID) }), "Optional MCP authorization resource identifiers, discovered in one request. Each narrows discovery like toolset_id; the result is the union of candidates across all of them.", func() { MaxLength(100) }) | ||
| Attribute("carry_exclusions", Boolean, "Keep a wildcard candidate that a narrower exclusion overlaps, and return that exclusion beside it as a blocklist-scope grant, instead of omitting the candidate. Requesting both on a key needs delegated grants version 4 or later.", func() { Default(false) }) |
There was a problem hiding this comment.
P3: The new carry_exclusions description says "Requesting both on a key", but "both" has no referent, and this endpoint takes no version. Name the pair and say that issuance enforces the version: "Storing the wildcard with its exclusion on one key requires delegated_grants_version 4 or later, enforced at key issuance."
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At server/design/agents/design.go, line 313:
<comment>The new `carry_exclusions` description says "Requesting both on a key", but "both" has no referent, and this endpoint takes no version. Name the pair and say that issuance enforces the version: "Storing the wildcard with its exclusion on one key requires delegated_grants_version 4 or later, enforced at key issuance."</comment>
<file context>
@@ -310,13 +310,15 @@ var _ = Service("agents", func() {
Extend(AgentIDForm)
Attribute("toolset_id", String, "Optional MCP authorization resource identifier: the toolset ID when present, otherwise the MCP server ID. Narrows discovery to this server and its project before evaluating exclusions.", func() { Format(FormatUUID) })
Attribute("toolset_ids", ArrayOf(String, func() { Format(FormatUUID) }), "Optional MCP authorization resource identifiers, discovered in one request. Each narrows discovery like toolset_id; the result is the union of candidates across all of them.", func() { MaxLength(100) })
+ Attribute("carry_exclusions", Boolean, "Keep a wildcard candidate that a narrower exclusion overlaps, and return that exclusion beside it as a blocklist-scope grant, instead of omitting the candidate. Requesting both on a key needs delegated grants version 4 or later.", func() { Default(false) })
})
Result(ArrayOf(PolicyGrantForm))
</file context>
| Attribute("carry_exclusions", Boolean, "Keep a wildcard candidate that a narrower exclusion overlaps, and return that exclusion beside it as a blocklist-scope grant, instead of omitting the candidate. Requesting both on a key needs delegated grants version 4 or later.", func() { Default(false) }) | |
| Attribute("carry_exclusions", Boolean, "Keep a wildcard candidate that a narrower exclusion overlaps, and return that exclusion beside it as a blocklist-scope grant, instead of omitting the candidate. Storing the wildcard with its exclusion on one key requires delegated_grants_version 4 or later, enforced at key issuance.", func() { Default(false) }) |
|
Merged into #8315 to keep these agent setup fixes in one PR. |
Why
An agent saved with "All servers" got a key that listed the servers that existed at issue time. A server added later never showed on the agent gateway (
/agent-mcp/{agentID}) until someone issued a new key, because the key's stored policy is one of three policies that must all allow. Re-saving the agent did not help.What changed
Before: the wizard expanded "All servers" into today's servers and dropped any wildcard candidate. Discovery also omitted a wildcard that an owner exclusion overlaps.
After:
listDelegableGrantstakescarry_exclusions. The wildcard comes back with the overlappingmcp:blocked_connectexclusions beside it, through the existingDelegableGrantsWithExclusionslogic.mcp:connectwildcard plus those exclusions, at delegated grants version 4. When no wildcard is delegable (an owner granted servers one by one), it falls back to the old per-server list.Gutternote review steps
No visible UI change. The fix shows on the agent gateway's
list_servers, not in the dashboard.Summary by cubic
Agent keys issued with "All servers" now reach MCP servers added after the key was issued; before, the key only listed servers that existed at the time, so new servers stayed hidden until a new key was issued.
carry_exclusionsso the wildcard isn't dropped when an owner exclusion overlaps it.Written for commit f0af20f. Summary will update on new commits.