@lpm-registry/mcp-server 0.1.1 → 0.3.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 +24 -564
- package/lib/auth.js +104 -12
- package/lib/cli.js +82 -29
- package/lib/constants.js +2 -2
- package/lib/format.js +8 -0
- package/lib/server.js +52 -17
- package/lib/tools/add.js +12 -40
- package/lib/tools/audit.js +19 -0
- package/lib/tools/docs.js +101 -0
- package/lib/tools/get-install-command.js +11 -11
- package/lib/tools/install.js +3 -26
- package/lib/tools/marketplace-info.js +80 -0
- package/lib/tools/package-info.js +12 -8
- package/lib/tools/search.js +14 -15
- package/package.json +1 -1
|
@@ -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(
|
package/lib/tools/install.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { runCli } from "../cli.js"
|
|
2
|
-
import { errorResponse, parseName
|
|
2
|
+
import { cliResponse, errorResponse, parseName } from "../format.js"
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Install an LPM package as a dependency via the CLI.
|
|
6
6
|
* Installs to node_modules like npm install.
|
|
7
7
|
*
|
|
8
8
|
* @param {{ name: string, version?: string }} params
|
|
9
|
-
* @param {{ getToken: () => Promise<string|null> }} ctx
|
|
10
9
|
*/
|
|
11
|
-
export async function install({ name, version }
|
|
10
|
+
export async function install({ name, version }) {
|
|
12
11
|
let owner, pkgName
|
|
13
12
|
try {
|
|
14
13
|
const parsed = parseName(name)
|
|
@@ -18,13 +17,6 @@ export async function install({ name, version }, ctx) {
|
|
|
18
17
|
return errorResponse(err.message)
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
const token = await ctx.getToken()
|
|
22
|
-
if (!token) {
|
|
23
|
-
return errorResponse(
|
|
24
|
-
"Authentication required. Set LPM_TOKEN environment variable or run `lpm login`.",
|
|
25
|
-
)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
20
|
let pkgRef = `@lpm.dev/${owner}.${pkgName}`
|
|
29
21
|
if (version) pkgRef += `@${version}`
|
|
30
22
|
|
|
@@ -32,20 +24,5 @@ export async function install({ name, version }, ctx) {
|
|
|
32
24
|
|
|
33
25
|
const result = await runCli(args)
|
|
34
26
|
|
|
35
|
-
|
|
36
|
-
return errorResponse(result.error || "Failed to install package.")
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const data = result.data
|
|
40
|
-
const summary = {
|
|
41
|
-
success: true,
|
|
42
|
-
packages: data.packages || [{ name: pkgRef }],
|
|
43
|
-
npmOutput: data.npmOutput || "",
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (data.warnings?.length > 0) {
|
|
47
|
-
summary.warnings = data.warnings
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return textResponse(JSON.stringify(summary, null, 2))
|
|
27
|
+
return cliResponse(result)
|
|
51
28
|
}
|
|
@@ -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/lib/tools/search.js
CHANGED
|
@@ -10,10 +10,10 @@ import { errorResponse, textResponse } from "../format.js"
|
|
|
10
10
|
* - Explore path (/packages/explore): when any structured filter is active
|
|
11
11
|
*
|
|
12
12
|
* @param {object} params
|
|
13
|
-
* @param {string} [params.query] - Search text (
|
|
13
|
+
* @param {string} [params.query] - Search text (optional with a structured filter)
|
|
14
14
|
* @param {string} [params.category] - Category slug filter
|
|
15
15
|
* @param {string} [params.ecosystem] - Ecosystem filter (js, swift, xcframework)
|
|
16
|
-
* @param {string} [params.distribution] - Distribution mode filter (marketplace, pool, private)
|
|
16
|
+
* @param {string} [params.distribution] - Distribution mode filter (free, marketplace, pool, private)
|
|
17
17
|
* @param {string} [params.packageType] - Package type filter
|
|
18
18
|
* @param {string} [params.sort] - Sort order (newest, popular, name)
|
|
19
19
|
* @param {boolean} [params.hasTypes] - TypeScript types filter
|
|
@@ -36,18 +36,6 @@ export async function search(params, ctx) {
|
|
|
36
36
|
minNodeVersion,
|
|
37
37
|
} = params
|
|
38
38
|
|
|
39
|
-
// Validation: at least query or category required
|
|
40
|
-
if (!query?.trim() && !category) {
|
|
41
|
-
return errorResponse(ERROR_MESSAGES.searchNoParams)
|
|
42
|
-
}
|
|
43
|
-
if (query && query.trim().length < 2) {
|
|
44
|
-
return errorResponse("Search query must be at least 2 characters.")
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const token = await ctx.getToken()
|
|
48
|
-
const baseUrl = ctx.getBaseUrl()
|
|
49
|
-
|
|
50
|
-
// Determine which path to use
|
|
51
39
|
const hasStructuredFilters =
|
|
52
40
|
category ||
|
|
53
41
|
distribution ||
|
|
@@ -56,7 +44,18 @@ export async function search(params, ctx) {
|
|
|
56
44
|
hasTypes ||
|
|
57
45
|
moduleType ||
|
|
58
46
|
license ||
|
|
59
|
-
minNodeVersion
|
|
47
|
+
minNodeVersion ||
|
|
48
|
+
(!query?.trim() && params.ecosystem)
|
|
49
|
+
|
|
50
|
+
if (!query?.trim() && !hasStructuredFilters) {
|
|
51
|
+
return errorResponse(ERROR_MESSAGES.searchNoParams)
|
|
52
|
+
}
|
|
53
|
+
if (query && query.trim().length < 2) {
|
|
54
|
+
return errorResponse("Search query must be at least 2 characters.")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const token = await ctx.getToken()
|
|
58
|
+
const baseUrl = ctx.getBaseUrl()
|
|
60
59
|
|
|
61
60
|
// Build deterministic cache key from all params
|
|
62
61
|
const cacheKey = buildCacheKey(params)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lpm-registry/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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": {
|