# Telescope Documentation (Full) > Consolidated documentation for context window loading. --- # Getting Started # Getting Started Everything you need to add `fluttersdk_telescope` to your project, wire the watchers, and start inspecting runtime state from the CLI or an MCP-connected AI agent. ## Pick your path - [**Installation**](installation): Add fluttersdk_telescope to a Flutter project step by step. - [**Quickstart**](quickstart): 3-step end-to-end walkthrough from install to first MCP tool call. ## What is fluttersdk_telescope? Telescope is a passive runtime inspector for Flutter apps. It registers a set of watchers and VM Service extensions at startup, captures every HTTP request, log line, exception, `debugPrint` call, DB query, and Magic-framework lifecycle event into ring buffers, then surfaces those buffers through CLI commands and MCP tools without ever modifying app behavior. One `telescope:install` command wires everything end-to-end. Telescope ships its own bootstrap CLI entry point so the command works from a fresh consumer without any prior `fluttersdk_artisan` wiring: ```bash flutter pub add fluttersdk_telescope dart run fluttersdk_telescope telescope:install ``` The command scaffolds the consumer artisan harness if it is missing, runs `plugin:install fluttersdk_telescope`, and patches `lib/main.dart` so `TelescopePlugin.install()` runs at startup inside a `kDebugMode` guard. Release builds tree-shake the entire subsystem; there is zero production overhead. After install, the artisan native AOT launcher at `./bin/fsa` gives ~110ms warm startup for every subsequent telescope command (`./bin/fsa telescope:tail`, etc.). ## Requirements | Dependency | Minimum Version | Notes | |:-----------|:----------------|:------| | Dart SDK | `>= 3.4.0` | Required. | | Flutter SDK | `>= 3.22.0` | Required. Telescope needs the Flutter runtime for VM Service extensions. | | fluttersdk_artisan | `^0.0.2` | Pulled in transitively by telescope; the install command and MCP tools work without prior setup. | | Magic stack | optional | Enables 6 additional watchers: HTTP facade, models, cache, events, gates, queries. | Telescope is a debug-only package. The `kDebugMode` gate at the consumer install site is load-bearing: all release-mode tree-shaking depends on it. ## What gets captured Out of the box after `telescope:install`, Telescope captures: - **Logs**: all `package:logging` records via `LogWatcher` (auto-installed). - **Exceptions**: uncaught errors via `ExceptionWatcher` (opt-in, chain-preserves Sentry/Bugsnag). - **debugPrint output**: via `DumpWatcher` (opt-in, chain-preserves prior override). With the Magic stack (`MagicTelescopeIntegration.install()`): - **HTTP traffic** through the Magic `Http` facade. - **Model lifecycle**: create, save, delete events on Magic Eloquent models. - **Cache operations**: hit, miss, put, forget, flush via the `Cache` facade. - **In-app events** dispatched through `Event.dispatch`. - **Gate checks**: every `Gate.allows` / `Gate.denies` call with result + user id. - **DB queries**: SQL, bindings, and execution time via the magic database connector. ## Next steps - New here? Start with [Installation](installation). - Already installed? Run the [Quickstart](quickstart). - Full reference at [fluttersdk.com/telescope](https://fluttersdk.com/telescope). --- # Installation # Installation - [Requirements](#requirements) - [Option A: one-shot install via artisan](#option-a-one-shot-install) - [Option B: manual wiring](#option-b-manual-wiring) - [Wire the Artisan provider](#wire-the-artisan-provider) - [Verify installation](#verify-installation) `fluttersdk_telescope` works from a fresh Flutter project. The recommended install path uses telescope's own bootstrap entry point (`dart run fluttersdk_telescope telescope:install`) which carries the artisan substrate, so no prior `fluttersdk_artisan` setup is required. ## Requirements | Dependency | Minimum Version | Notes | |:-----------|:----------------|:------| | Dart SDK | `>= 3.4.0` | | | Flutter SDK | `>= 3.22.0` | VM Service extensions require the Flutter runtime. | | fluttersdk_artisan | `^0.0.2` | Pulled in transitively by telescope; no manual setup needed for the install path. | | Magic stack | optional | Enables 6 additional Magic-specific watchers. | ## Option A: one-shot self-bootstrap install (recommended) Add `fluttersdk_telescope` to your project, then bootstrap via telescope's own CLI entry point. The standalone binary carries the artisan substrate, so this works from a completely fresh consumer with no prior `fluttersdk_artisan` wiring: ```bash flutter pub add fluttersdk_telescope dart run fluttersdk_telescope telescope:install ``` `flutter pub add` writes the dependency to `pubspec.yaml` and runs `dart pub get` in one step. The `dart run` line then invokes telescope's standalone CLI to scaffold and patch the consumer. The command performs three operations in order: 1. Scaffolds the consumer artisan harness (`bin/dispatcher.dart`, `lib/app/_plugins.g.dart`) if it is missing. This is a no-op when the harness is already present. 2. Runs `plugin:install fluttersdk_telescope`, which registers `TelescopeArtisanProvider` in `.artisan/plugins.json` and refreshes the codegen barrel. 3. Patches `lib/main.dart` to call `TelescopePlugin.install()` before `runApp`. When using the Magic framework, the patch places the call before `Magic.init()` so the Http facade is wired before MagicTelescopeIntegration runs. The patch is wrapped in a `kDebugMode` guard automatically. The command is idempotent. Re-running it when the files are already patched is safe. ### After install: prefer the artisan fast-cli The artisan scaffold ships a precompiled launcher at `./bin/fsa` (native AOT, ~110ms warm startup). For everyday telescope work, use it instead of `dart run`: ```bash ./bin/fsa telescope:tail ./bin/fsa telescope:requests ./bin/fsa telescope:clear ``` `dart run fluttersdk_telescope ` and `dart run fluttersdk_artisan ` keep working as ~3-second cold-start fallbacks; they are useful in CI or before the AOT bundle is built. ## Option B: manual wiring Use this path when you need fine-grained control over the install, or when the automated patch cannot locate the correct anchor in `lib/main.dart`. ### 1. Add the dependency Add `fluttersdk_telescope` to `pubspec.yaml`: ```yaml dependencies: fluttersdk_telescope: ^0.0.3 ``` Then fetch dependencies: ```bash dart pub get ``` ### 2. Wire TelescopePlugin in lib/main.dart Install Telescope before `runApp`, wrapped in `kDebugMode`. When using the Magic framework, place the call before `Magic.init()` so the Http facade is wired before MagicTelescopeIntegration runs: ```dart import 'package:flutter/foundation.dart'; import 'package:fluttersdk_telescope/telescope.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); if (kDebugMode) { // 1. Install Telescope core (LogWatcher auto-installs, VM extensions register). TelescopePlugin.install(); // 2. Opt-in to exception and debugPrint capture. TelescopePlugin.registerWatcher(ExceptionWatcher()); TelescopePlugin.registerWatcher(DumpWatcher()); } // 3. Framework init (e.g. Magic.init()) runs after Telescope so the Http facade // is wired before MagicTelescopeIntegration tries to wrap it. await Magic.init(configFactories: [...]); if (kDebugMode) { // 4. Magic-specific adapters resolve framework internals from the IoC container; // they must run after Magic.init(). MagicTelescopeIntegration.install(); } runApp(MyApp()); } ``` For vanilla Flutter (no Magic stack), omit `Magic.init()` and the `MagicTelescopeIntegration.install()` call. Wire `DioHttpAdapter` instead if you use Dio: ```dart if (kDebugMode) { TelescopePlugin.install(); TelescopePlugin.registerHttpAdapter(DioHttpAdapter(dio)); TelescopePlugin.registerWatcher(ExceptionWatcher()); TelescopePlugin.registerWatcher(DumpWatcher()); } ``` ## Wire the Artisan provider If you used Option A (`telescope:install`), the provider registration is written automatically: `plugin:install fluttersdk_telescope` adds `FluttersdkTelescopeArtisanProvider` to `lib/app/_plugins.g.dart`, which the scaffolded `bin/dispatcher.dart` reads via `plugins.autoDiscoveredProviders()`. No manual edit is required. If you used Option B (manual wiring), import the provider in your `bin/dispatcher.dart` yourself: ```dart import 'package:fluttersdk_artisan/artisan.dart'; import 'package:fluttersdk_telescope/cli.dart' show FluttersdkTelescopeArtisanProvider; Future main(List args) async { exit(await runArtisan( args, baseProviders: [ FluttersdkTelescopeArtisanProvider(), // ...other providers (DuskArtisanProvider, etc.) ], )); } ``` ## Verify installation Start your Flutter app and confirm Telescope is active by tailing the log buffer: ```bash ./bin/fsa telescope:tail ``` Expected output on a running app shows the most recent log records from the ring buffer. If you see `Error: no running app found`, the artisan state file is missing: run `./bin/fsa start` first, then retry `telescope:tail`. To confirm all 6 CLI commands are registered, list the artisan command catalog: ```bash ./bin/fsa list ``` The output includes the `telescope:` namespace with `telescope:install`, `telescope:tail`, `telescope:requests`, `telescope:queries`, `telescope:caches`, and `telescope:clear`. If the fast-cli is missing for any reason, the same commands run via `dart run fluttersdk_telescope list` or `dart run fluttersdk_artisan list`. --- # Quickstart # Quickstart A 3-step walkthrough taking a fresh Flutter project from zero to a working Telescope setup with runtime buffers populated and the first MCP tool call returning live data. Prerequisites: Flutter SDK 3.22+, Dart SDK 3.4+, a Flutter project (any vanilla `flutter create` output works). `fluttersdk_artisan` is pulled in transitively by telescope; you do not need to install it separately. The `telescope:install` command scaffolds `bin/dispatcher.dart` for you on first run. --- ### 1. Install Telescope Add the dependency, then run the one-shot install through telescope's own bootstrap entry point. The install command scaffolds the artisan harness if needed, registers the plugin, and patches `lib/main.dart` with the `kDebugMode`-guarded install call: ```bash flutter pub add fluttersdk_telescope dart run fluttersdk_telescope telescope:install ``` The command outputs a summary of every file it touched. For a fresh project the summary looks like: ``` [plugin:install] registered TelescopeArtisanProvider [main.dart] injected TelescopePlugin.install() before framework init [main.dart] injected MagicTelescopeIntegration.install() after Magic.init() (when using Magic framework) telescope:install done ``` When using the Magic framework, `MagicTelescopeIntegration.install()` is also injected after `Magic.init()` so all 9 watchers activate automatically. On a vanilla Flutter app only the core watchers (`LogWatcher`, plus any you opt into manually) are wired. Verify the provider registered correctly. From now on, the artisan fast-cli at `./bin/fsa` (native AOT, ~110ms warm) is the recommended entry point for every subsequent command: ```bash ./bin/fsa list ``` You should see the `telescope:` namespace with 6 commands: `telescope:install`, `telescope:tail`, `telescope:requests`, `telescope:queries`, `telescope:caches`, `telescope:clear`. The same surface is also reachable via the ~3s cold-start fallbacks `dart run fluttersdk_telescope list` and `dart run fluttersdk_artisan list`. --- ### 2. Start the app and generate traffic Boot the Flutter app via artisan so the VM Service URI is recorded to the state file. Artisan resolves the running instance from this file for every subsequent CLI and MCP call. ```bash ./bin/fsa start --device=chrome ``` The command scrapes the VM Service URI from `flutter run` output, normalizes it to a WebSocket address, and writes the full process state (PID, URI, device, project root) to `~/.artisan/state.json`. With the app running, navigate the UI to generate HTTP traffic, logs, and any exceptions. When using the Magic framework, every HTTP call through the `Http` facade, every model save, and every cache read goes straight into the Telescope ring buffers. On a vanilla Flutter app, `package:logging` output and `debugPrint` calls fill the log and dump buffers. You can also exercise the watchers programmatically from the app: ```dart // Generates a LogRecordEntry in the console buffer. Logger('MyFeature').info('quickstart test log'); // Generates a DumpRecord in the dump buffer. debugPrint('quickstart test dump'); ``` --- ### 3. Query the buffers from Claude Code (MCP) Start the artisan MCP server. When your project has `.mcp.json` wired with `dart run fluttersdk_artisan:mcp` as the entry point (written by `fluttersdk_artisan mcp:install`), Claude Code launches the server automatically on attach. To start it manually: ```bash ./bin/fsa mcp:serve ``` Inside a Claude Code session, the 9 `telescope_*` tools are now available. A typical inspection flow: ``` [agent] telescope_tail {} ``` Returns the most recent log records from the ring buffer, newest first. Filter by level: ``` [agent] telescope_tail { "level": "warning", "limit": 20 } ``` Inspect HTTP traffic: ``` [agent] telescope_requests {} ``` Returns every captured HTTP request with method, URL, status code, duration in ms, request headers, and a body snippet. Useful for verifying what the app actually sent to the API. Check for uncaught exceptions: ``` [agent] telescope_exceptions {} ``` Review DB queries with timings: ``` [agent] telescope_queries {} ``` Clear all buffers before the next test scenario: ``` [agent] telescope_clear {} ``` All 9 tools follow the same pattern: they query the `ext.telescope.*` VM Service extensions registered by `registerAllTelescopeExtensions()` at startup. No source changes are needed between queries; the buffers update passively as the app runs. --- ## What's next? - Read the full [Installation](installation) doc for manual wiring options and the `DioHttpAdapter` setup for vanilla Flutter. - Browse the watcher catalog at [fluttersdk.com/telescope/watchers](https://fluttersdk.com/telescope/watchers) to learn which watchers are opt-in and how chain-preservation works with Sentry and Bugsnag. - See the full MCP tool input schemas at [fluttersdk.com/telescope/mcp](https://fluttersdk.com/telescope/mcp). --- All three steps above use only the `fluttersdk_telescope ^0.0.3` package and its `fluttersdk_artisan` dependency. No additional packages are required for the core watcher surface. Magic-specific watchers activate automatically when the Magic stack is present and `MagicTelescopeIntegration.install()` is called. --- # Watchers # Watchers Telescope ships 9 watcher units across three categories. Each unit implements one of two contracts: `TelescopeWatcher` (for event-driven and hook-based capture) or `TelescopeHttpAdapter` (for HTTP traffic capture). All units feed the matching ring buffer inside `TelescopeStore` and expose their data via a VM Service extension method. ## kDebugMode gating The consumer is responsible for gating the entire telescope install behind `kDebugMode`: ```dart if (kDebugMode) { TelescopePlugin.install(); // register additional watchers here } ``` This single gate tree-shakes the entire subsystem in release builds (dart2js for web, dart2native for mobile/desktop AOT). Individual watchers do not need their own gate except `DumpWatcher`, which has an additional internal guard (see below). ## Chain-preserve pattern Any watcher that overrides a global Dart/Flutter hook saves the previous handler before replacing it and calls it inside the new handler body. On `uninstall()`, the previous handler is restored symmetrically. This is the Sentry/Bugsnag coexistence contract: telescope captures the record AND forwards to whatever was registered before it. The numbered step comments in `ExceptionWatcher` are the canonical reference for this pattern: ```dart // 1. Chain-preserve FlutterError.onError (sync framework errors). _previousOnError = FlutterError.onError; FlutterError.onError = (details) { TelescopeStore.recordException(...); _previousOnError?.call(details); }; // 2. Chain-preserve PlatformDispatcher.instance.onError (async + isolate errors). _previousPlatformOnError = PlatformDispatcher.instance.onError; PlatformDispatcher.instance.onError = (error, stack) { TelescopeStore.recordException(...); return _previousPlatformOnError?.call(error, stack) ?? true; }; ``` --- ## Framework-agnostic watchers These three units are part of the `fluttersdk_telescope` core package. They have no dependency on Magic or any other application framework. ### LogWatcher | Field | Value | |---|---| | Contract | `TelescopeWatcher` | | Name | `log` | | Auto-install | Yes (auto-registered by `TelescopePlugin.install()`) | | Ring buffer | `TelescopeStore._logs` | | VM extension | `ext.telescope.console` | | Opt-out | Call `TelescopePlugin.install()` then remove it by not calling `LogWatcher().install()` directly; or skip the default auto-install path and register manually | Subscribes to `Logger.root.onRecord` from `package:logging`. Every log record that flows through the root logger (at any level) is converted to a `LogRecordEntry` and pushed to the store. `hierarchicalLoggingEnabled` is set to `true` during install so named child loggers (`Logger('http')`, `Logger('auth')`, etc.) funnel through root. Install is idempotent: a second call when the subscription is already live is a no-op. If the app does not use `package:logging`, the watcher is dormant (no records flow through `Logger.root`). It is still registered because adding `package:logging` later immediately benefits from capture without any telescope config change. Registration (auto-installed, shown for completeness): ```dart TelescopePlugin.install(); // LogWatcher registered automatically ``` Manual opt-out (skip auto-registration) is not exposed in the V1 API; file a feature request if selective watcher exclusion is needed. --- ### ExceptionWatcher | Field | Value | |---|---| | Contract | `TelescopeWatcher` | | Name | `exception` | | Auto-install | No (opt-in via `TelescopePlugin.registerWatcher(ExceptionWatcher())` after install) | | Ring buffer | `TelescopeStore._exceptions` | | VM extension | `ext.telescope.exceptions` | | Opt-out | Simply do not register; absent registration means no error capture. To coexist with Sentry / Bugsnag, register telescope FIRST so the chain-preserve wraps the next handler. | Hooks both `FlutterError.onError` (synchronous framework and widget errors) and `PlatformDispatcher.instance.onError` (asynchronous errors, isolate errors, plugin-originated errors). Both hooks follow the chain-preserve pattern. The `PlatformDispatcher.onError` handler returns `_previousPlatformOnError?.call(...) ?? true`. When a previously-registered handler (e.g. Sentry) returns `false` to signal a fatal error and let the native crash reporter take over, telescope preserves that semantics. When no previous handler exists, the default `true` (handled) matches pre-install behavior. On `uninstall()`, both handlers are restored to exactly the values they held before `install()` was called. Calling `install()` while already installed is a no-op. Registration (opt-in; add after `TelescopePlugin.install()`): ```dart TelescopePlugin.install(); TelescopePlugin.registerWatcher(ExceptionWatcher()); ``` Coexistence with Sentry (the chain-preserve contract means order matters; install Sentry first): ```dart SentryFlutter.init((_) {}); // registers FlutterError.onError + PlatformDispatcher.onError if (kDebugMode) { TelescopePlugin.install(); // wraps Sentry's handlers; Sentry still fires } ``` --- ### DumpWatcher | Field | Value | |---|---| | Contract | `TelescopeWatcher` | | Name | `dump` | | Auto-install | No (opt-in via `TelescopePlugin.registerWatcher(DumpWatcher())`) | | Ring buffer | `TelescopeStore._dumps` | | VM extension | `ext.telescope.dumps` | | Opt-out | Do not register it; it is never auto-installed | Captures all `debugPrint` output by replacing the global `debugPrint` callback with an interceptor. The interceptor records a `DumpRecord` AND calls the previous `debugPrint` value (chain-preserve). On `uninstall()`, the previous callback is restored exactly. Internal `kDebugMode` gate: `install()` is a no-op in release builds unless `allowInRelease` is set to `true` before calling `install()`. This gate is load-bearing: in release builds `debugPrint` itself is a no-op, so capture would produce empty records; the gate makes the intent explicit and ensures AOT tree-shaking eliminates the interceptor. Registration: ```dart if (kDebugMode) { TelescopePlugin.install(); TelescopePlugin.registerWatcher(DumpWatcher()); // opt-in } ``` Opt-out: simply omit the `registerWatcher(DumpWatcher())` call. --- ## HTTP adapters HTTP adapters implement `TelescopeHttpAdapter` (three methods: `name`, `install`, `uninstall`, plus the optional `pendingCount` getter). They feed `TelescopeStore.recordHttp` and their in-flight counts sum into `TelescopeStore.pendingHttpCount` (consumed by Dusk's `wait_for_network_idle`). ### DioHttpAdapter | Field | Value | |---|---| | Contract | `TelescopeHttpAdapter` | | Name | `dio` | | Auto-install | No (opt-in via `TelescopePlugin.registerHttpAdapter(DioHttpAdapter())`) | | Ring buffer | `TelescopeStore._http` | | VM extension | `ext.telescope.requests` | | Opt-out | Do not register it | Vanilla Dio adapter for Flutter apps that use raw Dio instances (not the Magic Http facade). V1 ships as a stub: the actual Dio interceptor subclass requires `package:dio`, which is not in telescope's own pubspec to keep the core package HTTP-library-agnostic. The consumer adds `package:dio` to their own pubspec and wires the adapter by calling the static helper: ```dart // consumer pubspec: dio: ^5.x import 'package:fluttersdk_telescope/telescope.dart'; // After constructing your Dio instance: dio.interceptors.add(_MyTelescopeInterceptor()); class _MyTelescopeInterceptor extends Interceptor { @override void onResponse(Response response, ResponseInterceptorHandler handler) { DioHttpAdapter.recordRequest( url: response.requestOptions.uri.toString(), method: response.requestOptions.method, statusCode: response.statusCode ?? 0, durationMs: ..., ); handler.next(response); } } // Register so the adapter shows up in the store's adapter list: TelescopePlugin.registerHttpAdapter(DioHttpAdapter()); ``` V1.x will move the Dio-coupled glue into a `fluttersdk_telescope_dio` sub-package so the core stays HTTP-library-agnostic. --- ### MagicHttpFacadeAdapter | Field | Value | |---|---| | Contract | `TelescopeHttpAdapter` | | Name | `magic_http_facade` | | Auto-install | Yes (registered by `MagicTelescopeIntegration.install()`) | | Ring buffer | `TelescopeStore._http` | | VM extension | `ext.telescope.requests` | | Opt-out | Call `TelescopePlugin.install()` without calling `MagicTelescopeIntegration.install()` | Captures every request flowing through Magic's `network` driver by registering a `MagicNetworkInterceptor` on the driver. The interceptor pairs `onRequest` (start stopwatch) with `onResponse`/`onError` (stop stopwatch, emit `HttpRequestRecord`) via a FIFO in-flight list. Attribution is heuristic (`attributedHeuristically: true`) because the `MagicNetworkInterceptor` contract does not carry a correlation handle across `onRequest`/`onResponse` callbacks; best-effort matching by call order. `pendingCount` returns the current length of the in-flight FIFO, surfaced into `TelescopeStore.pendingHttpCount` for Dusk's network-idle detection. Registration (via `MagicTelescopeIntegration`, the only documented entry point): ```dart if (kDebugMode) { TelescopePlugin.install(); MagicTelescopeIntegration.install(); // registers MagicHttpFacadeAdapter + 5 watchers } ``` `install()` is a no-op if `Magic.bound('network')` returns false (called too early, before `Magic.init()` completes). Call `MagicTelescopeIntegration.install()` after `Magic.init()`. --- ## Magic-stack watchers These five watchers are shipped inside the `magic` package via `MagicTelescopeIntegration`. They are not part of the `fluttersdk_telescope` core. All five are registered by a single `MagicTelescopeIntegration.install()` call. ### MagicModelWatcher | Field | Value | |---|---| | Contract | `TelescopeWatcher` | | Name | `magic_model` | | Auto-install | Yes (via `MagicTelescopeIntegration.install()`) | | Ring buffer | `TelescopeStore._models` | | VM extension | `ext.telescope.requests` (via models key; MCP tool: `telescope_models`) | | Opt-out | Do not call `MagicTelescopeIntegration.install()` | Subscribes to `ModelCreated`, `ModelSaved`, and `ModelDeleted` events dispatched by Magic's `EventDispatcher`. Each event is converted to a `MagicModelRecord` carrying: `modelClass` (runtime type name), `event` tag (`created`/`saved`/`deleted`), `modelKey` (stringified primary key), `time`, and `attributes` (snapshot of `model.attributes` at capture time). Registration: ```dart if (kDebugMode) { TelescopePlugin.install(); MagicTelescopeIntegration.install(); // MagicModelWatcher is included } ``` `uninstall()` is a no-op: `EventDispatcher` has no per-listener removal API. Tests that need a clean dispatcher call `EventDispatcher.instance.clear()` in their `setUp`. --- ### MagicCacheWatcher | Field | Value | |---|---| | Contract | `TelescopeWatcher` | | Name | `magic_cache` | | Auto-install | Yes (via `MagicTelescopeIntegration.install()`) | | Ring buffer | `TelescopeStore._caches` | | VM extension | `ext.telescope.caches` | | Opt-out | Do not call `MagicTelescopeIntegration.install()` | Subscribes to five cache lifecycle events emitted by Magic's `CacheManager`: `CacheHit`, `CacheMiss`, `CachePut`, `CacheForget`, and `CacheFlush`. Each event is converted to a `MagicCacheRecord` carrying `operation` (`hit`/`miss`/`put`/`forget`/`flush`), `key` (or `*` for flush), `time`, and `ttl` (present only for `CachePut`). Registration: ```dart if (kDebugMode) { TelescopePlugin.install(); MagicTelescopeIntegration.install(); // MagicCacheWatcher is included } ``` `uninstall()` is a no-op (same `EventDispatcher` constraint as `MagicModelWatcher`). --- ### MagicEventWatcher | Field | Value | |---|---| | Contract | `TelescopeWatcher` | | Name | `magic_event` | | Auto-install | Yes (via `MagicTelescopeIntegration.install()`) | | Ring buffer | `TelescopeStore._events` | | VM extension | `ext.telescope.events` | | Opt-out | Do not call `MagicTelescopeIntegration.install()` | Subscribes to a curated set of Magic app-lifecycle events: `AuthLogin`, `AuthLogout`, `AuthFailed`, `AuthRestored` (auth lifecycle), `DatabaseConnected` (database connection), and `GateAbilityDefined`, `GateBeforeRegistered` (gate definition). Model lifecycle events are excluded (owned by `MagicModelWatcher`) and gate-result events are excluded (owned by `MagicGateWatcher`) to keep each record on a single channel. Current payload is the empty map for every event type. Per-event field extraction is deferred to a follow-up release while the `EventRecord` wire shape stabilises. Registration: ```dart if (kDebugMode) { TelescopePlugin.install(); MagicTelescopeIntegration.install(); // MagicEventWatcher is included } ``` `uninstall()` is a no-op (same `EventDispatcher` constraint). --- ### MagicGateWatcher | Field | Value | |---|---| | Contract | `TelescopeWatcher` | | Name | `magic_gate` | | Auto-install | Yes (via `MagicTelescopeIntegration.install()`) | | Ring buffer | `TelescopeStore._gates` | | VM extension | `ext.telescope.gates` | | Opt-out | Do not call `MagicTelescopeIntegration.install()` | Subscribes to `GateAccessChecked`, which covers both `Gate.allows` and `Gate.denies` outcomes via its `allowed: bool` field. `GateAccessDenied` is intentionally not subscribed to avoid double-recording every denial. Each event is converted to a `GateRecord` with two shape coercions: - `arguments` (single dynamic on the event) is wrapped into `List` of length 1. - `user.id` (dynamic primary key) is stringified; null user or null id both collapse to `userId: null`. Magic model arguments are converted via `toMap()`; primitives and collections pass through as-is; anything else falls back to `toString()`. Registration: ```dart if (kDebugMode) { TelescopePlugin.install(); MagicTelescopeIntegration.install(); // MagicGateWatcher is included } ``` `uninstall()` is a no-op (same `EventDispatcher` constraint). --- ### MagicQueryWatcher | Field | Value | |---|---| | Contract | `TelescopeWatcher` | | Name | `magic_query` | | Auto-install | Yes (via `MagicTelescopeIntegration.install()`) | | Ring buffer | `TelescopeStore._queries` | | VM extension | `ext.telescope.queries` | | Opt-out | Do not call `MagicTelescopeIntegration.install()` | Subscribes to `QueryExecuted`, dispatched by Magic's `QueryBuilder` after every SQL run. Each event is converted to a `QueryRecord` carrying: `sql` (the full SQL string), `bindings` (parameter list), `timeMs` (execution duration in milliseconds), `connectionName` (driver connection identifier), and `time` (capture timestamp). Surfaced via the `telescope:queries` CLI command and the `telescope_queries` MCP tool. Registration: ```dart if (kDebugMode) { TelescopePlugin.install(); MagicTelescopeIntegration.install(); // MagicQueryWatcher is included } ``` `uninstall()` is a no-op (same `EventDispatcher` constraint as the other Magic-stack watchers). --- # Telescope MCP Integration Overview # Telescope MCP Integration Overview - [What Telescope Contributes](#what-telescope-contributes) - [Substrate Tools vs Plugin Tools](#substrate-vs-plugin) - [How the 9 Tools Surface](#how-tools-surface) - [VM Service Extension Routing](#vm-service-routing) - [Architecture Diagram](#architecture-diagram) - [Related](#related) --- ## What Telescope Contributes `fluttersdk_telescope` is a plugin for `fluttersdk_artisan`. It contributes **9 MCP tools** via `TelescopeArtisanProvider.mcpTools()` and **6 CLI commands** via `TelescopeArtisanProvider.commands()`. The 9 MCP tools give an LLM agent read-only access to ring buffers that telescope maintains inside the running Flutter app. Each tool reads one buffer type: HTTP traffic, log lines, uncaught exceptions, in-app events, Gate authorization checks, `debugPrint` output, database queries, and cache operations. A tenth tool (`telescope_clear`) wipes all buffers at once as a "set zero" before a repro. The tools share a single design rule: **no side effects on the running app**. Reading a buffer is non-destructive; only `telescope_clear` mutates state, and that mutation is intentional. --- ## Substrate Tools vs Plugin Tools artisan's MCP server distinguishes two categories of tools. Understanding the distinction explains why telescope tools appear or disappear in Claude Code's tool list depending on registration. **Substrate tools** (`artisan_*` prefix) are built into `fluttersdk_artisan` itself. They are always registered when the MCP server starts; they require no plugin, no running app (for most of them), and no VM Service connection. Examples: `artisan_start`, `artisan_stop`, `artisan_status`, `artisan_logs`. **Plugin tools** (`telescope_*` prefix, `dusk_*` prefix, etc.) are contributed by packages that extend `ArtisanServiceProvider`. They surface in the MCP catalog **only when** the plugin's provider is registered in the consumer's `bin/dispatcher.dart` (auto-wired by `telescope:install`) and the MCP server is (re)started. Plugin tools always dispatch via the VM Service and therefore require a running Flutter app. `TelescopeArtisanProvider` contributes the `fluttersdk_telescope` plugin tools. Its `providerName` is `fluttersdk_telescope`, which is the key used in `.artisan/mcp.json` package filter rules. --- ## How the 9 Tools Surface The call chain from Claude Code to the Telescope ring buffer: 1. `TelescopeArtisanProvider.mcpTools()` returns a `List` with the 9 descriptors. 2. `McpServer.initialize()` collects descriptors from every registered provider and sends the full catalog to the MCP client (Claude Code) in the `initialize` response. 3. The agent invokes a tool by name (e.g. `telescope_tail`) with optional parameters. 4. `McpServer` looks up the `McpToolDescriptor.extensionMethod` for that tool name (e.g. `ext.telescope.console`) and dispatches via `VmServiceClient.callServiceExtension`. 5. The VM Service handler registered by `registerAllTelescopeExtensions()` in the running Flutter app reads the matching `TelescopeStore` ring buffer, applies any `limit` / `level` filter params, and returns a JSON-encoded payload. 6. `McpServer` wraps the payload in a `CallToolResult` text content block and returns it to the agent. The pause/resume extensions (`ext.telescope.pause` / `ext.telescope.resume`) are registered inside the app but intentionally absent from `mcpTools()` in the current release; they are V1.x backlog. --- ## VM Service Extension Routing Each MCP tool maps to exactly one VM Service extension: | MCP tool | VM Service extension | |---|---| | `telescope_tail` | `ext.telescope.console` | | `telescope_requests` | `ext.telescope.requests` | | `telescope_exceptions` | `ext.telescope.exceptions` | | `telescope_events` | `ext.telescope.events` | | `telescope_gates` | `ext.telescope.gates` | | `telescope_dumps` | `ext.telescope.dumps` | | `telescope_queries` | `ext.telescope.queries` | | `telescope_caches` | `ext.telescope.caches` | | `telescope_clear` | `ext.telescope.clear` | Every extension is registered via `registerExtensionIdempotent` (from `fluttersdk_artisan`) so that Flutter hot restarts do not throw `ArgumentError` on duplicate registration. `VmServiceClient` inside artisan lazy-reconnects on every dispatch call, so `artisan_start` followed immediately by a `telescope_*` call picks up the new VM Service URI automatically from `~/.artisan/state.json`. --- ## Architecture Diagram ``` Claude Code (MCP client) | | stdio JSON-RPC (initialize / tools/call) | dart run fluttersdk_artisan:mcp | McpServer | +-- substrate tools (artisan_start, artisan_stop, ...) | | | v | ArtisanRegistry (in-process command dispatch) | +-- telescope_* plugin tools | | VM Service WebSocket | ws://localhost:PORT//ws | VmServiceClient | | ext.telescope.* | Flutter app isolate (debug mode) | TelescopeStore (ring buffers) +--------+----------+---------+----------+ | | | | | console requests exceptions events gates | | | | | dumps queries caches ``` **Flow summary:** 1. The MCP client connects to the server over stdio and calls `initialize`. 2. `McpServer` collects `McpToolDescriptor` instances from `TelescopeArtisanProvider.mcpTools()` (and any other registered provider) and sends the unified catalog to the client. 3. On each `telescope_*` tool call, `McpServer` routes via `VmServiceClient` to the matching `ext.telescope.*` handler in the running Flutter isolate. 4. The handler reads the `TelescopeStore` ring buffer, applies filter parameters, and returns JSON. 5. Results return as `CallToolResult` text content; errors carry `isError: true` with an actionable message. --- ## Related - [artisan MCP overview](https://fluttersdk.com/artisan/mcp/overview): full substrate tool catalog, state file contract, and artisan's own architecture diagram. - [Setup guide](setup.md): how to wire `TelescopeArtisanProvider` and enable the 9 tools in Claude Code or Cursor. - [Tool reference](tool-reference.md): per-tool input schema, output shape, example invocations, and the VM Service extension each tool routes through. --- # Telescope MCP Setup # Telescope MCP Setup `fluttersdk_telescope` extends the artisan MCP server with 9 runtime-inspection tools (`telescope_*`). This page covers everything needed to make those tools appear in Claude Code, Cursor, or any other MCP-compatible client. **Prerequisite:** no prior artisan setup is required. Telescope's `telescope:install` command scaffolds the artisan harness on first run, and `fluttersdk_artisan` is pulled in transitively through telescope's pubspec. --- ## Step 1: Add the dependency Add `fluttersdk_telescope` to your `pubspec.yaml` under `dependencies` (not `dev_dependencies`, because `TelescopePlugin` is imported by `lib/main.dart`): ```yaml # pubspec.yaml dependencies: fluttersdk_telescope: ^0.0.3 ``` Run `dart pub get` after editing. ### Step 1a: bootstrap with `telescope:install` (optional but recommended) If you only want the MCP tools, jump to Step 2. If you also want the 6 CLI commands wired to the artisan dispatcher, run the one-shot bootstrap now; it scaffolds `bin/dispatcher.dart`, registers the plugin, and patches `lib/main.dart` in one go: ```bash dart run fluttersdk_telescope telescope:install ``` After install, the consumer's fast-cli is available at `./bin/fsa` (native AOT, ~110ms warm). --- ## Step 2: Install the Flutter-side plugin Inside `lib/main.dart`, install `TelescopePlugin` before `runApp`, gated on `kDebugMode` so the entire subsystem tree-shakes out of release builds. When using the Magic framework, place the call before `Magic.init()` so the Http facade is wired in time: ```dart import 'package:flutter/foundation.dart'; import 'package:fluttersdk_telescope/telescope.dart'; Future main() async { if (kDebugMode) { TelescopePlugin.install(); // Optional: when using the Magic framework, register its adapters and watchers. // Call MagicTelescopeIntegration.install() after Magic.init(). } await Magic.init(...); runApp(MagicApplication()); } ``` `TelescopePlugin.install()` registers all `ext.telescope.*` VM Service extensions and starts the ring buffers. Extensions are registered via `registerExtensionIdempotent` (from artisan), so hot restarts are safe. --- ## Step 3: Register TelescopeArtisanProvider in bin/dispatcher.dart If you ran Step 1a (`telescope:install`), the provider is registered automatically via `lib/app/_plugins.g.dart` (the codegen barrel auto-discovered by `bin/dispatcher.dart`); skip to Step 4. For manual wiring, open `bin/dispatcher.dart` and add `FluttersdkTelescopeArtisanProvider()` to the `baseProviders` list: ```dart import 'package:fluttersdk_artisan/artisan.dart'; import 'package:fluttersdk_telescope/cli.dart'; // FluttersdkTelescopeArtisanProvider typedef Future main(List args) async { exit(await runArtisan( args, baseProviders: [ FluttersdkTelescopeArtisanProvider(), // ...other providers (DuskArtisanProvider, etc.) ], )); } ``` The `cli.dart` barrel exports `FluttersdkTelescopeArtisanProvider` (a typedef alias for `TelescopeArtisanProvider`) so consumer-side auto-discovery can use a stable name. --- ## Step 4: Ensure the artisan MCP server entry is present The telescope tools surface through artisan's MCP server. The `.mcp.json` entry must point at `dart run fluttersdk_artisan:mcp` (not a separate telescope entry). If you ran `dart run fluttersdk_artisan mcp:install` during artisan setup, the entry is already in place: ```json { "mcpServers": { "fluttersdk": { "command": "dart", "args": ["run", "fluttersdk_artisan:mcp"], "cwd": "." } } } ``` The `cwd` field must resolve to the directory containing `pubspec.yaml` and `bin/dispatcher.dart`. The server reads both at startup: `pubspec.yaml` for the project root detection and `bin/dispatcher.dart` (via the registered providers) for the plugin tool catalog. --- ## Step 5: Reconnect the MCP server After editing `bin/dispatcher.dart` or `.mcp.json`, the running MCP server process must be restarted for the new tools to appear in the catalog: **Claude Code:** ``` /mcp reconnect fluttersdk ``` **Cursor / Windsurf:** Reload the MCP panel or restart the IDE session. **Claude Desktop:** Fully quit and relaunch. --- ## Step 6: Start the Flutter app and verify Start the app via artisan so the state file is written: ```bash ./bin/fsa start # web (chrome, default) ./bin/fsa start --device=macos # desktop ``` Then ask the agent to check telescope connectivity: ``` telescope_tail limit=5 ``` A successful response returns a JSON array of recent log records. An empty array means the app is running but no logs have been emitted yet. An error response means the VM Service URI in `~/.artisan/state.json` is stale; run `./bin/fsa stop && ./bin/fsa start` to refresh it. --- ## Filtering telescope tools To hide specific telescope tools from the agent's catalog, add deny rules to `.artisan/mcp.json`: ```json { "packages": { "allow": null, "deny": [] }, "tools": { "allow": null, "deny": ["telescope_dumps", "telescope_gates"] } } ``` To expose only telescope tools (hiding substrate and dusk tools): ```json { "packages": { "allow": ["fluttersdk_telescope"], "deny": [] }, "tools": { "allow": null, "deny": [] } } ``` After editing `.artisan/mcp.json`, run `/mcp reconnect fluttersdk` in Claude Code to reload the filter. See the [artisan filter reference](https://fluttersdk.com/artisan/mcp/tool-reference#filter-configuration) for the full three-layer precedence rules (file, env vars, CLI flags). --- ## Related - [artisan MCP setup](https://fluttersdk.com/artisan/mcp/setup): full client matrix (Cursor, Claude Desktop, VS Code, Windsurf, JetBrains, Cline, OpenCode, Gemini CLI). - [Overview](overview.md): how the 9 tools surface through `TelescopeArtisanProvider` and route through the VM Service. - [Tool reference](tool-reference.md): per-tool input schema, output shape, and example invocations. --- # Telescope MCP Tool Reference # Telescope MCP Tool Reference Catalog of every MCP tool contributed by `fluttersdk_telescope` via `TelescopeArtisanProvider.mcpTools()`. All 9 tools use the `telescope_` prefix and dispatch through `ext.telescope.*` VM Service extensions registered inside the running Flutter app by `registerAllTelescopeExtensions()`. **Requires a running app.** Every telescope tool dispatches via the VM Service. Boot the app via the artisan fast-cli (`./bin/fsa start`) or the MCP equivalent (`artisan_start`) and confirm `artisan_status` shows a live `vmServiceUri` before invoking these tools. --- ## Table of Contents - [Common conventions](#common-conventions) - [telescope_tail](#telescope_tail) - [telescope_requests](#telescope_requests) - [telescope_exceptions](#telescope_exceptions) - [telescope_events](#telescope_events) - [telescope_gates](#telescope_gates) - [telescope_dumps](#telescope_dumps) - [telescope_queries](#telescope_queries) - [telescope_caches](#telescope_caches) - [telescope_clear](#telescope_clear) - [Related](#related) --- ## Common Conventions All buffer-reading tools (every tool except `telescope_clear`) share these conventions: - **Newest-first ordering.** The most recent record is always at index 0 of the returned array. - **`limit` parameter.** All buffer tools accept an optional `limit: integer` parameter that caps the number of records returned. When omitted, the entire ring buffer is returned (subject to the ring-buffer capacity, typically 200-500 records depending on buffer type). - **Empty array on empty buffer.** When the buffer holds no records (fresh start or after `telescope_clear`), the response is `{"records": []}`. This is not an error. - **JSON envelope.** Every tool returns a JSON object. The outer shape is always `{"records": [...]}`. Individual record field shapes are documented per tool below. - **Error shape.** On failure (app not running, VM Service unreachable), `McpServer` returns `CallToolResult` with `isError: true` and an actionable plain-text message. --- ## telescope_tail Return recent log records from the running Flutter app. Reads the Telescope log ring buffer populated by every `package:logging` `Logger` call in the app. Use this to inspect what the app logged without scraping `flutter run` stdout via `artisan_logs`. **VM Service extension:** `ext.telescope.console` ### Input Schema | Parameter | Type | Required | Description | |---|---|---|---| | `limit` | integer | no | Maximum number of records to return, newest first. Omit for the whole buffer (cap enforced by ring-buffer size, typically 200). | | `level` | string | no | Minimum log level to include. Common values: `FINE`, `INFO`, `WARNING`, `SEVERE`, `SHOUT`. Omit for all levels. | ### Output Shape ```json { "records": [ { "level": "WARNING", "levelValue": 900, "message": "Monitor check timed out after 30s", "loggerName": "MonitorController", "time": "2026-05-20T14:32:10.123Z", "error": "TimeoutException: ...", "stackTrace": "#0 MonitorController.check ..." } ] } ``` | Field | Type | Always present | Description | |---|---|---|---| | `level` | string | yes | Log level name (e.g. `INFO`, `WARNING`, `SEVERE`) | | `levelValue` | integer | yes | Numeric level value from `package:logging` | | `message` | string | yes | Log message string | | `loggerName` | string | yes | Name of the `Logger` instance that emitted the record | | `time` | string | yes | ISO 8601 UTC timestamp | | `error` | string | no | Error object stringified, when one was attached | | `stackTrace` | string | no | Stack trace stringified, when one was attached | ### Example Invocations ``` # Last 10 log lines at any level telescope_tail limit=10 # Last 20 WARNING-and-above entries telescope_tail limit=20 level=WARNING # All SEVERE entries in the buffer telescope_tail level=SEVERE ``` ### Related Tools - `telescope_exceptions`: uncaught exceptions (not expected `try/catch` flows) - `telescope_requests`: HTTP traffic - `telescope_clear`: wipe the buffer before a repro --- ## telescope_requests Return recent HTTP request records from the running Flutter app. Reads the Telescope HTTP ring buffer populated by the Telescope Dio interceptor or any `TelescopeHttpAdapter` the app installs (e.g. `MagicHttpFacadeAdapter` from the magic package). Use this to debug API issues without instrumenting the app or watching network panels in DevTools. **VM Service extension:** `ext.telescope.requests` ### Input Schema | Parameter | Type | Required | Description | |---|---|---|---| | `limit` | integer | no | Maximum number of HTTP records to return, newest first. Omit for the whole buffer. | ### Output Shape ```json { "records": [ { "url": "https://api.example.com/monitors", "method": "GET", "statusCode": 200, "durationMs": 142, "isError": false, "timestamp": "2026-05-20T14:32:11.456Z", "requestHeaders": { "Authorization": "Bearer eyJ..." }, "requestBody": null, "responseBody": "{\"data\": [...]}" } ] } ``` | Field | Type | Always present | Description | |---|---|---|---| | `url` | string | yes | Full request URL | | `method` | string | yes | HTTP method (`GET`, `POST`, etc.) | | `statusCode` | integer | yes | HTTP status code; 0 when the call failed before a response | | `durationMs` | integer | yes | Round-trip time in milliseconds | | `isError` | boolean | yes | True when the adapter classified the call as failed | | `timestamp` | string | yes | ISO 8601 UTC timestamp of when the request was recorded | | `requestHeaders` | object | no | Request headers as a `Map` | | `requestBody` | string | no | Request body as a string, when present and readable | | `responseBody` | string | no | Response body snippet, when present | | `attributedHeuristically` | boolean | no | True when the adapter used best-effort FIFO attribution for concurrent requests | ### Example Invocations ``` # Last 5 HTTP requests telescope_requests limit=5 # All requests in the buffer telescope_requests ``` ### Notes - Only HTTP calls that go through an installed Telescope adapter are recorded. Raw `dart:io HttpClient` calls are invisible to this tool. - Pair with `telescope_clear` before a user action to isolate exactly the traffic that action produces. --- ## telescope_exceptions Return recent uncaught exception records from the running Flutter app. Reads the Telescope exception ring buffer populated by the `FlutterError.onError` hook that `TelescopePlugin.install()` chains. Use this when a crash or unhandled `Exception` was thrown and you need the stack without scraping `flutter run` stdout. **VM Service extension:** `ext.telescope.exceptions` ### Input Schema | Parameter | Type | Required | Description | |---|---|---|---| | `limit` | integer | no | Maximum number of exception records to return, newest first. Omit for the whole buffer. | ### Output Shape ```json { "records": [ { "exceptionType": "StateError", "message": "Bad state: Stream has already been listened to", "time": "2026-05-20T14:33:00.789Z", "stackTrace": "#0 _StreamController._subscribe ...", "isolate": "main" } ] } ``` | Field | Type | Always present | Description | |---|---|---|---| | `exceptionType` | string | yes | Runtime type name of the exception object | | `message` | string | yes | Exception message string | | `time` | string | yes | ISO 8601 UTC timestamp | | `stackTrace` | string | no | Full stack trace, when available | | `isolate` | string | no | Isolate name where the exception was caught | ### Example Invocations ``` # Last 3 uncaught exceptions telescope_exceptions limit=3 # All exceptions in the buffer telescope_exceptions ``` ### Notes - Only **uncaught** exceptions routed through `FlutterError.onError` are captured. Expected `try/catch` flows do not surface here. - The watcher chain-preserves the previous `FlutterError.onError` handler (e.g. Sentry) so both coexist safely. - Pair with `telescope_tail` to see what the app logged in the moments before the crash. --- ## telescope_events Return recent in-app event records from the running Flutter app. Reads the Telescope events ring buffer populated by `MagicEventWatcher` whenever `Event.dispatch()` is called via the Magic `Event` facade. Use this to trace event-driven side effects (cache invalidation, broadcast echoes, model lifecycle transitions) without adding `print` statements. **VM Service extension:** `ext.telescope.events` ### Input Schema | Parameter | Type | Required | Description | |---|---|---|---| | `limit` | integer | no | Maximum number of event records to return, newest first. Omit for the whole buffer (cap enforced by ring-buffer size, typically 500). | ### Output Shape ```json { "records": [ { "eventType": "MonitorChecked", "payload": { "monitorId": "abc-123", "status": "up" }, "time": "2026-05-20T14:34:00.001Z", "listenerCount": 3 } ] } ``` | Field | Type | Always present | Description | |---|---|---|---| | `eventType` | string | yes | Runtime class name of the dispatched event | | `payload` | object | yes | JSON snapshot of the event payload | | `time` | string | yes | ISO 8601 UTC timestamp | | `listenerCount` | integer | no | Number of listeners notified at dispatch time, when available | ### Example Invocations ``` # Last 10 events telescope_events limit=10 # All events in the buffer telescope_events ``` ### Notes - Only events dispatched through the Magic `Event` facade are captured. Raw `ChangeNotifier.notifyListeners` calls are invisible to this tool. - For Gate authorization checks use `telescope_gates`; for HTTP traffic use `telescope_requests`. --- ## telescope_gates Return recent Gate authorization check records from the running Flutter app. Reads the Telescope gates ring buffer populated by `MagicGateWatcher` on every `Gate.allows` / `Gate.denies` call via the Magic `Gate` facade. Use this to debug authorization issues (why a button is hidden, why a route is blocked) without modifying policy classes. **VM Service extension:** `ext.telescope.gates` ### Input Schema | Parameter | Type | Required | Description | |---|---|---|---| | `limit` | integer | no | Maximum number of gate check records to return, newest first. Omit for the whole buffer (cap enforced by ring-buffer size, typically 500). | ### Output Shape ```json { "records": [ { "ability": "monitors.destroy", "result": false, "arguments": ["Monitor"], "time": "2026-05-20T14:35:00.010Z", "userId": "user-42" } ] } ``` | Field | Type | Always present | Description | |---|---|---|---| | `ability` | string | yes | Ability name passed to `Gate.allows` / `Gate.denies` | | `result` | boolean | yes | `true` when the gate allowed the action; `false` when denied | | `arguments` | array | yes | List of argument values (class name strings or serialized primitives) | | `time` | string | yes | ISO 8601 UTC timestamp | | `userId` | string | no | Authenticated user ID at check time, when available | ### Example Invocations ``` # Last 20 gate checks telescope_gates limit=20 # All gate checks in the buffer telescope_gates ``` ### Notes - Only checks routed through the Magic `Gate` facade are captured. Direct policy class calls are not recorded. - Pair with `telescope_clear` before walking a guarded flow to isolate just the relevant checks. --- ## telescope_dumps Return recent `debugPrint` output records from the running Flutter app. Reads the Telescope dumps ring buffer populated by `DumpWatcher`, which overrides `debugPrint` globally. Use this to read `print()` / `debugPrint()` output from within the running app without scraping `flutter run` stdout. **VM Service extension:** `ext.telescope.dumps` ### Input Schema | Parameter | Type | Required | Description | |---|---|---|---| | `limit` | integer | no | Maximum number of dump records to return, newest first. Omit for the whole buffer (cap enforced by ring-buffer size, typically 500). | ### Output Shape ```json { "records": [ { "message": "[MonitorController] reloading monitors...", "time": "2026-05-20T14:36:00.200Z", "wrapWidth": 80 } ] } ``` | Field | Type | Always present | Description | |---|---|---|---| | `message` | string | yes | Full message string passed to `debugPrint` | | `time` | string | yes | ISO 8601 UTC timestamp | | `wrapWidth` | integer | no | Wrap width passed to the original `debugPrint` call, when provided | ### Example Invocations ``` # Last 10 debugPrint calls telescope_dumps limit=10 # All dump records in the buffer telescope_dumps ``` ### Notes - Only output routed through the `debugPrint` global override is captured. `dart:io stdout.write` calls are invisible to this tool. - `DumpWatcher` gates on `kDebugMode`: release builds tree-shake it entirely. - For structured logs use `telescope_tail`; for crashes use `telescope_exceptions`. --- ## telescope_queries Return recent database query records from the running Flutter app. Reads the Telescope queries ring buffer populated by `MagicQueryWatcher`, which subscribes to magic's `QueryExecuted` event dispatched by the Magic QueryBuilder. Each record carries the SQL string, bindings, execution time, and connection name. Use this to inspect what queries the app dispatched without attaching a separate SQL profiler. **VM Service extension:** `ext.telescope.queries` ### Input Schema | Parameter | Type | Required | Description | |---|---|---|---| | `limit` | integer | no | Maximum number of query records to return, newest first. Omit for the whole buffer (cap enforced by ring-buffer size, typically 500). | ### Output Shape ```json { "records": [ { "sql": "SELECT * FROM monitors WHERE team_id = ? LIMIT 50", "bindings": ["team-abc"], "timeMs": 4, "connectionName": "default", "time": "2026-05-20T14:37:00.100Z" } ] } ``` | Field | Type | Always present | Description | |---|---|---|---| | `sql` | string | yes | SQL string the QueryBuilder dispatched to the underlying driver | | `bindings` | array | yes | Positional or named query bindings | | `timeMs` | integer | yes | Execution time in milliseconds reported by magic's QueryBuilder | | `connectionName` | string | yes | Connection name; `"default"` when the consumer did not name it | | `time` | string | yes | ISO 8601 UTC timestamp | ### Example Invocations ``` # Last 5 queries telescope_queries limit=5 # All queries in the buffer telescope_queries ``` ### Notes - Only queries that go through magic's QueryBuilder (dispatching `QueryExecuted` via `EventDispatcher`) are recorded. Raw `sqlite3` or direct Dio SQL calls bypass this tool. - For Magic Cache traffic use `telescope_caches`; for HTTP use `telescope_requests`; for log lines use `telescope_tail`. --- ## telescope_caches Return recent Magic Cache operation records from the running Flutter app. Reads the Telescope caches ring buffer populated by `MagicCacheWatcher`, which subscribes to magic's `CacheHit` / `CacheMiss` / `CachePut` / `CacheForget` / `CacheFlush` events. Each record carries timestamp, operation tag, cache key, and optional TTL. Use this to inspect cache traffic without instrumenting the consumer code. **VM Service extension:** `ext.telescope.caches` ### Input Schema | Parameter | Type | Required | Description | |---|---|---|---| | `limit` | integer | no | Maximum number of cache records to return, newest first. Omit for the whole buffer (cap enforced by ring-buffer size, typically 500). | ### Output Shape ```json { "records": [ { "operation": "miss", "key": "monitors.team-abc", "time": "2026-05-20T14:38:00.300Z", "ttlMs": 300000 } ] } ``` | Field | Type | Always present | Description | |---|---|---|---| | `operation` | string | yes | One of: `hit`, `miss`, `put`, `forget`, `flush` | | `key` | string | yes | Cache key string | | `time` | string | yes | ISO 8601 UTC timestamp | | `ttlMs` | integer | no | TTL in milliseconds, when the operation included a TTL | ### Example Invocations ``` # Last 20 cache operations telescope_caches limit=20 # All cache operations in the buffer telescope_caches ``` ### Notes - Only Magic `Cache` facade calls dispatch the watcher events. Raw driver-level cache calls bypass this tool. - For DB queries use `telescope_queries`; for HTTP use `telescope_requests`. --- ## telescope_clear Clear every Telescope ring buffer. Wipes all ring buffers in one call so the next `telescope_tail` / `telescope_requests` / `telescope_exceptions` / `telescope_events` / `telescope_gates` / `telescope_dumps` / `telescope_queries` / `telescope_caches` returns only records produced **after** this clear. Useful as a "set zero" before reproducing a bug or capturing the output of a specific user action. **VM Service extension:** `ext.telescope.clear` ### Input Schema No parameters. ### Output Shape ```json { "cleared": true } ``` | Field | Type | Always present | Description | |---|---|---|---| | `cleared` | boolean | yes | Always `true` on success | ### Example Invocations ``` # Clear all buffers before a repro telescope_clear # Common pattern: clear, perform the action, then read telescope_clear # ... trigger the user action ... telescope_requests limit=20 telescope_tail limit=50 ``` ### Notes - Idempotent: safe to call when buffers are already empty. - Does NOT affect the live `package:logging` stream; only the captured ring buffers. - The `ext.telescope.pause` and `ext.telescope.resume` extensions exist in the running app but are not exposed as MCP tools in the current release (V1.x backlog). --- ## Related - [Overview](overview.md): how the 9 tools surface through `TelescopeArtisanProvider` and route through the VM Service. - [Setup guide](setup.md): install telescope, register `TelescopeArtisanProvider`, and connect Claude Code. - [artisan MCP tool reference](https://fluttersdk.com/artisan/mcp/tool-reference): the 10 substrate tools (`artisan_start`, `artisan_stop`, `artisan_tinker`, etc.) and filter configuration. ---