Read data from OPA
opa_get_dataFetch data from OPA by specifying a path under the data hierarchy using dotted or slash notation.
Instructions
Read a path from OPA's data hierarchy. The path argument may be in dotted form (users.alice) or slash form (users/alice).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Data path under `data.`, e.g. "users" or "users/alice". |
Implementation Reference
- The async handler function for the opa_get_data tool. It sends a GET request to OPA's /v1/data/{path} endpoint, unwraps the 'result' field from the response, and returns it. Uses withToolEnvelope for error handling and mapOpaClientError for HTTP error mapping.
async ({ path }) => { return withToolEnvelope<{ result: unknown }>(config, async () => { try { const data = await opa.request<{ result: unknown }>({ method: 'GET', path: dataPath(path), }); return ok({ result: data.result }); } catch (e) { return mapOpaClientError(e); } }); }, - Tool registration including input schema validation for opa_get_data. The tool accepts a single 'path' parameter as a non-empty string describing the data path under 'data.'. Defined inline in server.registerTool call.
server.registerTool( 'opa_get_data', { title: 'Read data from OPA', description: "Read a path from OPA's data hierarchy. The `path` argument may be in dotted form (`users.alice`) or slash form (`users/alice`).", inputSchema: { path: z.string().min(1).describe('Data path under `data.`, e.g. "users" or "users/alice".'), }, }, - src/tools/server-management/data.ts:21-47 (registration)The registerDataTools function registers opa_get_data (along with opa_put_data and opa_patch_data) onto the MCP server. This is called from src/tools/server-management/index.ts via registerServerManagementTools.
export function registerDataTools(server: McpServer, config: Config): void { const opa = new OpaClient(config); server.registerTool( 'opa_get_data', { title: 'Read data from OPA', description: "Read a path from OPA's data hierarchy. The `path` argument may be in dotted form (`users.alice`) or slash form (`users/alice`).", inputSchema: { path: z.string().min(1).describe('Data path under `data.`, e.g. "users" or "users/alice".'), }, }, async ({ path }) => { return withToolEnvelope<{ result: unknown }>(config, async () => { try { const data = await opa.request<{ result: unknown }>({ method: 'GET', path: dataPath(path), }); return ok({ result: data.result }); } catch (e) { return mapOpaClientError(e); } }); }, ); - Helper function dataPath converts a user-supplied path (dotted or slash form) into a proper /v1/data/{path} URL for the OPA REST API. Used by the opa_get_data handler to construct the request path.
function dataPath(path: string): string { // Strip leading "data." or "/" — server always prepends /v1/data/. const stripped = path.replace(/^data\./, '').replace(/^\/+/, ''); // Convert dotted form to slash form: "users.alice" -> "users/alice". return `/v1/data/${stripped.replace(/\./g, '/')}`; } - src/lib/opa-client.ts:59-136 (helper)The OpaClient class used by the handler to execute HTTP requests against the OPA server. The request method handles building the URL, setting headers (including Authorization), sending the request, and parsing JSON responses.
export class OpaClient { constructor(private readonly config: Config) {} async request<T = unknown>(opts: RequestOptions): Promise<T> { const url = this.buildUrl(opts.path, opts.query); const headers: Record<string, string> = { Accept: 'application/json', ...(opts.headers ?? {}), }; if (this.config.opaToken) { headers['Authorization'] = `Bearer ${this.config.opaToken}`; } let bodyToSend: string | undefined; if (opts.rawBody !== undefined) { if (opts.body !== undefined) { throw new Error('OpaClient.request: pass either `body` or `rawBody`, not both.'); } bodyToSend = opts.rawBody; if (!headers['Content-Type']) { headers['Content-Type'] = opts.rawContentType ?? 'text/plain'; } } else if (opts.body !== undefined) { bodyToSend = JSON.stringify(opts.body); if (!headers['Content-Type']) { headers['Content-Type'] = 'application/json'; } } const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), this.config.httpTimeoutMs); const init: RequestInit = { method: opts.method, headers, signal: controller.signal, }; if (bodyToSend !== undefined) { init.body = bodyToSend; } let response: Response; try { response = await fetch(url, init); } catch (e) { throw new OpaUnreachableError(this.config.opaUrl, e); } finally { clearTimeout(timer); } if (response.status === 401) { throw new OpaAuthError(); } const contentType = response.headers.get('content-type') ?? ''; const isJson = contentType.includes('application/json'); const payload: unknown = isJson ? await response.json() : await response.text(); if (!response.ok) { throw new OpaHttpError(response.status, payload); } return payload as T; } private buildUrl(path: string, query?: RequestOptions['query']): string { const base = this.config.opaUrl.replace(/\/+$/, ''); const normalizedPath = path.startsWith('/') ? path : `/${path}`; const url = new URL(`${base}${normalizedPath}`); if (query) { for (const [key, value] of Object.entries(query)) { if (value !== undefined) url.searchParams.set(key, String(value)); } } return url.toString(); } }