pattern-mcp 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -46
- package/dist/index.js +41 -1
- package/dist/telemetry.js +47 -30
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1644,74 +1644,96 @@ recommendations.
|
|
|
1644
1644
|
Local project memory and the local call log are stored on the machine
|
|
1645
1645
|
running Pattern. They are not sent anywhere by Pattern itself.
|
|
1646
1646
|
|
|
1647
|
-
The one exception is
|
|
1647
|
+
The one exception is telemetry, on by default -- see
|
|
1648
1648
|
[Telemetry](#telemetry) below for exactly what it sends and how to turn
|
|
1649
|
-
it
|
|
1649
|
+
it off.
|
|
1650
1650
|
|
|
1651
1651
|
Review [SECURITY.md](./SECURITY.md) before putting sensitive information
|
|
1652
1652
|
into fields such as `component_need`, `domain`, or project IDs.
|
|
1653
1653
|
|
|
1654
1654
|
## Telemetry
|
|
1655
1655
|
|
|
1656
|
-
|
|
1657
|
-
you explicitly set:
|
|
1656
|
+
On by default, as of v0.9.0. To turn it off:
|
|
1658
1657
|
|
|
1659
1658
|
```
|
|
1660
|
-
PATTERN_TELEMETRY=
|
|
1659
|
+
PATTERN_TELEMETRY=0
|
|
1661
1660
|
```
|
|
1662
1661
|
|
|
1662
|
+
(`false` and `no` also work, case-insensitively. Before v0.9.0 this was
|
|
1663
|
+
opt-in -- `PATTERN_TELEMETRY=1` to turn on. In practice that meant almost
|
|
1664
|
+
no signal: installs happened, but almost nobody set the env var. This
|
|
1665
|
+
release flips the default and adds a second, standard event stream, both
|
|
1666
|
+
described below, while keeping the same "never sent" guarantees.)
|
|
1667
|
+
|
|
1663
1668
|
**The one-time notice.** The first time you run this version of Pattern
|
|
1664
|
-
-- whether it's a brand-new install or an upgrade from
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
**Why it exists.**
|
|
1669
|
+
-- whether it's a brand-new install or an upgrade from an older version
|
|
1670
|
+
-- it prints a short notice to stderr explaining all of this and how to
|
|
1671
|
+
opt out. It prints exactly once, ever (tracked by a marker file at
|
|
1672
|
+
`~/.pattern/telemetry_notice_shown`), then never again, regardless of
|
|
1673
|
+
whether you act on it. There's no interactive y/n prompt: Pattern's stdin
|
|
1674
|
+
is the MCP JSON-RPC channel the client uses to talk to it, so blocking on
|
|
1675
|
+
stdin for a keypress would fight the protocol handshake instead of
|
|
1676
|
+
showing a dialog -- a stderr notice is the safe equivalent for a stdio
|
|
1677
|
+
MCP server.
|
|
1678
|
+
|
|
1679
|
+
**Why it exists.** Three things about real usage can't be answered from
|
|
1675
1680
|
this repo alone: whether people actually come back and use Pattern on a
|
|
1676
|
-
second or third project on their own
|
|
1681
|
+
second or third project on their own; how often a BYO Anthropic key
|
|
1677
1682
|
actually hits a rate limit or runs out of credit in real sessions, not
|
|
1678
1683
|
just the one time that happened during manual testing (see
|
|
1679
|
-
[Known limitations](#known-limitations))
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1684
|
+
[Known limitations](#known-limitations)); and, previously, whether
|
|
1685
|
+
`recommend_component` gets called at all after install -- opt-in
|
|
1686
|
+
telemetry couldn't answer that last one because the people who'd opt in
|
|
1687
|
+
are a biased, tiny sample of everyone who installs.
|
|
1688
|
+
|
|
1689
|
+
**What gets sent, when on -- two event streams, same distinct ID:**
|
|
1690
|
+
|
|
1691
|
+
1. Pattern's own events, sent directly to PostHog:
|
|
1692
|
+
- An anonymous, randomly generated install ID -- a UUID created once
|
|
1693
|
+
and stored at `~/.pattern/install_id` (overridable via
|
|
1694
|
+
`PATTERN_INSTALL_ID_PATH`), never derived from your machine,
|
|
1695
|
+
username, or any other identifying information. This is the distinct
|
|
1696
|
+
ID both event streams use, so an install counts as the same "user"
|
|
1697
|
+
in either.
|
|
1698
|
+
- A one-way SHA-256 hash of `project_id`, truncated to 16 hex characters
|
|
1699
|
+
-- never the raw `project_id` string. The hash lets Pattern count how
|
|
1700
|
+
many *distinct* projects one install has used, without ever seeing what
|
|
1701
|
+
those projects are named.
|
|
1702
|
+
- On every `recommend_component` call that reaches the API or the ledger
|
|
1703
|
+
cache-hit shortcut: `verdict`, `confidence`, `reason`,
|
|
1704
|
+
`ensemble_triggered`, `estimated_cost_usd`, and `served_from_ledger` --
|
|
1705
|
+
the same distilled shape already written to the
|
|
1706
|
+
[local call log](#local-call-log), not new information.
|
|
1707
|
+
- On a failed Anthropic API call specifically: the HTTP status code and a
|
|
1708
|
+
coarse classification (`rate_limit`, `insufficient_credit`, or `other`)
|
|
1709
|
+
-- never the request or response body.
|
|
1710
|
+
2. Standard MCP tool-call analytics, via
|
|
1711
|
+
[`@posthog/mcp`](https://posthog.com/docs/mcp-analytics): which tool
|
|
1712
|
+
was called, call duration, and success/failure, so unique installs and
|
|
1713
|
+
call counts per tool (including `recommend_component`) are visible the
|
|
1714
|
+
same way any other MCP server's usage would be. This SDK's defaults
|
|
1715
|
+
would otherwise also capture full tool call arguments, full response
|
|
1716
|
+
text, and the raw thrown error message on a failure -- Pattern
|
|
1717
|
+
explicitly strips all of that (`$mcp_parameters`, `$mcp_response`,
|
|
1718
|
+
`$mcp_intent`, `$mcp_error_message`) before anything leaves the process,
|
|
1719
|
+
via its `beforeSend` hook. What's left is the same shape as stream 1
|
|
1720
|
+
above: counts, timing, and coarse success/failure, never content.
|
|
1701
1721
|
|
|
1702
1722
|
**What never gets sent, telemetry on or off:** `component_need`,
|
|
1703
1723
|
`domain`, `framework`, `existing_stack`, `requirements_checked` evidence,
|
|
1704
|
-
the raw `project_id`, or
|
|
1724
|
+
the raw `project_id`, a failed API call's request or response body, or
|
|
1725
|
+
your Anthropic API key. This is a claim about both event streams above,
|
|
1726
|
+
not just the first one.
|
|
1705
1727
|
|
|
1706
|
-
**Where it goes.**
|
|
1707
|
-
write-only project key (safe to ship in source -- it can send
|
|
1708
|
-
cannot read data back). Set `PATTERN_POSTHOG_KEY` /
|
|
1728
|
+
**Where it goes.** Both streams go to Pattern's PostHog project via its
|
|
1729
|
+
public, write-only project key (safe to ship in source -- it can send
|
|
1730
|
+
events, it cannot read data back). Set `PATTERN_POSTHOG_KEY` /
|
|
1709
1731
|
`PATTERN_POSTHOG_HOST` to point at a different project, e.g. for
|
|
1710
1732
|
self-hosting.
|
|
1711
1733
|
|
|
1712
|
-
**Turning it off
|
|
1713
|
-
|
|
1714
|
-
|
|
1734
|
+
**Turning it off:** `PATTERN_TELEMETRY=0` disables both streams entirely
|
|
1735
|
+
-- when it's off, `instrument()` (stream 2) is never even called, and
|
|
1736
|
+
stream 1's `capture()` calls are no-ops.
|
|
1715
1737
|
|
|
1716
1738
|
## Cost
|
|
1717
1739
|
|
package/dist/index.js
CHANGED
|
@@ -30,12 +30,13 @@
|
|
|
30
30
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
31
31
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
32
32
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
33
|
+
import { instrument } from "@posthog/mcp";
|
|
33
34
|
import { execFileSync } from "node:child_process";
|
|
34
35
|
import { createHash, randomUUID } from "node:crypto";
|
|
35
36
|
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
36
37
|
import { homedir } from "node:os";
|
|
37
38
|
import { dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
38
|
-
import { captureApiError, captureRecommendation, printTelemetryNoticeOnce, shutdownTelemetry, } from "./telemetry.js";
|
|
39
|
+
import { captureApiError, captureRecommendation, getClient as getPostHogClient, installId, printTelemetryNoticeOnce, shutdownTelemetry, TELEMETRY_ENABLED, } from "./telemetry.js";
|
|
39
40
|
export const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
|
40
41
|
// Configurable so Sonnet vs. Haiku can be A/B tested without a code change.
|
|
41
42
|
// Defaults to Sonnet 5. Try MODEL=claude-haiku-4-5-20251001 to test the
|
|
@@ -3338,6 +3339,45 @@ export function extractJson(text) {
|
|
|
3338
3339
|
return text.slice(start, end + 1);
|
|
3339
3340
|
}
|
|
3340
3341
|
const server = new Server({ name: "pattern-mcp", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
3342
|
+
// Standard MCP tool-call analytics (tool name, duration, success/failure,
|
|
3343
|
+
// unique installs/sessions) via PostHog's own MCP SDK -- separate from
|
|
3344
|
+
// this file's captureRecommendation()/captureApiError() calls, which carry
|
|
3345
|
+
// the distilled verdict shape. Both are gated by the same TELEMETRY_ENABLED
|
|
3346
|
+
// flag (see telemetry.ts) and share installId() as the distinct_id so an
|
|
3347
|
+
// install counts as the same "user" across both event streams.
|
|
3348
|
+
//
|
|
3349
|
+
// context: false skips injecting an extra `context` argument into every
|
|
3350
|
+
// tool's schema. beforeSend strips $mcp_parameters/$mcp_response/$mcp_intent/
|
|
3351
|
+
// $mcp_error_message before anything is sent -- by default this SDK
|
|
3352
|
+
// captures full tool call arguments, response text, AND the raw thrown
|
|
3353
|
+
// error message (which for a failed Anthropic call is the full HTTP
|
|
3354
|
+
// response body -- verified live: a 401 test call shipped the entire
|
|
3355
|
+
// JSON error body in $mcp_error_message before this strip was added).
|
|
3356
|
+
// SECURITY.md and README.md both promise component_need/domain/framework/
|
|
3357
|
+
// existing_stack are never sent and that a failed API call never sends its
|
|
3358
|
+
// response body; this is what keeps both true for this event stream too.
|
|
3359
|
+
// enableExceptionAutocapture is off for the same reason -- it would also
|
|
3360
|
+
// fan the same raw message out to a separate $exception event.
|
|
3361
|
+
if (TELEMETRY_ENABLED) {
|
|
3362
|
+
const posthogClient = getPostHogClient();
|
|
3363
|
+
if (posthogClient) {
|
|
3364
|
+
instrument(server, posthogClient, {
|
|
3365
|
+
context: false,
|
|
3366
|
+
enableExceptionAutocapture: false,
|
|
3367
|
+
identify: async () => ({ distinctId: installId() }),
|
|
3368
|
+
beforeSend: (event) => {
|
|
3369
|
+
if (event.properties) {
|
|
3370
|
+
delete event.properties["$mcp_parameters"];
|
|
3371
|
+
delete event.properties["$mcp_response"];
|
|
3372
|
+
delete event.properties["$mcp_intent"];
|
|
3373
|
+
delete event.properties["$mcp_intent_source"];
|
|
3374
|
+
delete event.properties["$mcp_error_message"];
|
|
3375
|
+
}
|
|
3376
|
+
return event;
|
|
3377
|
+
},
|
|
3378
|
+
});
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3341
3381
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
3342
3382
|
tools: [
|
|
3343
3383
|
{
|
package/dist/telemetry.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Anonymous product telemetry.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* the product can't answer any
|
|
4
|
+
* On by default. Disabling it (PATTERN_TELEMETRY=0) turns off both halves
|
|
5
|
+
* described below. It answers two questions the product can't answer any
|
|
6
|
+
* other way without asking users directly:
|
|
6
7
|
*
|
|
7
8
|
* - Do people come back and use Pattern on a second or third project on
|
|
8
9
|
* their own, unprompted? (tracked via distinct project hashes seen per
|
|
@@ -11,16 +12,27 @@
|
|
|
11
12
|
* limited in real sessions, not just the one time it happened during
|
|
12
13
|
* manual testing? (tracked via captureApiError)
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
15
|
+
* There are two halves to what ships, both gated by the same flag:
|
|
16
|
+
*
|
|
17
|
+
* 1. This file's own capture() calls: an anonymous, randomly generated
|
|
18
|
+
* install ID (see installId() below); a one-way SHA-256 hash of
|
|
19
|
+
* project_id, truncated to 16 hex chars -- never the raw project_id
|
|
20
|
+
* string; the verdict shape already written to the local call log
|
|
21
|
+
* (verdict, confidence, ensemble_triggered, estimated cost); and, on a
|
|
22
|
+
* failed Anthropic API call, only the HTTP status and a coarse error
|
|
23
|
+
* classification (rate_limit / insufficient_credit / other) -- never
|
|
24
|
+
* the request or response body.
|
|
25
|
+
* 2. `@posthog/mcp`'s standard MCP instrumentation, wired up in index.ts:
|
|
26
|
+
* tool name, call duration, error/success, and the same anonymous
|
|
27
|
+
* install ID as the distinct_id (via its `identify` option), so both
|
|
28
|
+
* halves count the same "user." Its `beforeSend` hook strips
|
|
29
|
+
* `$mcp_parameters`, `$mcp_response`, and `$mcp_intent`/
|
|
30
|
+
* `$mcp_intent_source` before anything leaves the process -- those
|
|
31
|
+
* would otherwise carry tool call arguments and output text.
|
|
32
|
+
*
|
|
21
33
|
* component_need text, requirements_checked evidence, and the API key
|
|
22
|
-
* itself are never sent. See SECURITY.md and README.md for
|
|
23
|
-
* disclosure and
|
|
34
|
+
* itself are never sent, by either half. See SECURITY.md and README.md for
|
|
35
|
+
* the full disclosure and how to opt out.
|
|
24
36
|
*
|
|
25
37
|
* Reuses Pattern's existing PostHog project (the same one the marketing
|
|
26
38
|
* site sends browser events to) with its public, write-only project key --
|
|
@@ -34,12 +46,13 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
34
46
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
35
47
|
import { homedir } from "node:os";
|
|
36
48
|
import { dirname, join } from "node:path";
|
|
37
|
-
// Opt-
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
// Any of "
|
|
42
|
-
|
|
49
|
+
// Opt-out, not opt-in. Previously opt-in; changed because opt-in meant
|
|
50
|
+
// near-zero real signal in practice (installs happened, but almost no one
|
|
51
|
+
// ever set the env var) -- see the one-time notice below, which is the
|
|
52
|
+
// disclosure mechanism that replaces an interactive consent prompt for a
|
|
53
|
+
// stdio MCP server. Any of "0", "false", "no" (case-insensitive) turns it
|
|
54
|
+
// off; anything else, including unset, leaves it on.
|
|
55
|
+
export const TELEMETRY_ENABLED = !/^(0|false|no)$/i.test(process.env.PATTERN_TELEMETRY ?? "");
|
|
43
56
|
// One-time startup notice, printed to stderr -- the closest thing to an
|
|
44
57
|
// opt-in prompt an MCP stdio server can safely show. stdin is the JSON-RPC
|
|
45
58
|
// channel the client uses to talk to this process; blocking on it to read
|
|
@@ -60,23 +73,24 @@ export function printTelemetryNoticeOnce() {
|
|
|
60
73
|
// No marker yet -- fall through and show it.
|
|
61
74
|
}
|
|
62
75
|
const status = TELEMETRY_ENABLED
|
|
63
|
-
? "ON
|
|
64
|
-
: "OFF
|
|
76
|
+
? "ON (the default)"
|
|
77
|
+
: "OFF, because PATTERN_TELEMETRY is set to 0/false/no";
|
|
65
78
|
console.error([
|
|
66
79
|
"",
|
|
67
80
|
"Pattern -- one-time telemetry notice (this will not print again)",
|
|
68
81
|
`Anonymous usage telemetry is currently ${status}.`,
|
|
69
82
|
"",
|
|
70
|
-
"When
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
83
|
+
"When on, Pattern sends an anonymous per-install ID, a one-way hash",
|
|
84
|
+
"of project_id (never the raw string), the same verdict summary",
|
|
85
|
+
"already written to ~/.pattern/calls.log (verdict, confidence,",
|
|
86
|
+
"reason, estimated cost), and standard MCP tool-call analytics (tool",
|
|
87
|
+
"name, duration, success/failure) via PostHog's MCP SDK. Tool call",
|
|
88
|
+
"arguments and responses are stripped before sending -- component_need,",
|
|
89
|
+
"domain, framework, existing_stack, and your API key are never sent.",
|
|
75
90
|
"Full field list: https://github.com/donaldrichard19-LVD/pattern-mcp#telemetry",
|
|
76
91
|
"",
|
|
77
|
-
"To
|
|
78
|
-
"
|
|
79
|
-
"Already on and want it off instead? Unset PATTERN_TELEMETRY (or set it to 0).",
|
|
92
|
+
"To opt out: PATTERN_TELEMETRY=0",
|
|
93
|
+
"Already off and want it back on? Unset PATTERN_TELEMETRY (or set it to 1).",
|
|
80
94
|
"",
|
|
81
95
|
].join("\n"));
|
|
82
96
|
try {
|
|
@@ -109,7 +123,7 @@ let cachedInstallId;
|
|
|
109
123
|
// would look like a brand-new anonymous user. Never derived from anything
|
|
110
124
|
// that identifies a person or machine (no hostname, no MAC, no username) --
|
|
111
125
|
// purely a random UUID with no way to reverse it to an identity.
|
|
112
|
-
function installId() {
|
|
126
|
+
export function installId() {
|
|
113
127
|
if (cachedInstallId)
|
|
114
128
|
return cachedInstallId;
|
|
115
129
|
try {
|
|
@@ -155,7 +169,10 @@ export function classifyApiError(message) {
|
|
|
155
169
|
return { type: "other", status };
|
|
156
170
|
}
|
|
157
171
|
let client;
|
|
158
|
-
|
|
172
|
+
// Exported so index.ts's @posthog/mcp instrument() call shares this same
|
|
173
|
+
// client/project instead of standing up a second PostHog connection --
|
|
174
|
+
// same reason both halves share installId() as their distinct_id.
|
|
175
|
+
export function getClient() {
|
|
159
176
|
if (!TELEMETRY_ENABLED || !POSTHOG_KEY)
|
|
160
177
|
return undefined;
|
|
161
178
|
if (!client) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pattern-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "MCP server that turns your design guidance into a checkable process -- evaluates UI components from external libraries (shadcn/ui, 21st.dev, ReUI) or your own registered design system against a requirements checklist, then tells the agent whether to reuse an existing component or build one from a concrete design reference.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
40
|
+
"@posthog/mcp": "^0.13.0",
|
|
40
41
|
"posthog-node": "^5.51.4"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|