sh-mcp/packages/shared/src/visibility.ts
Adam Moussa c85789b75e Add shared transport: dispatch, MCP + OpenAPI adapters, local auth
Add the single authoritative tool-execution path (executeTool) plus the two
universal interfaces over it (design.md §2.5, §7.3):
- dispatch.ts: scope enforcement, ajv input validation, rate limiting, finance
  egress redaction (redactDeep), and structured audit emission on one path.
- audit.ts / rate-limit.ts: injected AuditLogger + RateLimiter abstractions.
- mcp.ts: low-level MCP Server with scope-filtered tools/list (tool-hiding) and
  tools/call routed through executeTool.
- http.ts: Express host mounting /mcp, /openapi.json, POST /tools/:name, /healthz.
- openapi.ts: buildOpenApiDocument wraps the existing path generator into a full
  OpenAPI 3.1 document.
- local-auth.ts: LocalAuthProvider (dev bearer tokens) that refuses to construct
  outside SH_MCP_ENV=local and enforces audience binding (design.md §3, §6).
2026-06-26 12:48:26 -04:00

26 lines
924 B
TypeScript

/**
* Scope-based tool visibility (tool-hiding).
*
* design.md §2.5: a caller sees only the tools whose `requiredScope` is in their
* `AuthContext`. Both transports use this so the MCP `tools/list` and the
* per-caller OpenAPI document stay consistent.
*
* IMPORTANT: hiding is a CONVENIENCE, never the access boundary. The dispatcher
* (`executeTool` → `requireScope`) is authoritative; a forced call to a hidden
* tool still returns 403 (design.md §2.5).
*/
import type { ToolRegistry } from './registry.js';
import type { AuthContext, ToolDef } from './types.js';
/** Tools the caller is permitted to see, in registry insertion order. */
export function visibleTools(
registry: ToolRegistry,
ctx: AuthContext,
): ToolDef<unknown, unknown>[] {
const scopes = new Set(ctx.scopes);
return registry.list().filter((tool) => scopes.has(tool.requiredScope)) as ToolDef<
unknown,
unknown
>[];
}