Skip to content

5.8 Search #7677

Description

@andypalmi

5.8 Search

Parent: Story 5 (#7232)
Tool file: new forge/ee/lib/mcp/tools/search.js

Summary

Global search across a team's resources, and an instance-scoped search. Read-only, useful for agent situational awareness ("find the instance named X"). This scope is entirely read-only: there are no write or destructive endpoints.

Read tools (phase 1)

readOnlyHint: true.

Tool Endpoint Scope Annotation
platform_search_team_resources GET /search?team=...&query=... (manual team:search) read
platform_search_instances GET /search/instances?team=...&query=... (manual team:search) read

Design notes:

  • Neither route uses needsPermission. Gating is manual inside the handler: request.session.User.admin || app.hasPermission(membership, 'team:search'). GET /search returns an empty result set (not 403) when the caller lacks team:search; GET /search/instances returns 403.
  • GET /search further filters results per-application via project:read (admin bypass; devices with no application always shown), so a team-scoped PAT sees a filtered subset. GET /search/instances does not apply per-row project:read filtering (it returns the full team result set after the team:search gate).
  • Both require a team query param. The tool inputSchema should require teamId and query.
  • team:search is a team-role scope (not admin). Add it to the allow-list and access-tag it read.

Scopes to allow-list + access-tag (#7445): team:search (read).

Tool definitions (description + zod inputSchema):

platform_search_team_resources: {
  description: 'Search across the resources of one team (global team search). An empty or blank query returns an empty result set.',
  inputSchema: z.object({
    teamId: z.string().describe('Team id (opaque hashid), carried as the `team` query param'),
    query: z.string().describe('Search term')
  })
}
platform_search_instances: {
  description: 'Search the instances of one team. An empty or blank query returns an empty result set.',
  inputSchema: z.object({
    teamId: z.string().describe('Team id (opaque hashid), carried as the `team` query param'),
    query: z.string().describe('Search term')
  })
}

Tests:

  • Both tools allowed for read-only PAT.
  • Caller without team:search gets empty results (global) / 403 (instances) as per the route behaviour.
  • Team-scoped PAT restricted to in-scope team.

Metadata

Metadata

Assignees

No one assigned

    Labels

    storyA user-oriented description of a feature

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions