osv-scanner-mcp 0.3.3 → 0.3.4

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
@@ -165,6 +165,8 @@ Java(Maven)プロジェクトをスキャンし、既知の脆弱性レポート
165
165
 
166
166
  > **Gradleプロジェクトについて**: 本ツールは**lockfile方式**のみ対応です(ビルド実行方式は build.gradle の任意コード実行を伴うため、セキュリティ上の理由から採用していません)。`gradle.lockfile` が無い場合は `./gradlew dependencies --write-locks` で生成してください(依存ロック未設定の場合は `build.gradle` に `dependencyLocking { lockAllConfigurations() }` の追加が必要です)。
167
167
 
168
+ > **スキャン範囲**: ディレクトリを指定すると、配下の `pom.xml` / `gradle.lockfile` / `buildscript-gradle.lockfile` を深さに関係なく検出し、**検出したファイルだけ**をスキャンします(応答の `manifests` がそのままスキャン範囲です)。同じディレクトリにある `package-lock.json` や `requirements.txt` などJava以外のファイルはスキャンしません。`.git`、`node_modules`、`target`、`build`、`.idea`、`.vscode` とシンボリックリンクは探索しません。探索するエントリが20万件、またはマニフェストが1,000件を超える場合は、結果を黙って省略せず `manifest_search_limit_exceeded` を返します。`pom.xml` などのマニフェストを直接指定した場合は、ディレクトリを探索せず**そのファイルだけ**をスキャンします(上限に達した場合の回避手段としても使えます)。
169
+
168
170
  **出力(成功時)**
169
171
 
170
172
  ```json
@@ -294,6 +296,7 @@ OSV-Scanner 2.4.0の `java/archive` プラグインを使用し、ネストJAR
294
296
  - MavenのOSV `ECOSYSTEM` 範囲(`introduced` / `fixed` / `last_affected` / 上限なし)を照合します。`versions` に明示された影響も確認します。範囲欠落・不正・未対応形式・`limit` による不完全な情報では安全と推定せず、候補を検証できなければ `recommended_upgrade: null`、`verification: "no_verified_candidate"` を返します。
295
297
  - 推奨時は `verification: "verified"`、CVEごとの `recommended_status` は `affected` / `not_affected` / `unknown` です。推奨保留時は `not_evaluated` になります。`per_cve_detail.fixed_in` は各CVE単独の候補であり、最終推奨先の判定は `recommended_status` を参照してください。
296
298
  - 現在より新しい修正版候補がないCVEは `tier: "unfixed"` として推奨の修正対象から除外します(情報欠落を含む場合があります)。除外したCVEも推奨先で判定し、その状態を表示します。全CVEがunfixedの場合も `recommended_upgrade` は `null` です。
299
+ - 修正版の推奨はMavenのみ対応です。Maven以外のパッケージは `verification: "unsupported_ecosystem"`、CVEごとの `tier: "unsupported"` を返し、`unfixed` には数えません(修正版の有無は判定していないため。修正版は `explain_vulnerability` で確認できます)。
297
300
 
298
301
  ### `explain_vulnerability`
299
302
 
@@ -317,7 +320,7 @@ OSV-Scanner 2.4.0の `java/archive` プラグインを使用し、ネストJAR
317
320
  {
318
321
  "error": {
319
322
  "kind": "no_manifest_found",
320
- "message": "対応マニフェスト(pom.xml / gradle.lockfile)が見つかりません(深さ3まで探索): /path/to/project"
323
+ "message": "対応マニフェスト(pom.xml / gradle.lockfile)が見つかりません: /path/to/project"
321
324
  }
322
325
  }
323
326
  ```
@@ -327,6 +330,7 @@ OSV-Scanner 2.4.0の `java/archive` プラグインを使用し、ネストJAR
327
330
  | `binary_not_found` | OSV-Scannerが見つからない(インストール案内をmessageに含む) |
328
331
  | `project_not_found` | 指定パスが存在しない・ディレクトリ/pom.xmlでない |
329
332
  | `no_manifest_found` | 対応マニフェスト(pom.xml / gradle.lockfile)が見つからない |
333
+ | `manifest_search_limit_exceeded` | マニフェスト探索が上限(20万エントリ・1,000マニフェスト)に達した。より狭いディレクトリかマニフェストを直接指定する |
330
334
  | `binary_download_failed` | バイナリのダウンロード失敗(未対応プラットフォーム含む) |
331
335
  | `binary_checksum_mismatch` | ダウンロードしたバイナリのチェックサム不一致(改ざん/破損の可能性) |
332
336
  | `gradle_lockfile_missing` | Gradleプロジェクトだがgradle.lockfileが無い(生成手順をmessageで案内) |
@@ -348,7 +352,7 @@ OSV-Scanner 2.4.0の `java/archive` プラグインを使用し、ネストJAR
348
352
 
349
353
  - **サプライチェーン対策**: バイナリの自動ダウンロードは公式GitHub Releasesに限定し、バージョンをピン留め。**パッケージに埋め込まれたSHA256チェックサム**で検証します(配布元のSHA256SUMSファイルは信用しないため、リリース側が改ざんされても検出可能)。検証合格まで実行権限を与えず、キャッシュ済みバイナリも使用のたびに再検証します。`OSV_MCP_PREFER_DOWNLOAD=1` でPATH上の未検証バイナリを使わない運用も選べます
350
354
  - **コマンドインジェクション対策**: シェルを経由しない `spawn` + 引数配列で実行。OSV-Scannerへの引数は固定リストのみで、可変部は検証済み絶対パス1つだけ
351
- - **パストラバーサル対策**: 入力パスは `realpath` でシンボリックリンク解決後に境界チェック。pom.xml探索ではシンボリックリンクを辿りません
355
+ - **パストラバーサル対策**: 入力パスは `realpath` でシンボリックリンク解決後に境界チェック。pom.xml探索ではシンボリックリンクを辿りません。OSV-Scannerにはディレクトリを渡さず、検出したマニフェストだけを形式を明示して個別に渡します(ディレクトリを渡すと、OSV-Scannerが同じディレクトリの `requirements.txt` も読み、その取り込み指定 `-r ../x.txt` でスキャン範囲の外のファイルを読むため)
352
356
  - **DoS対策**: タイムアウト・stdout上限・stderr抜粋上限を設定。スキャン結果は防御的にパースし、形式不正でも例外を投げません。同時実行スキャン数も上限(デフォルト2)を設け、並列リクエストによるプロセスの無制限起動を防ぎます
353
357
  - **fail-closedな運用モード**: `OSV_MCP_REQUIRE_ALLOWED_ROOT=1` で、スキャン許可ルート未設定時にサーバーの起動自体を拒否できます
354
358
  - **通信先の固定と明示**: osv-scannerの依存解決先は `deps.dev` を明示指定し、スキャン対象のpom.xmlが指定する任意のリポジトリへ接続するモード(`--data-source native`)は使いません(テストで保証)。通信先の一覧と、deps.devへの送信を止める `OSV_MCP_NO_REMOTE_RESOLUTION=1` は[通信先とプライバシー](#通信先とプライバシー)を参照
package/dist/index.js CHANGED
@@ -35,7 +35,7 @@ if (startupWarning !== null) {
35
35
  // NOTE: リリース時はpackage.jsonのversionと同じ値に更新すること
36
36
  const server = new McpServer({
37
37
  name: "osv-scanner-mcp",
38
- version: "0.3.3",
38
+ version: "0.3.4",
39
39
  });
40
40
  server.registerTool("scan_java_project", {
41
41
  title: "Javaプロジェクトの脆弱性スキャン",
@@ -4,7 +4,10 @@
4
4
  * セキュリティ設計(docs/DESIGN_TODO.md):
5
5
  * - シェルを経由しない`spawn`+引数配列で実行(コマンドインジェクション対策)
6
6
  * - OSV-Scannerへ渡す引数は固定リストのみ。呼び出し側から任意フラグは注入できない
7
- * (プロジェクトは検証済みディレクトリ1つ、実体スキャンは列挙済みのJAR/WAR絶対パスだけを渡す)
7
+ * (プロジェクトは検出済みマニフェストを`--lockfile <形式>:<絶対パス>`で個別に、
8
+ * 実体スキャンは列挙済みのJAR/WAR絶対パスだけを渡す)
9
+ * - プロジェクトにディレクトリ(`-r`)を渡さない。osv-scannerがディレクトリ内の
10
+ * requirements.txt等も読み、その`-r ../x.txt`の取り込みでスキャン範囲の外のファイルを読むため
8
11
  * - SBOMは検証・サイズ制限済みの専用一時コピー1つだけを渡す
9
12
  * - タイムアウトと出力サイズ上限を設ける(ハング・巨大出力によるDoS対策)
10
13
  *
@@ -12,6 +15,7 @@
12
15
  * 0 = スキャン成功・脆弱性なし / 1 = スキャン成功・脆弱性あり / 128 = 対象パッケージなし
13
16
  */
14
17
  import { spawn } from "node:child_process";
18
+ import path from "node:path";
15
19
  import { ScanToolError } from "../errors.js";
16
20
  import { resolveOsvScannerBinary } from "./binaryManager.js";
17
21
  import { parseOsvScanOutput } from "./scanReport.js";
@@ -53,8 +57,20 @@ export function isRemoteResolutionDisabled(options = {}) {
53
57
  * `--data-source native` はスキャン対象pom.xmlの<repositories>に書かれた任意のURLへ
54
58
  * 接続するため使わない。既定値の変更に備えてdeps.devを明示する。
55
59
  */
56
- const FIXED_SCAN_ARGS = ["scan", "source", "-r", "--format", "json", "--data-source", "deps.dev"];
60
+ const FIXED_SCAN_ARGS = ["scan", "source", "--format", "json", "--data-source", "deps.dev"];
57
61
  const NO_RESOLVE_ARG = "--no-resolve";
62
+ /** 個別に渡せるマニフェスト。ファイル名をそのまま解析形式として明示する(2.4.0で実機確認) */
63
+ const PROJECT_MANIFEST_FORMATS = new Set(["pom.xml", "gradle.lockfile", "buildscript-gradle.lockfile"]);
64
+ /** 検出済みマニフェストを`--lockfile <形式>:<絶対パス>`の組にする。対象外のパスは渡さない */
65
+ export function buildProjectTargetArgs(manifestPaths) {
66
+ return manifestPaths.flatMap((manifestPath) => {
67
+ const format = path.basename(manifestPath);
68
+ if (!PROJECT_MANIFEST_FORMATS.has(format) || !path.isAbsolute(manifestPath)) {
69
+ throw new Error(`Unsupported manifest path for project scan: ${manifestPath}`);
70
+ }
71
+ return ["--lockfile", `${format}:${manifestPath}`];
72
+ });
73
+ }
58
74
  const FIXED_ARTIFACT_ARGS = [
59
75
  "scan", "source", "--format", "json", "--all-packages", "--no-ignore",
60
76
  "--experimental-no-default-plugins", "--experimental-plugins", "java/archive",
@@ -130,13 +146,16 @@ function execOsvScanner(binaryPath, targetPaths, timeoutMs, maxOutputBytes, scan
130
146
  });
131
147
  }
132
148
  /**
133
- * projectDirをOSV-Scannerでスキャンし、整形済みレポートを返す。
149
+ * 検出済みマニフェストだけをOSV-Scannerでスキャンし、整形済みレポートを返す。
134
150
  *
135
- * @param projectDir スキャン対象ディレクトリ。**必ず`detectJavaProject`で検証済みの
136
- * 絶対パスを渡すこと**(このレイヤーではパス検証を行わない)
151
+ * @param manifestPaths **`detectJavaProject`が検出したマニフェストの絶対パス**
152
+ * (このレイヤーではスキャン範囲の検証を行わない。ディレクトリは渡さない)
137
153
  */
138
- export async function runOsvScan(projectDir, options = {}) {
139
- return parseOsvScanOutput(await runScan([projectDir], options, "project"));
154
+ export async function runOsvScan(manifestPaths, options = {}) {
155
+ if (manifestPaths.length === 0) {
156
+ throw new ScanToolError("no_manifest_found", "スキャン対象のマニフェストがありません");
157
+ }
158
+ return parseOsvScanOutput(await runScan(manifestPaths, options, "project"));
140
159
  }
141
160
  /** Accept only the exact absolute files enumerated by detectJavaArtifacts. */
142
161
  export async function runOsvArtifactScan(artifactPaths, options = {}) {
@@ -164,9 +183,9 @@ async function runScan(targetPaths, options, mode) {
164
183
  }
165
184
  async function runOsvScanUnguarded(targetPaths, options, mode) {
166
185
  const binaryPath = options.binaryPath ?? (await resolveOsvScannerBinary());
167
- const result = await execOsvScanner(binaryPath, targetPaths, options.timeoutMs ?? DEFAULT_TIMEOUT_MS, options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES, buildOsvScanArgs(mode, isRemoteResolutionDisabled(options)));
186
+ const result = await execOsvScanner(binaryPath, mode === "project" ? buildProjectTargetArgs(targetPaths) : targetPaths, options.timeoutMs ?? DEFAULT_TIMEOUT_MS, options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES, buildOsvScanArgs(mode, isRemoteResolutionDisabled(options)));
168
187
  if (result.exitCode === EXIT_NO_PACKAGES && mode === "project") {
169
- throw new ScanToolError("no_packages_found", `OSV-Scannerがスキャン対象のパッケージを検出できませんでした: ${targetPaths[0]}(pom.xmlに依存関係が定義されているか確認してください)`, result.stderr);
188
+ throw new ScanToolError("no_packages_found", `OSV-Scannerがスキャン対象のパッケージを検出できませんでした(マニフェスト${targetPaths.length}件。pom.xmlに依存関係が定義されているか確認してください)`, result.stderr);
170
189
  }
171
190
  if (mode !== "project" && result.exitCode === EXIT_NO_PACKAGES && result.stdout.trim() === "") {
172
191
  return { results: [] };
@@ -16,6 +16,32 @@
16
16
  */
17
17
  import { compareMavenVersions, mavenVersionSeries } from "../utils/mavenVersion.js";
18
18
  import { candidateStatus } from "./affectedVersions.js";
19
+ /** バージョン比較・影響範囲の検証が対応済みのエコシステム */
20
+ const SUPPORTED_ECOSYSTEMS = new Set(["Maven"]);
21
+ /**
22
+ * 未対応エコシステムは推奨を出さず、CVEをunfixedにも数えない。
23
+ * 修正版の抽出がMaven専用のため、そのまま処理すると修正版のある脆弱性を
24
+ * 「修正版なし」と誤表示する(v0.3.3で確認した不具合)。
25
+ */
26
+ function unsupportedEcosystemSuggestion(pkg) {
27
+ return {
28
+ package: pkg.name,
29
+ current_version: pkg.version,
30
+ ecosystem: pkg.ecosystem,
31
+ recommended_upgrade: null,
32
+ upgrade_tier: null,
33
+ upgrade_note: `${pkg.ecosystem}の修正版推奨には未対応です(修正版の有無は判定していません)。` +
34
+ "各脆弱性の修正版はexplain_vulnerabilityで確認してください",
35
+ per_cve_detail: pkg.vulnerabilities.map((vuln) => ({
36
+ id: vuln.id,
37
+ cve: vuln.cve,
38
+ severity: vuln.severity,
39
+ fixed_in: null,
40
+ tier: "unsupported",
41
+ })),
42
+ verification: "unsupported_ecosystem",
43
+ };
44
+ }
19
45
  function classifyTier(currentSeries, candidate) {
20
46
  const candidateSeries = mavenVersionSeries(candidate);
21
47
  if (currentSeries === null || candidateSeries === null)
@@ -73,6 +99,8 @@ function buildNote(currentSeries, recommended, tier, fixableCount, unfixedCount)
73
99
  }
74
100
  /** 1パッケージ分のアップグレード提案を組み立てる。 */
75
101
  export function suggestUpgradeForPackage(pkg) {
102
+ if (!SUPPORTED_ECOSYSTEMS.has(pkg.ecosystem))
103
+ return unsupportedEcosystemSuggestion(pkg);
76
104
  const currentSeries = mavenVersionSeries(pkg.version);
77
105
  const details = [];
78
106
  let recommended = null;
@@ -24,7 +24,7 @@ export async function handleScanJavaProject(args, options = {}) {
24
24
  allowedRoot: options.allowedRoot,
25
25
  });
26
26
  const noRemoteResolution = isRemoteResolutionDisabled(options);
27
- const report = await runOsvScan(project.projectDir, { ...options, noRemoteResolution });
27
+ const report = await runOsvScan(project.manifestPaths, { ...options, noRemoteResolution });
28
28
  return jsonResult({
29
29
  project_dir: project.projectDir,
30
30
  manifests: project.manifests,
@@ -14,7 +14,7 @@ export async function handleSuggestFix(args, options = {}) {
14
14
  allowedRoot: options.allowedRoot,
15
15
  });
16
16
  const noRemoteResolution = isRemoteResolutionDisabled(options);
17
- const report = await runOsvScan(project.projectDir, { ...options, noRemoteResolution });
17
+ const report = await runOsvScan(project.manifestPaths, { ...options, noRemoteResolution });
18
18
  const suggestions = suggestUpgrades(report.packages);
19
19
  const unfixedVulnerabilities = suggestions.reduce((sum, s) => sum + s.per_cve_detail.filter((d) => d.tier === "unfixed").length, 0);
20
20
  return jsonResult({
@@ -11,14 +11,18 @@
11
11
  * `project_path`はLLM・ユーザー由来の信頼できない入力として扱う:
12
12
  * - `realpath`で正規化し、シンボリックリンクを解決した実体パスで判定する
13
13
  * - `allowedRoot`指定時は、解決後のパスがその配下にあることを検証する(パストラバーサル対策)
14
- * - マニフェスト探索は深さ・件数に上限を設け、シンボリックリンクのディレクトリは辿らない
14
+ * - マニフェスト探索はエントリ数・件数・深さに上限を設け、シンボリックリンクのディレクトリは辿らない
15
+ *
16
+ * 検出したマニフェストが、そのままOSV-Scannerのスキャン範囲になる(ディレクトリは渡さない)。
17
+ * そのため探索を深さで打ち切らず(Javaのソースツリーは深い)、上限に達した場合は
18
+ * 結果を黙って欠落させずにエラーにする。
15
19
  */
16
20
  import { readdir, realpath, stat } from "node:fs/promises";
17
21
  import path from "node:path";
18
22
  import { ScanToolError } from "../errors.js";
19
- const DEFAULT_MAX_DEPTH = 3;
20
- /** 探索を打ち切るマニフェスト件数の上限(巨大モノレポでの暴走防止) */
21
- const MAX_MANIFESTS = 100;
23
+ const DEFAULT_MAX_DEPTH = 64;
24
+ const DEFAULT_MAX_ENTRIES = 200_000;
25
+ const DEFAULT_MAX_MANIFESTS = 1_000;
22
26
  /** ビルド成果物・VCS等、マニフェスト探索でスキップするディレクトリ */
23
27
  const SKIPPED_DIRS = new Set([".git", "node_modules", "target", "build", ".idea", ".vscode"]);
24
28
  /** OSV-Scannerがスキャンできるマニフェスト(実機確認済み) */
@@ -48,12 +52,19 @@ function assertInsideAllowedRoot(resolvedDir, allowedRootReal) {
48
52
  throw new ScanToolError("path_outside_allowed_root", `指定されたパスは許可されたディレクトリ(${allowedRootReal})の外にあります`);
49
53
  }
50
54
  }
51
- /** 深さ・件数上限付きでマニフェストを探索する。シンボリックリンクは辿らない。 */
52
- async function findManifests(rootDir, maxDepth) {
55
+ function searchLimitError(reason) {
56
+ return new ScanToolError("manifest_search_limit_exceeded", `マニフェスト探索が上限(${reason})に達したため、スキャンを中止しました。` +
57
+ "結果の欠落を避けるため途中までの結果は返しません。より狭いディレクトリ、またはpom.xml / gradle.lockfileを直接指定してください");
58
+ }
59
+ /** 上限付きでマニフェストを探索する。シンボリックリンクは辿らない。上限到達はエラー。 */
60
+ async function findManifests(rootDir, limits) {
53
61
  const manifests = [];
54
62
  let gradleBuildFileFound = false;
63
+ let visited = 0;
55
64
  let currentLevel = [rootDir];
56
- for (let depth = 1; depth <= maxDepth && currentLevel.length > 0; depth++) {
65
+ for (let depth = 1; currentLevel.length > 0; depth++) {
66
+ if (depth > limits.maxDepth)
67
+ throw searchLimitError(`深さ${limits.maxDepth}`);
57
68
  const nextLevel = [];
58
69
  for (const dir of currentLevel) {
59
70
  let entries;
@@ -64,10 +75,12 @@ async function findManifests(rootDir, maxDepth) {
64
75
  continue; // 読めないディレクトリはスキップ(権限不足等)
65
76
  }
66
77
  for (const entry of entries) {
78
+ if (++visited > limits.maxEntries)
79
+ throw searchLimitError(`${limits.maxEntries}エントリ`);
67
80
  if (entry.isFile() && MANIFEST_FILENAMES.has(entry.name)) {
81
+ if (manifests.length >= limits.maxManifests)
82
+ throw searchLimitError(`マニフェスト${limits.maxManifests}件`);
68
83
  manifests.push(path.relative(rootDir, path.join(dir, entry.name)));
69
- if (manifests.length >= MAX_MANIFESTS)
70
- return { manifests, gradleBuildFileFound };
71
84
  }
72
85
  else if (entry.isFile() && GRADLE_BUILD_FILENAMES.has(entry.name)) {
73
86
  gradleBuildFileFound = true;
@@ -93,14 +106,21 @@ export async function detectJavaProject(inputPath, options = {}) {
93
106
  }
94
107
  const resolved = await resolveExistingPath(inputPath);
95
108
  const stats = await stat(resolved);
109
+ // マニフェストの直接指定は、そのファイル1件だけをスキャン範囲にする。
110
+ // 親ディレクトリを探索しないため、探索上限エラーの回避手段として使える
111
+ if (stats.isFile() && MANIFEST_FILENAMES.has(path.basename(resolved))) {
112
+ const projectDir = path.dirname(resolved);
113
+ if (options.allowedRoot !== undefined) {
114
+ assertInsideAllowedRoot(projectDir, await resolveExistingPath(options.allowedRoot));
115
+ }
116
+ return { projectDir, manifests: [path.basename(resolved)], manifestPaths: [resolved] };
117
+ }
96
118
  let projectDir;
97
119
  if (stats.isDirectory()) {
98
120
  projectDir = resolved;
99
121
  }
100
- else if (stats.isFile() &&
101
- (MANIFEST_FILENAMES.has(path.basename(resolved)) ||
102
- GRADLE_BUILD_FILENAMES.has(path.basename(resolved)))) {
103
- // build.gradle等の直接指定も受け付け、ディレクトリとして解決する
122
+ else if (stats.isFile() && GRADLE_BUILD_FILENAMES.has(path.basename(resolved))) {
123
+ // build.gradle等の直接指定は、lockfileを探すためディレクトリとして解決する
104
124
  // (lockfileが無ければ後段でgradle_lockfile_missingの案内になる)
105
125
  projectDir = path.dirname(resolved);
106
126
  }
@@ -110,13 +130,20 @@ export async function detectJavaProject(inputPath, options = {}) {
110
130
  if (options.allowedRoot !== undefined) {
111
131
  assertInsideAllowedRoot(projectDir, await resolveExistingPath(options.allowedRoot));
112
132
  }
113
- const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
114
- const { manifests, gradleBuildFileFound } = await findManifests(projectDir, maxDepth);
133
+ const { manifests, gradleBuildFileFound } = await findManifests(projectDir, {
134
+ maxDepth: options.maxDepth ?? DEFAULT_MAX_DEPTH,
135
+ maxEntries: options.maxEntries ?? DEFAULT_MAX_ENTRIES,
136
+ maxManifests: options.maxManifests ?? DEFAULT_MAX_MANIFESTS,
137
+ });
115
138
  if (manifests.length === 0) {
116
139
  if (gradleBuildFileFound) {
117
140
  throw new ScanToolError("gradle_lockfile_missing", GRADLE_LOCKFILE_GUIDANCE);
118
141
  }
119
- throw new ScanToolError("no_manifest_found", `対応マニフェスト(pom.xml / gradle.lockfile)が見つかりません(深さ${maxDepth}まで探索): ${projectDir}`);
142
+ throw new ScanToolError("no_manifest_found", `対応マニフェスト(pom.xml / gradle.lockfile)が見つかりません: ${projectDir}`);
120
143
  }
121
- return { projectDir, manifests };
144
+ return {
145
+ projectDir,
146
+ manifests,
147
+ manifestPaths: manifests.map((manifest) => path.join(projectDir, manifest)),
148
+ };
122
149
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osv-scanner-mcp",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "MCP server that wraps Google's OSV-Scanner to scan Java projects for known vulnerabilities",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",