Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
"cwd": "."
}
}
}
}
55 changes: 6 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -562,55 +562,12 @@ deno task fmt
### Project Structure

```
mcp-dependency-version/
├── main.ts # MCP server entry point
├── deno.json # Deno configuration
├── Dockerfile # Docker container build
├── .mcp.json # MCP server registration
├── README.md # Documentation
├── src/
│ ├── config/ # Configuration management
│ │ ├── types.ts # Config types and defaults
│ │ ├── loader.ts # Config file loading
│ │ └── index.ts # Module exports
│ ├── registries/ # Package registry clients
│ │ ├── types.ts # Shared types
│ │ ├── index.ts # Client factory
│ │ ├── npm.ts # npm registry
│ │ ├── maven.ts # Maven Central
│ │ ├── pypi.ts # PyPI
│ │ ├── cargo.ts # crates.io
│ │ ├── go.ts # Go proxy
│ │ ├── jsr.ts # JSR (jsr.io)
│ │ ├── nuget.ts # NuGet
│ │ └── docker.ts # Docker Hub
│ ├── parsers/ # Dependency file parsers
│ │ ├── types.ts # Parser types
│ │ ├── index.ts # Parser factory
│ │ ├── npm.ts # package.json
│ │ ├── pypi.ts # requirements.txt
│ │ ├── cargo.ts # Cargo.toml
│ │ ├── go.ts # go.mod
│ │ ├── maven.ts # pom.xml
│ │ ├── gradle-groovy.ts # build.gradle
│ │ ├── gradle-kotlin.ts # build.gradle.kts
│ │ ├── deno.ts # deno.json
│ │ ├── nuget.ts # *.csproj
│ │ └── docker.ts # Dockerfile, docker-compose.yml
│ ├── tools/ # MCP tool implementations
│ │ ├── types.ts # Tool input/output types
│ │ ├── lookup-version.ts # lookup_version tool
│ │ ├── list-versions.ts # list_versions tool
│ │ ├── check-vulnerabilities.ts # check_vulnerabilities tool
│ │ ├── analyze-dependencies.ts # analyze_dependencies tool
│ │ └── get-package-docs.ts # get_package_docs tool
│ └── utils/
│ ├── version.ts # Semver utilities
│ ├── version.test.ts # Version tests
│ ├── http.ts # HTTP utilities with auth
│ ├── github.ts # GitHub API utilities
│ ├── cache.ts # TTL cache
│ └── vulnerability.ts # OSV vulnerability checking
src/
├── config/ # Configuration loading
├── registries/ # Registry client implementations (npm, maven, pypi, etc.)
├── parsers/ # Dependency file parsers (package.json, pom.xml, etc.)
├── tools/ # MCP tool implementations
└── utils/ # Shared utilities (version parsing, caching, HTTP)
```

## API Reference
Expand Down
3 changes: 2 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
},
"imports": {
"@modelcontextprotocol/sdk": "npm:@modelcontextprotocol/sdk@1.25.3",
"zod": "npm:zod@3.25.76"
"zod": "npm:zod@3.25.76",
"@std/assert": "jsr:@std/assert@1.0.17"
},
"compilerOptions": {
"strict": true,
Expand Down
7 changes: 7 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 11 additions & 5 deletions src/config/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function expandPath(path: string): string {
*/
function getConfigPath(): string {
return expandPath(
Deno.env.get("MCP_DEPENDENCY_VERSION_CONFIG") || DEFAULT_CONFIG_PATH
Deno.env.get("MCP_DEPENDENCY_VERSION_CONFIG") || DEFAULT_CONFIG_PATH,
);
}

Expand Down Expand Up @@ -68,7 +68,10 @@ export async function loadConfig(): Promise<Config> {
userConfig = JSON.parse(content) as Partial<Config>;
} catch (error) {
if (!(error instanceof Deno.errors.NotFound)) {
console.error(`Warning: Failed to load config from ${configPath}:`, error);
console.error(
`Warning: Failed to load config from ${configPath}:`,
error,
);
}
// Use defaults if no config file exists
}
Expand All @@ -94,7 +97,10 @@ export function loadConfigSync(): Config {
userConfig = JSON.parse(content) as Partial<Config>;
} catch (error) {
if (!(error instanceof Deno.errors.NotFound)) {
console.error(`Warning: Failed to load config from ${configPath}:`, error);
console.error(
`Warning: Failed to load config from ${configPath}:`,
error,
);
}
}

Expand All @@ -109,7 +115,7 @@ export function loadConfigSync(): Config {
*/
export function getRepositoryConfig(
registry: Registry,
repository?: string
repository?: string,
): RepositoryConfig {
const config = loadConfigSync();
const repos = config.repositories[registry];
Expand All @@ -123,7 +129,7 @@ export function getRepositoryConfig(
if (!repo) {
const available = Object.keys(repos).join(", ");
throw new Error(
`Repository '${repository}' not found for ${registry}. Available: ${available}`
`Repository '${repository}' not found for ${registry}. Available: ${available}`,
);
}
return repo;
Expand Down
2 changes: 1 addition & 1 deletion src/parsers/cargo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function parse(content: string): ParsedDependency[] {
}

const complexMatch = line.match(
/^([a-zA-Z0-9_-]+)\s*=\s*\{.*version\s*=\s*"([^"]+)"/
/^([a-zA-Z0-9_-]+)\s*=\s*\{.*version\s*=\s*"([^"]+)"/,
);
if (complexMatch) {
deps.push({ name: complexMatch[1], version: complexMatch[2] });
Expand Down
6 changes: 4 additions & 2 deletions src/parsers/deno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function stripJsonComments(content: string): string {
* jsr:@oak/oak@17 -> { registry: "jsr", name: "@oak/oak", version: "17" }
*/
function parseImportSpecifier(
specifier: string
specifier: string,
): { registry: "jsr" | "npm"; name: string; version: string } | null {
// Skip URL imports
if (specifier.startsWith("http://") || specifier.startsWith("https://")) {
Expand Down Expand Up @@ -129,7 +129,9 @@ function parse(content: string): ParsedDependency[] {

// For npm: packages, prefix the name to indicate registry routing
// For jsr: packages, use the name as-is
const name = parsed.registry === "npm" ? `npm:${parsed.name}` : parsed.name;
const name = parsed.registry === "npm"
? `npm:${parsed.name}`
: parsed.name;

deps.push({
name,
Expand Down
14 changes: 11 additions & 3 deletions src/parsers/docker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,19 @@ function parseDockerfile(content: string): ParsedDependency[] {
// Match FROM statements
// FROM [--platform=...] image[:tag|@digest] [AS name]
const fromMatch = trimmed.match(
/^FROM\s+(?:--platform=[^\s]+\s+)?([^\s]+)(?:\s+AS\s+\w+)?$/i
/^FROM\s+(?:--platform=[^\s]+\s+)?([^\s]+)(?:\s+AS\s+\w+)?$/i,
);

if (fromMatch) {
const imageRef = fromMatch[1];
const parsed = parseImageReference(imageRef);
if (parsed) {
// Avoid duplicates
if (!deps.some((d) => d.name === parsed.name && d.version === parsed.version)) {
if (
!deps.some((d) =>
d.name === parsed.name && d.version === parsed.version
)
) {
deps.push(parsed);
}
}
Expand Down Expand Up @@ -119,7 +123,11 @@ function parseDockerCompose(content: string): ParsedDependency[] {
const parsed = parseImageReference(imageRef);
if (parsed) {
// Avoid duplicates
if (!deps.some((d) => d.name === parsed.name && d.version === parsed.version)) {
if (
!deps.some((d) =>
d.name === parsed.name && d.version === parsed.version
)
) {
deps.push(parsed);
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/parsers/gradle-groovy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ const configurations = [
"annotationProcessor",
"kapt",
"ksp",
"compile", // deprecated but still used
"testCompile", // deprecated but still used
"runtime", // deprecated but still used
"compile", // deprecated but still used
"testCompile", // deprecated but still used
"runtime", // deprecated but still used
];

/**
Expand All @@ -32,7 +32,7 @@ function parse(content: string): ParsedDependency[] {
// Handles both single and double quotes
const stringNotationRegex = new RegExp(
`(?:${configPattern})\\s*[("']([a-zA-Z0-9._-]+):([a-zA-Z0-9._-]+):([^'"\\s:]+)[)'"]`,
"g"
"g",
);

let match;
Expand All @@ -47,7 +47,7 @@ function parse(content: string): ParsedDependency[] {
// Pattern 2: Map notation - implementation group: 'com.example', name: 'lib', version: '1.0'
const mapNotationRegex = new RegExp(
`(?:${configPattern})\\s+group:\\s*['"]([^'"]+)['"]\\s*,\\s*name:\\s*['"]([^'"]+)['"]\\s*,\\s*version:\\s*['"]([^'"]+)['"]`,
"g"
"g",
);

while ((match = mapNotationRegex.exec(content)) !== null) {
Expand Down
2 changes: 1 addition & 1 deletion src/parsers/gradle-kotlin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function parse(content: string): ParsedDependency[] {
// Pattern: implementation("group:artifact:version")
const funcNotationRegex = new RegExp(
`(?:${configPattern})\\s*\\(\\s*["']([a-zA-Z0-9._-]+):([a-zA-Z0-9._-]+):([^"'\\s:]+)["']\\s*\\)`,
"g"
"g",
);

let match;
Expand Down
11 changes: 8 additions & 3 deletions src/parsers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ function parseMavenDependencies(content: string): ParsedDependency[] {

// Check for Groovy DSL (build.gradle patterns)
// Groovy can use: implementation 'group:artifact:version' or implementation "..."
if (/(?:implementation|api|testImplementation|compile)\s+['"]/.test(content) ||
/(?:implementation|api|testImplementation)\s+group:/.test(content)) {
if (
/(?:implementation|api|testImplementation|compile)\s+['"]/.test(content) ||
/(?:implementation|api|testImplementation)\s+group:/.test(content)
) {
return gradleGroovyParser.parse(content);
}

Expand All @@ -82,7 +84,10 @@ function parseMavenDependencies(content: string): ParsedDependency[] {
* Parse dependencies from file content based on registry
* For maven registry, auto-detects between pom.xml, build.gradle, and build.gradle.kts
*/
export function parseDependencies(content: string, registry: Registry): ParsedDependency[] {
export function parseDependencies(
content: string,
registry: Registry,
): ParsedDependency[] {
switch (registry) {
case "npm":
return npmParser.parse(content);
Expand Down
3 changes: 2 additions & 1 deletion src/parsers/maven.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ function parse(content: string): ParsedDependency[] {
const deps: ParsedDependency[] = [];

// Match dependency blocks - handle various whitespace patterns
const depRegex = /<dependency>[\s\S]*?<groupId>([^<]+)<\/groupId>[\s\S]*?<artifactId>([^<]+)<\/artifactId>[\s\S]*?<version>([^<]+)<\/version>[\s\S]*?<\/dependency>/g;
const depRegex =
/<dependency>[\s\S]*?<groupId>([^<]+)<\/groupId>[\s\S]*?<artifactId>([^<]+)<\/artifactId>[\s\S]*?<version>([^<]+)<\/version>[\s\S]*?<\/dependency>/g;

let match;
while ((match = depRegex.exec(content)) !== null) {
Expand Down
4 changes: 3 additions & 1 deletion src/parsers/npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ function parse(content: string): ParsedDependency[] {
const pkg = JSON.parse(content);
const deps: ParsedDependency[] = [];

for (const depType of ["dependencies", "devDependencies", "peerDependencies"]) {
for (
const depType of ["dependencies", "devDependencies", "peerDependencies"]
) {
const section = pkg[depType];
if (section && typeof section === "object") {
for (const [name, version] of Object.entries(section)) {
Expand Down
9 changes: 6 additions & 3 deletions src/parsers/nuget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,19 @@ function parse(content: string): ParsedDependency[] {

// Match PackageReference with Version as attribute
// <PackageReference Include="Name" Version="1.0.0" />
const attrRegex = /<PackageReference\s+[^>]*Include\s*=\s*["']([^"']+)["'][^>]*Version\s*=\s*["']([^"']+)["'][^>]*\/?>/gi;
const attrRegex =
/<PackageReference\s+[^>]*Include\s*=\s*["']([^"']+)["'][^>]*Version\s*=\s*["']([^"']+)["'][^>]*\/?>/gi;

// Also match when Version comes before Include
const attrRegex2 = /<PackageReference\s+[^>]*Version\s*=\s*["']([^"']+)["'][^>]*Include\s*=\s*["']([^"']+)["'][^>]*\/?>/gi;
const attrRegex2 =
/<PackageReference\s+[^>]*Version\s*=\s*["']([^"']+)["'][^>]*Include\s*=\s*["']([^"']+)["'][^>]*\/?>/gi;

// Match PackageReference with Version as child element
// <PackageReference Include="Name">
// <Version>1.0.0</Version>
// </PackageReference>
const elementRegex = /<PackageReference\s+[^>]*Include\s*=\s*["']([^"']+)["'][^>]*>[\s\S]*?<Version>([^<]+)<\/Version>[\s\S]*?<\/PackageReference>/gi;
const elementRegex =
/<PackageReference\s+[^>]*Include\s*=\s*["']([^"']+)["'][^>]*>[\s\S]*?<Version>([^<]+)<\/Version>[\s\S]*?<\/PackageReference>/gi;

let match: RegExpExecArray | null;

Expand Down
Loading