package pier import ( "context" "fmt" "git.golem15.com/golem15/summercms/modules/pact" ) // allowed computes the tools and prompts an identity may see and call: a // tool needs a subject under its guard, the read/write ceiling hides // non-read-only tools and prompts declared with Writes from an identity // without write (D-20), and the // guard provider's own check decides the rest (D-16, D-23). A provider // error hides the item and is logged. The same function builds the // per-identity server and re-checks every list and call for the current // request's identity (D-08, D-25). func (c *catalog) allowed(ctx context.Context, id Identity) ([]*tool, []*prompt) { tools, prompts, _ := c.allowedReport(ctx, id) return tools, prompts } // allowedReport is allowed plus the first provider error, so a caller that // re-filters a cached server can tell a refusal from a failed check // (RA-WR-03): widening leaves the server as it is on an error and retries on // the next request, and a fan-out recompute marks the server for that retry. // Every failed check is still logged and hides its item. func (c *catalog) allowedReport(ctx context.Context, id Identity) ([]*tool, []*prompt, error) { var first error var tools []*tool for _, t := range c.tools { ok, err := c.allowsTool(ctx, id, t) if err != nil { logger().Warn("pier: guard check failed", "tool", t.name, "guard", t.guardName, "error", err) if first == nil { first = fmt.Errorf("tool %s: %w", t.name, err) } continue } if ok { tools = append(tools, t) } } var prompts []*prompt for _, p := range c.prompts { ok, err := c.allowsPrompt(ctx, id, p) if err != nil { logger().Warn("pier: guard check failed", "prompt", p.name, "guard", p.guardName, "error", err) if first == nil { first = fmt.Errorf("prompt %s: %w", p.name, err) } continue } if ok { prompts = append(prompts, p) } } return tools, prompts, first } func (c *catalog) allowsTool(ctx context.Context, id Identity, t *tool) (bool, error) { subject := id.subjectFor(t.guardName) if subject == nil { return false, nil } if !t.readOnly && !id.writes() { return false, nil } return allows(ctx, t.guard, *subject, t.req, t.decl.Permissions, t.method) } func (c *catalog) allowsPrompt(ctx context.Context, id Identity, p *prompt) (bool, error) { // A prompt that directs write tools sits under the read/write ceiling // like a tool that is not read-only (P-28). if p.decl.Writes && !id.writes() { return false, nil } subject := id.subjectFor(p.guardName) if subject == nil { return false, nil } return allows(ctx, p.guard, *subject, pact.MCPRequirements{}, p.decl.Permissions, "") } // allows asks the provider, method-aware when it supports it. func allows(ctx context.Context, g pact.MCPGuard, subject pact.MCPSubject, req pact.MCPRequirements, permissions []string, method string) (bool, error) { if ma, ok := g.(methodAllower); ok && method != "" { return ma.MCPAllowsMethod(ctx, subject, req, permissions, method) } return g.MCPAllows(ctx, subject, req, permissions) }