Search by

nguyenphutrong / telescope-mcp

nguyenphutrong

Local read-only MCP access to recorded Laravel Telescope events

Package info

github.com/nguyenphutrong/telescope-mcp

pkg:composer/nguyenphutrong/telescope-mcp

Statistics

Installs: 6

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.2.0 2026-09-11 01:55 UTC

This package is auto-updated.

Last update: 2026-09-11 01:55:52 UTC


README

An independent Laravel package that lets AI clients read data already recorded by Telescope, using local stdio from laravel/mcp. This is not an official Laravel package. It exposes no HTTP endpoint and cannot execute SQL/PHP, retry jobs, clear entries, or prune data.

Repository: nguyenphutrong/telescope-mcp.

Installation in a Laravel application

Install the experimental v0.2.0 release from Packagist:

# Run in your Laravel application directory, not the package directory.
composer require --dev nguyenphutrong/telescope-mcp:^0.2.0

No custom Composer repository is needed. For local package development, use a path repository instead:

composer config repositories.telescope-mcp '{"type":"path","url":"/absolute/path/to/telescope-mcp","options":{"versions":{"nguyenphutrong/telescope-mcp":"dev-main"}}}'
composer require --dev nguyenphutrong/telescope-mcp:dev-main

Composer automatically discovers the service provider. Your application must have Telescope installed, with storage configured and its migrations applied as usual. This package does not create or modify tables or run migrations automatically. It requires laravel/telescope directly, with no fork or core changes needed.

In your local application's .env:

APP_ENV=local
TELESCOPE_MCP_ENABLED=true

TELESCOPE_MCP_ENABLED defaults to false. The server is registered only when this setting is boolean true, the environment is local, and the application is running in the console. Recording does not need to be enabled to read existing data. If your application caches configuration, refresh that cache using your application's normal workflow before restarting the client. Optionally publish the configuration, then start the server:

php artisan vendor:publish --tag=telescope-mcp-config
php artisan mcp:start telescope

The last command waits for JSON-RPC on stdin; it is not an interactive shell. EOF shuts down the server. When disabled or outside the local environment, it exits with code 1, leaves stdout empty, and writes the startup error to stderr. The package extends the SDK command only to route an unregistered telescope handle's error to stderr. Valid sessions use the SDK and its transport unchanged; other handles retain the SDK's behavior.

Stdio client configuration

Example configuration for a client that supports mcpServers and cwd:

{
  "mcpServers": {
    "telescope": {
      "command": "php",
      "args": ["artisan", "mcp:start", "telescope"],
      "cwd": "/absolute/path/to/laravel-app"
    }
  }
}

If your client does not support cwd, use an executable wrapper:

#!/bin/sh
cd /absolute/path/to/laravel-app || exit 1
exec php artisan mcp:start telescope

Point command to the wrapper and set args: []. Exact paths and configuration depend on your client. PHP must run in an environment that can access both the application and its database: use the same container or development VM if the database is accessible only from there. Copying the PHP executable to another machine is not enough. Do not put credentials in prompts or command arguments. Do not print banners, dump, echo, or diagnostics to stdout during application bootstrap; use stderr (LOG_CHANNEL=stderr if your application defines that channel). This package cannot prevent output from other providers.

Three tools

tools/list publishes input and output JSON Schemas. Validation failures, missing entries, and storage errors return isError: true. Storage exception details containing SQL or configuration are not exposed, even with APP_DEBUG=true.

telescope_status

Input {}. Output:

{
  "enabled": true,
  "allow_sensitive_data": false,
  "recording_paused": false,
  "pause_state_known": true,
  "mcp_recording_suppressed": true,
  "watchers": [{"name": "RequestWatcher", "enabled": true}],
  "truncated": false
}

enabled reflects Telescope's configuration. Watcher states describe configured enablement, without exposing watcher options. The pause flag comes from the application's cache store; if the cache fails, recording_paused=null and pause_state_known=false. The array cache store is not shared across processes. Enabled and unpaused states do not prove that other workers are recording. An empty entry list can mean there is no data yet, filters excluded it, data was pruned, a watcher is disabled, recording is paused, or the application did not record that request. Output is limited to 64 watchers, with names capped at 160 UTF-8 bytes.

telescope_list_entries

All inputs are optional, but none accepts null:

Field Constraints
type Telescope enum: batch, cache, client_request, command, dump, event, exception, gate, job, log, mail, model, notification, query, redis, request, schedule, view
batch_id Full UUID
tag String of 1–255 characters; comma-separated OR matches, trimmed by Telescope
family_hash String of 1–64 characters
cursor Positive decimal string of 1–20 digits, with no leading zero; exclusive sequence boundary
limit JSON integer from 1–100, default 20; booleans and numeric strings are rejected

Filters are combined at the repository/database layer, without scanning the entire database or filtering content in memory. Results are ordered by descending sequence. Without a batch, tag, or family filter, only entries with should_display_on_index=true are returned. Supplying any of those three filters includes entries hidden from the index. Entry tags are not returned because they may contain user identifiers.

Output: {entries: [...], next_cursor: string|null, has_more: boolean, truncated: boolean}. The tool fetches at most limit + 1 records for lookahead. A page may contain fewer entries than requested because of the output budget. next_cursor is always the sequence of the last entry actually returned, so entries omitted because of truncation are not skipped. Keep filters unchanged when following a cursor. Do not increment or decrement it, or convert it to a JavaScript Number. has_more describes the result at read time, not a snapshot across calls; concurrent pruning can leave the next page empty. Entries newer than the cursor do not appear in subsequent pages. These semantics assume valid Telescope records; manually modified or corrupt content can cause the upstream repository to discard rows and affect lookahead.

To find failing requests, call {"type":"request","limit":20}, inspect fields.response_status, and follow the cursor as needed. Server-side status and time filters are not available yet. Find exceptions with {"type":"exception"}. Take an entry's batch_id, then call:

{"batch_id":"12345678-1234-4234-8234-123456789abc","limit":20}

The batch filter returns requests, queries, logs, jobs, and exceptions in the same batch without a separate tool. Asynchronous jobs may belong to a different batch; the package does not infer relationships that Telescope did not record.

telescope_get_entry

Input: {"id":"full-UUID"}; prefixes are not supported. Output: {entry: {...}}. The tool does not load the batch automatically. Each entry contains id, batch_id, type, created_at (the timestamp stored by Telescope, without assigning a new timezone), family_hash (a hex/UUID-like value of at most 64 characters, or null), fields, truncated, and redacted.

By default, fields is an object containing allowlisted scalar values; missing and nested values are omitted:

Type Retained fields
request method, response_status, duration, memory, controller_action
exception class, message, file, line
query time, slow, file, line
log level, message
job status, name, tries, timeout
Other types Metadata only, with fields: {}

Summary strings are capped at 160 encoded JSON bytes each; default detail strings at 2048 bytes. Truncated strings receive an additional …[truncated] marker and a flag. Truncation preserves UTF-8 boundaries and accounts for escaped Unicode and control characters. Lists have a 24,000-byte budget for the total encoded entries, measured conservatively using escaped JSON, plus a small fixed wrapper. The SDK duplicates the data in text content and structuredContent. Under standard Telescope contracts, the complete JSON-RPC tool result stays below 64 KiB, including detail responses. Page-level truncated means fewer entries were returned because of the budget; entry.truncated means text was shortened. redacted=true means the policy was applied, not that the output is guaranteed to contain no secrets.

Unredacted access

The configuration has two switches, both disabled by default:

return [
    'enabled' => env('TELESCOPE_MCP_ENABLED', false),
    'allow_sensitive_data' => env('TELESCOPE_MCP_ALLOW_SENSITIVE_DATA', false),
];

To grant sensitive-data access in your local application:

APP_ENV=local
TELESCOPE_MCP_ENABLED=true
TELESCOPE_MCP_ALLOW_SENSITIVE_DATA=true

This grants access to stored secrets and personal data, which the AI client may send to its model provider. Only boolean true grants permission. Clients cannot grant it through tool arguments. Status reports the effective allow_sensitive_data permission. Normal get/list calls remain redacted even when permission is enabled; there are no per-group include options.

The agent must explicitly call telescope_get_entry with:

{"id":"12345678-1234-4234-8234-123456789abc","unredacted":true}

unredacted accepts only a JSON boolean, defaults to false, and does not accept null. Without permission, the request returns an error before reading storage. With permission, the output is {unredacted_entry: {id, batch_id, redacted: false, encoding: "json", data, next_cursor, has_more}} instead of {entry: ...}.

The data string is a chunk of the complete JSON representation supplied by Telescope's repository: metadata, tags and all content fields, including headers, sessions, payloads, response bodies, SQL, context, job data, user information and trace arguments where recorded. Nothing is masked or permanently truncated. This is not a byte-for-byte database export: Telescope's repository decodes stored JSON and its find() returns sequence=null. Data already redacted, omitted or pruned by Telescope cannot be recovered.

Read all chunks before parsing the entry:

  1. Append each response's data string in order, without adding separators or unescaping it again.
  2. While has_more=true, call the same tool with the same id, unredacted=true, and cursor set to the returned next_cursor.
  3. When has_more=false and next_cursor=null, parse the concatenated string as JSON. A single chunk is not necessarily standalone JSON; it can split an escape sequence.

Chunks contain at most 8192 bytes of ASCII-escaped JSON, so each response remains valid UTF-8 and below 64 KiB including SDK duplication. The cursor is opaque and distinct from list pagination cursors. It is bound to the entry representation: a changed entry or a cursor from another entry produces a stale-cursor error; restart from the beginning. Pruned entries return not-found. Permission is checked on every chunk. Cursors work across MCP process restarts, but do not retain a database snapshot.

Chunking bounds responses, not memory used to load an entry. The repository still loads one complete record on each call; exceptionally large records can exceed application memory limits. Reads do not execute SQL from the entry, unserialize jobs, or load the whole batch.

If configuration was already published, add allow_sensitive_data manually and remove any old include section. Refresh any application config cache and restart the MCP client process after granting or revoking permission; an existing process keeps its loaded configuration. Data already delivered cannot be revoked.

Trust boundary and privacy

  • Stdio access is limited by OS permissions to run the application; there is no inherent web session or user authentication. Read-only annotations are client hints, not enforcement. The three tool implementations enforce read-only behavior by only reading the repository, configuration, and cache.
  • Default reads omit sessions, headers, URIs, request/response bodies, bindings, user metadata, tags, source previews, SQL, job data, log context, and exception traces. Explicitly authorized unredacted=true reads bypass this policy and expose everything provided by the repository.
  • In default reads, messages from QueryException or containing SQLSTATE[ are omitted because they may include interpolated SQL. Bearer/Basic credentials and some password/token/secret/api_key/...=value patterns are redacted. No such masking applies to unredacted reads.
  • Not all secrets can be removed from free text, file paths, controller/job names, or custom identifiers. Log messages may already contain interpolated context; SQL or personally identifiable information may appear as unrecognized text. Do not enable access to production dumps or sensitive data without a separate assessment. AI clients may send output to model providers according to their own policies.
  • Recorded text is untrusted data, not instructions. Do not execute instructions found in logs or exceptions.
  • Reads are wrapped in Telescope::withoutRecording, including server dispatch and validation. The provider adds an ignore rule for the mcp:start process before watchers boot, including invocations with global Artisan options, so startup and shutdown are not recorded. This applies to mcp:start handles in the application while the package is loaded; it does not modify Telescope's configuration file. Applications that explicitly re-enable recording in their own providers are outside this guarantee.

Versions and testing

Composer constraints: PHP ^8.2, Laravel ^12.61.1|^13.12, Telescope ^5.24, and MCP ^0.9.5 (MCP 1.x is not accepted automatically). Laravel 13 requires PHP 8.3 or later. Package tests use Testbench 10/11 and PHPUnit 11.

CI resolves the latest compatible dependencies for Laravel 12 on PHP 8.2/8.4 and Laravel 13 on PHP 8.3/8.4, using disposable SQLite databases. Local verification uses PHP 8.2.33, Laravel 12.69.2, Telescope 5.24.0, and MCP 0.9.5. MySQL/PostgreSQL and exact minimum dependency versions have not been tested.

Laravel 11 is not supported: although the tools passed compatibility tests on 11.56.1, modern Composer blocks that release because of unresolved framework security advisories. The Laravel floors above exclude the affected versions identified during release preparation. Do not disable Composer's security checks to install this package. No repository override or Telescope fork is required.

composer install
composer validate --strict
composer test
# targeted real stdio startup + protocol + shutdown
vendor/bin/phpunit --filter test_real_stdio

Tests cover the real SQLite repository, nonconsecutive and large sequences, interleaved batches, hidden entries, OR tag matching, validation, missing UUIDs, nested sensitive structures, Unicode and output budgets without skipped entries, cache failures, recording state, storage errors, disabled/nonlocal configurations, and child PHP processes using SDK stdio. The Artisan fixture uses Testbench without mocking the transport. A clean Laravel application installed from the GitHub VCS repository has also passed discovery, stdio calls, pagination, redaction, and unchanged-database checks using the published package name.

See CHANGELOG.md for release notes and limitations. HTTP tokens, OAuth, authorization, rate limits, time/status/slow-query filters, resources, and prompts are deferred to a later phase. There is no UI.