@lpm-registry/mcp-server 0.1.1 → 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 +67 -18
- package/lib/server.js +47 -15
- package/lib/tools/audit.js +50 -0
- package/lib/tools/docs.js +101 -0
- package/lib/tools/get-install-command.js +11 -11
- package/lib/tools/marketplace-info.js +80 -0
- package/lib/tools/package-info.js +12 -8
- package/package.json +1 -1
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
|
|
102
|
-
| `
|
|
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
|
|
105
|
-
| `
|
|
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. **
|
|
119
|
-
3. **
|
|
120
|
-
4. **
|
|
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:**
|
|
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,
|
|
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
|
|
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
|
-
###
|
|
514
|
+
### lpm_audit
|
|
513
515
|
|
|
514
|
-
|
|
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
|
-
"
|
|
526
|
-
"
|
|
527
|
-
"
|
|
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
|
-
| `
|
|
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 {
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
"
|
|
174
|
-
"
|
|
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 =>
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
8
|
-
* or `lpm install` for
|
|
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
|
|
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
|
|
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
|
|
49
|
-
//
|
|
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
|
-
|
|
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
|
-
:
|
|
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
|
-
|
|
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
|
|
19
|
+
description: "Extracts source files into your project for customization",
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
// Swift/XCFramework use `lpm install` (SE-0292 registry mode)
|
|
24
|
+
if (ecosystem === "swift" || ecosystem === "xcframework") {
|
|
23
25
|
return {
|
|
24
|
-
command: "lpm
|
|
25
|
-
description:
|
|
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
|
|
41
|
+
// Default: lpm install for managed dependencies
|
|
38
42
|
return {
|
|
39
|
-
command: "lpm
|
|
40
|
-
description: "
|
|
43
|
+
command: "lpm install",
|
|
44
|
+
description: "Installs as a managed dependency",
|
|
41
45
|
}
|
|
42
46
|
}
|
|
43
47
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lpm-registry/mcp-server",
|
|
3
|
-
"version": "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
6
|
"repository": {
|