feat(tools): make query_lfx_semantic_layer staff-aware with optional project scoping - #105
Conversation
…project scoping Parameterize RegisterSemanticLayer by isStaff so the lens semantic-layer tool has two variants sharing one tool name: - Staff variant: project_slug is optional for all actions and where is optional for the query action, enabling global and cross-foundation queries. The request body omits project_slug/where entirely when empty (the lens API treats absence as "skip project scope validation"). Descriptions state capabilities without prescribing when to pass the slug, plus the tlf membership-vs-activity data caveat. - Non-staff variant: preserves the current descriptions, schema (project_slug required), and hard enforcement byte-for-byte, guarded by a verbatim-description test. It is future-proofing for when tool registration is widened beyond staff. The registration gate in main.go stays canRead && isStaff, so today only staff (and stdio mode, where callerToken == nil implies isStaff) get the tool — stdio now serves the staff variant. The two variants use distinct arg struct types because the shared MCP schema cache is keyed by reflect.Type. query_lfx_lens is unchanged. Adds internal/tools/lens_test.go covering both handler variants, request body key presence/absence, describe text variants, description assembly, and ListTools schema differences across a shared schema cache. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Josep Garcia-Reyero Sais <josepreyero@gmail.com>
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
There was a problem hiding this comment.
Pull request overview
Makes query_lfx_semantic_layer staff-aware while preserving scoped non-staff behavior.
Changes:
- Adds staff-specific schema, descriptions, and dispatch behavior.
- Omits empty project scope fields for global staff queries.
- Adds comprehensive variant and request-body tests.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
internal/tools/lens.go |
Implements staff-aware semantic-layer variants. |
internal/tools/lens_test.go |
Tests schemas, enforcement, descriptions, and request bodies. |
cmd/lfx-mcp-server/main.go |
Passes staff status during registration. |
The query_lfx_semantic_layer handler now includes is_staff (the verified lf_staff signal plumbed at tool registration) in the JSON body POSTed to /lfx-lens/semantic-layer/query, so lfx-lens can keep project-scope enforcement fail-closed unless the capability is explicitly claimed. Explicit false is sent for non-staff callers; lfx-lens treats an absent field as false, so old-mcp -> new-lens stays safe. Companion to lfx-lens PR #26. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Josep Garcia-Reyero Sais <josepreyero@gmail.com>
|
New commit 44af199 adds the explicit |
| } | ||
| if enabledTools["query_lfx_semantic_layer"] && canRead && isStaff { | ||
| tools.RegisterSemanticLayer(server) | ||
| tools.RegisterSemanticLayer(server, isStaff) |
There was a problem hiding this comment.
I'm going to disagree with Copilot -- anybody running this in stdio would need M2M secrets to hit the semantic layer upstream anyhow, so the fact that staff checks are bypassed for stdio is expected.
HOWEVER, I will call out that the idea of making the semantic layer staff-aware via this line is useless, because the semantic layer tool is already 100% excluded to staff via the immediately preceding && isStaff line ... meaning the tool is never called with anything except true.
emsearcy
left a comment
There was a problem hiding this comment.
The semantic layer tool is ALREADY restricted to only staff, so simply making the project optional is all that is needed -- no "two modes".
| } | ||
| if enabledTools["query_lfx_semantic_layer"] && canRead && isStaff { | ||
| tools.RegisterSemanticLayer(server) | ||
| tools.RegisterSemanticLayer(server, isStaff) |
There was a problem hiding this comment.
I'm going to disagree with Copilot -- anybody running this in stdio would need M2M secrets to hit the semantic layer upstream anyhow, so the fact that staff checks are bypassed for stdio is expected.
HOWEVER, I will call out that the idea of making the semantic layer staff-aware via this line is useless, because the semantic layer tool is already 100% excluded to staff via the immediately preceding && isStaff line ... meaning the tool is never called with anything except true.
…d variant The tool is only registered for staff callers (canRead && isStaff gate), so the non-staff variant and the is_staff plumbing were dead code paths. Per maintainer review, keep one tool definition: project_slug and where are optional, and lfx-lens validates any provided project filters against the requested foundation's subtree. The is_staff request-body field is dropped to match the simplified lfx-lens contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Josep Garcia-Reyero Sais <josepreyero@gmail.com>
|
Simplified per review: collapsed the two-variant implementation to a single code path — commit 16be5dc removes the non-staff variant, the |
| semanticLayerSlotScopeRule = `project_slug is optional. When provided, where-clause project filters are validated against that foundation's subtree. It may be omitted for global or cross-foundation questions. To scope to a project, add a where filter — check the dimensions list for the correct one (e.g. registration_id__project_slug). Some models don't have project_slug — they use project_name instead. In that case, use the full project name from search_projects (e.g. "Cloud Native Computing Foundation (CNCF)").` | ||
|
|
||
| // semanticLayerSlotFinalTip: final tip. | ||
| semanticLayerSlotFinalTip = `For membership metrics, a Linux Foundation ('tlf') filter only captures direct LF memberships — for global membership aggregates, omit the project filter. Activity metrics are fanned out to foundations, so either a 'tlf' foundation filter or no filter works for global questions.` |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
internal/tools/lens.go:209
- This new membership guidance conflicts with the same tool description's earlier “All membership questions” instruction to use
query_lfx_lensinstead (and with theActionschema wording at line 235). An agent cannot reliably determine whether global membership aggregates should use this tool orquery_lfx_lens; please make the routing guidance consistent, either by narrowing the earlier prohibition or removing membership instructions from this tool.
semanticLayerSlotFinalTip = `For membership metrics, a Linux Foundation ('tlf') filter only captures direct LF memberships — for global membership aggregates, omit the project filter. Activity metrics are fanned out to foundations, so either a 'tlf' foundation filter or no filter works for global questions.`
Makes the
query_lfx_semantic_layerlens tool support global and cross-foundation queries:project_slugand thewhereproject filter are now optional, and empty values are omitted from the lens request body. Whenproject_slugis provided, lfx-lens still validates where-clause project filters against that foundation's subtree, so scoped queries behave exactly as before.Since the tool is only registered for staff callers (
canRead && isStaffgate, unchanged), this is a single code path — per maintainer review, the earlier two-variant (staff/non-staff) implementation and theis_staffrequest field were collapsed because the non-staff mode was unreachable dead code. Thequery_lfx_lensSQL-agent tool is unchanged.Validated with unit tests covering request-body construction, schema optionality, and description content, plus
gofmt,go build,go vet, andgo test ./...all green.Depends on linuxfoundation/lfx-lens#26 (makes
project_slugoptional on/semantic-layer/query) being deployed first.🤖 Generated with Claude Code