flutter_webmcp
Expose Flutter Web application actions as typed WebMCP tools without writing JavaScript interop code.
WebMCP is experimental. This package keeps the changing browser bindings behind a small Dart API and adds Flutter lifecycle management on top.
Compatibility
| Runtime | Behavior |
|---|---|
| Flutter Web (JavaScript) | Supported when the browser exposes WebMCP |
| Flutter Web (WebAssembly) | Supported when the browser exposes WebMCP |
| Android, iOS, desktop, and Dart VM | Safe no-op detection; registration is unsupported |
The package follows the WebMCP Draft Community Group Report. Because that API is not yet a web standard, minor package releases may add compatibility shims for browser changes.
Install
dependencies:
flutter_webmcp: ^0.2.0
For a local checkout:
dependencies:
flutter_webmcp:
path: ../flutter_webmcp
Flutter usage
Create tools once and let WebMcpToolScope register them while the feature is
mounted:
class TasksPageState extends State<TasksPage> {
late final WebMcpTool addTaskTool;
@override
void initState() {
super.initState();
addTaskTool = WebMcpTypedTool<AddTaskInput>(
name: 'add_task',
title: 'Add task',
description: 'Adds a task to the list currently open in the app.',
inputSchema: const {
'type': 'object',
'properties': {
'title': {'type': 'string'},
},
'required': ['title'],
},
decodeInput: AddTaskInput.fromJson,
execute: (input, context) async {
final task = await taskRepository.add(input.title);
return WebMcpResult.structured(
{'id': task.id, 'title': task.title},
text: 'Task "${task.title}" was added.',
);
},
);
}
@override
Widget build(BuildContext context) {
return WebMcpToolScope(
tools: [addTaskTool],
child: const TasksView(),
);
}
}
Keep tool instances stable, for example in initState, a ViewModel, or a
dependency-injection container. Replacing a tool instance tells the scope to
unregister the old configuration and register the new one.
Typed input
final class AddTaskInput {
const AddTaskInput(this.title);
factory AddTaskInput.fromJson(Map<String, Object?> json) {
final title = json['title'];
if (title is! String || title.trim().isEmpty) {
throw const WebMcpToolException(
code: 'invalid_title',
message: 'Task title must be a non-empty string.',
);
}
return AddTaskInput(title.trim());
}
final String title;
}
The original map-based WebMcpTool remains available for simple integrations.
Results and errors
Use helpers instead of building protocol maps manually:
return WebMcpResult.text('Done');
return WebMcpResult.structured({'taskId': task.id});
throw const WebMcpToolException(
code: 'task_not_found',
message: 'The selected task no longer exists.',
);
WebMcpToolException is converted into a structured isError result that an
agent can understand. Unexpected Dart errors are logged and returned as a safe
internal_error without exposing local details.
Feature detection
final support = WebMcp.support;
if (!support.isSupported) {
debugPrint(support.message);
}
The result distinguishes unsupported platforms, insecure pages, and unavailable browser APIs. Permission-policy errors are reported when registration is attempted because current browsers do not expose a reliable read-only policy signal for WebMCP.
Logging
WebMcp.logger = (event) {
debugPrint(
'${event.toolName}: ${event.status} in ${event.duration.inMilliseconds}ms',
);
};
Logs include the tool name, decoded JSON input, duration, result, and local error information. Logger failures never break tool execution.
Inputs and results can contain sensitive data. Redact them before forwarding events to production telemetry.
Security
WebMCP tools run with the same authority as your application code. Validate
input in decodeInput, enforce authorization inside the handler, and require
normal user confirmation for destructive or sensitive operations. Annotations
are agent hints, not security boundaries.
Only use exposedTo with origins you trust. See SECURITY.md
for reporting and deployment guidance.
Core Dart API
Code that does not need Flutter widgets can import the core library directly:
import 'package:flutter_webmcp/webmcp.dart';
Then call WebMcp.registerTool() and keep the returned
WebMcpRegistration for manual cleanup.
Browser setup
WebMCP requires document.modelContext. It is available in ChatGPT's in-app
browser. For local Chrome testing, enable
chrome://flags/#enable-webmcp-testing and restart Chrome.
- Flutter Web compiled to JavaScript or WebAssembly is supported when the browser implements WebMCP.
- Android, iOS, macOS, Windows, Linux, and Dart VM report unsupported.
See the complete application in example/lib/main.dart.
Libraries
- flutter_webmcp
- WebMCP bindings and lifecycle helpers for Flutter Web applications.
- webmcp
- Framework-independent WebMCP API for Dart web applications.