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.
Try the live Flutter Web demo.
| 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 is tested against the WebMCP Draft Community Group Report dated
26 August 2026. It implements the current document.modelContext.registerTool
shape, execution cancellation through AbortSignal, signal-based
unregistration, tool annotations, and exposedTo origins. Because WebMCP is
not yet a web standard, minor package releases may add compatibility shims for
browser changes.
Some experimental Chromium builds return undefined from registerTool
instead of the draft's Promise<void>. Both forms are supported.
dependencies:
flutter_webmcp: ^0.2.1For a local checkout:
dependencies:
flutter_webmcp:
path: ../flutter_webmcpCreate 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.
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.
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.
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.
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.
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.
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.
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.
Issues and pull requests are welcome. Read CONTRIBUTING.md
and follow the CODE_OF_CONDUCT.md. Report security
issues privately as described in SECURITY.md.
flutter_webmcp is available under the MIT License.