跳至主要内容

WebMCP 教程:用 Chrome 新标准构建可供智能体使用的网站

一份实践指南,讲解如何用 HTML、JavaScript、安全模式与测试流程实现 Chrome 正在推出的 WebMCP 智能体接口标准。
· 14分钟 阅读

使用 AI 探索

现在,AI 智能体浏览网页就像游客看外文菜单:眯着眼认像素,猜按钮作用,还得祈祷页面不要在点击中途变换布局。Google 和 Microsoft 认为这个问题值得被好好解决。

2026 年 2 月,Chrome 146 率先发布了 WebMCP 的早期预览版——这是一个拟议中的网页标准,允许网站明确告知智能体自己能做什么以及如何去做。 

不再需要屏幕抓取。 

不再需要对 DOM 进行逆向工程。

在本教程中,我将带您了解 WebMCP 究竟是什么、如何搭建环境,并使用基于 HTML 和基于 JavaScript 的两种方式构建可运行的集成。 

我还会介绍处理智能体调用的安全要点,以及如何使用 Chrome 的调试工具测试全部流程。

什么是 WebMCP?

WebMCP(Web Model Context Protocol,网页模型上下文协议)是一项新的浏览器标准,让网站可以将自身能力声明为结构化的工具,供 AI 智能体直接调用。与其强迫 AI “阅读”并点击可视化界面,WebMCP 将网页本身转换为一个干净的、基于客户端的 API。

想象一下,如今一个智能体尝试在旅游网站订票时会发生什么。它必须弄清哪个字段输入出发城市、猜日期格式是 MM/DD 还是 YYYY-MM-DD、找到搜索按钮,并祈祷页面不会重新加载成它从未见过的布局。如果网站能发布一个名为 searchFlights 的工具,并为出发地、目的地、日期和乘客数提供类型化参数,那么这些猜测都会消失。

WebMCP 让这一切成为可能。站点通过名为 navigator.modelContext 的浏览器 API 注册工具,每个工具包含名称、自然语言描述和其输入输出的 JSON 架构。智能体发现这些工具,根据架构发送参数,并接收结构化数据作为返回。

Google 与 Microsoft 的工程师在 W3C Web Machine Learning 社区组下共同制定了该规范。其架构完全在客户端侧运行,这与典型 MCP 部署不同——后者通常由独立后端服务器执行工具。标准 MCP 通过 JSON-RPC 将智能体连接到后端服务器,而 WebMCP 则将一切保留在浏览器标签页内。 

工具在页面的 JavaScript 中执行;它们共享用户已有的会话,由浏览器调控智能体可执行的操作。如果您此前用过 MCP 服务器(例如我们 MCP 模型上下文协议教程 中的示例),工具注册的理念会很眼熟,但部署模型截然不同。

WebMCP 的关键特性与能力

可用两种 API,且用途不同:

  • 通过 HTML 注解的 声明式 API。您在 <form> 元素上添加 toolname 与 tooldescription 属性,并可在各字段上添加可选的 toolparamdescription。浏览器会基于现有表单结构生成 JSON 架构。已有完善表单的网站仅需极少改动即可对智能体开放。
  • 提供完整编程控制的 命令式 API。您调用 navigator.modelContext.registerTool(),定义工具名称、描述、输入架构与 execute 回调。对于普通表单难以覆盖的动态场景,如多步流程、API 调用、计算结果与自定义校验逻辑,应选择此路径。

两种 API 都支持 动态注册,即工具可随页面状态变化而出现或消失。在用户登录前,您也许只暴露 login 工具;认证之后,再注册与账户相关的工具。

  • JSON 架构为每个工具指定类型、枚举、必填字段和参数描述。这样的结构可防止智能体臆造参数值或将数据放入错误字段。
  • CSS 伪类在智能体交互时提供可视反馈。Chrome 会将 :tool-form-active 应用于当前由智能体填写的任何表单,并将 :tool-submit-active 应用于该表单的提交按钮。观看屏幕的人可以清楚看到智能体正在操作哪一个表单。

目前尚无内置的发现机制。智能体必须先访问您的页面,才能了解您提供的工具。Chrome 团队讨论过通过 .well-known/webmcp 清单文件实现访问前发现的想法,但尚未有类似规范。

由于 WebMCP 仍处于早期预览,您应预期会有一些粗糙之处与持续变化,尤其是规范已经在不同版本间演进,包括在 2026 年 3 月移除了 provideContext 和 clearContext 方法。 

工具的可发现性仅限已访问过的页面;规范中也承认并讨论了安全问题,如通过工具链的提示注入与数据外泄,但尚未完全解决。在开始之前,您可以查看 Chrome 文档中的变更日志,因为在本教程撰写后,API 表面可能已有变化。

WebMCP 前置条件与环境配置

环境配置几分钟即可完成,但装错 Chrome 版本会让您浪费更多时间。

需要 Chrome Canary 版本 146.0.7672.0 或更高。Chrome 的稳定版、Beta 与 Dev 通道不包含 WebMCP 标志。在 Chrome 论坛中已有多位开发者提到,安装了错误版本而困惑为何找不到该标志。请务必从 Canary 页面下载。

Chrome Canary 下载页面。

Chrome Canary 下载页面

除浏览器外,您需要具备 HTML 表单与 JavaScript 事件处理的基础知识。如需复习,DataCamp 的 JavaScript 入门课程涵盖了基础内容。 

虽然不是必需,但建议安装Model Context Tool Inspector 扩展。它会列出当前页面注册的所有工具,允许您手动输入参数执行工具,并内置 Gemini API,便于用自然语言测试工具调用。

启用 WebMCP 实验性标志

安装好 Chrome Canary 后:

  1. 在地址栏访问 chrome://flags/#enable-webmcp-testing。
  2. 将 "WebMCP for testing" 标志设置为启用。
  3. 点击页面底部的重新启动。

chrome://flags 页面中突出显示并设置为 Enabled 的 WebMCP 标志

chrome://flags 页面中突出显示并设置为 Enabled 的 WebMCP 标志

重启后,打开开发者工具(F12),转到 Console 选项卡,运行:

console.log(navigator.modelContext);

DevTools 控制台显示 navigator.modelContext 对象

DevTools 控制台显示 navigator.modelContext 对象。

输出为对象(而非 undefined)即确认 API 可用。接下来,您可以安装Model Context Tool Inspector 扩展,并打开 Google 的旅行演示,在真实页面上查看工具注册的实际效果。 

在那里,您只需启动扩展,即可看到类似如下界面:

在 Chrome 的 Model Context Tool Inspector 扩展中检查 Google 的 Flight Search 演示网站的 searchFlights 工具。

使用Chrome 的 Model Context Tool Inspector 扩展在Google 的 Flight Search 演示网站中检查 searchFlights 工具。

如您所见,Model Context Tool Inspector 能检测到 searchFlights 工具并展示其输入架构,从而让模型更清楚地理解如何与页面交互。

构建您的第一个 WebMCP 集成

构建首个 WebMCP 集成有两种方式,下面分别介绍:

声明式方法(HTML)

注册 WebMCP 工具的最简单方式,是给页面上已有的表单添加注解。以下是一个没有添加 WebMCP 注解的标准产品搜索表单:

<form action="/search" method="GET">
  <label for="query">Search term</label>
  <input type="text" name="query" id="query" required>

  <label for="category">Category</label>
  <select name="category" id="category">
    <option value="all">All categories</option>
    <option value="electronics">Electronics</option>
    <option value="books">Books</option>
    <option value="clothing">Clothing</option>
  </select>

  <button type="submit">Search</button>
</form>

四个属性即可将其变为可由智能体调用的工具。toolname 和 tooldescription 加在 <form> 标签本身。对于各个字段,当默认标签不足以让智能体理解时,可用 toolparamtitle 与 toolparamdescription 覆盖默认标签:

<form toolname="search_products"
      tooldescription="Search the product catalog by keyword and optional category filter"
      action="/search" method="GET">

  <label for="query">Search term</label>
  <input type="text" name="query" id="query" required
         toolparamdescription="The keyword or phrase to search for in product titles and descriptions">

  <label for="category">Category</label>
  <select name="category" id="category"
          toolparamtitle="Product Category"
          toolparamdescription="Filter results to a specific product category. Use 'all' for no filter.">
    <option value="all">All categories</option>
    <option value="electronics">Electronics</option>
    <option value="books">Books</option>
    <option value="clothing">Clothing</option>
  </select>

  <button type="submit">Search</button>
</form>

浏览器会基于这些属性在内部生成 JSON 架构。工具名、参数类型、来源于 <select> 选项的枚举值,以及来自 HTML 的必填标记,都会无需额外配置地传递过去。

要测试它,请在本地用任意文本编辑器创建一个 .html 文件,粘贴以上代码,并用 Chrome 通过 file:///{your_document_path.html} 打开;为使扩展能处理本地文件,您还需要打开扩展设置,进入“详细信息”,启用对文件 URL 的访问,如下图所示。

允许扩展访问本地文件。

允许扩展访问本地文件。

否则,您的浏览器将无法与本地 HTML 文件交互。完成后,用 Chrome 打开该文件并检查 WebMCP 扩展,您应能看到类似如下的截图。 

添加注解后,扩展显示为 search_products 工具生成的架构。

添加注解后,扩展显示为 search_products 工具生成的架构。

那么,当智能体调用该工具时实际发生了什么? 

Chrome 会聚焦该表单,并用智能体提供的值填充字段,然后停在这里。用户仍需点击“提交”。这种默认行为是有意为之,因为应由人类确认操作再执行。

在表单标签上添加 toolautosubmit 属性可以移除这一步确认。对于只读操作(如搜索)很合适。凡是修改数据、触发购买或更改账户设置的操作,请不要开启它。

也可以在提交时拦截,并向智能体返回结构化结果。submit 事件上的 respondWith() 方法接收一个 promise,您 resolve 的内容会成为工具的输出:

<form toolname="search_products"
      tooldescription="Search the product catalog"
      toolautosubmit
      action="/search">
  <input type="text" name="query" required>
  <button type="submit">Search</button>
</form>

<script>
  document.querySelector("form").addEventListener("submit", (e) => {
    e.preventDefault();

    const query = new FormData(e.target).get("query");

    if (!query || query.trim().length === 0) {
      if (e.agentInvoked) {
        e.respondWith(Promise.resolve({
          error: "Search query cannot be empty. Please provide a keyword."
        }));
      }
      return;
    }

    const results = performSearch(query);

    if (e.agentInvoked) {
      e.respondWith(Promise.resolve({
        content: [{ type: "text", text: JSON.stringify(results) }]
      }));
    }
  });
</script>

submit 事件上的 agentInvoked 布尔值在智能体触发提交时为 true,普通用户点击则为 false。您可以据此在智能体专用响应格式与常规表单处理之间分支。

当字段上未提供 toolparamdescription 时,浏览器会从关联的 <label> 元素中提取描述;若无,则从 aria-description 属性获取。因此,遵循无障碍规范的标记无需额外工作即可提供合理的参数描述。

当前有一个小怪癖:对于一组 <input type="radio"> 元素,toolparamdescription 必须放在该组的第一个单选按钮上,并作用于整个参数。

命令式方法(JavaScript)

表单能覆盖许多场景,但某些工具需要与应用状态交互、发起 API 调用,或返回不适合传统表单提交的计算结果。以下是一个向待办清单添加条目的基础工具:

window.navigator.modelContext.registerTool({
  name: "addTodo",
  description: "Add a new item to the user's to-do list. Use when the user asks to create, add, or remember a task.",
  inputSchema: {
    type: "object",
    properties: {
      text: {
        type: "string",
        description: "The task description"
      },
      priority: {
        type: "string",
        enum: ["low", "medium", "high"],
        description: "Task priority level. Default to 'medium' if not specified by the user."
      }
    },
    required: ["text"]
  },
  execute: ({ text, priority = "medium" }) => {
    const newItem = { id: Date.now(), text, priority, done: false };
    todoApp.addItem(newItem);
    todoApp.renderList();

    return {
      content: [{
        type: "text",
        text: Added task: "${text}" with ${priority} priority. The to-do list now has ${todoApp.getCount()} items.
      }]
    };
  }
});

execute 回调会接收智能体发送的参数(应与 inputSchema 匹配),运行您的应用逻辑,并返回一个 content 数组。 

有个重要细节:请在返回前更新 UI。 

智能体有时会在调用工具后检查页面是否发生变化以确认是否成功,如果 UI 尚未更新,智能体可能会判断调用失败。您可以随着页面状态演变动态注册和注销工具:

function onUserLogin(user) {
  navigator.modelContext.registerTool({
    name: "viewOrderHistory",
    description: "Show the user's past orders with dates and totals",
    inputSchema: {
      type: "object",
      properties: {
        limit: {
          type: "number",
          description: "Maximum number of orders to return. Defaults to 10."
        }
      }
    },
    execute: async ({ limit = 10 }) => {
      const orders = await fetchOrders(user.id, limit);
      return {
        content: [{ type: "text", text: JSON.stringify(orders) }]
      };
    }
  });
}

function onUserLogout() {
  navigator.modelContext.unregisterTool("viewOrderHistory");
}

一个更贴近现实的场景:餐厅订位页面检查桌位可用性。这个示例还展示了代码内校验。Chrome 文档建议这样做,因为虽然架构约束对智能体很有帮助,但并非所有智能体都会强制执行这些约束。

navigator.modelContext.registerTool({
  name: "checkAvailability",
  description: "Check table availability for a given date and party size at this restaurant. Returns available time slots.",
  inputSchema: {
    type: "object",
    properties: {
      date: {
        type: "string",
        description: "The desired reservation date in YYYY-MM-DD format"
      },
      partySize: {
        type: "number",
        description: "Number of guests, between 1 and 12"
      }
    },
    required: ["date", "partySize"]
  },
  execute: async ({ date, partySize }) => {
    if (partySize < 1 || partySize > 12) {
      return {
        content: [{
          type: "text",
          text: "Party size must be between 1 and 12. For larger groups, please call the restaurant directly at (555) 123-4567."
        }]
      };
    }

    const parsedDate = new Date(date);
    if (isNaN(parsedDate.getTime()) || parsedDate < new Date()) {
      return {
        content: [{
          type: "text",
          text: "Please provide a valid future date in YYYY-MM-DD format."
        }]
      };
    }

    const slots = await api.getAvailableSlots(date, partySize);
    renderAvailabilityCalendar(date, slots);

    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          date,
          partySize,
          availableSlots: slots.map(s => s.time),
          message: slots.length > 0
            ? Found ${slots.length} available time slots.
            : "No availability for this date. Try a different date or smaller party."
        })
      }]
    };
  }
});

有几点来自 Chrome 文档的建议值得强调。尽可能接收原始的用户输入。如果有人说“11:00 到 15:00”,您的工具应直接接收这些字符串,而不是要求智能体先把它们转换成例如“自午夜起的分钟数”之类的内部格式。 

请在代码中进行校验,而不仅依赖架构,因为不同智能体对架构的执行力度不一。并且务必在 UI 更新后再返回结果,因为智能体可能会查看页面以确认操作是否完成。

若要在客户端 WebMCP 的同时补充服务端 MCP 工具,请参阅我们的 使用 FastMCP 构建 MCP 服务器指南,获取详细设置说明。

在 WebMCP 中处理智能体调用与安全

WebMCP 规范假定有人在旁监督。Google 与 Microsoft 的作者将其设计为协作式工作流,而非完全自治。以下机制反映了这种理念。

检测 AI 与人类用户

在 SubmitEvent 接口上新增了一个名为 agentInvoked 的布尔属性。由智能体触发表单提交时,该属性为 true;由人正常点击提交时,则为 false。

document.querySelector("form").addEventListener("submit", (e) => {
  if (e.agentInvoked) {
    console.log("AI agent submitted the form");
    if (!isWithinAgentRateLimit()) {
      e.preventDefault();
      e.respondWith(Promise.resolve({
        error: "Too many requests. Please wait before trying again."
      }));
      return;
    }
  }
});

除表单提交外,还有两个窗口级事件用于追踪智能体活动。toolactivated 会在智能体调用工具并由 Chrome 预填关联表单字段后触发。若用户取消操作或重置表单,则触发 toolcancel。 

这两个事件都不可取消,但都带有 toolName 属性,方便您了解涉及的是哪个工具。

window.addEventListener("toolactivated", ({ toolName }) => {
  showAgentActivityBanner(toolName);
});

window.addEventListener("toolcancel", ({ toolName }) => {
  hideAgentActivityBanner();
});

强制落实“人参与环”的权限

对于声明式工具,默认行为是:智能体填完字段后,需要人类点击“提交”。对于有实际后果的操作,应保留该默认设置。

toolautosubmit 属性会移除此步骤。它适用于只读或低风险的工具:搜索、可用性查询、状态检查。 

凡是创建、修改或删除数据的操作,都应要求手动点击。

您可以用 CSS 伪类为智能体交互状态设置样式:

form:tool-form-active {
  border: 2px solid #2563eb;
  background-color: #eff6ff;
  font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1rem;
  border-radius: 8px;
}

form:tool-form-active::before {
  content: "AI agent is filling this form";
  display: block;
  font-size: 0.875rem;
  color: #2563eb;
  font-weight: 600;
  font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">0.5rem;
}

应用了 :tool-form-active 样式的表单,显示蓝色边框以及 "AI agent is filling this form" 标签。

应用了 :tool-form-active 样式的表单,显示蓝色边框以及 "AI agent is filling this form" 标签。

命令式工具则是另一种挑战。因为没有表单与提交按钮,所以在智能体调用工具时会立即执行。 

对于不应在未经人类批准下执行的任何操作,您需要将确认流程内置到工具本身:

navigator.modelContext.registerTool({
  name: "deleteAccount",
  description: "Permanently delete the user's account and all associated data. Cannot be undone.",
  inputSchema: {
    type: "object",
    properties: {
      confirmPhrase: {
        type: "string",
        description: "The user must type 'DELETE MY ACCOUNT' to confirm."
      }
    },
    required: ["confirmPhrase"]
  },
  execute: ({ confirmPhrase }) => {
    if (confirmPhrase !== "DELETE MY ACCOUNT") {
      return {
        content: [{
          type: "text",
          text: "Confirmation phrase does not match. Ask the user to confirm explicitly."
        }]
      };
    }

    return new Promise((resolve) => {
      showConfirmationModal("Are you sure?", {
        onConfirm: async () => {
          await api.deleteAccount();
          resolve({ content: [{ type: "text", text: "Account deleted." }] });
        },
        onCancel: () => {
          resolve({ content: [{ type: "text", text: "Deletion cancelled by user." }] });
        }
      });
    });
  }
});

整个 MCP 生态的模式是一致的:智能体表达意图,由人类确认执行。想要了解这种模式在不同 MCP 实现中的运作,请参阅我们的 MCP 服务器与客户端精选概览。

测试您的 WebMCP 实现

当您完成首个 WebMCP 集成后,就可以开始测试了。 

使用 Chrome DevTools 智能体模拟器

安装 Model Context Tool Inspector 扩展后,其图标上的小徽标会显示当前页面注册了多少工具。打开弹窗可查看各工具的名称、描述与输入架构。如果您注册的工具未出现在这里,说明注册未成功。

扩展面板显示多个已注册工具列表,并展开了它们的架构。

扩展面板显示多个已注册工具列表,并展开了它们的架构。

要试用智能体模拟器,让我们回到Google 的 Flight Search 演示网站。现在我们可以测试如何手动或使用自然语言来执行该应用。 

手动执行方法

手动执行去除了智能体测试中的不确定因素。在扩展面板中从下拉列表选择一个工具,在 “Input Arguments” 字段中输入一个 JSON 对象,然后点击 “Execute Tool”。扩展会用您提供的参数直接调用该工具。

扩展中选择了 searchFlights 工具、填写了 JSON 参数,并显示 Execute Tool 按钮。

扩展中选择了 searchFlights 工具、填写了 JSON 参数,并显示 Execute Tool 按钮。

这对于定位问题非常有价值。如果工具在手写参数下工作正常,但智能体调用时失败,您就知道问题出在工具描述或架构上,而不是 execute 函数。例如,使用 Google 的旅行演示:

{
  "origin": "LON",
  "destination": "NYC",
  "tripType": "round-trip",
  "outboundDate": "2026-06-10",
  "inboundDate": "2026-06-17",
  "passengers": 2
}

手动执行工具后,旅行演示页面显示搜索结果。

手动执行工具后,旅行演示页面显示搜索结果。

自然语言测试

自然语言测试使用 Gemini API。在扩展设置中粘贴一把来自 AI Studio 的密钥,然后输入如下提示词 

Search for round-trip flights from London to New York leaving June 10th, returning June 17th, for 2 passengers.

扩展会把该提示词与您的工具定义一并发送给 Gemini 2.5 Flash,由模型决定调用哪个工具以及传递哪些参数。

扩展的自然语言输入框中输入了提示词,下方显示智能体调用工具的响应。

扩展的自然语言输入框中输入了提示词,下方显示智能体调用工具的响应。

如果智能体选择了错误的工具或参数映射不正确,您的工具描述可能需要更具体。好的描述应以正面表述工具的用途(“用于查询航班可用性”),而不是强调不该用于什么。

一个实用的测试序列:

  1. 确认页面加载后,工具会出现在扩展中。
  2. 用正确参数手动执行每个工具。确认执行逻辑正常,UI 如预期更新。
  3. 用错误参数执行。检查校验是否捕获错误,以及消息是否足够清晰,便于智能体自我纠正。
  4. 进行自然语言测试。观察智能体是否选对工具,并能正确将用户表述映射到架构参数。
  5. 测试边界情况:未认证用户、禁用表单、API 宕机、快速重复调用。

关于 Google 生态内 MCP 工具的背景,我们的 Google MCP 服务器教程涵盖了多个相关模式。若想了解超出 Chrome 的更广泛协议格局,请查看 Top Remote MCP Servers。

结语

WebMCP 为网站与 AI 智能体提供了一条直接的沟通通道。网站发布结构化的工具定义,智能体用类型化参数调用它们,而不再凭猜测穿行页面。声明式 API 仅用几个 HTML 属性就能将现有表单变为可调用工具;命令式 API 则覆盖更复杂的场景。安全模型确保在人们真正重要的操作中,仍由人类掌控。

该规范仍处早期阶段,仅在启用标志的 Chrome Canary 中可用,尚不适合生产环境。但事实是,Google 与 Microsoft 共同编写,且由 W3C 治理,说明其有望成为稳定的网页标准。现在投入时间熟悉该 API,意味着等到稳定版到来时,您已掌握工具设计模式与安全机制。

如果您想现在开始准备,请审视现有站点中的表单,找出最适合开放给智能体的部分。搜索表单与可用性查询这类低风险入口是良好起点。当您在这些场景中熟练掌握声明式 API 后,再把需要自定义逻辑的工作流迁移到命令式工具。 

若想进一步探索该主题,我推荐:

WebMCP 常见问题

在网站上实现 WebMCP 有哪两种方式?

WebMCP 可通过两种方式实现:使用 HTML 表单注解的声明式方法,以及使用 JavaScript 的命令式方法。声明式方法更简单,适合现有表单;命令式方法为动态工作流、API 调用和自定义校验提供更多控制。

我现在可以在生产环境中使用 WebMCP 吗?

还不行。它目前位于 Chrome Canary 的功能标志之后,且 W3C 规范仍在草案阶段。仅适用于原型与试验。

其他浏览器是否支持?

目前只有 Chrome 有可用实现。Microsoft 参与了规范编写,这使得 Edge 未来很可能支持。Mozilla 与 Apple 都在 W3C 工作组中有代表,但二者都未公布时间表。

WebMCP 是否需要后端服务器?

不需要。这一点对来自传统 MCP 设置的读者尤为重要,传统 MCP 需要独立的服务器进程。使用 WebMCP,您现有的前端 JavaScript 就是智能体接口。工具在页面上下文中运行,使用与人类用户相同的会话,不需要额外的后端基础设施。

如果我不在站点上实现 WebMCP,会怎样?

什么也不会“坏掉”。智能体会继续通过截图和 DOM 抓取与页面交互,就像现在一样。只是这些交互会更慢、错误率更高、且对智能体的计算成本更高。

Josep Ferrer

作者

Josep Ferrer

Josep 是一名专注于欧洲项目的自由职业数据科学家,擅长数据存储、处理、高级分析,以及具有影响力的数据叙事。 

作为一名教育者,他在纳瓦拉大学硕士项目教授大数据课程,并在 Medium、KDNuggets 和 DataCamp 等平台发表文章分享见解。Josep 还在其通讯 Databites(databites.tech)撰写有关数据与技术的内容。 

他拥有加泰罗尼亚理工大学工程物理学学士学位,以及庞培法布拉大学智能交互系统硕士学位。

主题