claudeup 4.32.1 → 4.33.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeup",
3
- "version": "4.32.1",
3
+ "version": "4.33.0",
4
4
  "description": "TUI tool for managing Claude Code plugins, MCPs, and configuration",
5
5
  "type": "module",
6
6
  "main": "src/main.tsx",
@@ -64,8 +64,8 @@
64
64
  "typescript": "^5.6.3"
65
65
  },
66
66
  "optionalDependencies": {
67
- "claudeup-darwin-arm64": "4.32.1",
68
- "claudeup-darwin-x64": "4.32.1",
69
- "claudeup-linux-x64": "4.32.1"
67
+ "claudeup-darwin-arm64": "4.33.0",
68
+ "claudeup-darwin-x64": "4.33.0",
69
+ "claudeup-linux-x64": "4.33.0"
70
70
  }
71
71
  }
@@ -0,0 +1,158 @@
1
+ import { afterEach, describe, expect, it } from "bun:test";
2
+ import {
3
+ fetchCuratedMcpServers,
4
+ searchMcpServers,
5
+ } from "../services/mcp-registry.js";
6
+
7
+ const realFetch = globalThis.fetch;
8
+ const originalApiUrl = process.env.MCP_REGISTRY_API_URL;
9
+
10
+ const server = {
11
+ name: "io.example/filesystem",
12
+ url: "npm:@example/filesystem",
13
+ short_description: "Filesystem tools",
14
+ version: "1.2.3",
15
+ source_code_url: "https://github.com/example/filesystem",
16
+ package_registry: "npm",
17
+ published_at: "2026-08-01T00:00:00.000Z",
18
+ };
19
+
20
+ afterEach(() => {
21
+ globalThis.fetch = realFetch;
22
+ if (originalApiUrl === undefined) {
23
+ Reflect.deleteProperty(process.env, "MCP_REGISTRY_API_URL");
24
+ } else {
25
+ process.env.MCP_REGISTRY_API_URL = originalApiUrl;
26
+ }
27
+ });
28
+
29
+ describe("searchMcpServers", () => {
30
+ it("calls the models-index endpoint with encoded normalized parameters", async () => {
31
+ process.env.MCP_REGISTRY_API_URL = "";
32
+ let requestedUrl = "";
33
+ globalThis.fetch = (async (input) => {
34
+ requestedUrl = String(input);
35
+ return Response.json({ servers: [server], next_cursor: "next page" });
36
+ }) as typeof fetch;
37
+
38
+ const result = await searchMcpServers({
39
+ query: "file system & tools",
40
+ limit: 7,
41
+ cursor: "page/2?x=1",
42
+ });
43
+
44
+ const url = new URL(requestedUrl);
45
+ expect(`${url.origin}${url.pathname}`).toBe(
46
+ "https://us-central1-claudish-6da10.cloudfunctions.net/mcpRegistry/search",
47
+ );
48
+ expect(Object.fromEntries(url.searchParams)).toEqual({
49
+ q: "file system & tools",
50
+ limit: "7",
51
+ cursor: "page/2?x=1",
52
+ });
53
+ expect(result).toEqual({ servers: [server], next_cursor: "next page" });
54
+ });
55
+
56
+ it("uses MCP_REGISTRY_API_URL and omits an empty query", async () => {
57
+ process.env.MCP_REGISTRY_API_URL = "https://registry.example.test/base/";
58
+ let requestedUrl = "";
59
+ globalThis.fetch = (async (input) => {
60
+ requestedUrl = String(input);
61
+ return Response.json({ servers: [] });
62
+ }) as typeof fetch;
63
+
64
+ await searchMcpServers({ query: "", limit: 4 });
65
+
66
+ expect(requestedUrl).toBe(
67
+ "https://registry.example.test/base/search?limit=4",
68
+ );
69
+ });
70
+
71
+ it("throws on an HTTP failure", async () => {
72
+ globalThis.fetch = (async () =>
73
+ new Response("unavailable", {
74
+ status: 503,
75
+ statusText: "Service Unavailable",
76
+ })) as typeof fetch;
77
+
78
+ expect(searchMcpServers({ query: "filesystem" })).rejects.toThrow(
79
+ "MCP Registry API error: 503 Service Unavailable",
80
+ );
81
+ });
82
+
83
+ it("throws when the normalized response or a server is malformed", async () => {
84
+ globalThis.fetch = (async () =>
85
+ Response.json({
86
+ servers: [{ name: "missing-url", short_description: "Broken" }],
87
+ })) as typeof fetch;
88
+
89
+ expect(searchMcpServers()).rejects.toThrow(
90
+ "MCP Registry API returned a malformed response",
91
+ );
92
+ });
93
+
94
+ it("throws when the response is not valid JSON", async () => {
95
+ globalThis.fetch = (async () =>
96
+ new Response("not json", {
97
+ status: 200,
98
+ headers: { "content-type": "application/json" },
99
+ })) as typeof fetch;
100
+
101
+ expect(searchMcpServers()).rejects.toThrow(
102
+ "MCP Registry API returned a malformed response",
103
+ );
104
+ });
105
+
106
+ it("passes the caller abort signal to fetch", async () => {
107
+ const controller = new AbortController();
108
+ let receivedSignal: AbortSignal | null | undefined;
109
+ globalThis.fetch = (async (_input, init) => {
110
+ receivedSignal = init?.signal;
111
+ return Response.json({ servers: [] });
112
+ }) as typeof fetch;
113
+
114
+ await searchMcpServers({ signal: controller.signal });
115
+
116
+ expect(receivedSignal).toBe(controller.signal);
117
+ });
118
+ });
119
+
120
+ describe("fetchCuratedMcpServers", () => {
121
+ it("loads install-ready configurations from models-index", async () => {
122
+ process.env.MCP_REGISTRY_API_URL = "https://catalog.example.test/";
123
+ let requestedUrl = "";
124
+ globalThis.fetch = (async (input) => {
125
+ requestedUrl = String(input);
126
+ return Response.json({
127
+ schemaVersion: 1,
128
+ version: 2,
129
+ servers: [
130
+ {
131
+ name: "memory",
132
+ description: "Persistent memory",
133
+ command: "npx",
134
+ args: ["-y", "@modelcontextprotocol/server-memory@latest"],
135
+ category: "productivity",
136
+ },
137
+ ],
138
+ });
139
+ }) as typeof fetch;
140
+
141
+ const result = await fetchCuratedMcpServers();
142
+ expect(requestedUrl).toBe("https://catalog.example.test/recommended");
143
+ expect(result).toHaveLength(1);
144
+ expect(result[0].name).toBe("memory");
145
+ });
146
+
147
+ it("rejects malformed install configurations", async () => {
148
+ globalThis.fetch = (async () =>
149
+ Response.json({
150
+ servers: [
151
+ { name: "missing-command", description: "Broken", category: "ai" },
152
+ ],
153
+ })) as typeof fetch;
154
+ expect(fetchCuratedMcpServers()).rejects.toThrow(
155
+ "MCP catalog API returned a malformed response",
156
+ );
157
+ });
158
+ });
@@ -0,0 +1,48 @@
1
+ import { afterEach, describe, expect, it } from "bun:test";
2
+ import { fetchCuratedSkillsCatalog } from "../services/skillsmp-client.js";
3
+
4
+ const realFetch = globalThis.fetch;
5
+
6
+ afterEach(() => {
7
+ globalThis.fetch = realFetch;
8
+ });
9
+
10
+ describe("fetchCuratedSkillsCatalog", () => {
11
+ it("loads the versioned models-index catalog", async () => {
12
+ let requestedUrl = "";
13
+ globalThis.fetch = (async (input) => {
14
+ requestedUrl = String(input);
15
+ return Response.json({
16
+ schemaVersion: 1,
17
+ version: 1,
18
+ generatedAt: "2026-08-07T00:00:00.000Z",
19
+ skills: [
20
+ {
21
+ name: "Find Skills",
22
+ repo: "vercel-labs/skills",
23
+ skillPath: "skills/find-skills",
24
+ description: "Find skills",
25
+ category: "search",
26
+ },
27
+ ],
28
+ skillSets: [],
29
+ });
30
+ }) as typeof fetch;
31
+
32
+ const catalog = await fetchCuratedSkillsCatalog();
33
+ expect(requestedUrl).toBe(
34
+ "https://us-central1-claudish-6da10.cloudfunctions.net/skills/recommended",
35
+ );
36
+ expect(catalog.skills[0].skillPath).toBe("skills/find-skills");
37
+ });
38
+
39
+ it("rejects HTTP and malformed responses", async () => {
40
+ globalThis.fetch = (async () =>
41
+ new Response("down", { status: 503 })) as typeof fetch;
42
+ expect(fetchCuratedSkillsCatalog()).rejects.toThrow("HTTP 503");
43
+
44
+ globalThis.fetch = (async () =>
45
+ Response.json({ skills: [] })) as typeof fetch;
46
+ expect(fetchCuratedSkillsCatalog()).rejects.toThrow("malformed response");
47
+ });
48
+ });