Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---

Expand Down
18 changes: 17 additions & 1 deletion bin/fluttersdk_telescope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 <cmd>` ; telescope-flavoured artisan wrapper.
Expand All @@ -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<void> main(List<String> 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,
),
);
Expand Down
37 changes: 37 additions & 0 deletions lib/src/cli_args.dart
Original file line number Diff line number Diff line change
@@ -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=<invocation>` appended when:
/// - the first non-flag arg is exactly `mcp:install`, AND
/// - [args] does NOT already contain a user-supplied `--invocation` (either
/// `--invocation=<value>` or the whitespace form `--invocation <value>`).
///
/// 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 <invocation> mcp:serve`
/// instead of the legacy `:dispatcher` fallback.
List<String> injectInvocationForMcpInstall(
List<String> 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'];
}
48 changes: 48 additions & 0 deletions test/src/cli_args_test.dart
Original file line number Diff line number Diff line change
@@ -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']));
});
});
}