Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ This MCP is being run as an experiment. While it is an experiment, we will be st

As this MCP is experimental it may be withdrawn at any time, in particular, the URL will change before it becomes production ready.

To opt out of first-party analytics, send the `X-Moz-1st-Party-Data-Opt-Out: 1` header along with requests to the MCP.

## Using the remote server

Add the remote server to your tool of choice, e.g. in Claude Code:
Expand Down
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const gitignorePath = path.resolve(__dirname, ".gitignore");

export default defineConfig([
includeIgnoreFile(gitignorePath),
{ ignores: ["glean/generated/"] },
jsdoc.configs["flat/recommended"],
n.configs["flat/recommended"],
unicorn.configs["recommended"],
Expand Down
9 changes: 9 additions & 0 deletions glean/generated/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Glean generated files

The files in this directory are autogenerated by [`glean_parser`](https://mozilla.github.io/glean_parser/) from [`../metrics.yml`](../metrics.yml) and are committed to avoid requiring a Python environment to run the server or tests.

If you change `metrics.yml`, or update glean, regenerate these files with:

```
npm run glean:generate
```
23 changes: 23 additions & 0 deletions glean/generated/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// AUTOGENERATED BY glean_parser v8.1.1. DO NOT EDIT.

import EventMetricType from "@mozilla/glean/private/metrics/event";

/**
* Recorded when a non-critical error occurs.
*
* Generated from `error.silent_error`.
*/
export const silentError = new EventMetricType(
{
category: "error",
name: "silent_error",
sendInPings: ["events"],
lifetime: "ping",
disabled: false,
},
["reason", "tool", "user_agent"],
);
27 changes: 27 additions & 0 deletions glean/generated/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// AUTOGENERATED BY glean_parser v8.1.1. DO NOT EDIT.

import EventMetricType from "@mozilla/glean/private/metrics/event";

/**
* Recorded when a non-critical error occurs.
*
* Generated from `error.silent_error`.
*/
export const silentError = new EventMetricType<{
reason?: string;
tool?: string;
user_agent?: string;
}>(
{
category: "error",
name: "silent_error",
sendInPings: ["events"],
lifetime: "ping",
disabled: false,
},
["reason", "tool", "user_agent"],
);
23 changes: 23 additions & 0 deletions glean/generated/getCompat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// AUTOGENERATED BY glean_parser v8.1.1. DO NOT EDIT.

import EventMetricType from "@mozilla/glean/private/metrics/event";

/**
* Recorded when a BCD key is successfully fetched.
*
* Generated from `get_compat.fetched`.
*/
export const fetched = new EventMetricType(
{
category: "get_compat",
name: "fetched",
sendInPings: ["events"],
lifetime: "ping",
disabled: false,
},
["key", "user_agent"],
);
26 changes: 26 additions & 0 deletions glean/generated/getCompat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// AUTOGENERATED BY glean_parser v8.1.1. DO NOT EDIT.

import EventMetricType from "@mozilla/glean/private/metrics/event";

/**
* Recorded when a BCD key is successfully fetched.
*
* Generated from `get_compat.fetched`.
*/
export const fetched = new EventMetricType<{
key?: string;
user_agent?: string;
}>(
{
category: "get_compat",
name: "fetched",
sendInPings: ["events"],
lifetime: "ping",
disabled: false,
},
["key", "user_agent"],
);
23 changes: 23 additions & 0 deletions glean/generated/getDoc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// AUTOGENERATED BY glean_parser v8.1.1. DO NOT EDIT.

import EventMetricType from "@mozilla/glean/private/metrics/event";

/**
* Recorded when an MDN documentation page is successfully fetched.
*
* Generated from `get_doc.fetched`.
*/
export const fetched = new EventMetricType(
{
category: "get_doc",
name: "fetched",
sendInPings: ["events"],
lifetime: "ping",
disabled: false,
},
["path", "user_agent"],
);
26 changes: 26 additions & 0 deletions glean/generated/getDoc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// AUTOGENERATED BY glean_parser v8.1.1. DO NOT EDIT.

import EventMetricType from "@mozilla/glean/private/metrics/event";

/**
* Recorded when an MDN documentation page is successfully fetched.
*
* Generated from `get_doc.fetched`.
*/
export const fetched = new EventMetricType<{
path?: string;
user_agent?: string;
}>(
{
category: "get_doc",
name: "fetched",
sendInPings: ["events"],
lifetime: "ping",
disabled: false,
},
["path", "user_agent"],
);
23 changes: 23 additions & 0 deletions glean/generated/search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// AUTOGENERATED BY glean_parser v8.1.1. DO NOT EDIT.

import EventMetricType from "@mozilla/glean/private/metrics/event";

/**
* Recorded when a search of MDN documentation is completed.
*
* Generated from `search.completed`.
*/
export const completed = new EventMetricType(
{
category: "search",
name: "completed",
sendInPings: ["events"],
lifetime: "ping",
disabled: false,
},
["result_count", "top_path", "top_score", "user_agent"],
);
28 changes: 28 additions & 0 deletions glean/generated/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// AUTOGENERATED BY glean_parser v8.1.1. DO NOT EDIT.

import EventMetricType from "@mozilla/glean/private/metrics/event";

/**
* Recorded when a search of MDN documentation is completed.
*
* Generated from `search.completed`.
*/
export const completed = new EventMetricType<{
result_count?: number;
top_path?: string;
top_score?: number;
user_agent?: string;
}>(
{
category: "search",
name: "completed",
sendInPings: ["events"],
lifetime: "ping",
disabled: false,
},
["result_count", "top_path", "top_score", "user_agent"],
);
56 changes: 56 additions & 0 deletions glean/glean.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Glean from "@mozilla/glean/node";
import { captureException } from "@sentry/node";

/**
* @import EventMetricType from "@mozilla/glean/private/metrics/event";
* @import { ExtraMap } from "@mozilla/glean/private/metrics/events_database/recorded_event";
* @import { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
* @import { Request, Notification } from "@modelcontextprotocol/sdk/types.js";
*/

const uploadEnabled = process.env.GLEAN_ENABLED === "true";
const GLEAN_CHANNEL = process.env.GLEAN_CHANNEL || "dev";
const GLEAN_DEBUG = process.env.GLEAN_DEBUG === "true";

/* node:coverage disable */
if (GLEAN_DEBUG) {
Glean.setDebugViewTag("mdn-dev");
Glean.setLogPings(true);
}
/* node:coverage enable */

Glean.initialize("mdn-mcp", uploadEnabled, {
channel: GLEAN_CHANNEL,
});

/**
* @template {ExtraMap} E
* @template {Request} R
* @template {Notification} N
* @param {EventMetricType<E>} event
* @param {RequestHandlerExtra<R, N>} [request]
* @param {E} [extra]
*/
export function submitEvent(event, request, extra) {
try {
const optOut =
request?.requestInfo?.headers?.["x-moz-1st-party-data-opt-out"] === "1";
if (optOut) return;

const user_agent =
request?.requestInfo?.headers?.["user-agent"]?.toString();
event.record(
/** @type {E} */ ({
...extra,
...(user_agent ? { user_agent } : undefined),
}),
);
/* node:coverage disable */
} catch (error) {
// we don't want to throw, the request should work
// even if glean fails in some way
console.error(error);
captureException(error);
}
/* node:coverage enable */
}
99 changes: 99 additions & 0 deletions glean/metrics.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Schema
$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0

get_doc:
fetched:
type: event
description: Recorded when an MDN documentation page is successfully fetched.
data_sensitivity:
- interaction
notification_emails:
- leo@mozilla.com
- mdn-team@mozilla.com
bugs:
- https://github.com/mdn/mcp/issues/98
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=2020347
expires: never
extra_keys:
path:
description: Normalised path of the page fetched from MDN.
type: string
user_agent:
description: The HTTP User-Agent of the AI harness making the request.
type: string

search:
completed:
type: event
description: Recorded when a search of MDN documentation is completed.
data_sensitivity:
- interaction
notification_emails:
- leo@mozilla.com
- mdn-team@mozilla.com
bugs:
- https://github.com/mdn/mcp/issues/98
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=2020347
expires: never
extra_keys:
result_count:
description: The number of results returned.
type: quantity
top_score:
description: The search relevancy score of the top result.
type: quantity
top_path:
description: The path to the top result.
type: string
user_agent:
description: The HTTP User-Agent of the AI harness making the request.
type: string

get_compat:
fetched:
type: event
description: Recorded when a BCD key is successfully fetched.
data_sensitivity:
- interaction
notification_emails:
- leo@mozilla.com
- mdn-team@mozilla.com
bugs:
- https://github.com/mdn/mcp/issues/98
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=2020347
expires: never
extra_keys:
key:
description: The BCD key that was fetched.
type: string
user_agent:
description: The HTTP User-Agent of the AI harness making the request.
type: string

error:
silent_error:
type: event
description: Recorded when a non-critical error occurs.
data_sensitivity:
- technical
notification_emails:
- leo@mozilla.com
- mdn-team@mozilla.com
bugs:
- https://github.com/mdn/mcp/issues/98
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=2020347
expires: never
extra_keys:
tool:
description: The MCP tool where the error occurred.
type: string
reason:
description: A hard-coded reason the error occurred.
type: string
user_agent:
description: The HTTP User-Agent of the AI harness making the request.
type: string
Loading
Loading