AgentStack
MCP verified MIT Self-run

WebMcpBundle

mcp-yoanbernabeu-webmcpbundle · by yoanbernabeu

Symfony bundle that exposes controller routes as Web MCP tools using PHP attributes

No reviews yet
0 installs
7 views
0.0% view→install

Install

$ agentstack add mcp-yoanbernabeu-webmcpbundle

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of WebMcpBundle? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

WebMcpBundle

[](https://github.com/yoanbernabeu/WebMcpBundle/actions/workflows/testing.yml) [](https://github.com/yoanbernabeu/WebMcpBundle/actions/workflows/code_analysis.yml) [](https://www.php.net/) [](https://symfony.com/) [](LICENSE)

> Experimental — Web MCP is a draft specification currently being developed by the W3C Web Machine Learning Community Group. The protocol, the browser APIs, and this bundle's API may change at any time. See the Chrome for Developers blog post for more context.

A Symfony bundle that exposes your controller routes as Web MCP tools using simple PHP attributes. No JavaScript to write.

#[Route('/api/restaurants', name: 'api_restaurants', methods: ['GET'])]
#[AsWebMcpTool(
    name: 'search_restaurants',
    description: 'Search restaurants by cuisine type',
    inputs: [
        new WebMcpInput('cuisine', type: 'string', description: 'Cuisine type'),
    ]
)]
public function search(): JsonResponse { /* ... */ }
{{ webmcp_tools() }}

That's it. The browser AI can now call your API.

What is Web MCP?

Web MCP (Web Model Context Protocol) is an emerging standard, currently in draft at the W3C (Web Machine Learning Community Group), that provides a browser-level API allowing web pages to expose structured tools to AI.

It builds on top of the Model Context Protocol (MCP) created by Anthropic, but targets the browser: instead of server-side integrations, the page itself registers tools via navigator.modelContext.registerTool().

AI browser extensions (like Claude, ChatGPT, etc.) can then:

  • Discover available tools, their names, descriptions, and input schemas
  • Call them with structured parameters
  • Read the JSON response and use it in the conversation

This turns any web page into an AI-capable endpoint. Instead of the user copy-pasting data between your app and an AI extension, the extension talks directly to your backend.

Example use case: A user browsing your app asks the Claude browser extension "What Italian restaurants are nearby?". The extension sees a search_restaurants tool is available on the page, calls it with {"cuisine": "italian"}, and returns the results in natural language.

You can test and inspect your Web MCP tools with the Model Context Tool Inspector Chrome extension.

Learn more:

Installation

composer require yoanbernabeu/webmcp-bundle
php bin/console assets:install

The bundle registers itself automatically via Symfony Flex. The assets:install command copies the bundle's JavaScript runtime to public/bundles/webmcp/.

Usage

Annotate your controllers

Each controller method you want to expose as an MCP tool gets two attributes: #[Route] (with an explicit name:, required) and #[AsWebMcpTool].

Simple GET endpoint
use Symfony\Component\Routing\Attribute\Route;
use YoanBernabeu\WebMcpBundle\Attribute\AsWebMcpTool;

#[Route('/api/menu', name: 'api_menu', methods: ['GET'])]
#[AsWebMcpTool(
    name: 'get_menu',
    description: 'Get the full restaurant menu',
)]
public function menu(): JsonResponse
{
    return $this->json($this->menuRepository->findAll());
}
GET with query parameters
use YoanBernabeu\WebMcpBundle\Attribute\WebMcpInput;

#[Route('/api/restaurants', name: 'api_restaurants', methods: ['GET'])]
#[AsWebMcpTool(
    name: 'search_restaurants',
    description: 'Search restaurants, optionally filter by cuisine type',
    inputs: [
        new WebMcpInput('cuisine', type: 'string', description: 'Cuisine type filter'),
        new WebMcpInput('limit', type: 'number', description: 'Max results'),
    ]
)]
public function search(Request $request): JsonResponse
{
    $cuisine = $request->query->get('cuisine');
    // ...
}

Inputs without mapTo are sent as query string parameters for GET requests.

GET with path parameter
#[Route('/api/restaurants/{id}/menu', name: 'api_restaurant_menu', methods: ['GET'])]
#[AsWebMcpTool(
    name: 'get_restaurant_menu',
    description: 'Get menu for a specific restaurant',
    inputs: [
        new WebMcpInput('restaurant_id', type: 'number', description: 'Restaurant ID', required: true, mapTo: 'id'),
    ]
)]
public function restaurantMenu(int $id): JsonResponse
{
    return $this->json($this->menuRepository->findByRestaurant($id));
}

mapTo: 'id' maps the input restaurant_id to the route parameter {id}.

POST with JSON body
#[Route('/api/orders', name: 'api_create_order', methods: ['POST'])]
#[AsWebMcpTool(
    name: 'place_order',
    description: 'Place a new order',
    inputs: [
        new WebMcpInput('restaurant_id', type: 'number', description: 'Restaurant ID', required: true),
        new WebMcpInput('items', type: 'array', description: 'List of item IDs', required: true),
        new WebMcpInput('notes', type: 'string', description: 'Special instructions'),
    ]
)]
public function placeOrder(Request $request): JsonResponse
{
    $data = json_decode($request->getContent(), true);
    // ...
}

For non-GET methods, inputs (except path params) are sent as a JSON body with Content-Type: application/json.

Render the JavaScript

Add the Twig function to your template, typically before ``:

{# templates/base.html.twig #}

...

    {% block body %}{% endblock %}

    {{ webmcp_tools() }}

This outputs a ` tag that registers all tools via navigator.modelContext`. If the browser does not support Web MCP, the script does nothing.

Attribute reference

#[AsWebMcpTool]

Placed on a controller method. The method must also have a #[Route] with an explicit name:.

| Parameter | Type | Required | Description | |---------------|-----------------|----------|------------------------------------------------------| | name | string | Yes | Unique tool name (duplicates throw at compile time) | | description | string | Yes | Tool description visible to the AI | | inputs | WebMcpInput[] | No | Input parameters |

WebMcpInput

Value object describing a tool input parameter.

| Parameter | Type | Required | Default | Description | |---------------|-----------|----------|------------|-----------------------------------------------------------------| | name | string | Yes | | Parameter name | | type | string | No | 'string' | JSON Schema type (string, number, boolean, array) | | description | string | No | '' | Parameter description visible to the AI | | required | bool | No | false | Whether the parameter is required | | mapTo | ?string | No | null | Route parameter name if different from input name (mapTo: 'id' for {id}) |

Security

The bundle does not handle security. Your existing Symfony security applies as-is:

  • Firewalls — the generated fetch() calls carry the browser's session cookies
  • #[IsGranted] — works normally, the AI will receive a 403 error
  • Voters — same, the controller is called through a standard HTTP request

Rules

  • Each #[Route] paired with #[AsWebMcpTool] must have an explicit name: (otherwise: LogicException at compile time)
  • Tool names must be unique across the application (otherwise: LogicException indicating both locations)
  • Controllers should return JSON (the generated JS calls response.json())

Requirements

  • PHP >= 8.2
  • Symfony 6.4 / 7.x / 8.x
  • Twig 3.x / 4.x
  • A browser supporting the Web MCP API (navigator.modelContext)

Contributing

composer install

# Full QA (PHPStan + CS-Fixer + PHPUnit)
composer test:all

# PHPUnit only
composer test:fast

# Fix code style
composer test:cs-fix

License

[MIT](LICENSE)

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.