@linktr.ee/arbor-mcp 0.2.0 → 0.4.0-rc.ef036d0d6.7141
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 +50 -1
- package/dist/index.js +265 -29
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,7 +6,10 @@ Claude Desktop, Cursor, Zed, etc.).
|
|
|
6
6
|
|
|
7
7
|
## What it does
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Registers a set of read-only Arbor design system tools. The
|
|
10
|
+
component-resolution tool is documented in detail below; the DS-as-data
|
|
11
|
+
metadata tools (component inventory, single-component lookup, version
|
|
12
|
+
history) are described under [DS-as-data tools](#ds-as-data-tools).
|
|
10
13
|
|
|
11
14
|
### `arbor_open_in_playroom`
|
|
12
15
|
|
|
@@ -54,6 +57,52 @@ and no `slug`.
|
|
|
54
57
|
> on the Figma plugin's selection-driven node-ID path. This by-name MCP tool
|
|
55
58
|
> never returns it — its `outputSchema` is `mapped | not_found`.
|
|
56
59
|
|
|
60
|
+
## DS-as-data tools
|
|
61
|
+
|
|
62
|
+
Three read-only metadata tools (shipping in a forthcoming release) expose the
|
|
63
|
+
design system as queryable data. They answer "what components / versions exist"
|
|
64
|
+
— **not** token values. (For token values, read the compiled CSS in
|
|
65
|
+
`packages/design-tokens/dist/web/` or the shadcn `registry.json`, which are
|
|
66
|
+
fresher.)
|
|
67
|
+
|
|
68
|
+
- **`arbor_list_versions`** — design system version history: each entry is
|
|
69
|
+
`{ id, name, createdAt, readOnly }`.
|
|
70
|
+
- **`arbor_list_components`** — component inventory: `name`, `id`, and a short
|
|
71
|
+
`description` per component. Returns top-level components only (Figma variant
|
|
72
|
+
instances are filtered out). Accepts an optional case-insensitive `query`
|
|
73
|
+
string that filters by name or description.
|
|
74
|
+
- **`arbor_get_component`** — one component's metadata, looked up by `name`
|
|
75
|
+
(case-insensitive) or `id`. Returns `{ found, component }`.
|
|
76
|
+
|
|
77
|
+
(A fourth tool, `arbor_get_docs`, is deliberately **not** built yet — deferred
|
|
78
|
+
until a docs page-content route exists.)
|
|
79
|
+
|
|
80
|
+
### Runtime config + graceful degradation
|
|
81
|
+
|
|
82
|
+
These tools read the design system's metadata from an internal Lambda
|
|
83
|
+
federation proxy. The proxy **URL is built in** (override with
|
|
84
|
+
`ARBOR_MCP_SUPERNOVA_PROXY_URL`). While the route is access-gated, also set
|
|
85
|
+
`ARBOR_MCP_SUPERNOVA_PROXY_TOKEN` — one shared internal value provisioned by
|
|
86
|
+
the Arbor team (not a per-engineer token, and never bundled in this package).
|
|
87
|
+
If the route is open, the tools work with no configuration at all:
|
|
88
|
+
|
|
89
|
+
| Variable | Purpose |
|
|
90
|
+
| --------------------------------- | -------------------------------------------------------- |
|
|
91
|
+
| `ARBOR_MCP_SUPERNOVA_PROXY_URL` | Full proxy URL, including the `/supernova/query` path |
|
|
92
|
+
| `ARBOR_MCP_SUPERNOVA_PROXY_TOKEN` | Shared inbound bearer token presented to the proxy |
|
|
93
|
+
|
|
94
|
+
When either var is unset — or the proxy is unreachable — the tools **degrade
|
|
95
|
+
gracefully**: they return a successful result with `source: "unavailable"` and
|
|
96
|
+
a `detail` string explaining why. They never throw and never crash the server.
|
|
97
|
+
An agent should treat `"unavailable"` as "couldn't reach the data" rather than
|
|
98
|
+
"the design system has no components/versions."
|
|
99
|
+
|
|
100
|
+
> **Metadata, not token values.** These tools surface Supernova *metadata*
|
|
101
|
+
> (component names, ids, descriptions, version history) only. They deliberately
|
|
102
|
+
> do **not** return token values — those live in the compiled CSS
|
|
103
|
+
> (`packages/design-tokens/dist/web/`) and the shadcn `registry.json`, which are
|
|
104
|
+
> the fresher source of truth.
|
|
105
|
+
|
|
57
106
|
## Install
|
|
58
107
|
|
|
59
108
|
The package is published publicly to npm as `@linktr.ee/arbor-mcp`. The
|
package/dist/index.js
CHANGED
|
@@ -1582,8 +1582,8 @@ var require_lz_string = __commonJS({
|
|
|
1582
1582
|
var require_hash_code = __commonJS({
|
|
1583
1583
|
"../../apps/playroom/src/hash-code.js"(exports, module) {
|
|
1584
1584
|
"use strict";
|
|
1585
|
-
function hashCode2(
|
|
1586
|
-
var s = String(
|
|
1585
|
+
function hashCode2(str3) {
|
|
1586
|
+
var s = String(str3 == null ? "" : str3);
|
|
1587
1587
|
var h = 2166136261;
|
|
1588
1588
|
for (var i = 0; i < s.length; i++) {
|
|
1589
1589
|
h ^= s.charCodeAt(i);
|
|
@@ -5913,14 +5913,247 @@ var checkUxWritingTool = {
|
|
|
5913
5913
|
handler: handleCheckUxWriting
|
|
5914
5914
|
};
|
|
5915
5915
|
|
|
5916
|
+
// src/lib/supernova-client.ts
|
|
5917
|
+
var CACHE_TTL_MS2 = 5 * 60 * 1e3;
|
|
5918
|
+
var DEFAULT_TIMEOUT_MS2 = 9e3;
|
|
5919
|
+
var DEFAULT_MAX_CHARS2 = 4e6;
|
|
5920
|
+
var DEFAULT_PROXY_URL = "https://on7uu5jns5nmsusrk6gg3suu640clium.lambda-url.us-west-2.on.aws/supernova/query";
|
|
5921
|
+
var moduleCache2 = /* @__PURE__ */ new Map();
|
|
5922
|
+
function statusReason(status) {
|
|
5923
|
+
if (status === 401) return "unauthorized";
|
|
5924
|
+
if (status === 400) return "bad_request";
|
|
5925
|
+
if (status === 502) return "upstream_error";
|
|
5926
|
+
if (status === 503) return "upstream_unconfigured";
|
|
5927
|
+
return "http_error";
|
|
5928
|
+
}
|
|
5929
|
+
async function querySupernova(resource, opts = {}) {
|
|
5930
|
+
const {
|
|
5931
|
+
designSystemId,
|
|
5932
|
+
versionId,
|
|
5933
|
+
proxyUrl = process.env.ARBOR_MCP_SUPERNOVA_PROXY_URL ?? "",
|
|
5934
|
+
proxyToken = process.env.ARBOR_MCP_SUPERNOVA_PROXY_TOKEN ?? "",
|
|
5935
|
+
fetchImpl = globalThis.fetch,
|
|
5936
|
+
now = Date.now,
|
|
5937
|
+
cache = moduleCache2,
|
|
5938
|
+
timeoutMs = DEFAULT_TIMEOUT_MS2,
|
|
5939
|
+
maxChars = DEFAULT_MAX_CHARS2
|
|
5940
|
+
} = opts;
|
|
5941
|
+
const url = proxyUrl || DEFAULT_PROXY_URL;
|
|
5942
|
+
let parsed;
|
|
5943
|
+
try {
|
|
5944
|
+
parsed = new URL(url);
|
|
5945
|
+
} catch {
|
|
5946
|
+
return { ok: false, reason: "not_configured", detail: `proxy URL is not valid: ${url}` };
|
|
5947
|
+
}
|
|
5948
|
+
if (parsed.protocol !== "https:") {
|
|
5949
|
+
return { ok: false, reason: "not_configured", detail: `proxy URL must be https (got ${parsed.protocol})` };
|
|
5950
|
+
}
|
|
5951
|
+
const cacheKey = `${resource}|${designSystemId ?? ""}|${versionId ?? ""}`;
|
|
5952
|
+
const cached = cache.get(cacheKey);
|
|
5953
|
+
if (cached && now() - cached.fetchedAt < CACHE_TTL_MS2) return cached.value;
|
|
5954
|
+
const body = { resource };
|
|
5955
|
+
if (designSystemId) body.designSystemId = designSystemId;
|
|
5956
|
+
if (versionId) body.versionId = versionId;
|
|
5957
|
+
const controller = new AbortController();
|
|
5958
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
5959
|
+
let res;
|
|
5960
|
+
let text;
|
|
5961
|
+
try {
|
|
5962
|
+
res = await fetchImpl(url, {
|
|
5963
|
+
method: "POST",
|
|
5964
|
+
headers: { "Content-Type": "application/json", ...proxyToken ? { Authorization: `Bearer ${proxyToken}` } : {} },
|
|
5965
|
+
body: JSON.stringify(body),
|
|
5966
|
+
signal: controller.signal,
|
|
5967
|
+
redirect: "error"
|
|
5968
|
+
});
|
|
5969
|
+
text = await res.text();
|
|
5970
|
+
} catch (err) {
|
|
5971
|
+
const e = err;
|
|
5972
|
+
return { ok: false, reason: e && e.name === "AbortError" ? "timeout" : "network", detail: e?.message ?? String(err) };
|
|
5973
|
+
} finally {
|
|
5974
|
+
clearTimeout(timer);
|
|
5975
|
+
}
|
|
5976
|
+
if (!res.ok) {
|
|
5977
|
+
return { ok: false, reason: statusReason(res.status), detail: `HTTP ${res.status} from proxy`, status: res.status };
|
|
5978
|
+
}
|
|
5979
|
+
if (text.length > maxChars) {
|
|
5980
|
+
return { ok: false, reason: "too_large", detail: `${text.length} chars > ${maxChars} cap` };
|
|
5981
|
+
}
|
|
5982
|
+
let envelope;
|
|
5983
|
+
try {
|
|
5984
|
+
envelope = JSON.parse(text);
|
|
5985
|
+
} catch (err) {
|
|
5986
|
+
return { ok: false, reason: "parse_error", detail: err?.message ?? String(err) };
|
|
5987
|
+
}
|
|
5988
|
+
if (envelope.data == null) {
|
|
5989
|
+
return { ok: false, reason: "parse_error", detail: "proxy returned 200 with no data payload" };
|
|
5990
|
+
}
|
|
5991
|
+
const value = {
|
|
5992
|
+
ok: true,
|
|
5993
|
+
data: envelope.data,
|
|
5994
|
+
versionId: typeof envelope.versionId === "string" ? envelope.versionId : null,
|
|
5995
|
+
fetchedAt: typeof envelope.fetchedAt === "string" ? envelope.fetchedAt : null
|
|
5996
|
+
};
|
|
5997
|
+
cache.set(cacheKey, { value, fetchedAt: now() });
|
|
5998
|
+
return value;
|
|
5999
|
+
}
|
|
6000
|
+
|
|
6001
|
+
// src/tools/list-components.ts
|
|
6002
|
+
var TOOL_NAME3 = "arbor_list_components";
|
|
6003
|
+
var TOOL_TITLE3 = "List Arbor components";
|
|
6004
|
+
var TOOL_DESCRIPTION3 = "List the components in the Arbor design system (name, id, short description) from Supernova via the Arbor federation proxy. Read-only metadata for discovery; returns top-level components, not Figma variants. Does NOT return token values, source code, or props \u2014 use arbor_get_component for one component, the shadcn registry/Storybook for source, arbor_open_in_playroom for a runnable snippet. Does NOT change anything.";
|
|
6005
|
+
var inputSchema3 = external_exports.object({ query: external_exports.string().trim().min(1).max(100).optional().describe("Optional case-insensitive name/description filter") }).strict();
|
|
6006
|
+
var outputSchema3 = external_exports.object({
|
|
6007
|
+
source: external_exports.enum(["remote", "unavailable"]),
|
|
6008
|
+
total: external_exports.number(),
|
|
6009
|
+
components: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string(), description: external_exports.string().nullable() })),
|
|
6010
|
+
detail: external_exports.string().nullable()
|
|
6011
|
+
});
|
|
6012
|
+
var annotations3 = { readOnlyHint: true, idempotentHint: true, openWorldHint: true, destructiveHint: false };
|
|
6013
|
+
function extractComponents(data) {
|
|
6014
|
+
const list = data?.result?.components;
|
|
6015
|
+
if (!Array.isArray(list)) return null;
|
|
6016
|
+
return list.filter((c) => {
|
|
6017
|
+
if (!c || typeof c !== "object") return false;
|
|
6018
|
+
const parent = c.parentComponentPersistentId;
|
|
6019
|
+
return !(typeof parent === "string" && parent.length > 0);
|
|
6020
|
+
}).map((c) => {
|
|
6021
|
+
const meta = c.meta && typeof c.meta === "object" ? c.meta : {};
|
|
6022
|
+
const name = typeof meta.name === "string" && meta.name.length > 0 ? meta.name : "(unnamed)";
|
|
6023
|
+
const desc = typeof meta.description === "string" ? meta.description.trim() : "";
|
|
6024
|
+
return { id: String(c.id ?? ""), name, description: desc.length > 0 ? desc : null };
|
|
6025
|
+
});
|
|
6026
|
+
}
|
|
6027
|
+
async function handleListComponents(args, deps = {}) {
|
|
6028
|
+
const query = deps.query ?? ((r) => querySupernova(r));
|
|
6029
|
+
const result = await query("components");
|
|
6030
|
+
let out;
|
|
6031
|
+
const extracted = result.ok ? extractComponents(result.data) : null;
|
|
6032
|
+
if (!result.ok) {
|
|
6033
|
+
out = { source: "unavailable", total: 0, components: [], detail: `could not load components (${result.reason})` };
|
|
6034
|
+
} else if (extracted === null) {
|
|
6035
|
+
out = { source: "unavailable", total: 0, components: [], detail: "proxy returned an unrecognized components payload" };
|
|
6036
|
+
} else {
|
|
6037
|
+
let components = extracted;
|
|
6038
|
+
const q = args.query?.toLowerCase();
|
|
6039
|
+
if (q) {
|
|
6040
|
+
components = components.filter(
|
|
6041
|
+
(c) => c.name.toLowerCase().includes(q) || (c.description ? c.description.toLowerCase().includes(q) : false)
|
|
6042
|
+
);
|
|
6043
|
+
}
|
|
6044
|
+
out = { source: "remote", total: components.length, components, detail: null };
|
|
6045
|
+
}
|
|
6046
|
+
const summary = out.source === "unavailable" ? `Could not load components (${out.detail}). Treat as unavailable \u2014 do not assume there are none.` : `${out.total} component${out.total === 1 ? "" : "s"}${args.query ? ` matching "${args.query}"` : ""}.`;
|
|
6047
|
+
return { content: [{ type: "text", text: summary }], structuredContent: out };
|
|
6048
|
+
}
|
|
6049
|
+
var listComponentsTool = {
|
|
6050
|
+
name: TOOL_NAME3,
|
|
6051
|
+
config: { title: TOOL_TITLE3, description: TOOL_DESCRIPTION3, inputSchema: inputSchema3, outputSchema: outputSchema3, annotations: annotations3 },
|
|
6052
|
+
handler: handleListComponents
|
|
6053
|
+
};
|
|
6054
|
+
|
|
6055
|
+
// src/tools/get-component.ts
|
|
6056
|
+
var TOOL_NAME4 = "arbor_get_component";
|
|
6057
|
+
var TOOL_TITLE4 = "Get one Arbor component";
|
|
6058
|
+
var TOOL_DESCRIPTION4 = "Get a single Arbor component's metadata by name (case-insensitive) or id from Supernova via the Arbor federation proxy (name, id, description). Read-only; matches top-level components, not Figma variants. Does NOT return token values or rendered source \u2014 use the shadcn registry / Storybook for implementation and arbor_open_in_playroom for a runnable snippet. Does NOT change anything.";
|
|
6059
|
+
var inputSchema4 = external_exports.object({
|
|
6060
|
+
name: external_exports.string().trim().min(1).max(100).optional().describe('Component name (case-insensitive), e.g. "Button"'),
|
|
6061
|
+
id: external_exports.string().trim().min(1).max(100).optional().describe("Supernova component id")
|
|
6062
|
+
}).strict();
|
|
6063
|
+
var outputSchema4 = external_exports.object({
|
|
6064
|
+
source: external_exports.enum(["remote", "unavailable"]),
|
|
6065
|
+
found: external_exports.boolean(),
|
|
6066
|
+
component: external_exports.object({ id: external_exports.string(), name: external_exports.string(), description: external_exports.string().nullable() }).nullable(),
|
|
6067
|
+
detail: external_exports.string().nullable()
|
|
6068
|
+
});
|
|
6069
|
+
var annotations4 = { readOnlyHint: true, idempotentHint: true, openWorldHint: true, destructiveHint: false };
|
|
6070
|
+
async function handleGetComponent(args, deps = {}) {
|
|
6071
|
+
const name = args.name?.trim();
|
|
6072
|
+
const id = args.id?.trim();
|
|
6073
|
+
if (!name && !id) {
|
|
6074
|
+
const out2 = { source: "remote", found: false, component: null, detail: "provide name or id" };
|
|
6075
|
+
return {
|
|
6076
|
+
content: [{ type: "text", text: 'Provide a component name or id \u2014 e.g. {"name":"Button"} or {"id":"35988997"}.' }],
|
|
6077
|
+
structuredContent: out2
|
|
6078
|
+
};
|
|
6079
|
+
}
|
|
6080
|
+
const query = deps.query ?? ((r) => querySupernova(r));
|
|
6081
|
+
const result = await query("components");
|
|
6082
|
+
let out;
|
|
6083
|
+
if (!result.ok) {
|
|
6084
|
+
out = { source: "unavailable", found: false, component: null, detail: `could not load components (${result.reason})` };
|
|
6085
|
+
} else {
|
|
6086
|
+
const components = extractComponents(result.data);
|
|
6087
|
+
if (components === null) {
|
|
6088
|
+
out = { source: "unavailable", found: false, component: null, detail: "proxy returned an unrecognized components payload" };
|
|
6089
|
+
} else {
|
|
6090
|
+
const match = id ? components.find((c) => c.id === id) : components.find((c) => c.name.toLowerCase() === name.toLowerCase());
|
|
6091
|
+
out = match ? { source: "remote", found: true, component: match, detail: null } : { source: "remote", found: false, component: null, detail: `no component ${id ? `with id ${id}` : `named "${name}"`}` };
|
|
6092
|
+
}
|
|
6093
|
+
}
|
|
6094
|
+
const summary = out.source === "unavailable" ? `Could not load components (${out.detail}). Treat as unavailable.` : out.found ? `${out.component.name}${out.component.description ? ` \u2014 ${out.component.description}` : " (no description)"}.` : `No Arbor component ${args.id ? `with id ${args.id.trim()}` : `named "${args.name?.trim()}"`} exists.`;
|
|
6095
|
+
return { content: [{ type: "text", text: summary }], structuredContent: out };
|
|
6096
|
+
}
|
|
6097
|
+
var getComponentTool = {
|
|
6098
|
+
name: TOOL_NAME4,
|
|
6099
|
+
config: { title: TOOL_TITLE4, description: TOOL_DESCRIPTION4, inputSchema: inputSchema4, outputSchema: outputSchema4, annotations: annotations4 },
|
|
6100
|
+
handler: handleGetComponent
|
|
6101
|
+
};
|
|
6102
|
+
|
|
6103
|
+
// src/tools/list-versions.ts
|
|
6104
|
+
var TOOL_NAME5 = "arbor_list_versions";
|
|
6105
|
+
var TOOL_TITLE5 = "List Arbor design system versions";
|
|
6106
|
+
var TOOL_DESCRIPTION5 = "List the version history of the Arbor design system (id, name, created date, read-only flag) from Supernova via the Arbor federation proxy. Read-only metadata. Does NOT return token values (use the compiled CSS / registry) and does NOT change anything.";
|
|
6107
|
+
var inputSchema5 = external_exports.object({}).strict();
|
|
6108
|
+
var outputSchema5 = external_exports.object({
|
|
6109
|
+
source: external_exports.enum(["remote", "unavailable"]).describe("'remote' = fetched; 'unavailable' = proxy fetch failed"),
|
|
6110
|
+
total: external_exports.number().describe("Number of versions returned"),
|
|
6111
|
+
versions: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string(), createdAt: external_exports.string().nullable(), readOnly: external_exports.boolean().nullable() })).describe("Versions, in the order Supernova returned them"),
|
|
6112
|
+
detail: external_exports.string().nullable().describe("Degraded reason when source is unavailable")
|
|
6113
|
+
});
|
|
6114
|
+
var annotations5 = { readOnlyHint: true, idempotentHint: true, openWorldHint: true, destructiveHint: false };
|
|
6115
|
+
function extractList(data) {
|
|
6116
|
+
const wrapped = data?.result?.designSystemVersions;
|
|
6117
|
+
const list = Array.isArray(wrapped) ? wrapped : Array.isArray(data) ? data : null;
|
|
6118
|
+
if (list === null) return null;
|
|
6119
|
+
return list.filter((x) => !!x && typeof x === "object");
|
|
6120
|
+
}
|
|
6121
|
+
var str2 = (v) => typeof v === "string" && v.length > 0 ? v : null;
|
|
6122
|
+
async function handleListVersions(_args, deps = {}) {
|
|
6123
|
+
const query = deps.query ?? ((r) => querySupernova(r));
|
|
6124
|
+
const result = await query("versions");
|
|
6125
|
+
let out;
|
|
6126
|
+
const list = result.ok ? extractList(result.data) : null;
|
|
6127
|
+
if (!result.ok) {
|
|
6128
|
+
out = { source: "unavailable", total: 0, versions: [], detail: `could not load versions (${result.reason})` };
|
|
6129
|
+
} else if (list === null) {
|
|
6130
|
+
out = { source: "unavailable", total: 0, versions: [], detail: "proxy returned an unrecognized versions payload" };
|
|
6131
|
+
} else {
|
|
6132
|
+
const versions = list.map((v) => ({
|
|
6133
|
+
id: String(v.id ?? ""),
|
|
6134
|
+
name: str2(v.version) ?? str2(v.name) ?? "(unnamed)",
|
|
6135
|
+
createdAt: str2(v.createdAt),
|
|
6136
|
+
readOnly: typeof v.isReadonly === "boolean" ? v.isReadonly : null
|
|
6137
|
+
}));
|
|
6138
|
+
out = { source: "remote", total: versions.length, versions, detail: null };
|
|
6139
|
+
}
|
|
6140
|
+
const summary = out.source === "unavailable" ? `Could not load design system versions (${out.detail}). Treat as unavailable \u2014 do not assume there are none.` : `${out.total} design system version${out.total === 1 ? "" : "s"}: ${out.versions.map((v) => v.name).join(", ") || "(none)"}.`;
|
|
6141
|
+
return { content: [{ type: "text", text: summary }], structuredContent: out };
|
|
6142
|
+
}
|
|
6143
|
+
var listVersionsTool = {
|
|
6144
|
+
name: TOOL_NAME5,
|
|
6145
|
+
config: { title: TOOL_TITLE5, description: TOOL_DESCRIPTION5, inputSchema: inputSchema5, outputSchema: outputSchema5, annotations: annotations5 },
|
|
6146
|
+
handler: handleListVersions
|
|
6147
|
+
};
|
|
6148
|
+
|
|
5916
6149
|
// src/tools/lookup-decision.ts
|
|
5917
6150
|
var ARTIFACT2 = "decision-log.json";
|
|
5918
6151
|
var CHARACTER_LIMIT = 25e3;
|
|
5919
6152
|
var DEFAULT_LIMIT = 5;
|
|
5920
|
-
var
|
|
5921
|
-
var
|
|
5922
|
-
var
|
|
5923
|
-
var
|
|
6153
|
+
var TOOL_NAME6 = "arbor_lookup_decision";
|
|
6154
|
+
var TOOL_TITLE6 = "Look up an Arbor decision-log entry";
|
|
6155
|
+
var TOOL_DESCRIPTION6 = 'Look up Arbor decision-log entries \u2014 the record of WHY design-system decisions were made \u2014 by id (e.g. "DL-067"), tag, or keyword. Keyword search spans the title, tags, and every section (context, decision, outcome, rationale, lessons, references, \u2026). Results are newest-first and paginated. Use this for "why did Arbor decide X?" / "what resolved <problem>?". Does NOT return component metadata or token values (those are separate); it is the decision/rationale record only.';
|
|
6156
|
+
var inputSchema6 = external_exports.object({
|
|
5924
6157
|
id: external_exports.string().optional().describe('A specific decision id, e.g. "DL-067" (a bare "67" also resolves)'),
|
|
5925
6158
|
tag: external_exports.string().optional().describe('Filter to entries carrying this tag (case-insensitive), e.g. "tokens"'),
|
|
5926
6159
|
query: external_exports.string().optional().describe("Keyword search across title, tags, and all sections (case-insensitive)"),
|
|
@@ -5938,7 +6171,7 @@ var decisionEntrySchema = external_exports.object({
|
|
|
5938
6171
|
tags: external_exports.array(external_exports.string()),
|
|
5939
6172
|
sections: external_exports.array(sectionSchema).describe("Ordered sections (Context, Decision, \u2026, plus any non-standard headings)")
|
|
5940
6173
|
});
|
|
5941
|
-
var
|
|
6174
|
+
var outputSchema6 = external_exports.object({
|
|
5942
6175
|
total: external_exports.number().describe("Total entries matching the filter (before pagination)"),
|
|
5943
6176
|
count: external_exports.number().describe("Entries actually returned (after pagination + character budget)"),
|
|
5944
6177
|
offset: external_exports.number(),
|
|
@@ -5949,7 +6182,7 @@ var outputSchema3 = external_exports.object({
|
|
|
5949
6182
|
stale: external_exports.boolean().describe("True when the snapshot is older than the freshness threshold (or unavailable)"),
|
|
5950
6183
|
entries: external_exports.array(decisionEntrySchema)
|
|
5951
6184
|
});
|
|
5952
|
-
var
|
|
6185
|
+
var annotations6 = {
|
|
5953
6186
|
readOnlyHint: true,
|
|
5954
6187
|
idempotentHint: true,
|
|
5955
6188
|
openWorldHint: true,
|
|
@@ -6065,13 +6298,13 @@ ${included.map(renderEntry).join("\n\n---\n\n")}${note}`;
|
|
|
6065
6298
|
};
|
|
6066
6299
|
}
|
|
6067
6300
|
var lookupDecisionTool = {
|
|
6068
|
-
name:
|
|
6301
|
+
name: TOOL_NAME6,
|
|
6069
6302
|
config: {
|
|
6070
|
-
title:
|
|
6071
|
-
description:
|
|
6072
|
-
inputSchema:
|
|
6073
|
-
outputSchema:
|
|
6074
|
-
annotations:
|
|
6303
|
+
title: TOOL_TITLE6,
|
|
6304
|
+
description: TOOL_DESCRIPTION6,
|
|
6305
|
+
inputSchema: inputSchema6,
|
|
6306
|
+
outputSchema: outputSchema6,
|
|
6307
|
+
annotations: annotations6
|
|
6075
6308
|
},
|
|
6076
6309
|
handler: handleLookupDecision
|
|
6077
6310
|
};
|
|
@@ -7185,7 +7418,7 @@ var { hashCode } = require_hash_code();
|
|
|
7185
7418
|
var MAX_SNIPPET_BYTES = 200 * 1024;
|
|
7186
7419
|
var SLUG_PATTERN = /^[a-z]{3,11}-[a-z]{3,11}-[a-z]{3,11}-(?:[1-9]|[1-9][0-9])$/;
|
|
7187
7420
|
var PLAYROOM_BASE_URL = "https://arbor.linktr.ee/playroom/";
|
|
7188
|
-
var
|
|
7421
|
+
var DEFAULT_TIMEOUT_MS3 = 5e3;
|
|
7189
7422
|
function createInMemoryCache() {
|
|
7190
7423
|
const store = /* @__PURE__ */ new Map();
|
|
7191
7424
|
return {
|
|
@@ -7216,7 +7449,7 @@ function createSlugClient(opts) {
|
|
|
7216
7449
|
);
|
|
7217
7450
|
}
|
|
7218
7451
|
const cache = opts.cache ?? createInMemoryCache();
|
|
7219
|
-
const timeoutMs = opts.timeoutMs ??
|
|
7452
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
|
|
7220
7453
|
const endpointUrl = opts.endpoint.toString();
|
|
7221
7454
|
const inflight = /* @__PURE__ */ new Map();
|
|
7222
7455
|
async function performMint(jsx, key, mintOpts) {
|
|
@@ -7304,14 +7537,14 @@ function extractSlug(payload) {
|
|
|
7304
7537
|
}
|
|
7305
7538
|
|
|
7306
7539
|
// src/tools/open-in-playroom.ts
|
|
7307
|
-
var
|
|
7540
|
+
var TOOL_NAME7 = "arbor_open_in_playroom";
|
|
7308
7541
|
var DEPRECATED_TOOL_ALIAS = "arbor.open_in_playroom";
|
|
7309
|
-
var
|
|
7310
|
-
var
|
|
7311
|
-
var
|
|
7542
|
+
var TOOL_TITLE7 = "Open in Playroom";
|
|
7543
|
+
var TOOL_DESCRIPTION7 = `Given an Arbor component name (PascalCase), return the Playroom URL with the canonical snippet pre-loaded. coverage="mapped" resolves to a canonical snippet (use with high confidence); coverage="not_found" links to the Playroom homepage (surface this to the human rather than following it silently). Does NOT do fuzzy/Figma-name matching \u2014 that is the Figma plugin's selection-driven path.`;
|
|
7544
|
+
var inputSchema7 = external_exports.object({
|
|
7312
7545
|
componentName: external_exports.string().trim().min(1, "componentName is required and must be a non-empty string").describe('Arbor PascalCase component name, e.g. "Button" or "HeaderBar"')
|
|
7313
7546
|
}).strict();
|
|
7314
|
-
var
|
|
7547
|
+
var outputSchema7 = external_exports.object({
|
|
7315
7548
|
coverage: external_exports.enum(["mapped", "not_found"]).describe("mapped = resolved to a canonical snippet; not_found = no match, URL is the Playroom home"),
|
|
7316
7549
|
url: external_exports.string().describe("Playroom URL \u2014 a ?slug= link, an lz-string #?code= fallback, or the homepage"),
|
|
7317
7550
|
arborComponentName: external_exports.string().optional().describe("Resolved Arbor component name (mapped only)"),
|
|
@@ -7322,7 +7555,7 @@ var outputSchema4 = external_exports.object({
|
|
|
7322
7555
|
degraded: external_exports.literal(true).optional().describe("True when slug minting failed and the URL fell back to lz-string"),
|
|
7323
7556
|
reason: external_exports.enum(["timeout", "http_error", "network", "oversize", "malformed_response", "unconfigured"]).optional().describe("Why the result is degraded")
|
|
7324
7557
|
});
|
|
7325
|
-
var
|
|
7558
|
+
var annotations7 = {
|
|
7326
7559
|
readOnlyHint: false,
|
|
7327
7560
|
idempotentHint: true,
|
|
7328
7561
|
openWorldHint: true,
|
|
@@ -7423,14 +7656,14 @@ async function handleOpenInPlayroom(args) {
|
|
|
7423
7656
|
};
|
|
7424
7657
|
}
|
|
7425
7658
|
var openInPlayroomTool = {
|
|
7426
|
-
name:
|
|
7659
|
+
name: TOOL_NAME7,
|
|
7427
7660
|
aliases: [DEPRECATED_TOOL_ALIAS],
|
|
7428
7661
|
config: {
|
|
7429
|
-
title:
|
|
7430
|
-
description:
|
|
7431
|
-
inputSchema:
|
|
7432
|
-
outputSchema:
|
|
7433
|
-
annotations:
|
|
7662
|
+
title: TOOL_TITLE7,
|
|
7663
|
+
description: TOOL_DESCRIPTION7,
|
|
7664
|
+
inputSchema: inputSchema7,
|
|
7665
|
+
outputSchema: outputSchema7,
|
|
7666
|
+
annotations: annotations7
|
|
7434
7667
|
},
|
|
7435
7668
|
handler: handleOpenInPlayroom
|
|
7436
7669
|
};
|
|
@@ -7465,7 +7698,10 @@ var ARBOR_TOOLS = [
|
|
|
7465
7698
|
openInPlayroomTool,
|
|
7466
7699
|
checkSyncHealthTool,
|
|
7467
7700
|
checkUxWritingTool,
|
|
7468
|
-
lookupDecisionTool
|
|
7701
|
+
lookupDecisionTool,
|
|
7702
|
+
listVersionsTool,
|
|
7703
|
+
listComponentsTool,
|
|
7704
|
+
getComponentTool
|
|
7469
7705
|
];
|
|
7470
7706
|
function createArborMcpServer() {
|
|
7471
7707
|
const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linktr.ee/arbor-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Model Context Protocol server exposing Arbor design system tools: Playroom snippets, Figma→code sync health, UX-writing checks,
|
|
3
|
+
"version": "0.4.0-rc.ef036d0d6.7141",
|
|
4
|
+
"description": "Model Context Protocol server exposing Arbor design system tools: Playroom snippets, Figma→code sync health, UX-writing checks, decision-log lookup, and federated component/version metadata.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"arbor",
|
|
7
7
|
"linktree",
|