diff --git a/CHANGELOG.md b/CHANGELOG.md index d899fdd..904ac08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,13 @@ This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0. ## [Unreleased] -_No unreleased changes yet._ +### Changed + +- mcp:install fallback now writes dart run fluttersdk_telescope mcp:serve when bin/fsa is absent (via wrapper's --invocation pass-through to fluttersdk_artisan's mcp:install). + +### Fixed + +- bin/fluttersdk_telescope.dart now forces collectMcpTools: true when dispatching mcp:serve, so dart run fluttersdk_telescope mcp:serve surfaces all 9 telescope_* MCP tools. Previously returned 0 plugin tools. --- diff --git a/bin/fluttersdk_telescope.dart b/bin/fluttersdk_telescope.dart index 238923d..1a09c8f 100644 --- a/bin/fluttersdk_telescope.dart +++ b/bin/fluttersdk_telescope.dart @@ -7,6 +7,7 @@ import 'package:fluttersdk_artisan/artisan.dart'; // `dart:ui` (PlatformDispatcher, debugPrint). `dart run fluttersdk_telescope` // runs on the plain Dart VM; touching `dart:ui` from this binary would break // CLI invocation outside `flutter run`. +import 'package:fluttersdk_telescope/src/cli_args.dart'; import 'package:fluttersdk_telescope/src/telescope_artisan_provider.dart'; /// `dart run fluttersdk_telescope ` ; telescope-flavoured artisan wrapper. @@ -19,13 +20,28 @@ import 'package:fluttersdk_telescope/src/telescope_artisan_provider.dart'; /// `telescope:queries`, `telescope:caches`, `telescope:clear`) plus the 9 /// `telescope_*` MCP tools surface in the same `list` output. /// +/// When forwarding `mcp:install`, [injectInvocationForMcpInstall] appends +/// `--invocation=fluttersdk_telescope` so the substrate writes the correct +/// `dart run fluttersdk_telescope mcp:serve` entry into `.mcp.json`. +/// +/// When forwarding `mcp:serve`, [collectMcpTools] is forced `true` so that +/// all 9 `telescope_*` MCP tools surface alongside the substrate `artisan_*` tools. +/// /// Run from any consumer directory that has `fluttersdk_telescope` in its /// pubspec as a dependency. Future main(List args) async { + final injected = injectInvocationForMcpInstall(args, 'fluttersdk_telescope'); + final firstNonFlag = injected.firstWhere( + (a) => !a.startsWith('-'), + orElse: () => '', + ); + final isMcpServe = firstNonFlag == 'mcp:serve'; + exit( await runArtisan( - args, + injected, baseProviders: [TelescopeArtisanProvider()], + collectMcpTools: isMcpServe, delegateToConsumer: false, ), ); diff --git a/lib/src/cli_args.dart b/lib/src/cli_args.dart new file mode 100644 index 0000000..62e4766 --- /dev/null +++ b/lib/src/cli_args.dart @@ -0,0 +1,37 @@ +/// Args massagers for the telescope CLI wrapper. +/// +/// Pure functions (no IO, no Flutter dependencies) so the bin entrypoint +/// stays Flutter-free and the logic is unit-testable. +library; + +/// Returns [args] with `--invocation=` appended when: +/// - the first non-flag arg is exactly `mcp:install`, AND +/// - [args] does NOT already contain a user-supplied `--invocation` (either +/// `--invocation=` or the whitespace form `--invocation `). +/// +/// Otherwise returns [args] unchanged. +/// +/// Reason: surfaces plugin invocation context to the substrate's `.mcp.json` +/// writer (`fluttersdk_artisan` McpInstallCommand three-branch payload). +/// When fastcli is absent, substrate writes `dart run mcp:serve` +/// instead of the legacy `:dispatcher` fallback. +List injectInvocationForMcpInstall( + List args, + String invocation, +) { + // 1. Find the first non-flag arg. + final firstNonFlag = args.firstWhere( + (a) => !a.startsWith('-'), + orElse: () => '', + ); + if (firstNonFlag != 'mcp:install') return args; + + // 2. Honor user-supplied --invocation (either equal-form or whitespace form). + final hasOverride = args.any( + (a) => a.startsWith('--invocation=') || a == '--invocation', + ); + if (hasOverride) return args; + + // 3. Inject the canonical invocation flag. + return [...args, '--invocation=$invocation']; +} diff --git a/test/src/cli_args_test.dart b/test/src/cli_args_test.dart new file mode 100644 index 0000000..5280011 --- /dev/null +++ b/test/src/cli_args_test.dart @@ -0,0 +1,48 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:fluttersdk_telescope/src/cli_args.dart'; + +void main() { + group('injectInvocationForMcpInstall()', () { + test('returns args unchanged when first non-flag arg is not mcp:install', + () { + final result = injectInvocationForMcpInstall( + ['list'], + 'fluttersdk_telescope', + ); + expect(result, equals(['list'])); + }); + + test('appends --invocation flag when first non-flag arg is mcp:install', + () { + final result = injectInvocationForMcpInstall( + ['mcp:install'], + 'fluttersdk_telescope', + ); + expect( + result, + equals(['mcp:install', '--invocation=fluttersdk_telescope']), + ); + }); + + test( + 'preserves user-supplied --invocation=foo equal-form without injecting', + () { + final result = injectInvocationForMcpInstall( + ['mcp:install', '--invocation=foo'], + 'fluttersdk_telescope', + ); + expect(result, equals(['mcp:install', '--invocation=foo'])); + }); + + test( + 'preserves user-supplied --invocation whitespace-form without injecting', + () { + final result = injectInvocationForMcpInstall( + ['mcp:install', '--invocation', 'foo'], + 'fluttersdk_telescope', + ); + expect(result, equals(['mcp:install', '--invocation', 'foo'])); + }); + }); +}