From apoxy-query
Use when querying Apoxy proxy logs via the apoxy MCP server tools (describe_schema, query). Provides PRQL syntax, field reference, query patterns, and engine behavior details.
How this skill is triggered — by the user, by Claude, or both
Slash command
/apoxy-query:apoxy-queryThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You have access to the `apoxy` MCP server with two tools for querying Envoy proxy access logs.
You have access to the apoxy MCP server with two tools for querying Envoy proxy access logs.
describe_schemaReturns all available log fields with their types, sources, and descriptions. Call this first if you are unsure what fields exist.
queryExecutes a PRQL query against your proxy access logs.
Parameters:
prql (required) — PRQL query string. The table source is implicit — start directly with transforms like filter, select, derive, group, sort, or take.start_time (optional) — RFC 3339 timestamp. Defaults to 24 hours ago.end_time (optional) — RFC 3339 timestamp. Defaults to now.cursor (optional) — Pagination cursor from a previous response's next_cursor.There is no separate limit parameter in the MCP tool. Use take N in your PRQL query to control the number of rows returned.
Queries are pipelines of transforms separated by newlines. Backtick-quote any field name containing dots.
filter `http.response.status_code` >= 500
select {Timestamp, `url.path`, `http.response.status_code`}
sort {-Timestamp}
take 10
Key transforms:
filter <condition> — keep rows matching a condition.select {col1, col2} — choose columns.derive {name = expression} — add computed columns.group {col} (aggregate {name = func this}) — group and aggregate.sort {-col} — sort (prefix - for descending).take N — limit to N rows.Understanding the engine's behavior helps you write correct queries and interpret compiled_sql output.
Dotted field names in backticks are rewritten to the appropriate storage-level access expressions and aliased back to the clean field name:
`url.path` → accessed from log attributes, aliased as "url.path" in results`cluster_name` → accessed from resource attributes, aliased as "cluster_name" in resultsThe engine uses a field registry to determine which attribute group each field belongs to. Top-level columns (like Timestamp, Body) are not rewritten.
Attribute fields are stored as strings internally but integer and float fields are automatically cast to their numeric types:
http.response.status_code): cast to 64-bit integerhttp.request.duration_ms): cast to 64-bit floatThis means you can use numeric comparisons directly: filter http.response.status_code >= 400 works without manual casting.
The rewriter does not touch string literals in your PRQL. A filter like filter url.path == "/api/users" correctly rewrites url.path to LogAttributes['url.path'] but leaves "/api/users" untouched.
take get a default limit of 1000 rows.take N, the engine detects it and uses your limit directly (compiled SQL shows LIMIT N). When you omit take, the engine appends its own limit placeholder.The engine automatically injects WHERE conditions for project filtering and time range before any GROUP BY clause. These appear in compiled_sql but do not need to be written by the user.
Fields come from three sources. Call describe_schema for the full list including dynamically discovered fields.
Top-level columns (use unquoted names):
| Field | Type | Description |
|---|---|---|
| Timestamp | timestamp | Event timestamp (DateTime64) |
| TimestampTime | timestamp | Truncated timestamp (DateTime, useful for grouping) |
| Body | string | Log message body |
| ServiceName | string | Service name |
| SeverityText | string | Log severity text |
| SeverityNumber | integer | Log severity number (UInt8) |
| TraceId | string | Trace correlation ID |
| SpanId | string | Span correlation ID |
| TraceFlags | integer | Trace flags (UInt8) |
| EventName | string | Event name |
LogAttributes (backtick-quote names, sourced from Envoy access logs):
| Field | Type | Description |
|---|---|---|
http.request.method | string | HTTP request method |
http.response.status_code | integer | HTTP response status code |
url.path | string | Request URL path |
client.address | string | Client IP:port |
upstream.address | string | Upstream host address |
upstream.cluster | string | Envoy upstream cluster name |
http.request.duration_ms | float | Request duration in milliseconds |
http.request.body.size | integer | Request body bytes |
http.response.body.size | integer | Response body bytes |
http.request.id | uuid | Request UUID |
user_agent.original | string | User-Agent header |
url.host | string | Request host header |
network.protocol.name | string | Protocol (HTTP/1.1, HTTP/2) |
envoy.response_flags | string | Envoy response flags |
ResourceAttributes (backtick-quote names, infrastructure metadata):
| Field | Type | Description |
|---|---|---|
cluster_name | string | Cluster identifier |
log_name | string | Log source name |
project_id | uuid | Project UUID |
node_name | string | Node UUID |
service.name | string | Service name |
zone_name | string | Zone identifier |
Additional fields may be discovered dynamically from the data. Use describe_schema to see the current full list.
Filter by status code:
filter `http.response.status_code` >= 500
select {Timestamp, `url.path`, `http.response.status_code`}
sort {-Timestamp}
Aggregate by field:
group {`http.response.status_code`} (aggregate {count = count this})
Derived/computed columns:
derive {is_error = `http.response.status_code` >= 400}
select {Timestamp, `url.path`, is_error}
Filter with string equality:
filter `url.path` == "/api/users"
filter `http.request.method` == "POST"
select {Timestamp, `http.response.status_code`, `http.request.duration_ms`}
Mixed top-level and attribute fields:
select {Timestamp, Body, `http.request.method`, `client.address`, ServiceName}
sort {-Timestamp}
take 20
ResourceAttributes fields:
select {Timestamp, `cluster_name`, `node_name`, `service.name`}
sort {-Timestamp}
describe_schema first if you are unsure about available fields or their exact names.start_time and end_time to narrow the window.take 10 before fetching large result sets. Without take, you get up to 1000 rows.start_time and end_time — both are mandatory when using a cursor. The server rejects cursor requests that omit either timestamp. Use the same values across pages.compiled_sql in the response if results look unexpected — it shows the SQL that was executed, including field rewriting, type casting, and injected WHERE conditions.Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub apoxy-dev/claude-plugin --plugin apoxy-query