qyl provides MCP tools for investigating traces, logs, metrics and sessions in a qyl Collector. It measures and correlates; the agent reasons and acts.
| Component | Source role |
|---|---|
server/ |
Telemetry MCP server and bundled MCP App viewers |
workbench/ |
Local client for inspecting and invoking other MCP servers |
dashboard/ |
Browser UI built alongside the Workbench |
Endpoint: https://mcp.qyl.at/mcp, deployed from main on Railway. An
unauthenticated request is answered 401 with resource_metadata; the
metadata names that resource, issuer https://qyl-eu.eu.auth0.com/ and scopes
qyl:read qyl:write qyl:delete. Only the write tools need
qyl:write and qyl:delete; they ask for them through a step-up challenge.
Configure that endpoint in the intended client and authorize an account with access to the intended Collector project. Client registration and user/project authorization must both be configured by the operator.
| Client | Connection setting | Registration path |
|---|---|---|
| ChatGPT web | In Plugins, choose Add custom MCP server, enter https://mcp.qyl.at/mcp, select OAuth and request qyl:read qyl:write qyl:delete; create and install the personal plugin. |
Choose CIMD, DCR or a provisioned OAuth client as offered. For CIMD, use the metadata document and callback shown by the connection page; stable metadata is https://chatgpt.com/oauth/client.json where issuer-bound callbacks are supported. |
| claude.ai | In Customize → Connectors, add a custom connector for https://mcp.qyl.at/mcp, select sign-in and complete OAuth. Organization users first need their owner to add the connector under organization settings. |
Select Claude's published identity (CIMD), automatic registration (DCR), or a provisioned client. Public CIMD: https://claude.ai/oauth/mcp-oauth-client-metadata. |
| Claude Code | Run claude mcp add --transport http qyl https://mcp.qyl.at/mcp, then open /mcp to authenticate. |
Automatic CIMD/DCR discovery; if a provisioned client is required, use the CLI's --client-id and --client-secret prompt, with its actual callback registered. |
| Codex CLI | Add the TOML below to the intended Codex configuration, then run codex mcp login qyl --scopes "qyl:read qyl:write qyl:delete". |
Automatic CIMD/DCR selection; for a targeted registration check, the CLI accepts --oauth-client-registration cimd or dcr on login. A configured client ID takes precedence. |
| MCP Inspector | Save the JSON below as inspector-modern.json; run bunx @modelcontextprotocol/inspector@2.9.0 --config inspector-modern.json, complete OAuth with qyl:read qyl:write qyl:delete, then invoke tools/list. |
Use the provisioned client or registration flow offered by Inspector and the issuer. Registration alone does not grant resource access. |
Codex configuration:
[mcp_servers.qyl]
url = "https://mcp.qyl.at/mcp"Inspector configuration (repeat with "legacy" for the legacy check):
{"mcpServers":{"qyl":{"type":"streamable-http","url":"https://mcp.qyl.at/mcp","protocolEra":"modern"}}}To check the modern era against production, connect Inspector 2.9.0 with OAuth
and protocolEra: "modern", call tools/list and confirm the request carries
MCP-Protocol-Version: 2026-07-28. A legacy-only connection or an initialize
response does not show it.
Run the published package with Bun:
bunx qyl-mcp-server --stdio
bunx qyl-mcp-servernpx and pnpm dlx also work when Bun 1.4 or newer is installed.
From a checkout, build the server and start stdio:
bun install --frozen-lockfile
bun run build
bun server/dist/main.js --stdioConfigure QYL_COLLECTOR_URL and, when
needed, QYL_API_KEY. For local HTTP, run bun server/dist/main.js; the source
defaults to loopback port 3001. QYL_API_KEY is an outgoing Collector
credential, not an incoming MCP login.
./release.sh <version> publishes qyl-mcp-server from the developer's machine
(see SKILL.md). The executable uses Bun; the exported library subpaths support
Node 24. C# and TypeScript telemetry definitions come from the qyl convention
registry, while product API contracts come from qyl-api-schema. The official
MCP SDK, OpenTelemetry SDKs and exporters remain in use. See
release notes; the npm registry's latest tag
identifies the published version.
The generated manifest is the tool
contract. It has 32 tools: 26 read tools annotated readOnlyHint: true and the
six write tools; fetch_telemetry is app-only through
_meta.ui.visibility: ["app"]. bun run smoke checks that tools/list
returns exactly these 32 tools on 2026-07-28 and over the 2025-era handshake.
Start with sessions and traces, drill into one trace and its error logs,
then discover metric instruments and series before querying. get_trace
returns the complete span tree by default. Use errors_only to select error
spans, max_spans to cap the result after filtering, and
include_attributes=false to omit attribute collections. Original trace totals
are retained; the summary states how many matching spans were returned.
display_traces supplies the waterfall and search_logs supplies filtered
detail. ci_log accepts telemetry from any CI following its emitter convention;
service_prefix selects services in both run lists and phase breakdowns and
defaults to qyl-ci. The shared skill
contains the full workflow; the server also serves it through the MCP skills
extension (skills/list, skills/get, skill://qyl/qyl-investigate/SKILL.md),
which the OpenAI plugin portal imports when it scans the server, and the build
copies it into server/dist/skills.
Six tools change something, and only records inside the caller's Collector project: annotations (plain-text notes of at most 2000 characters on a trace, span or session), saved queries (named filters for traces, logs or metrics) and the status of recorded failures. No tool deletes or edits traces, logs or metrics; retention alone removes telemetry.
| Tool | Changes | Annotations | Scope |
|---|---|---|---|
create_annotation |
adds a note | readOnlyHint: false, destructiveHint: false, idempotentHint: true |
qyl:write |
update_annotation |
replaces a note's text | readOnlyHint: false, destructiveHint: true, idempotentHint: true |
qyl:write |
create_saved_query |
adds a named filter | readOnlyHint: false, destructiveHint: false, idempotentHint: true |
qyl:write |
update_saved_query |
renames it or replaces its description or filter | readOnlyHint: false, destructiveHint: true, idempotentHint: true |
qyl:write |
update_failure_status |
sets a failure's status (open, resolved or ignored), assignee or ignore-until time |
readOnlyHint: false, destructiveHint: true, idempotentHint: true |
qyl:write |
delete |
removes one annotation or saved query (kind is annotation or saved_query) |
readOnlyHint: false, destructiveHint: true, idempotentHint: true |
qyl:delete |
openWorldHint is false on all of them. list_saved_queries and
run_saved_query read saved queries and keep qyl:read; get_trace and
list_sessions return the annotations on what they read, so there is no
annotation listing tool. Updates and deletes send the object's etag as
If-Match: a stale etag is refused with precondition_failed, a duplicate
saved-query name with conflict. Alert rules are outside the current write surface.
On the hosted server a write call without its
scope gets a 403 insufficient_scope challenge naming the scopes to request,
in both protocol eras, before any Collector request.
The two viewers declare versioned resource URIs, a configured hosted origin and empty external connection/resource CSP allowlists. Local tests cover restoring the original query when refresh input is empty.
The source supports opt-in trace.error subscriptions on authenticated
Auth0 deployments configured with MCP_EVENTS_STORE. The optional
service_name filter narrows delivery. The implementation verifies callbacks,
signs deliveries, checks ongoing authorization, expires subscriptions and
cancels delivery on unsubscribe or confirmed revocation. Subscription state
is bounded and excludes OAuth tokens and copied telemetry.
bun run build
bun run start:workbenchThe default URL is http://127.0.0.1:18888; QYL_MCP_WORKBENCH_PORT changes
the port. Workbench can connect to HTTP and stdio servers, negotiate the
protocol automatically, inspect schemas, invoke tools and retain execution
evidence. It uses a loopback browser session and checks host/origin access.
Tool annotations guide confirmation; they do not grant permission.
Review an external stdio command before launching it: it executes locally.
Use environment-variable references for credentials instead of embedding
secrets in URLs or saved headers. Workbench state defaults to
~/.qyl/mcp-workbench.json, overridden by QYL_MCP_STATE_PATH. Its execution
records are distinct from the server's minimized incoming-call records.
The server's strict native record schema contains its own ID, tool name,
status, timestamps, duration and error type. It contains no arguments,
_meta, result body or error-message field. It defaults to retaining 1,000
records at ~/.qyl/mcp-native-executions.json; QYL_MCP_NATIVE_STATE_PATH
overrides the path. Hosted HTTP with MCP_PUBLIC_URL disables that local store.
Known version-1/2 data is projected into the minimized schema; unreadable
archives require operator review.
OTLP exports include operation timing/status and trace correlation. Native incoming calls do not supply argument/result bodies to that telemetry. Workbench has a separate optional content-capture setting. Local OTLP tests check redaction and matching trace/span IDs; they do not establish a production retention policy.
| Variable | Purpose |
|---|---|
QYL_COLLECTOR_URL |
Collector base URL; local default http://127.0.0.1:5100. |
QYL_API_KEY |
Outgoing Collector credential; the write tools need a key with the Collector's write and delete roles. |
MCP_ALLOWED_HOSTS |
Comma-separated additional request hostnames, added to the public URL hostname for hosted HTTP. |
MCP_ALLOWED_ORIGIN_HOSTS |
Comma-separated additional browser-origin hostnames, added to the public URL hostname for hosted HTTP. |
QYL_PROJECT |
Server-configured Collector project scope; its default is stated in the server README. |
MCP_COLLECTOR_PROJECTS |
Operator-controlled verified-subject-to-project mapping. |
QYL_MCP_TELEMETRY=0 |
Disable MCP self-telemetry. |
QYL_MCP_CAPTURE_CONTENT=1 |
Optional Workbench capture; does not add incoming-call payloads to native records. |
QYL_OTLP_ENDPOINT |
Optional telemetry base; signal-specific OTel endpoints take precedence. |
QYL_MCP_STATE_PATH |
Workbench state location. |
QYL_MCP_NATIVE_STATE_PATH |
Native incoming-call record location. |
QYL_MCP_WORKBENCH_PORT |
Workbench port, default 18888. |
These are operator setup instructions. The threat model describes the authorization boundary.
.railway/railway.ts declares build bun run --cwd server build, start
bun server/dist/main.js, healthcheck /healthz and an Events volume at
/data. Railway deploys mcp.qyl.at from every push to main.
For a hosted bind, configure MCP_PUBLIC_URL, MCP_BIND_HOST, allowed hosts
and origins, and the intended Collector URL/credential. The public URL sets
the resource audience and canonical /mcp connector URL. For Claude, qyl sets
ui.domain to the first 32 hex characters of SHA-256 over that exact connector
URL plus .claudemcpcontent.com. Claude requires this format when the optional
field is supplied. ChatGPT receives qyl's configured HTTPS origin in ui.domain
and its openai/widgetDomain alias. OpenAI documents an origin-valued field
and requires a dedicated, unique origin for UI plugin submission; choosing
the MCP server's origin is qyl's configuration choice.
The SDK's per-request client name and User-Agent are both checked. A recognized
Claude or ChatGPT name wins conflicting hints. A missing or unrecognized name
can use a recognized UA, including Claude-User, openai-mcp/1.0.0 or
ChatGPT-User. If neither hint is recognized, qyl omits both domain fields.
A host that supports MCP Apps can then use its default sandbox; omission alone
does not disable the viewer or reject the MCP client. Local viewers also omit
the domain. The hints and their precedence are qyl's compatibility policy, not
a documented client format.
Keep Collector credentials on the server. With project mapping enabled, only the verified subject selects the project; client hints, tool arguments and metadata do not select credentials.
The default Auth0 mode accepts only the qyl production Auth0 issuer
https://qyl-eu.eu.auth0.com/; arbitrary issuer substitution is not an
available configuration. Non-loopback serving requires a public URL and
hosted authorization. For Events, configure persistent MCP_EVENTS_STORE
and the dedicated Auth0 authorization-check credentials listed in the
server README.
The hosted profile is CIMD or DCR + OAuth 2.1 Authorization Code with
PKCE S256 + issuer identification. ChatGPT's client metadata advertises
none and private_key_jwt and uses the latter;
Claude's CIMD is a public client using none.
Operator setup:
- Register the resource audience
<public-url>/mcp, RS256 access tokens and the permissionsqyl:read,qyl:writeandqyl:delete. Configure the provider capabilities required by startup. - Provision the intended client using its current CIMD document or DCR, preserving its actual callback and authentication method.
- If DCR is needed, Enable Dynamic Client Registration and set
dynamic_client_registration_security_modetostrict. The DCR endpoint is open to registration when enabled; keep third-party API default permissions empty. qyl:readexposes traces, logs, metrics, sessions, and CI evidence; grant it only to intended clients and users. Verify a real authenticated read and project isolation after deployment; discovery alone is insufficient.qyl:writelets a client create and update annotations and saved queries and set failure status, andqyl:deletelets it delete annotations and saved queries, in the caller's project only; neither reaches telemetry. Grant them only to users who should change those records; a token without them keeps the read tools. The Collector key the server uses needs the Collector'swriteanddeleteroles as well.
server/verify-deployment-guidance.mjs holds this section to these
requirements after every server build; provider configuration is an owner action.
The source validates signatures, issuer, resource audience, expiry and scopes;
malformed credentials, invalid tokens and insufficient scopes have separate
error paths. A write tool called without qyl:write or qyl:delete is answered
403 with error="insufficient_scope" and the scopes to request, so the client
can re-authorize for them. Both /.well-known/oauth-protected-resource/mcp and
/.well-known/oauth-protected-resource describe the same /mcp resource.
Auth0 is the OIDC issuer; qyl.mcp does not expose its own OIDC issuer discovery.
For workspace domain claiming, the owner must verify any openid/email
requirements and the provider's verified-email response separately; these do
not replace qyl:read.
The source selects extensions explicitly through:
MCP_AUTH_EXTENSIONS=enterprise-managed-authorization@1.0.0,oauth-client-credentials@1.0.0| Integration | Source-declared stability | Additional provider requirement |
|---|---|---|
enterprise-managed-authorization@1.0.0 |
Stable | JWT bearer grant and ID-JAG grant profile. |
oauth-client-credentials@1.0.0 |
Draft; explicitly opt in | Client credentials and private_key_jwt. |
These are the installed integration definitions, not proof of a provider grant. Unknown versions and conflicting provider requirements are rejected. They do not issue tokens or replace the core verifier.
The alternative source configuration uses MCP_AUTH_PROVIDER=cloudflare-access,
MCP_ACCESS_TEAM_DOMAIN and MCP_ACCESS_AUD, plus MCP_PUBLIC_URL.
The origin checks Cf-Access-Jwt-Assertion: issuer, application audience, expiry and subject,
with RS256 signature validation. Configure and verify the Access application
before selecting this mode. It does not establish that the public endpoint
uses Access, and Auth0-backed Events cannot be enabled in this mode. Access
grants the read tools only: its assertion carries no qyl scopes, so the write
tools answer insufficient_scope.
submission/qyl is the plugin bundle for both directories: the Anthropic
files in .claude-plugin/ and .mcp.json, the OpenAI root plugin.json and
mcp.json. python3 submission/build-openai-package.py builds the OpenAI ZIP
into submission/packages/ (ignored by Git), and
the connector listing draft holds
the Anthropic connector fields. The owner handles identity, public pages,
reviewer accounts, recording, attestations, uploads, submission and
publication. GET /.well-known/openai-apps-challenge returns the trimmed
value of the hosted OPENAI_APPS_CHALLENGE variable as plain text, and 404
while it is blank; on mcp.qyl.at it carries the OpenAI portal's domain token.
The rules in AGENTS.md follow the directories' requirements: OpenAI
MCP server,
authentication,
Events,
MCP UI,
plugin guidelines;
Anthropic
connector checklist,
plugin checklist,
directory policy.
mise run check
bun run smoke:otlp
bun run smoke:projectsBuild the collector once in Release configuration; these checks reuse its output.
Set QYL_COLLECTOR_DLL to use a different completed build. Local checks do not
cover hosted connections, production Events, the deployed commit or a fresh npm
consumer.
The generated contracts come from @ancplua/qyl-api-schema; exact dependency
versions are recorded in server/package.json and bun.lock.
SDK major version and wire revision are
independent. The server answers MCP 2026-07-28 and negotiates down for
2025-era clients that send initialize. Server HTTP/stdio use SDK factories
and negotiation defaults; Workbench uses automatic negotiation.