@lpm-registry/mcp-server 0.1.0 → 0.2.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 CHANGED
@@ -98,15 +98,17 @@ For local development, copy `.env.example` to `.env.local` and set your local re
98
98
  | `lpm_package_info` | Get package metadata, install method, access model, and readme | Optional |
99
99
  | `lpm_api_docs` | Get structured API docs — functions, classes, types, signatures | Optional |
100
100
  | `lpm_llm_context` | Get LLM-optimized usage guide — quickStart, patterns, gotchas | Optional |
101
- | `lpm_package_context` | Get complete package context in one call — metadata, API docs, and usage guide | Optional |
102
- | `lpm_browse_source` | Browse package source code — file tree and file contents | Yes |
101
+ | `lpm_package_context` | Get complete package context in one call (pre-install evaluation) | Optional |
102
+ | `lpm_package_skills` | Get Agent Skills for building with an installed package | Optional |
103
+ | `lpm_docs` | Search or read LPM documentation (setup, CLI, publishing, etc.) | No |
104
+ | `lpm_browse_source` | Browse package source code remotely (last resort) | Yes |
103
105
  | `lpm_add` | Add a package by extracting source files into the project | Yes |
104
- | `lpm_install` | Install a package as a dependency to node_modules | Yes |
105
- | `lpm_get_install_command` | Get the correct CLI command (`lpm add` vs `lpm install`) | Optional |
106
+ | `lpm_install` | Install a managed dependency (JS → node_modules, Swift → Package.swift) | Yes |
107
+ | `lpm_audit` | Security audit — behavioral tags, AI findings, quality scores | Yes |
108
+ | `lpm_marketplace_info` | Marketplace pricing, licensing, and seat management | Optional |
106
109
  | `lpm_quality_report` | Get quality score and 28-check breakdown | Optional |
107
110
  | `lpm_search_owners` | Search for users or organizations by name | No |
108
111
  | `lpm_packages_by_owner` | List packages published by a specific user or org | No |
109
- | `lpm_package_skills` | Fetch Agent Skills for a package | Optional |
110
112
  | `lpm_pool_stats` | Get your Pool revenue earnings for the current month | Yes |
111
113
  | `lpm_user_info` | Get authenticated user info, orgs, and usage | Yes |
112
114
 
@@ -115,13 +117,13 @@ For local development, copy `.env.example` to `.env.local` and set your local re
115
117
  A typical AI agent workflow for finding and adding a package:
116
118
 
117
119
  1. **Search** — `lpm_search` to find packages matching the need
118
- 2. **Understand** — `lpm_package_context` to get everything in one call: metadata, API docs, and usage guide
119
- 3. **Browse** — `lpm_browse_source` (tree first, then specific files) for deeper code understanding if needed
120
- 4. **Install** — `lpm_add` for components/blocks/Swift or `lpm_install` for dependencies
120
+ 2. **Evaluate** — `lpm_package_context` to understand the package before installing (metadata, API docs, usage guide)
121
+ 3. **Install** — `lpm_install` for managed dependencies (JS + Swift) or `lpm_add` for source components
122
+ 4. **Build** — `lpm_package_skills` for usage patterns, then read local files directly
121
123
 
122
124
  The `lpm_package_context` response includes `package.installMethod.command` (`lpm add` or `lpm install`) so agents know which tool to use.
123
125
 
124
- > **Tip:** Use `lpm_package_context` as the default. Fall back to individual tools (`lpm_package_info`, `lpm_api_docs`, `lpm_llm_context`) only when you need the full readme or want to re-check a single aspect.
126
+ > **Tip:** After installing, prefer reading local files over `lpm_browse_source`. Local reads are faster and don't hit the server. Use `lpm_browse_source` only when evaluating a package you can't install (e.g., checking before purchase).
125
127
 
126
128
  > **Version resolution:** Version-sensitive tools (`lpm_api_docs`, `lpm_llm_context`, `lpm_package_context`, `lpm_package_skills`, `lpm_browse_source`) resolve the version from the local project's package.json dependencies when no explicit version is specified. This ensures you get docs matching the version you actually have installed.
127
129
 
@@ -462,7 +464,7 @@ Browse source code of an LPM package you have access to. Call without `path` to
462
464
 
463
465
  ### lpm_add
464
466
 
465
- Add an LPM package to the project by extracting source files for customization. Use for UI components, blocks, templates, Swift packages, and MCP servers. Requires LPM CLI installed locally.
467
+ Add an LPM package to the project by extracting source files for customization. Use for UI components, blocks, templates, and MCP servers. Requires LPM CLI installed locally.
466
468
 
467
469
  **Parameters:**
468
470
 
@@ -492,7 +494,7 @@ Add an LPM package to the project by extracting source files for customization.
492
494
 
493
495
  ### lpm_install
494
496
 
495
- Install an LPM package as a dependency to node_modules (like npm install). Use for JS libraries, utilities, and SDKs. Requires LPM CLI installed locally.
497
+ Install an LPM package as a managed dependency. JS packages go to node_modules, Swift packages edit Package.swift via SE-0292. Requires LPM CLI installed locally.
496
498
 
497
499
  **Parameters:**
498
500
 
@@ -509,25 +511,70 @@ Install an LPM package as a dependency to node_modules (like npm install). Use f
509
511
  }
510
512
  ```
511
513
 
512
- ### lpm_get_install_command
514
+ ### lpm_audit
513
515
 
514
- Get the correct CLI command to install an LPM package. Returns `lpm add` (source extraction) or `lpm install` (node_modules) based on the package type and ecosystem.
516
+ Run a security audit on the project's LPM dependencies. Returns behavioral tags (eval, childProcess, shell), AI security findings, quality scores, and lifecycle scripts.
517
+
518
+ **Parameters:**
519
+
520
+ - `path` (string, optional) — Project directory to audit (defaults to current directory)
521
+
522
+ **Example response:**
523
+
524
+ ```json
525
+ {
526
+ "success": true,
527
+ "totalPackages": 3,
528
+ "packagesWithIssues": 1,
529
+ "packages": [
530
+ {
531
+ "name": "@lpm.dev/alice.ui-kit",
532
+ "version": "2.0.0",
533
+ "qualityScore": 85,
534
+ "issues": []
535
+ },
536
+ {
537
+ "name": "@lpm.dev/bob.exec-tool",
538
+ "version": "1.0.0",
539
+ "qualityScore": 45,
540
+ "issues": ["uses child_process", "[moderate] Executes shell commands with user input"]
541
+ }
542
+ ]
543
+ }
544
+ ```
545
+
546
+ ### lpm_marketplace_info
547
+
548
+ Get marketplace information for an LPM package — pricing, licensing, seat management, and purchase status.
515
549
 
516
550
  **Parameters:**
517
551
 
518
552
  - `name` (string, required) — Package name
519
- - `version` (string, optional) — Specific version
520
553
 
521
554
  **Example response:**
522
555
 
523
556
  ```json
524
557
  {
525
- "command": "lpm add @lpm.dev/alice.ui-kit",
526
- "method": "add",
527
- "explanation": "Extracts source files into your project for customization."
558
+ "name": "@lpm.dev/acme.pro-suite",
559
+ "isMarketplace": true,
560
+ "distributionMode": "marketplace",
561
+ "pricing": { "monthlyPriceCents": 2900, "yearlyPriceCents": 29000 },
562
+ "licenseType": "per-seat",
563
+ "accessInfo": { "hasLicense": true, "seatsUsed": 3, "seatsTotal": 5 }
528
564
  }
529
565
  ```
530
566
 
567
+ ### lpm_docs
568
+
569
+ Search or read LPM documentation. Use when the user asks how to use LPM itself.
570
+
571
+ **Parameters:**
572
+
573
+ - `page` (string, optional) — Doc page slug (e.g., `cli/commands`, `packages/swift-registry`)
574
+ - `query` (string, optional) — Search query (e.g., `npmrc`, `CI deployment`)
575
+
576
+ Without parameters: returns the full docs index.
577
+
531
578
  ### lpm_quality_report
532
579
 
533
580
  Get the quality score and detailed check breakdown for an LPM package. Covers 28 checks across documentation, code quality, testing, and maintenance health.
@@ -649,12 +696,14 @@ Responses are cached in memory to reduce API calls:
649
696
  | `lpm_package_context` | 5 minutes |
650
697
  | `lpm_package_skills` | 5 minutes |
651
698
  | `lpm_browse_source` | 5 minutes |
652
- | `lpm_get_install_command` | 5 minutes |
699
+ | `lpm_marketplace_info` | 5 minutes |
700
+ | `lpm_docs` | 30 minutes |
653
701
  | `lpm_quality_report` | 5 minutes |
654
702
  | `lpm_search_owners` | 5 minutes |
655
703
  | `lpm_packages_by_owner` | 5 minutes |
656
704
  | `lpm_pool_stats` | 1 hour |
657
705
  | `lpm_user_info` | 5 minutes |
706
+ | `lpm_audit` | No cache |
658
707
  | `lpm_add` | No cache |
659
708
  | `lpm_install` | No cache |
660
709
 
package/lib/server.js CHANGED
@@ -4,10 +4,12 @@ import { getBaseUrl, getToken } from "./auth.js"
4
4
  import { MemoryCache } from "./cache.js"
5
5
  import { add } from "./tools/add.js"
6
6
  import { apiDocs } from "./tools/api-docs.js"
7
+ import { audit } from "./tools/audit.js"
7
8
  import { browseSource } from "./tools/browse-source.js"
8
- import { getInstallCommand } from "./tools/get-install-command.js"
9
+ import { docs } from "./tools/docs.js"
9
10
  import { install } from "./tools/install.js"
10
11
  import { llmContext } from "./tools/llm-context.js"
12
+ import { marketplaceInfo } from "./tools/marketplace-info.js"
11
13
  import { packageContext } from "./tools/package-context.js"
12
14
  import { packageInfo } from "./tools/package-info.js"
13
15
  import { packageSkills } from "./tools/package-skills.js"
@@ -25,7 +27,7 @@ import { userInfo } from "./tools/user-info.js"
25
27
  export function createServer() {
26
28
  const server = new McpServer({
27
29
  name: "lpm-registry",
28
- version: "0.1.0",
30
+ version: "0.2.0",
29
31
  })
30
32
 
31
33
  const cache = new MemoryCache()
@@ -89,7 +91,7 @@ export function createServer() {
89
91
 
90
92
  server.tool(
91
93
  "lpm_package_context",
92
- "Get complete context for an LPM package in a single call — combines condensed package metadata (name, version, description, install method, dependencies), structured API docs (functions, classes, types), and LLM usage guide (quickStart, patterns, gotchas). Use this as the primary tool when you need to understand and use a package.",
94
+ "Get complete context for an LPM package in a single call — combines condensed package metadata (name, version, description, install method, dependencies), structured API docs (functions, classes, types), and LLM usage guide (quickStart, patterns, gotchas). Use this BEFORE installing to evaluate and understand a package. If the package is already installed locally, prefer reading local files directly and use lpm_package_skills for usage patterns instead.",
93
95
  {
94
96
  name: z
95
97
  .string()
@@ -106,7 +108,7 @@ export function createServer() {
106
108
 
107
109
  server.tool(
108
110
  "lpm_package_skills",
109
- "Get author-written Agent Skills for an LPM package - usage patterns, anti-patterns, gotchas, and best practices. Skills are version-specific and automatically resolve from local package.json if no version is specified.",
111
+ "Get author-written Agent Skills for an LPM package — usage patterns, anti-patterns, gotchas, and best practices for code generation. Use this when BUILDING with an already-installed package. Skills are version-specific and automatically resolve from local package.json if no version is specified.",
110
112
  {
111
113
  name: z
112
114
  .string()
@@ -170,25 +172,33 @@ export function createServer() {
170
172
  )
171
173
 
172
174
  server.tool(
173
- "lpm_get_install_command",
174
- "Get the correct CLI command to install an LPM package. Returns either `lpm add` (extracts source files for customization — components, blocks, Swift packages) or `lpm install` (installs to node_modules as a dependency — libraries, utilities).",
175
+ "lpm_audit",
176
+ "Run a security audit on the project's LPM dependencies. Returns behavioral tags (eval, childProcess, shell, dynamicRequire), AI security findings, quality scores, and lifecycle scripts. Requires LPM CLI installed and authentication.",
177
+ {
178
+ path: z
179
+ .string()
180
+ .optional()
181
+ .describe("Project directory to audit (defaults to current directory)"),
182
+ },
183
+ params => audit(params, context),
184
+ )
185
+
186
+ server.tool(
187
+ "lpm_marketplace_info",
188
+ "Get marketplace information for an LPM package — pricing, licensing model, seat management, and purchase status. Use this before recommending a paid package to check cost and access.",
175
189
  {
176
190
  name: z
177
191
  .string()
178
192
  .describe(
179
193
  "Package name in owner.package-name or @lpm.dev/owner.package-name format",
180
194
  ),
181
- version: z
182
- .string()
183
- .optional()
184
- .describe("Specific version to install (defaults to latest)"),
185
195
  },
186
- params => getInstallCommand(params, context),
196
+ params => marketplaceInfo(params, context),
187
197
  )
188
198
 
189
199
  server.tool(
190
200
  "lpm_add",
191
- "Add an LPM package to the project by extracting source files for customization. Use for UI components, blocks, templates, Swift packages, and MCP servers. Requires LPM CLI installed.",
201
+ "Add an LPM package to the project by extracting source files for customization. Use for UI components, blocks, templates, and MCP servers. Requires LPM CLI installed.",
192
202
  {
193
203
  name: z
194
204
  .string()
@@ -235,7 +245,7 @@ export function createServer() {
235
245
 
236
246
  server.tool(
237
247
  "lpm_install",
238
- "Install an LPM package as a dependency to node_modules (like npm install). Use for JS libraries, utilities, and SDKs. Requires LPM CLI installed.",
248
+ "Install an LPM package as a managed dependency. JS packages go to node_modules (like npm install), Swift packages edit Package.swift via SE-0292. Use for libraries, utilities, and SDKs. Requires LPM CLI installed.",
239
249
  {
240
250
  name: z
241
251
  .string()
@@ -252,9 +262,11 @@ export function createServer() {
252
262
 
253
263
  server.tool(
254
264
  "lpm_browse_source",
255
- `Browse source code of an LPM package you have access to. Use without \`path\` to see the file tree first, then request specific files or directories with \`path\`. Requires authentication. Pool packages require Pool subscription, marketplace packages require a license.
265
+ `LAST RESORT: Browse source code of an LPM package remotely. Only use this when you cannot install the package (e.g., evaluating before purchase, checking capabilities without access). If you can install the package, prefer lpm_add or lpm_install first, then read the local files directly — it is faster.
266
+
267
+ Requires authentication. Pool packages require Pool subscription, marketplace packages require a license.
256
268
 
257
- IMPORTANT: Prefer fetching directory paths (e.g., "src", "components") to get all files under that directory in a single request, rather than requesting individual files one by one. To get ALL source files at once, pass an empty string "" as path.`,
269
+ If you must use this tool: fetch directory paths (e.g., "src") to get multiple files in one request rather than individual files.`,
258
270
  {
259
271
  name: z
260
272
  .string()
@@ -353,5 +365,25 @@ IMPORTANT: Prefer fetching directory paths (e.g., "src", "components") to get al
353
365
  params => search(params, context),
354
366
  )
355
367
 
368
+ server.tool(
369
+ "lpm_docs",
370
+ "Search or read LPM documentation. Use this when the user asks how to use LPM itself — setup, CLI commands, publishing, CI/CD, Swift registry, organizations, billing, etc. Without parameters: returns the docs index. With `page`: returns that specific page. With `query`: searches page titles and returns matching content.",
371
+ {
372
+ page: z
373
+ .string()
374
+ .optional()
375
+ .describe(
376
+ 'Documentation page slug (e.g., "cli/commands", "packages/swift-registry", "getting-started/installation")',
377
+ ),
378
+ query: z
379
+ .string()
380
+ .optional()
381
+ .describe(
382
+ 'Search query to find relevant docs (e.g., "npmrc", "swift", "CI deployment")',
383
+ ),
384
+ },
385
+ params => docs(params, context),
386
+ )
387
+
356
388
  return server
357
389
  }
@@ -0,0 +1,50 @@
1
+ import { runCli } from "../cli.js"
2
+ import { errorResponse, textResponse } from "../format.js"
3
+
4
+ /**
5
+ * Run a security audit on the project's LPM dependencies.
6
+ * Returns behavioral tags, AI security findings, quality scores, and lifecycle scripts.
7
+ *
8
+ * @param {{ path?: string }} params
9
+ * @param {{ getToken: () => Promise<string|null> }} ctx
10
+ */
11
+ export async function audit({ path }, ctx) {
12
+ const token = await ctx.getToken()
13
+ if (!token) {
14
+ return errorResponse(
15
+ "Authentication required. Set LPM_TOKEN environment variable or run `lpm login`.",
16
+ )
17
+ }
18
+
19
+ const args = ["audit", "--json"]
20
+
21
+ const result = await runCli(args, {
22
+ timeout: 30_000,
23
+ ...(path && { cwd: path }),
24
+ })
25
+
26
+ if (!result.success) {
27
+ return errorResponse(result.error || "Failed to run audit.")
28
+ }
29
+
30
+ const data = result.data
31
+
32
+ // Format for AI consumption
33
+ const summary = {
34
+ success: true,
35
+ totalPackages: data.totalPackages || 0,
36
+ packagesWithIssues: data.packagesWithIssues || 0,
37
+ packages: (data.packages || []).map(pkg => ({
38
+ name: pkg.name,
39
+ version: pkg.version,
40
+ qualityScore: pkg.qualityScore,
41
+ issues: pkg.issues || [],
42
+ })),
43
+ }
44
+
45
+ if (data.summary) {
46
+ summary.summary = data.summary
47
+ }
48
+
49
+ return textResponse(JSON.stringify(summary, null, 2))
50
+ }
@@ -0,0 +1,101 @@
1
+ import { CACHE_TTL } from "../constants.js"
2
+ import { errorResponse, textResponse } from "../format.js"
3
+
4
+ const DOCS_BASE_URL = "https://lpm.dev/api/docs"
5
+
6
+ /**
7
+ * Search or read LPM documentation.
8
+ *
9
+ * - Without `page`: returns the full docs index (page titles and slugs).
10
+ * - With `page`: returns the content of that specific documentation page.
11
+ * - With `query`: searches page titles for matches and returns their content.
12
+ *
13
+ * @param {{ page?: string, query?: string }} params
14
+ * @param {{ cache: import('../cache.js').MemoryCache }} ctx
15
+ */
16
+ export async function docs({ page, query }, ctx) {
17
+ // Mode 1: Fetch a specific page
18
+ if (page) {
19
+ const slug = page.replace(/^\//, "").replace(/\/$/, "")
20
+ const cacheKey = `docs:page:${slug}`
21
+
22
+ const cached = ctx.cache.get(cacheKey)
23
+ if (cached) return cached
24
+
25
+ const response = await fetchText(`${DOCS_BASE_URL}/${slug}`)
26
+ if (!response.ok) {
27
+ if (response.status === 404) {
28
+ return errorResponse(
29
+ `Documentation page "${slug}" not found. Use this tool without a page parameter to see all available pages.`,
30
+ )
31
+ }
32
+ return errorResponse(`Failed to fetch docs (${response.status})`)
33
+ }
34
+
35
+ const content = await response.text()
36
+ const result = textResponse(content)
37
+ ctx.cache.set(cacheKey, result, CACHE_TTL.LONG)
38
+ return result
39
+ }
40
+
41
+ // Mode 2: Search or list index
42
+ const cacheKey = "docs:index"
43
+ let indexText = ctx.cache.get(cacheKey)
44
+
45
+ if (!indexText) {
46
+ const response = await fetchText(DOCS_BASE_URL)
47
+ if (!response.ok) {
48
+ return errorResponse(`Failed to fetch docs index (${response.status})`)
49
+ }
50
+ indexText = await response.text()
51
+ ctx.cache.set(cacheKey, indexText, CACHE_TTL.LONG)
52
+ } else {
53
+ // Cache stores the textResponse wrapper, extract the text
54
+ indexText =
55
+ typeof indexText === "string"
56
+ ? indexText
57
+ : indexText?.content?.[0]?.text || indexText
58
+ }
59
+
60
+ // If query is provided, filter the index and fetch matching pages
61
+ if (query) {
62
+ const queryLower = query.toLowerCase()
63
+ const lines =
64
+ typeof indexText === "string"
65
+ ? indexText.split("\n")
66
+ : String(indexText).split("\n")
67
+ const matches = lines.filter(
68
+ line => line.includes(" — ") && line.toLowerCase().includes(queryLower),
69
+ )
70
+
71
+ if (matches.length === 0) {
72
+ return textResponse(
73
+ `No documentation pages found matching "${query}".\n\nAvailable pages:\n${indexText}`,
74
+ )
75
+ }
76
+
77
+ // Fetch content of matching pages (up to 3)
78
+ const results = []
79
+ for (const match of matches.slice(0, 3)) {
80
+ const slug = match.split(" — ")[0].trim()
81
+ const response = await fetchText(`${DOCS_BASE_URL}/${slug}`)
82
+ if (response.ok) {
83
+ const content = await response.text()
84
+ results.push(`--- ${slug} ---\n${content}`)
85
+ }
86
+ }
87
+
88
+ return textResponse(results.join("\n\n"))
89
+ }
90
+
91
+ // No query, no page — return the full index
92
+ return textResponse(
93
+ typeof indexText === "string" ? indexText : String(indexText),
94
+ )
95
+ }
96
+
97
+ async function fetchText(url) {
98
+ return fetch(url, {
99
+ headers: { Accept: "text/plain" },
100
+ })
101
+ }
@@ -4,8 +4,8 @@ import { errorResponse, parseName, textResponse } from "../format.js"
4
4
 
5
5
  /**
6
6
  * Get the correct CLI install command for an LPM package.
7
- * Returns `lpm add` for source packages (components, blocks, MCP servers, Swift)
8
- * or `lpm install` for dependency packages (installed to node_modules).
7
+ * Returns `lpm add` for source packages (components, blocks, MCP servers)
8
+ * or `lpm install` for managed dependencies (JS to node_modules, Swift to Package.swift).
9
9
  *
10
10
  * @param {{ name: string, version?: string }} params
11
11
  * @param {{ cache: import('../cache.js').MemoryCache, getToken: () => Promise<string|null>, getBaseUrl: () => string }} ctx
@@ -33,11 +33,11 @@ export async function getInstallCommand({ name, version }, ctx) {
33
33
  const result = await registryGet(`/${fullName}`, token, baseUrl)
34
34
 
35
35
  if (!result.ok) {
36
- // Fallback: return `lpm add` as the default command when metadata is unavailable
36
+ // Fallback: return `lpm install` as the default command when metadata is unavailable
37
37
  if (result.status === 404) {
38
38
  return errorResponse(`Package ${fullName} not found.`)
39
39
  }
40
- const response = textResponse(`lpm add ${fullName}${versionSuffix}`)
40
+ const response = textResponse(`lpm install ${fullName}${versionSuffix}`)
41
41
  return response
42
42
  }
43
43
 
@@ -45,13 +45,11 @@ export async function getInstallCommand({ name, version }, ctx) {
45
45
  const ecosystem = data.ecosystem || "js"
46
46
  const packageType = data.packageType || "package"
47
47
 
48
- // Source-based packages use `lpm add` (extracts files into the project)
49
- // Only plain JS packages without a special type use `lpm install` (node_modules)
48
+ // Source packages (components, blocks, templates) use `lpm add`
49
+ // Everything else uses `lpm install` (JS → node_modules, Swift → Package.swift)
50
+ const sourceTypes = new Set(["component", "block", "template", "mcp-server"])
50
51
  const useAdd =
51
- ecosystem !== "js" ||
52
- packageType !== "package" ||
53
- data.lpmSource ||
54
- data.hasLpmConfig
52
+ sourceTypes.has(packageType) || data.lpmSource || data.hasLpmConfig
55
53
 
56
54
  const command = useAdd
57
55
  ? `lpm add ${fullName}${versionSuffix}`
@@ -59,7 +57,9 @@ export async function getInstallCommand({ name, version }, ctx) {
59
57
 
60
58
  const explanation = useAdd
61
59
  ? "Extracts source files into your project for customization."
62
- : "Installs as a dependency in node_modules (like npm install)."
60
+ : ecosystem === "swift"
61
+ ? "Installs as a managed dependency via SE-0292 (edits Package.swift)."
62
+ : "Installs as a dependency in node_modules (like npm install)."
63
63
 
64
64
  const response = textResponse(
65
65
  JSON.stringify(
@@ -0,0 +1,80 @@
1
+ import { registryGet } from "../api.js"
2
+ import { CACHE_TTL } from "../constants.js"
3
+ import { errorResponse, parseName, textResponse } from "../format.js"
4
+
5
+ /**
6
+ * Get marketplace information for an LPM package.
7
+ * Returns pricing, licensing model, seat management details, and purchase status.
8
+ *
9
+ * @param {{ name: string }} params
10
+ * @param {{ cache: import('../cache.js').MemoryCache, getToken: () => Promise<string|null>, getBaseUrl: () => string }} ctx
11
+ */
12
+ export async function marketplaceInfo({ name }, ctx) {
13
+ let owner, pkgName
14
+ try {
15
+ const parsed = parseName(name)
16
+ owner = parsed.owner
17
+ pkgName = parsed.name
18
+ } catch (err) {
19
+ return errorResponse(err.message)
20
+ }
21
+
22
+ const fullName = `@lpm.dev/${owner}.${pkgName}`
23
+ const token = await ctx.getToken()
24
+ const cacheKey = `marketplace-info:${owner}.${pkgName}:auth=${!!token}`
25
+
26
+ const cached = ctx.cache.get(cacheKey)
27
+ if (cached) return cached
28
+
29
+ const baseUrl = ctx.getBaseUrl()
30
+ const result = await registryGet(`/${fullName}`, token, baseUrl)
31
+
32
+ if (!result.ok) {
33
+ if (result.status === 404) {
34
+ return errorResponse(`Package ${fullName} not found.`)
35
+ }
36
+ return errorResponse(
37
+ result.data?.error || `Request failed (${result.status})`,
38
+ )
39
+ }
40
+
41
+ const data = result.data
42
+ const distributionMode = data.distributionMode || "private"
43
+
44
+ if (distributionMode !== "marketplace") {
45
+ const response = textResponse(
46
+ JSON.stringify(
47
+ {
48
+ name: fullName,
49
+ distributionMode,
50
+ isMarketplace: false,
51
+ message: `This package uses ${distributionMode} distribution, not marketplace.`,
52
+ },
53
+ null,
54
+ 2,
55
+ ),
56
+ )
57
+ ctx.cache.set(cacheKey, response, CACHE_TTL.SHORT)
58
+ return response
59
+ }
60
+
61
+ const summary = {
62
+ name: fullName,
63
+ isMarketplace: true,
64
+ distributionMode: "marketplace",
65
+ description: data.description || "",
66
+ // Pricing
67
+ pricing: data.marketplace?.pricing || null,
68
+ licenseType: data.marketplace?.licenseType || null,
69
+ // Seat management
70
+ seats: data.marketplace?.seats || null,
71
+ // Access info for the current user
72
+ accessInfo: data.accessInfo || null,
73
+ // Latest version
74
+ latestVersion: data["dist-tags"]?.latest || null,
75
+ }
76
+
77
+ const response = textResponse(JSON.stringify(summary, null, 2))
78
+ ctx.cache.set(cacheKey, response, CACHE_TTL.SHORT)
79
+ return response
80
+ }
@@ -12,20 +12,24 @@ const ADD_TYPES = new Set(["component", "block", "template", "mcp-server"])
12
12
  * @returns {{ command: string, description: string }}
13
13
  */
14
14
  export function getInstallMethod(packageType, ecosystem) {
15
- if (ecosystem === "swift" || ecosystem === "xcframework") {
15
+ // Source-extract types always use `lpm add`
16
+ if (ADD_TYPES.has(packageType)) {
16
17
  return {
17
18
  command: "lpm add",
18
- description: "Extracts Swift source files into your project",
19
+ description: "Extracts source files into your project for customization",
19
20
  }
20
21
  }
21
22
 
22
- if (ADD_TYPES.has(packageType)) {
23
+ // Swift/XCFramework use `lpm install` (SE-0292 registry mode)
24
+ if (ecosystem === "swift" || ecosystem === "xcframework") {
23
25
  return {
24
- command: "lpm add",
25
- description: "Extracts source files into your project for customization",
26
+ command: "lpm install",
27
+ description:
28
+ "Installs as a managed dependency via SE-0292 (edits Package.swift)",
26
29
  }
27
30
  }
28
31
 
32
+ // JS dependencies/libraries use `lpm install`
29
33
  if (packageType === "dependency" || packageType === "library") {
30
34
  return {
31
35
  command: "lpm install",
@@ -34,10 +38,10 @@ export function getInstallMethod(packageType, ecosystem) {
34
38
  }
35
39
  }
36
40
 
37
- // Default: lpm add for source packages, lpm install for generic packages
41
+ // Default: lpm install for managed dependencies
38
42
  return {
39
- command: "lpm add",
40
- description: "Extracts source files into your project for customization",
43
+ command: "lpm install",
44
+ description: "Installs as a managed dependency",
41
45
  }
42
46
  }
43
47
 
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@lpm-registry/mcp-server",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server for the LPM package registry — exposes package info, quality reports, name checks, pool stats, and marketplace search as AI tools",
5
5
  "author": "LPM <hello@lpm.dev> (https://lpm.dev)",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/lpm-dev/mcp-server"
9
+ },
6
10
  "homepage": "https://lpm.dev",
7
11
  "license": "ISC",
8
12
  "keywords": [