Skip to content

Commit e391a27

Browse files
authored
Add web platform support and pana CI validation (#49)
- Fix dart:io dependency blocking web compilation - Add platform-specific logging implementations (logging_io.dart, logging_web.dart) - Use conditional imports for cross-platform compatibility - Add comprehensive logging tests (5 new test cases) - Integrate pana analysis in CI with 160/160 score requirement - Use JSON output for reliable pana score parsing - Format example files for consistency - Version bump to 1.0.2 Fixes web support for all platforms (iOS, Android, Web, Windows, macOS, Linux) Achieves perfect 160/160 pana score with WASM compatibility
1 parent df51ef4 commit e391a27

File tree

13 files changed

+195
-40
lines changed

13 files changed

+195
-40
lines changed

.github/workflows/test.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,24 @@ jobs:
3030
- name: Validate package for publishing
3131
run: dart pub publish --dry-run
3232

33+
- name: Run pana (package analysis)
34+
run: |
35+
dart pub global activate pana
36+
dart pub global run pana --json --no-warning --exit-code-threshold 0 > pana_output.json
37+
# Filter out log lines and extract scores from the summary
38+
RESULT=$(cat pana_output.json | grep -v '^{"logName"')
39+
SCORE=$(echo "$RESULT" | grep -o '"grantedPoints": *[0-9]*' | tail -1 | grep -o '[0-9]*$')
40+
MAX_POINTS=$(echo "$RESULT" | grep -o '"maxPoints": *[0-9]*' | tail -1 | grep -o '[0-9]*$')
41+
echo "Pana score: $SCORE/$MAX_POINTS"
42+
if [ "$SCORE" != "160" ] || [ "$MAX_POINTS" != "160" ]; then
43+
echo "Error: Package score is $SCORE/$MAX_POINTS. Required: 160/160"
44+
echo ""
45+
echo "Detailed report:"
46+
dart pub global run pana --no-warning
47+
exit 1
48+
fi
49+
echo "✓ Package achieves perfect 160/160 score"
50+
3351
- name: Upload coverage to Codecov
3452
uses: codecov/codecov-action@v5
3553
with:

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 1.0.2
2+
3+
- Fix pana analysis issues
4+
- Fix Web support for StreamableHTTP client
5+
16
## 1.0.1
27

38
- Fix Documentation links in README.md

example/authentication/github_pat_example.dart

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ Future<void> main(List<String> args) async {
7272
print(' dart run example/authentication/github_pat_example.dart');
7373
print('');
7474
print('Method 2 - Command Line Argument:');
75-
print(' dart run example/authentication/github_pat_example.dart your_token_here');
75+
print(
76+
' dart run example/authentication/github_pat_example.dart your_token_here');
7677
print('');
7778
print('To create a GitHub PAT:');
7879
print(' 1. Visit: https://github.com/settings/tokens');
@@ -191,13 +192,16 @@ Future<void> main(List<String> args) async {
191192
print('');
192193
print('Troubleshooting:');
193194
print(' 1. Verify your token is correct');
194-
print(' 2. Check token has required scopes: repo, read:packages, read:org');
195+
print(
196+
' 2. Check token has required scopes: repo, read:packages, read:org');
195197
print(' 3. Ensure token has not expired');
196198
print(' 4. Create a new token at: https://github.com/settings/tokens');
197-
} else if (e.toString().contains('404') || e.toString().contains('Not Found')) {
199+
} else if (e.toString().contains('404') ||
200+
e.toString().contains('Not Found')) {
198201
print('The GitHub MCP server endpoint may not be available.');
199202
print('Verify the URL: https://api.githubcopilot.com/mcp/');
200-
} else if (e.toString().contains('network') || e.toString().contains('connection')) {
203+
} else if (e.toString().contains('network') ||
204+
e.toString().contains('connection')) {
201205
print('Network connection issue. Check your internet connection.');
202206
}
203207

example/authentication/oauth_client_example.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class OAuthConfig {
2828
final Uri tokenEndpoint;
2929
final List<String> scopes;
3030
final Uri redirectUri;
31+
3132
/// MCP server URI for resource parameter (audience validation)
3233
final String serverUri;
3334

@@ -125,7 +126,8 @@ class OAuth2Provider implements OAuthClientProvider {
125126
'scope': config.scopes.join(' '),
126127
'state': state,
127128
'code_challenge': codeChallenge, // PKCE parameter
128-
'code_challenge_method': 'plain', // Using plain for demo (use S256 in production)
129+
'code_challenge_method':
130+
'plain', // Using plain for demo (use S256 in production)
129131
},
130132
);
131133

@@ -198,7 +200,8 @@ class OAuth2Provider implements OAuthClientProvider {
198200
// Retrieve stored code verifier
199201
final codeVerifier = await storage.getCodeVerifier();
200202
if (codeVerifier == null) {
201-
throw Exception('Code verifier not found. Complete authorization flow first.');
203+
throw Exception(
204+
'Code verifier not found. Complete authorization flow first.');
202205
}
203206

204207
final body = {

example/authentication/oauth_server_example.dart

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,14 @@ class OAuthServerConfig {
114114
final Uri tokenEndpoint;
115115
final Uri userInfoEndpoint;
116116
final List<String> requiredScopes;
117+
117118
/// Canonical server URI for resource parameter and audience validation
118119
/// Must be HTTPS and match the actual server URI
119120
final String serverUri;
121+
120122
/// Authorization server metadata endpoint
121123
final Uri? authServerMetadataEndpoint;
124+
122125
/// Allowed redirect URIs for validation
123126
final List<String> allowedRedirectUris;
124127

@@ -168,8 +171,8 @@ class OAuthServerConfig {
168171
Uri.parse('https://www.googleapis.com/oauth2/v2/userinfo'),
169172
requiredScopes: requiredScopes,
170173
serverUri: serverUri,
171-
authServerMetadataEndpoint:
172-
Uri.parse('https://accounts.google.com/.well-known/openid-configuration'),
174+
authServerMetadataEndpoint: Uri.parse(
175+
'https://accounts.google.com/.well-known/openid-configuration'),
173176
allowedRedirectUris: allowedRedirectUris,
174177
);
175178
}
@@ -193,11 +196,13 @@ class OAuthTokenInfo {
193196

194197
bool get isExpired => DateTime.now().isAfter(expiresAt);
195198

196-
String get userId => userInfo['id']?.toString() ?? userInfo['sub']?.toString() ?? 'unknown';
197-
String get username => userInfo['login']?.toString() ??
198-
userInfo['name']?.toString() ??
199-
userInfo['email']?.toString() ??
200-
'unknown';
199+
String get userId =>
200+
userInfo['id']?.toString() ?? userInfo['sub']?.toString() ?? 'unknown';
201+
String get username =>
202+
userInfo['login']?.toString() ??
203+
userInfo['name']?.toString() ??
204+
userInfo['email']?.toString() ??
205+
'unknown';
201206
}
202207

203208
/// OAuth validator for MCP servers
@@ -362,7 +367,8 @@ class OAuthServerValidator {
362367
'redirect_uri': redirectUri,
363368
'grant_type': 'authorization_code',
364369
'code_verifier': codeVerifier, // PKCE requirement
365-
'resource': config.serverUri, // MCP spec requirement for audience validation
370+
'resource':
371+
config.serverUri, // MCP spec requirement for audience validation
366372
};
367373

368374
final response = await http.post(
@@ -574,7 +580,8 @@ class OAuthServerTransport implements Transport {
574580
_sessionTokens[sessionId] = tokenInfo;
575581
}
576582

577-
print('✓ Authenticated request from ${tokenInfo.username} (${tokenInfo.userId})');
583+
print(
584+
'✓ Authenticated request from ${tokenInfo.username} (${tokenInfo.userId})');
578585

579586
// Forward to inner transport
580587
await _innerTransport.handleRequest(req, parsedBody);
@@ -839,7 +846,8 @@ Future<void> main(List<String> args) async {
839846
print(' -out server_cert.pem -days 365 -nodes \\');
840847
print(' -subj "/CN=localhost"');
841848
print('');
842-
print('For production, use a reverse proxy with proper TLS certificates.');
849+
print(
850+
'For production, use a reverse proxy with proper TLS certificates.');
843851
print('Falling back to HTTP mode...');
844852
print('');
845853
httpServer = await HttpServer.bind(host, port);
@@ -857,12 +865,14 @@ Future<void> main(List<String> args) async {
857865
print(' ✅ Token audience validation');
858866
print(' ✅ Redirect URI validation');
859867
print(' ✅ OAuth metadata discovery');
860-
print(' ${useHttps ? "✅" : "⚠️ "} HTTPS ${useHttps ? "enabled" : "not enabled (use --https)"}');
868+
print(
869+
' ${useHttps ? "✅" : "⚠️ "} HTTPS ${useHttps ? "enabled" : "not enabled (use --https)"}');
861870
print('');
862871
print('Usage:');
863872
print(' 1. Obtain OAuth access token from provider');
864873
print(' 2. Make requests with: Authorization: Bearer <token>');
865-
print(' 3. Access metadata: GET $serverUri/.well-known/oauth-authorization-server');
874+
print(
875+
' 3. Access metadata: GET $serverUri/.well-known/oauth-authorization-server');
866876
print('');
867877
print('Server running. Press Ctrl+C to stop.\n');
868878

@@ -971,8 +981,8 @@ Future<void> main(List<String> args) async {
971981
} catch (e) {
972982
print('Error handling request: $e');
973983
if (!request.response.headers.contentType
974-
.toString()
975-
.contains('event-stream')) {
984+
.toString()
985+
.contains('event-stream')) {
976986
request.response
977987
..statusCode = HttpStatus.internalServerError
978988
..write('Internal server error');

example/completions_capability_demo.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,12 @@ void main() async {
113113
def: CompletableDef(
114114
complete: (value) async {
115115
// Provide style completions
116-
return ['concise', 'detailed', 'security-focused', 'performance-focused'];
116+
return [
117+
'concise',
118+
'detailed',
119+
'security-focused',
120+
'performance-focused'
121+
];
117122
},
118123
),
119124
),
@@ -128,7 +133,8 @@ void main() async {
128133
PromptMessage(
129134
role: PromptMessageRole.user,
130135
content: TextContent(
131-
text: 'Please provide a $style code review for $language code:\n\n'
136+
text:
137+
'Please provide a $style code review for $language code:\n\n'
132138
'(This is where the code would be inserted)',
133139
),
134140
),

example/elicitation_http_server.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,9 @@ Newsletter: ${newsletter ? 'Yes' : 'No'}''',
236236
);
237237

238238
final description = descriptionResult.accepted &&
239-
(descriptionResult.content?['description'] as String? ?? '').toLowerCase() != 'skip'
239+
(descriptionResult.content?['description'] as String? ?? '')
240+
.toLowerCase() !=
241+
'skip'
240242
? (descriptionResult.content?['description'] as String? ?? '')
241243
: '';
242244

@@ -483,7 +485,9 @@ Duration: $duration minutes''',
483485
);
484486

485487
final phone = phoneResult.accepted &&
486-
(phoneResult.content?['phone'] as String? ?? '').toLowerCase() != 'skip'
488+
(phoneResult.content?['phone'] as String? ?? '')
489+
.toLowerCase() !=
490+
'skip'
487491
? (phoneResult.content?['phone'] as String? ?? '')
488492
: '';
489493

lib/src/shared/logging.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import 'dart:io';
1+
import 'logging_io.dart' if (dart.library.js_interop) 'logging_web.dart';
22

33
enum LogLevel { debug, info, warn, error }
44

@@ -23,7 +23,7 @@ final class Logger {
2323
LogLevel level,
2424
String message,
2525
) {
26-
stderr.writeln("[${level.name.toUpperCase()}][$loggerName] $message");
26+
writeLog("[${level.name.toUpperCase()}][$loggerName] $message");
2727
}
2828

2929
void log(LogLevel level, String message) {

lib/src/shared/logging_io.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import 'dart:io';
2+
3+
void writeLog(String message) {
4+
stderr.writeln(message);
5+
}

lib/src/shared/logging_web.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// ignore_for_file: avoid_print
2+
3+
void writeLog(String message) {
4+
// Use print for web - it goes to console
5+
print(message);
6+
}

0 commit comments

Comments
 (0)