Skip to content

Commit edd5e44

Browse files
Merge pull request #290 from basementstudio/canary
v0.5.1
2 parents 076dae2 + 82d6207 commit edd5e44

File tree

20 files changed

+1331
-69
lines changed

20 files changed

+1331
-69
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import type { Metadata } from "next";
2+
import { DocsBody } from "@/components/layout/page";
3+
4+
export const dynamic = "force-static";
5+
6+
export const metadata: Metadata = {
7+
title: "Telemetry - xmcp",
8+
description:
9+
"Learn what xmcp collects, why the data matters, and how to disable telemetry at any time.",
10+
alternates: {
11+
canonical: "https://xmcp.dev/telemetry",
12+
},
13+
};
14+
15+
export default function TelemetryPage() {
16+
return (
17+
<main className="flex w-full justify-center px-4 lg:px-8">
18+
<article className="flex w-full max-w-[860px] flex-col gap-6 py-16">
19+
<header className="flex flex-col gap-4">
20+
<h1 className="display font-medium text-white">Telemetry</h1>
21+
<p>
22+
xmcp traces a handful of anonymous build events so we can keep
23+
product decisions grounded in real-world usage. You stay in control
24+
and can switch it off at any moment.
25+
</p>
26+
</header>
27+
<DocsBody className="w-full">
28+
<section>
29+
<h2>Why we collect telemetry</h2>
30+
<p>
31+
Anonymous signals highlight whether transports, adapters, or new
32+
compiler behavior works in practice without requiring manual bug
33+
reports. Seeing which builds succeed, how long they take, and
34+
where they fail lets us prioritize fixes that unblock the most
35+
people.
36+
</p>
37+
</section>
38+
39+
<section>
40+
<h2>What is being collected?</h2>
41+
<p>
42+
We capture general usage information tied to the command you run (
43+
<code>xmcp dev</code> or <code>xmcp build</code>) so we can spot
44+
regressions. Specifically, we track the following anonymously:
45+
</p>
46+
<ul>
47+
<li>
48+
<strong>Command + versions.</strong> Which command was invoked,
49+
the xmcp release, and the Node.js version in use.
50+
</li>
51+
<li>
52+
<strong>General machine info.</strong> OS family and release,
53+
CPU architecture and count, total memory, and whether the run
54+
happened in CI, Docker, or WSL.
55+
</li>
56+
<li>
57+
<strong>Project traits.</strong> The selected adapter/transport
58+
and counts of components (tools, prompts, resources) so we know
59+
which surfaces are being exercised.
60+
</li>
61+
<li>
62+
<strong>Performance snapshots.</strong> Duration of each run,
63+
bundle size for successful builds, and a coarse error category
64+
if something fails.
65+
</li>
66+
<li>
67+
<strong>Privacy guardrails.</strong> A random install ID links
68+
related events so we can detect regressions, but it never
69+
contains repository details. No code, prompts, environment
70+
variables, logs, or secrets leave your machine.
71+
</li>
72+
</ul>
73+
<p>
74+
This list is audited regularly to ensure it stays accurate, and it
75+
explicitly excludes personal data, email addresses, access tokens,
76+
and any identifiers tied to you or your organization.
77+
</p>
78+
<p>
79+
Set <code>XMCP_DEBUG_TELEMETRY=true</code> to print every payload
80+
locally. This flag mirrors the data to <code>stderr</code> with
81+
the <code>[telemetry]</code> prefix but does <em>not</em> stop
82+
events from being sent; use{" "}
83+
<code>XMCP_TELEMETRY_DISABLED=true</code> if you want to pause
84+
telemetry entirely.
85+
</p>
86+
</section>
87+
88+
<section>
89+
<h2>Opt out at any time</h2>
90+
<p>
91+
Telemetry is optional. Disable it temporarily, per environment, or
92+
everywhere:
93+
</p>
94+
<ul>
95+
<li>
96+
<strong>Per command or CI job.</strong>{" "}
97+
<code>XMCP_TELEMETRY_DISABLED=true npx xmcp dev</code> (or{" "}
98+
<code>build</code>) prevents telemetry from booting for that
99+
run, which is ideal for sensitive builds or automated pipelines.
100+
</li>
101+
<li>
102+
<strong>Persistent opt-out.</strong> Add
103+
<code>XMCP_TELEMETRY_DISABLED=true</code> to your shell profile,
104+
project <code>.env</code>, or CI secrets. xmcp checks the
105+
variable before creating an anonymous ID, so no data leaves your
106+
machine.
107+
</li>
108+
<li>
109+
<strong>Re-enabling.</strong> Remove the environment variable
110+
(or delete <code>telemetry.json</code>) and rerun{" "}
111+
<code>xmcp dev</code> to recreate a fresh anonymous ID along
112+
with the opt-in notice.
113+
</li>
114+
</ul>
115+
<p>
116+
If you have additional privacy requirements, please reach out at{" "}
117+
<a href="mailto:support@xmcp.dev">support@xmcp.dev</a>—your
118+
feedback directly shapes what we measure next.
119+
</p>
120+
</section>
121+
</DocsBody>
122+
</article>
123+
</main>
124+
);
125+
}

packages/xmcp/bundler/build-main.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ function getConfig() {
5757
entry: {
5858
index: path.join(srcPath, "index.ts"),
5959
cli: path.join(srcPath, "cli.ts"),
60+
"detached-flush": path.join(
61+
srcPath,
62+
"telemetry/events/detached-flush.ts"
63+
),
6064
},
6165
mode,
6266
devtool: mode === "production" ? false : "source-map",

packages/xmcp/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,18 @@
5353
"typescript": "^5.8.3"
5454
},
5555
"devDependencies": {
56+
"@types/async-retry": "^1.4.9",
5657
"@types/content-type": "^1.1.9",
5758
"@types/express": "^5.0.1",
5859
"@types/fs-extra": "^11.0.4",
5960
"@types/jsonwebtoken": "^9.0.9",
6061
"@types/node": "^22",
6162
"@types/react": "^19.2.2",
6263
"@types/react-dom": "^19.2.2",
64+
"async-retry": "^1.3.3",
6365
"chalk": "^5.3.0",
6466
"chokidar": "^3.6.0",
67+
"ci-info": "^4.3.1",
6568
"commander": "^11.1.0",
6669
"content-type": "^1.0.5",
6770
"cross-env": "^7.0.3",
@@ -70,12 +73,13 @@
7073
"express": "^4.19.1",
7174
"fs-extra": "^11.3.0",
7275
"glob": "^11.0.2",
76+
"is-docker": "^4.0.0",
77+
"is-wsl": "^3.1.0",
7378
"json5": "^2.2.3",
7479
"jsonwebtoken": "^9.0.2",
7580
"memfs": "^4.17.2",
7681
"raw-body": "^3.0.0",
77-
"tsx": "^4.19.4",
78-
"zod": "^3.24.4"
82+
"tsx": "^4.19.4"
7983
},
8084
"peerDependencies": {
8185
"react": ">=19.0.0",

packages/xmcp/src/cli.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ program.name("xmcp").description("The MCP framework CLI").version("0.0.1");
1313
program
1414
.command("dev")
1515
.description("Start development mode")
16-
.action(() => {
16+
.action(async () => {
1717
console.log(`${xmcpLogo} Starting development mode...`);
18-
compilerContextProvider(
18+
await compilerContextProvider(
1919
{
2020
mode: "development",
2121
// Ignore platforms on dev mode
2222
platforms: {},
2323
},
24-
() => {
25-
compile();
24+
async () => {
25+
await compile();
2626
}
2727
);
2828
});
@@ -35,15 +35,15 @@ program
3535
console.log(`${xmcpLogo} Building for production...`);
3636
const isVercelBuild = options.vercel || process.env.VERCEL === "1";
3737

38-
compilerContextProvider(
38+
await compilerContextProvider(
3939
{
4040
mode: "production",
4141
platforms: {
4242
vercel: isVercelBuild,
4343
},
4444
},
45-
() => {
46-
compile({
45+
async () => {
46+
await compile({
4747
onBuild: async () => {
4848
if (isVercelBuild) {
4949
console.log(`${xmcpLogo} Building for Vercel...`);
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import {
2+
AdapterType,
3+
ErrorPhase,
4+
TelemetryEventName,
5+
TransportType,
6+
telemetry,
7+
} from "../telemetry";
8+
import type { BuildEventData } from "../telemetry/events/post-payload";
9+
import { isTelemetryDebugEnabled } from "../telemetry/debug";
10+
11+
type TelemetryClient = Pick<typeof telemetry, "record" | "flushDetached">;
12+
13+
interface BaseTelemetryData {
14+
duration: number;
15+
toolsCount: number;
16+
reactToolsCount: number;
17+
promptsCount: number;
18+
resourcesCount: number;
19+
transport: TransportType;
20+
adapter: AdapterType;
21+
nodeVersion: string;
22+
xmcpVersion: string;
23+
}
24+
25+
export interface BuildSuccessTelemetry extends BaseTelemetryData {
26+
outputSize: number;
27+
}
28+
29+
export interface BuildFailureTelemetry extends BaseTelemetryData {
30+
errorPhase: ErrorPhase;
31+
errorType: string;
32+
}
33+
34+
function enqueueTelemetryEvent(
35+
client: TelemetryClient,
36+
event: { eventName: TelemetryEventName; fields: BuildEventData }
37+
) {
38+
if (isTelemetryDebugEnabled()) {
39+
console.log("[telemetry] Tracking event", event);
40+
}
41+
client.record(event, true);
42+
client.flushDetached("build");
43+
}
44+
45+
export function logBuildSuccess(
46+
data: BuildSuccessTelemetry,
47+
client: TelemetryClient = telemetry
48+
) {
49+
enqueueTelemetryEvent(client, {
50+
eventName: TelemetryEventName.BUILD_COMPLETED,
51+
fields: {
52+
success: true,
53+
duration: data.duration,
54+
toolsCount: data.toolsCount,
55+
reactToolsCount: data.reactToolsCount,
56+
promptsCount: data.promptsCount,
57+
resourcesCount: data.resourcesCount,
58+
outputSize: data.outputSize,
59+
transport: data.transport,
60+
adapter: data.adapter,
61+
nodeVersion: data.nodeVersion,
62+
xmcpVersion: data.xmcpVersion,
63+
},
64+
});
65+
}
66+
67+
export function logBuildFailure(
68+
data: BuildFailureTelemetry,
69+
client: TelemetryClient = telemetry
70+
) {
71+
enqueueTelemetryEvent(client, {
72+
eventName: TelemetryEventName.BUILD_FAILED,
73+
fields: {
74+
success: false,
75+
duration: data.duration,
76+
errorPhase: data.errorPhase,
77+
errorType: data.errorType,
78+
toolsCount: data.toolsCount,
79+
reactToolsCount: data.reactToolsCount,
80+
promptsCount: data.promptsCount,
81+
resourcesCount: data.resourcesCount,
82+
transport: data.transport,
83+
adapter: data.adapter,
84+
nodeVersion: data.nodeVersion,
85+
xmcpVersion: data.xmcpVersion,
86+
},
87+
});
88+
}

packages/xmcp/src/compiler/compiler-context.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const compilerContext = createContext<CompilerContext>({
3131
});
3232

3333
// Preset some defaults for the compiler context
34-
export const compilerContextProvider = (
34+
export const compilerContextProvider = async (
3535
initialValue: Omit<
3636
CompilerContext,
3737
"toolPaths" | "promptPaths" | "resourcePaths" | "hasMiddleware"
@@ -46,7 +46,7 @@ export const compilerContextProvider = (
4646
resourcePaths: new Set(),
4747
hasMiddleware: false,
4848
},
49-
callback
49+
() => Promise.resolve(callback())
5050
);
5151
};
5252

0 commit comments

Comments
 (0)