registryx-server 0.1.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.
Files changed (49) hide show
  1. package/README.md +203 -0
  2. package/coverage/lcov-report/base.css +224 -0
  3. package/coverage/lcov-report/block-navigation.js +87 -0
  4. package/coverage/lcov-report/favicon.png +0 -0
  5. package/coverage/lcov-report/index.html +146 -0
  6. package/coverage/lcov-report/prettify.css +1 -0
  7. package/coverage/lcov-report/prettify.js +2 -0
  8. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  9. package/coverage/lcov-report/sorter.js +210 -0
  10. package/coverage/lcov-report/src/adapters/crates.ts.html +847 -0
  11. package/coverage/lcov-report/src/adapters/index.html +176 -0
  12. package/coverage/lcov-report/src/adapters/index.ts.html +97 -0
  13. package/coverage/lcov-report/src/adapters/maven.ts.html +637 -0
  14. package/coverage/lcov-report/src/adapters/npm.ts.html +817 -0
  15. package/coverage/lcov-report/src/adapters/pypi.ts.html +730 -0
  16. package/coverage/lcov-report/src/cache.ts.html +202 -0
  17. package/coverage/lcov-report/src/config.ts.html +208 -0
  18. package/coverage/lcov-report/src/index.html +161 -0
  19. package/coverage/lcov-report/src/server.ts.html +1339 -0
  20. package/coverage/lcov-report/src/types.ts.html +373 -0
  21. package/coverage/lcov-report/src/utils/fetch.ts.html +220 -0
  22. package/coverage/lcov-report/src/utils/format.ts.html +130 -0
  23. package/coverage/lcov-report/src/utils/index.html +131 -0
  24. package/coverage/lcov.info +1686 -0
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +1210 -0
  27. package/eslint.config.mjs +16 -0
  28. package/package.json +41 -0
  29. package/src/adapters/crates.ts +254 -0
  30. package/src/adapters/index.ts +4 -0
  31. package/src/adapters/maven.ts +184 -0
  32. package/src/adapters/npm.ts +244 -0
  33. package/src/adapters/pypi.ts +215 -0
  34. package/src/cache.ts +39 -0
  35. package/src/config.ts +41 -0
  36. package/src/index.ts +25 -0
  37. package/src/server.ts +418 -0
  38. package/src/types.ts +96 -0
  39. package/src/utils/fetch.ts +45 -0
  40. package/src/utils/format.ts +15 -0
  41. package/test/adapters.test.ts +575 -0
  42. package/test/cache.test.ts +47 -0
  43. package/test/config.test.ts +69 -0
  44. package/test/fetch.test.ts +51 -0
  45. package/test/format.test.ts +35 -0
  46. package/test/server.test.ts +23 -0
  47. package/tsconfig.json +18 -0
  48. package/tsup.config.ts +11 -0
  49. package/vitest.config.ts +19 -0
@@ -0,0 +1,244 @@
1
+ import type { Config } from '../config.js';
2
+ import type { Cache } from '../cache.js';
3
+ import type {
4
+ RegistryAdapter,
5
+ PackageSearchResult,
6
+ PackageMetadata,
7
+ VersionInfo,
8
+ VersionDetail,
9
+ Dependency,
10
+ DownloadStats,
11
+ MaintainerInfo,
12
+ PackageHealth,
13
+ SecurityAdvisory,
14
+ } from '../types.js';
15
+ import { registryFetch } from '../utils/fetch.js';
16
+
17
+ export class NpmAdapter implements RegistryAdapter {
18
+ readonly name = 'npm' as const;
19
+ constructor(
20
+ private config: Config,
21
+ private cache: Cache
22
+ ) {}
23
+
24
+ async search(query: string, limit: number): Promise<PackageSearchResult[]> {
25
+ const key = `npm:search:${query}:${limit}`;
26
+ const cached = this.cache.get<PackageSearchResult[]>(key);
27
+ if (cached) return cached;
28
+
29
+ const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(query)}&size=${limit}`;
30
+ const res = await registryFetch(url, this.config);
31
+ const data = (await res.json()) as any;
32
+
33
+ const results: PackageSearchResult[] = (data.objects ?? []).map(
34
+ (obj: {
35
+ package: { name: string; version: string; description?: string };
36
+ downloads?: { monthly?: number };
37
+ }) => ({
38
+ name: obj.package.name,
39
+ version: obj.package.version,
40
+ description: obj.package.description ?? '',
41
+ downloads: '',
42
+ registry: 'npm' as const,
43
+ })
44
+ );
45
+ this.cache.set(key, results);
46
+ return results;
47
+ }
48
+
49
+ async getPackage(name: string): Promise<PackageMetadata> {
50
+ const key = `npm:pkg:${name}`;
51
+ const cached = this.cache.get<PackageMetadata>(key);
52
+ if (cached) return cached;
53
+
54
+ const url = `https://registry.npmjs.org/${encodeURIComponent(name)}`;
55
+ const res = await registryFetch(url, this.config);
56
+ const data = (await res.json()) as any;
57
+ const latest = data['dist-tags']?.latest ?? '';
58
+
59
+ const result: PackageMetadata = {
60
+ name: data.name,
61
+ version: latest,
62
+ description: data.description ?? '',
63
+ license: typeof data.license === 'string' ? data.license : (data.license?.type ?? 'Unknown'),
64
+ homepage: data.homepage ?? '',
65
+ repository:
66
+ typeof data.repository === 'string' ? data.repository : (data.repository?.url ?? ''),
67
+ keywords: data.keywords ?? [],
68
+ registry: 'npm',
69
+ };
70
+ this.cache.set(key, result);
71
+ return result;
72
+ }
73
+
74
+ async getReadme(name: string): Promise<string> {
75
+ const url = `https://registry.npmjs.org/${encodeURIComponent(name)}`;
76
+ const res = await registryFetch(url, this.config);
77
+ const data = (await res.json()) as any;
78
+ return data.readme ?? 'No README available.';
79
+ }
80
+
81
+ async getMaintainers(name: string): Promise<MaintainerInfo[]> {
82
+ const url = `https://registry.npmjs.org/${encodeURIComponent(name)}`;
83
+ const res = await registryFetch(url, this.config);
84
+ const data = (await res.json()) as any;
85
+ return (data.maintainers ?? []).map((m: { name: string; email?: string }) => ({
86
+ name: m.name,
87
+ email: m.email,
88
+ }));
89
+ }
90
+
91
+ async listVersions(name: string, limit: number, stableOnly: boolean): Promise<VersionInfo[]> {
92
+ const url = `https://registry.npmjs.org/${encodeURIComponent(name)}`;
93
+ const res = await registryFetch(url, this.config);
94
+ const data = (await res.json()) as any;
95
+ const versions = Object.keys(data.versions ?? {}).reverse();
96
+ const timeMap = data.time ?? {};
97
+
98
+ let results: VersionInfo[] = versions.map((v) => ({
99
+ version: v,
100
+ date: timeMap[v] ?? '',
101
+ prerelease: /[-+]/.test(v.replace(/^\d+\.\d+\.\d+/, '')),
102
+ }));
103
+
104
+ if (stableOnly) results = results.filter((v) => !v.prerelease);
105
+ return results.slice(0, limit);
106
+ }
107
+
108
+ async getVersion(name: string, version: string): Promise<VersionDetail> {
109
+ const url = `https://registry.npmjs.org/${encodeURIComponent(name)}/${version}`;
110
+ const res = await registryFetch(url, this.config);
111
+ const data = (await res.json()) as any;
112
+
113
+ const deps: Dependency[] = Object.entries(data.dependencies ?? {}).map(([n, v]) => ({
114
+ name: n,
115
+ version: v as string,
116
+ type: 'runtime' as const,
117
+ }));
118
+
119
+ return {
120
+ name: data.name,
121
+ version: data.version,
122
+ date: '',
123
+ license: typeof data.license === 'string' ? data.license : 'Unknown',
124
+ size: data.dist?.unpackedSize ? `${Math.round(data.dist.unpackedSize / 1024)}KB` : 'Unknown',
125
+ dependencies: deps,
126
+ registry: 'npm',
127
+ };
128
+ }
129
+
130
+ async getDependencies(
131
+ name: string,
132
+ version: string,
133
+ type: 'runtime' | 'dev' | 'all'
134
+ ): Promise<Dependency[]> {
135
+ const url = `https://registry.npmjs.org/${encodeURIComponent(name)}/${version}`;
136
+ const res = await registryFetch(url, this.config);
137
+ const data = (await res.json()) as any;
138
+
139
+ const deps: Dependency[] = [];
140
+ if (type === 'runtime' || type === 'all') {
141
+ for (const [n, v] of Object.entries(data.dependencies ?? {})) {
142
+ deps.push({ name: n, version: v as string, type: 'runtime' });
143
+ }
144
+ }
145
+ if (type === 'dev' || type === 'all') {
146
+ for (const [n, v] of Object.entries(data.devDependencies ?? {})) {
147
+ deps.push({ name: n, version: v as string, type: 'dev' });
148
+ }
149
+ }
150
+ return deps;
151
+ }
152
+
153
+ async getReverseDependencies(name: string, limit: number): Promise<PackageSearchResult[]> {
154
+ const url = `https://registry.npmjs.org/-/v1/search?text=dependencies:${encodeURIComponent(name)}&size=${limit}`;
155
+ const res = await registryFetch(url, this.config);
156
+ const data = (await res.json()) as any;
157
+
158
+ return (data.objects ?? []).map(
159
+ (obj: { package: { name: string; version: string; description?: string } }) => ({
160
+ name: obj.package.name,
161
+ version: obj.package.version,
162
+ description: obj.package.description ?? '',
163
+ downloads: '',
164
+ registry: 'npm' as const,
165
+ })
166
+ );
167
+ }
168
+
169
+ async getDownloadStats(name: string, period: string): Promise<DownloadStats> {
170
+ const npmPeriod = period === 'last-year' ? 'last-year' : period;
171
+ const url = `https://api.npmjs.org/downloads/point/${npmPeriod}/${encodeURIComponent(name)}`;
172
+ const res = await registryFetch(url, this.config);
173
+ const data = (await res.json()) as any;
174
+
175
+ return {
176
+ name,
177
+ registry: 'npm',
178
+ period,
179
+ total: data.downloads ?? 0,
180
+ breakdown: [],
181
+ };
182
+ }
183
+
184
+ async getPackageHealth(name: string): Promise<PackageHealth> {
185
+ const url = `https://registry.npmjs.org/${encodeURIComponent(name)}`;
186
+ const res = await registryFetch(url, this.config);
187
+ const data = (await res.json()) as any;
188
+
189
+ const latest = data['dist-tags']?.latest ?? '';
190
+ const latestInfo = data.versions?.[latest] ?? {};
191
+ const timeMap = data.time ?? {};
192
+ const lastPublish = timeMap[latest] ?? '';
193
+ const depCount = Object.keys(latestInfo.dependencies ?? {}).length;
194
+ const hasTypings = !!latestInfo.types || !!latestInfo.typings;
195
+
196
+ const signals: string[] = [];
197
+ if (hasTypings) signals.push('Has TypeScript typings');
198
+ if (latestInfo.scripts?.test) signals.push('Has test script');
199
+ if (data.readme && data.readme.length > 500) signals.push('Detailed README');
200
+
201
+ return {
202
+ name,
203
+ registry: 'npm',
204
+ score: Math.min(10, signals.length * 3 + 4),
205
+ lastPublish,
206
+ dependencyCount: depCount,
207
+ hasTests: !!latestInfo.scripts?.test,
208
+ hasTypings,
209
+ signals,
210
+ };
211
+ }
212
+
213
+ async getSecurityAdvisories(name: string, _version?: string): Promise<SecurityAdvisory[]> {
214
+ try {
215
+ const url = 'https://osv.dev/v1/query';
216
+ const body = { package: { name, ecosystem: 'npm' } };
217
+ const res = await fetch(url, {
218
+ method: 'POST',
219
+ headers: { 'Content-Type': 'application/json' },
220
+ body: JSON.stringify(body),
221
+ signal: AbortSignal.timeout(this.config.timeoutMs),
222
+ });
223
+ if (!res.ok) return [];
224
+ const data = (await res.json()) as any;
225
+ return (data.vulns ?? []).map(
226
+ (v: {
227
+ id: string;
228
+ summary?: string;
229
+ severity?: { type: string; score: string }[];
230
+ references?: { url: string }[];
231
+ affected?: { ranges?: { events?: { introduced?: string; fixed?: string }[] }[] }[];
232
+ }) => ({
233
+ id: v.id,
234
+ title: v.summary ?? v.id,
235
+ severity: v.severity?.[0]?.score ?? 'Unknown',
236
+ url: v.references?.[0]?.url ?? `https://osv.dev/vulnerability/${v.id}`,
237
+ affectedVersions: 'See advisory',
238
+ })
239
+ );
240
+ } catch {
241
+ return [];
242
+ }
243
+ }
244
+ }
@@ -0,0 +1,215 @@
1
+ import type { Config } from '../config.js';
2
+ import type { Cache } from '../cache.js';
3
+ import type {
4
+ RegistryAdapter,
5
+ PackageSearchResult,
6
+ PackageMetadata,
7
+ VersionInfo,
8
+ VersionDetail,
9
+ Dependency,
10
+ DownloadStats,
11
+ MaintainerInfo,
12
+ PackageHealth,
13
+ SecurityAdvisory,
14
+ } from '../types.js';
15
+ import { registryFetch } from '../utils/fetch.js';
16
+
17
+ export class PypiAdapter implements RegistryAdapter {
18
+ readonly name = 'pypi' as const;
19
+ constructor(
20
+ private config: Config,
21
+ private cache: Cache
22
+ ) {}
23
+
24
+ async search(query: string, limit: number): Promise<PackageSearchResult[]> {
25
+ const key = `pypi:search:${query}:${limit}`;
26
+ const cached = this.cache.get<PackageSearchResult[]>(key);
27
+ if (cached) return cached;
28
+
29
+ const results: PackageSearchResult[] = [];
30
+ try {
31
+ const pkgUrl = `https://pypi.org/pypi/${encodeURIComponent(query)}/json`;
32
+ const res = await registryFetch(pkgUrl, this.config);
33
+ const data = (await res.json()) as any;
34
+ results.push({
35
+ name: data.info.name,
36
+ version: data.info.version,
37
+ description: data.info.summary ?? '',
38
+ downloads: '',
39
+ registry: 'pypi',
40
+ });
41
+ } catch {
42
+ // Package not found directly — that's OK
43
+ }
44
+ this.cache.set(key, results.slice(0, limit));
45
+ return results.slice(0, limit);
46
+ }
47
+
48
+ async getPackage(name: string): Promise<PackageMetadata> {
49
+ const key = `pypi:pkg:${name}`;
50
+ const cached = this.cache.get<PackageMetadata>(key);
51
+ if (cached) return cached;
52
+
53
+ const url = `https://pypi.org/pypi/${encodeURIComponent(name)}/json`;
54
+ const res = await registryFetch(url, this.config);
55
+ const data = (await res.json()) as any;
56
+ const info = data.info;
57
+
58
+ const result: PackageMetadata = {
59
+ name: info.name,
60
+ version: info.version,
61
+ description: info.summary ?? '',
62
+ license: info.license ?? 'Unknown',
63
+ homepage: info.home_page ?? info.project_url ?? '',
64
+ repository: info.project_urls?.Source ?? info.project_urls?.Repository ?? '',
65
+ keywords: info.keywords ? info.keywords.split(',').map((k: string) => k.trim()) : [],
66
+ registry: 'pypi',
67
+ };
68
+ this.cache.set(key, result);
69
+ return result;
70
+ }
71
+
72
+ async getReadme(name: string): Promise<string> {
73
+ const url = `https://pypi.org/pypi/${encodeURIComponent(name)}/json`;
74
+ const res = await registryFetch(url, this.config);
75
+ const data = (await res.json()) as any;
76
+ return data.info.description ?? 'No README available.';
77
+ }
78
+
79
+ async getMaintainers(name: string): Promise<MaintainerInfo[]> {
80
+ const url = `https://pypi.org/pypi/${encodeURIComponent(name)}/json`;
81
+ const res = await registryFetch(url, this.config);
82
+ const data = (await res.json()) as any;
83
+ const info = data.info;
84
+ const maintainers: MaintainerInfo[] = [];
85
+ if (info.author) maintainers.push({ name: info.author, email: info.author_email });
86
+ if (info.maintainer) maintainers.push({ name: info.maintainer, email: info.maintainer_email });
87
+ return maintainers;
88
+ }
89
+
90
+ async listVersions(name: string, limit: number, stableOnly: boolean): Promise<VersionInfo[]> {
91
+ const url = `https://pypi.org/pypi/${encodeURIComponent(name)}/json`;
92
+ const res = await registryFetch(url, this.config);
93
+ const data = (await res.json()) as any;
94
+
95
+ const releases = Object.keys(data.releases ?? {}).reverse();
96
+ let results: VersionInfo[] = releases.map((v) => {
97
+ const files = data.releases[v] ?? [];
98
+ const date = files[0]?.upload_time_iso_8601 ?? '';
99
+ return {
100
+ version: v,
101
+ date,
102
+ prerelease: /(?:a|b|rc|dev|alpha|beta)\d*/i.test(v),
103
+ };
104
+ });
105
+
106
+ if (stableOnly) results = results.filter((v) => !v.prerelease);
107
+ return results.slice(0, limit);
108
+ }
109
+
110
+ async getVersion(name: string, version: string): Promise<VersionDetail> {
111
+ const url = `https://pypi.org/pypi/${encodeURIComponent(name)}/${version}/json`;
112
+ const res = await registryFetch(url, this.config);
113
+ const data = (await res.json()) as any;
114
+ const info = data.info;
115
+
116
+ const deps: Dependency[] = (info.requires_dist ?? []).map((d: string) => {
117
+ const match = d.match(/^([^\s;]+)/);
118
+ return {
119
+ name: match?.[1] ?? d,
120
+ version: '',
121
+ type: 'runtime' as const,
122
+ };
123
+ });
124
+
125
+ return {
126
+ name: info.name,
127
+ version: info.version,
128
+ date: data.urls?.[0]?.upload_time_iso_8601 ?? '',
129
+ license: info.license ?? 'Unknown',
130
+ size: data.urls?.[0]?.size ? `${Math.round(data.urls[0].size / 1024)}KB` : 'Unknown',
131
+ dependencies: deps,
132
+ registry: 'pypi',
133
+ };
134
+ }
135
+
136
+ async getDependencies(
137
+ name: string,
138
+ version: string,
139
+ _type: 'runtime' | 'dev' | 'all'
140
+ ): Promise<Dependency[]> {
141
+ const vd = await this.getVersion(name, version);
142
+ return vd.dependencies;
143
+ }
144
+
145
+ async getReverseDependencies(_name: string, _limit: number): Promise<PackageSearchResult[]> {
146
+ return []; // PyPI doesn't expose reverse dependencies
147
+ }
148
+
149
+ async getDownloadStats(name: string, period: string): Promise<DownloadStats> {
150
+ return {
151
+ name,
152
+ registry: 'pypi',
153
+ period,
154
+ total: 0, // PyPI doesn't have a public real-time download API
155
+ breakdown: [],
156
+ };
157
+ }
158
+
159
+ async getPackageHealth(name: string): Promise<PackageHealth> {
160
+ const url = `https://pypi.org/pypi/${encodeURIComponent(name)}/json`;
161
+ const res = await registryFetch(url, this.config);
162
+ const data = (await res.json()) as any;
163
+ const info = data.info;
164
+
165
+ const signals: string[] = [];
166
+ if (info.description && info.description.length > 500) signals.push('Detailed description');
167
+ if (info.license) signals.push('License specified');
168
+ if (info.requires_python) signals.push('Python version specified');
169
+ if (info.project_urls?.Documentation) signals.push('Has documentation URL');
170
+
171
+ const depCount = (info.requires_dist ?? []).length;
172
+
173
+ return {
174
+ name,
175
+ registry: 'pypi',
176
+ score: Math.min(10, signals.length * 2 + 3),
177
+ lastPublish: data.urls?.[0]?.upload_time_iso_8601 ?? '',
178
+ dependencyCount: depCount,
179
+ hasTests: false,
180
+ hasTypings: info.classifiers?.some((c: string) => c.includes('Typing')) ?? false,
181
+ signals,
182
+ };
183
+ }
184
+
185
+ async getSecurityAdvisories(name: string, _version?: string): Promise<SecurityAdvisory[]> {
186
+ try {
187
+ const url = 'https://osv.dev/v1/query';
188
+ const body = { package: { name, ecosystem: 'PyPI' } };
189
+ const res = await fetch(url, {
190
+ method: 'POST',
191
+ headers: { 'Content-Type': 'application/json' },
192
+ body: JSON.stringify(body),
193
+ signal: AbortSignal.timeout(this.config.timeoutMs),
194
+ });
195
+ if (!res.ok) return [];
196
+ const data = (await res.json()) as any;
197
+ return (data.vulns ?? []).map(
198
+ (v: {
199
+ id: string;
200
+ summary?: string;
201
+ severity?: { score: string }[];
202
+ references?: { url: string }[];
203
+ }) => ({
204
+ id: v.id,
205
+ title: v.summary ?? v.id,
206
+ severity: v.severity?.[0]?.score ?? 'Unknown',
207
+ url: v.references?.[0]?.url ?? `https://osv.dev/vulnerability/${v.id}`,
208
+ affectedVersions: 'See advisory',
209
+ })
210
+ );
211
+ } catch {
212
+ return [];
213
+ }
214
+ }
215
+ }
package/src/cache.ts ADDED
@@ -0,0 +1,39 @@
1
+ interface CacheEntry<T> {
2
+ value: T;
3
+ expiresAt: number;
4
+ }
5
+
6
+ export class Cache {
7
+ private store = new Map<string, CacheEntry<unknown>>();
8
+ private readonly ttlMs: number;
9
+
10
+ constructor(ttlMs: number) {
11
+ this.ttlMs = ttlMs;
12
+ }
13
+
14
+ get<T>(key: string): T | undefined {
15
+ const entry = this.store.get(key);
16
+ if (!entry) return undefined;
17
+ if (Date.now() > entry.expiresAt) {
18
+ this.store.delete(key);
19
+ return undefined;
20
+ }
21
+ return entry.value as T;
22
+ }
23
+
24
+ set<T>(key: string, value: T): void {
25
+ if (this.ttlMs <= 0) return;
26
+ this.store.set(key, {
27
+ value,
28
+ expiresAt: Date.now() + this.ttlMs,
29
+ });
30
+ }
31
+
32
+ clear(): void {
33
+ this.store.clear();
34
+ }
35
+
36
+ get size(): number {
37
+ return this.store.size;
38
+ }
39
+ }
package/src/config.ts ADDED
@@ -0,0 +1,41 @@
1
+ import type { RegistryName } from './types.js';
2
+
3
+ export interface Config {
4
+ registries: RegistryName[];
5
+ npmToken?: string;
6
+ timeoutMs: number;
7
+ cacheTtlMs: number;
8
+ }
9
+
10
+ const VALID_REGISTRIES = new Set<RegistryName>(['npm', 'pypi', 'maven', 'crates']);
11
+
12
+ export function loadConfig(): Config {
13
+ const registriesEnv = process.env.REGISTRYX_MCP_REGISTRIES ?? 'npm,pypi,maven,crates';
14
+ const registries = registriesEnv
15
+ .split(',')
16
+ .map((r) => r.trim().toLowerCase())
17
+ .filter((r): r is RegistryName => VALID_REGISTRIES.has(r as RegistryName));
18
+
19
+ if (registries.length === 0) {
20
+ throw new Error(
21
+ 'No valid registries configured. Set REGISTRYX_MCP_REGISTRIES to comma-separated values: npm, pypi, maven, crates'
22
+ );
23
+ }
24
+
25
+ const timeoutMs = parseInt(process.env.REGISTRYX_MCP_TIMEOUT_MS ?? '15000', 10);
26
+ if (isNaN(timeoutMs) || timeoutMs < 1000 || timeoutMs > 120000) {
27
+ throw new Error('REGISTRYX_MCP_TIMEOUT_MS must be between 1000 and 120000');
28
+ }
29
+
30
+ const cacheTtlMs = parseInt(process.env.REGISTRYX_MCP_CACHE_TTL_MS ?? '300000', 10);
31
+ if (isNaN(cacheTtlMs) || cacheTtlMs < 0) {
32
+ throw new Error('REGISTRYX_MCP_CACHE_TTL_MS must be >= 0');
33
+ }
34
+
35
+ return {
36
+ registries,
37
+ npmToken: process.env.REGISTRYX_MCP_NPM_TOKEN || undefined,
38
+ timeoutMs,
39
+ cacheTtlMs,
40
+ };
41
+ }
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { loadConfig } from './config.js';
4
+ import { createServer } from './server.js';
5
+
6
+ async function main() {
7
+ const config = loadConfig();
8
+ const server = createServer(config);
9
+ const transport = new StdioServerTransport();
10
+ await server.connect(transport);
11
+
12
+ process.on('SIGINT', async () => {
13
+ await server.close();
14
+ process.exit(0);
15
+ });
16
+ process.on('SIGTERM', async () => {
17
+ await server.close();
18
+ process.exit(0);
19
+ });
20
+ }
21
+
22
+ main().catch((err) => {
23
+ console.error('Fatal:', err);
24
+ process.exit(1);
25
+ });