@4da/mcp-server 4.6.6 → 5.0.1

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/dist/index.js CHANGED
@@ -8,12 +8,12 @@
8
8
  * Claude Desktop, and other MCP hosts. Runs locally; the only data leaving the
9
9
  * machine is public package names/versions sent to registries (OSV, npm, etc.).
10
10
  *
11
- * SECURITY: the server trusts its local environment and does NOT implement
12
- * authentication. The optional --http transport is intended for localhost use;
13
- * do NOT expose it over a network without putting your own auth in front.
14
- */
15
- /**
16
- * 4DA MCP Server v4.6.3 — Dependency Intelligence for AI Coding Agents
11
+ * SECURITY: stdio serving is unauthenticated by design — the MCP host launches
12
+ * this process and already holds local process rights. The optional --http
13
+ * transport defaults to 127.0.0.1 and applies a Host-header DNS rebinding
14
+ * guard to every request. Binding it anywhere else requires a shared auth
15
+ * secret (MCP_AUTH_SECRET) and then verifies an HMAC-SHA256-signed Bearer
16
+ * token on every request, enforcing the token's role at tool dispatch.
17
17
  *
18
18
  * 14 tools across 5 categories. Live vulnerability scanning (OSV.dev),
19
19
  * ecosystem news, persistent memory, and tech stack awareness for any MCP host.
@@ -25,10 +25,14 @@
25
25
  * Decisions (2) — decision memory, alignment checking
26
26
  * Agent (1) — cross-session persistent memory
27
27
  * Identity (1) — developer DNA profile
28
+ *
29
+ * Protocol: MCP TypeScript SDK v2. stdio serving goes through `serveStdio`,
30
+ * HTTP through `createMcpHandler` — both entries negotiate the era per
31
+ * connection/request, so 2025-era hosts (classic `initialize` handshake) and
32
+ * 2026-07-28 hosts (stateless `server/discover`) are both supported.
28
33
  */
29
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
30
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
31
- import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
34
+ import { serveStdio } from "@modelcontextprotocol/server/stdio";
35
+ import { Server } from "@modelcontextprotocol/server";
32
36
  import { readFileSync, existsSync } from "node:fs";
33
37
  import { join, dirname } from "node:path";
34
38
  import { fileURLToPath } from "node:url";
@@ -73,15 +77,6 @@ function resolveProjectDir() {
73
77
  return dir;
74
78
  return process.cwd();
75
79
  }
76
- const server = new Server({
77
- name: "4da-server",
78
- version: SERVER_VERSION,
79
- }, {
80
- capabilities: {
81
- tools: { listChanged: true },
82
- resources: {},
83
- },
84
- });
85
80
  // Database instance (lazy initialized)
86
81
  let db = null;
87
82
  let liveIntel = null;
@@ -183,128 +178,136 @@ function getDatabase() {
183
178
  return db;
184
179
  }
185
180
  // =============================================================================
186
- // Tool Handlers
181
+ // Server Factory
187
182
  // =============================================================================
188
183
  /**
189
- * List available tools (SLIM)
184
+ * Build a Server instance with every handler registered.
190
185
  *
191
- * Returns one-liner descriptions only (~500 tokens vs ~4500).
192
- * Full schemas available via MCP Resources: 4da://schema/{tool_name}
186
+ * The v2 serving entries take a factory: `serveStdio` pins one instance per
187
+ * connection; stateless HTTP (`createMcpHandler`) builds one per request.
188
+ * Handlers close over the module-level database singleton, so every instance
189
+ * shares the same lazily-initialized database and live-intelligence layer.
193
190
  */
194
- server.setRequestHandler(ListToolsRequestSchema, async () => {
195
- const database = getDatabase();
196
- return {
197
- tools: getSlimToolList(database.isStandalone ? true : undefined),
198
- };
199
- });
200
- /**
201
- * List schema resources
202
- *
203
- * Exposes full tool schemas as MCP Resources for lazy loading.
204
- * Also exposes skill manifest for agent dispatch.
205
- */
206
- server.setRequestHandler(ListResourcesRequestSchema, async () => {
207
- const resources = [
208
- ...getSchemaResources(),
209
- // Skill manifest for agent dispatch
210
- {
211
- uri: "4da://skills",
212
- name: "Skill manifest",
213
- description: "Registry of 4DA skills for Claude Code agent dispatch",
214
- mimeType: "application/json",
215
- },
216
- // Category manifest for tool discovery
217
- {
218
- uri: "4da://categories",
219
- name: "Tool categories",
220
- description: "Tool groupings by category with tag metadata",
221
- mimeType: "application/json",
191
+ export function buildServer() {
192
+ const server = new Server({
193
+ name: "4da-server",
194
+ version: SERVER_VERSION,
195
+ }, {
196
+ capabilities: {
197
+ tools: { listChanged: true },
198
+ resources: {},
222
199
  },
223
- ];
224
- return { resources };
225
- });
226
- /**
227
- * Read a resource (schema or skill manifest)
228
- */
229
- server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
230
- const uri = request.params.uri;
231
- // Handle category manifest
232
- if (uri === "4da://categories") {
200
+ });
201
+ // List available tools (SLIM): one-liner descriptions only (~500 tokens vs
202
+ // ~4500). Full schemas available via MCP Resources: 4da://schema/{tool_name}
203
+ server.setRequestHandler("tools/list", async () => {
204
+ const database = getDatabase();
233
205
  return {
234
- contents: [
235
- {
236
- uri,
237
- mimeType: "application/json",
238
- text: JSON.stringify(getCategoryManifest(), null, 2),
239
- },
240
- ],
206
+ tools: getSlimToolList(database.isStandalone ? true : undefined),
241
207
  };
242
- }
243
- // Handle skill manifest
244
- if (uri === "4da://skills") {
245
- const skillsPath = join(homedir(), ".local", "share", "4da", "skills", "registry.json");
246
- if (!existsSync(skillsPath)) {
247
- throw new Error("Skill manifest not found. Run 4DA setup to create it.");
208
+ });
209
+ // List schema resources: full tool schemas as MCP Resources for lazy
210
+ // loading, plus the skill and category manifests.
211
+ server.setRequestHandler("resources/list", async () => {
212
+ const resources = [
213
+ ...getSchemaResources(),
214
+ // Skill manifest for agent dispatch
215
+ {
216
+ uri: "4da://skills",
217
+ name: "Skill manifest",
218
+ description: "Registry of 4DA skills for Claude Code agent dispatch",
219
+ mimeType: "application/json",
220
+ },
221
+ // Category manifest for tool discovery
222
+ {
223
+ uri: "4da://categories",
224
+ name: "Tool categories",
225
+ description: "Tool groupings by category with tag metadata",
226
+ mimeType: "application/json",
227
+ },
228
+ ];
229
+ return { resources };
230
+ });
231
+ // Read a resource (schema or skill manifest)
232
+ server.setRequestHandler("resources/read", async (request) => {
233
+ const uri = request.params.uri;
234
+ // Handle category manifest
235
+ if (uri === "4da://categories") {
236
+ return {
237
+ contents: [
238
+ {
239
+ uri,
240
+ mimeType: "application/json",
241
+ text: JSON.stringify(getCategoryManifest(), null, 2),
242
+ },
243
+ ],
244
+ };
245
+ }
246
+ // Handle skill manifest
247
+ if (uri === "4da://skills") {
248
+ const skillsPath = join(homedir(), ".local", "share", "4da", "skills", "registry.json");
249
+ if (!existsSync(skillsPath)) {
250
+ throw new Error("Skill manifest not found. Run 4DA setup to create it.");
251
+ }
252
+ const skillsContent = readFileSync(skillsPath, "utf-8");
253
+ return {
254
+ contents: [
255
+ {
256
+ uri,
257
+ mimeType: "application/json",
258
+ text: skillsContent,
259
+ },
260
+ ],
261
+ };
262
+ }
263
+ // Parse tool name from URI: 4da://schema/{tool_name}
264
+ const match = uri.match(/^4da:\/\/schema\/(.+)$/);
265
+ if (!match) {
266
+ throw new Error(`Invalid resource URI: ${uri}`);
267
+ }
268
+ const toolName = match[1];
269
+ if (!hasToolSchema(toolName)) {
270
+ throw new Error(`Unknown tool: ${toolName}`);
248
271
  }
249
- const skillsContent = readFileSync(skillsPath, "utf-8");
272
+ const schemaFile = getSchemaFilename(toolName);
273
+ if (!schemaFile) {
274
+ throw new Error(`No schema file for tool: ${toolName}`);
275
+ }
276
+ // Read schema from file
277
+ const schemaPath = join(__dirname, "schemas", schemaFile);
278
+ const schemaContent = readFileSync(schemaPath, "utf-8");
250
279
  return {
251
280
  contents: [
252
281
  {
253
282
  uri,
254
283
  mimeType: "application/json",
255
- text: skillsContent,
284
+ text: schemaContent,
256
285
  },
257
286
  ],
258
287
  };
259
- }
260
- // Parse tool name from URI: 4da://schema/{tool_name}
261
- const match = uri.match(/^4da:\/\/schema\/(.+)$/);
262
- if (!match) {
263
- throw new Error(`Invalid resource URI: ${uri}`);
264
- }
265
- const toolName = match[1];
266
- if (!hasToolSchema(toolName)) {
267
- throw new Error(`Unknown tool: ${toolName}`);
268
- }
269
- const schemaFile = getSchemaFilename(toolName);
270
- if (!schemaFile) {
271
- throw new Error(`No schema file for tool: ${toolName}`);
272
- }
273
- // Read schema from file
274
- const schemaPath = join(__dirname, "schemas", schemaFile);
275
- const schemaContent = readFileSync(schemaPath, "utf-8");
276
- return {
277
- contents: [
278
- {
279
- uri,
280
- mimeType: "application/json",
281
- text: schemaContent,
282
- },
283
- ],
284
- };
285
- });
286
- /**
287
- * Execute a tool
288
- */
289
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
290
- const { name, arguments: args } = request.params;
291
- try {
292
- const database = getDatabase();
293
- return await dispatchTool(name, database, args);
294
- }
295
- catch (error) {
296
- const errorMessage = error instanceof Error ? error.message : String(error);
297
- return {
298
- content: [
299
- {
300
- type: "text",
301
- text: JSON.stringify({ error: errorMessage }, null, 2),
302
- },
303
- ],
304
- isError: true,
305
- };
306
- }
307
- });
288
+ });
289
+ // Execute a tool
290
+ server.setRequestHandler("tools/call", async (request) => {
291
+ const { name, arguments: args } = request.params;
292
+ try {
293
+ const database = getDatabase();
294
+ return await dispatchTool(name, database, args);
295
+ }
296
+ catch (error) {
297
+ const errorMessage = error instanceof Error ? error.message : String(error);
298
+ return {
299
+ content: [
300
+ {
301
+ type: "text",
302
+ text: JSON.stringify({ error: errorMessage }, null, 2),
303
+ },
304
+ ],
305
+ isError: true,
306
+ };
307
+ }
308
+ });
309
+ return server;
310
+ }
308
311
  // =============================================================================
309
312
  // Server Lifecycle
310
313
  // =============================================================================
@@ -312,8 +315,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
312
315
  * Start the MCP server
313
316
  *
314
317
  * Supports three modes:
315
- * (default) stdio transport — classic MCP, works with all hosts
316
- * --http Streamable HTTP transport (spec 2025-03-26)
318
+ * (default) stdio transport — serves 2025-era and 2026-07-28 hosts
319
+ * --http Streamable HTTP transport — stateless, both eras
317
320
  * --setup Configure editors for 4DA MCP
318
321
  */
319
322
  async function main() {
@@ -338,13 +341,23 @@ async function main() {
338
341
  Options:
339
342
  --http Use Streamable HTTP instead of stdio
340
343
  --port <number> HTTP port (default: 4840)
341
- --host <address> HTTP bind address (default: 127.0.0.1)
344
+ --host <address> HTTP bind address (default: 127.0.0.1). A non-loopback
345
+ address exposes every tool to the network and is
346
+ refused unless MCP_AUTH_SECRET is set; authentication
347
+ is then mandatory for every request.
342
348
  --setup Detect editors and write MCP config
343
349
  --doctor Validate database, bindings, and LLM providers
344
350
 
345
351
  Environment:
346
352
  FOURDA_DB_PATH Path to 4DA's SQLite database (auto-detected if omitted)
347
353
  FOURDA_OFFLINE Set to "true" to disable all network calls (OSV.dev, HN)
354
+ MCP_AUTH_SECRET Shared secret for verifying relay-issued Bearer tokens
355
+ (HMAC-SHA256). Falls back to JWT_SECRET. Without it, no
356
+ token is accepted.
357
+ MCP_AUTH_REQUIRED Set to "true" to require auth on a loopback --http bind
358
+ (always required on a non-loopback bind)
359
+ MCP_ALLOWED_HOSTS Extra comma-separated hostnames accepted in the Host /
360
+ Origin headers (needed when binding to 0.0.0.0)
348
361
 
349
362
  Works standalone (scans your project on startup) or with the full
350
363
  4DA desktop app for content scoring, source monitoring, and more.
@@ -365,10 +378,35 @@ async function main() {
365
378
  }
366
379
  // HTTP transport mode
367
380
  if (args.includes("--http")) {
368
- const portIndex = args.indexOf("--port");
369
- const port = portIndex !== -1 ? parseInt(args[portIndex + 1], 10) : 4840;
370
- const host = args.includes("--host") ? args[args.indexOf("--host") + 1] : "127.0.0.1";
371
- await startHttpServer(server, { port, host });
381
+ const flagValue = (flag) => {
382
+ const i = args.indexOf(flag);
383
+ if (i === -1)
384
+ return undefined;
385
+ const value = args[i + 1];
386
+ // A flag with no value (or followed by another flag) is a typo, not a
387
+ // request to bind everywhere — refuse rather than guess.
388
+ if (value === undefined || value.startsWith("-")) {
389
+ console.error(`[4DA] ${flag} requires a value`);
390
+ process.exit(1);
391
+ }
392
+ return value;
393
+ };
394
+ const rawPort = flagValue("--port");
395
+ const port = rawPort === undefined ? 4840 : Number.parseInt(rawPort, 10);
396
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
397
+ console.error(`[4DA] Invalid --port value: ${rawPort}`);
398
+ process.exit(1);
399
+ }
400
+ const host = flagValue("--host") ?? "127.0.0.1";
401
+ try {
402
+ await startHttpServer(buildServer, { port, host });
403
+ }
404
+ catch (error) {
405
+ // Bind-policy refusals carry an actionable message; print it plainly
406
+ // rather than as an unhandled stack trace.
407
+ console.error(`[4DA] ${error instanceof Error ? error.message : String(error)}`);
408
+ process.exit(1);
409
+ }
372
410
  return;
373
411
  }
374
412
  // -------------------------------------------------------------------------
@@ -392,9 +430,14 @@ async function main() {
392
430
  console.error(` Or run: npx @4da/mcp-server --doctor for diagnostics`);
393
431
  console.error(``);
394
432
  }
395
- // Default: stdio transport (existing behavior)
396
- const transport = new StdioServerTransport();
397
- await server.connect(transport);
433
+ // Default: stdio serving. `serveStdio` owns the era decision per connection —
434
+ // a 2025-era `initialize` opening is served exactly as before; a 2026-07-28
435
+ // `server/discover` opening gets the stateless modern protocol.
436
+ serveStdio(buildServer, {
437
+ onerror: (error) => {
438
+ console.error(`[4DA] stdio serving error: ${error.message}`);
439
+ },
440
+ });
398
441
  // Handle graceful shutdown
399
442
  process.on("SIGINT", () => {
400
443
  console.error("[4DA] Received SIGINT — shutting down gracefully");
@@ -409,7 +452,7 @@ async function main() {
409
452
  process.exit(0);
410
453
  });
411
454
  const toolCount = getSlimToolList().length;
412
- console.error(`4DA MCP Server v4.6.3 started — ${toolCount} tools, stdio transport`);
455
+ console.error(`4DA MCP Server v${SERVER_VERSION} started — ${toolCount} tools, stdio transport`);
413
456
  console.error(" Use --http for Streamable HTTP, --setup to configure editors, --doctor to check health");
414
457
  }
415
458
  main().catch((error) => {
package/dist/setup.js CHANGED
File without changes
@@ -4,6 +4,7 @@
4
4
  * Map-based dispatch for the 14 active tools.
5
5
  * Adding a new tool = add to this map + schema-registry + barrel export.
6
6
  */
7
+ import type { CallToolResult } from "@modelcontextprotocol/server";
7
8
  import type { FourDADatabase } from "./db.js";
8
9
  /**
9
10
  * Executor signature — all tool execute functions follow this shape.
@@ -13,13 +14,12 @@ export type ToolExecutor = (db: FourDADatabase, params: any) => unknown | Promis
13
14
  /**
14
15
  * Dispatch a tool call by name. Awaits uniformly (no-op for sync executors).
15
16
  * Returns MCP-formatted response with JSON-serialized result.
17
+ *
18
+ * Authenticated HTTP callers are role-checked here — the single choke point
19
+ * every tool call passes through. Stdio callers are unaffected (no auth
20
+ * context; the host already owns the process).
16
21
  */
17
- export declare function dispatchTool(name: string, db: FourDADatabase, args: Record<string, unknown> | undefined): Promise<{
18
- content: Array<{
19
- type: string;
20
- text: string;
21
- }>;
22
- }>;
22
+ export declare function dispatchTool(name: string, db: FourDADatabase, args: Record<string, unknown> | undefined): Promise<CallToolResult>;
23
23
  /** Check if a tool exists in the dispatch map */
24
24
  export declare function hasDispatchTool(name: string): boolean;
25
25
  /** Get count of registered tool executors */
@@ -5,6 +5,7 @@
5
5
  * Map-based dispatch for the 14 active tools.
6
6
  * Adding a new tool = add to this map + schema-registry + barrel export.
7
7
  */
8
+ import { assertToolPermission } from "./auth-context.js";
8
9
  import { executeGetRelevantContent, executeGetContext, executeRecordFeedback, executeGetActionableSignals, executeKnowledgeGaps, executeDecisionMemory, executeCheckDecisionAlignment, executeAgentMemory, executeDeveloperDna, executeWhatShouldIKnow, executeVulnerabilityScan, executeEcosystemPulse, executeDependencyHealth, executeUpgradePlanner, } from "./tools/index.js";
9
10
  import { getLiveIntelligence } from "./live-singleton.js";
10
11
  /**
@@ -46,12 +47,17 @@ const FRESHNESS_TOOLS = new Set([
46
47
  /**
47
48
  * Dispatch a tool call by name. Awaits uniformly (no-op for sync executors).
48
49
  * Returns MCP-formatted response with JSON-serialized result.
50
+ *
51
+ * Authenticated HTTP callers are role-checked here — the single choke point
52
+ * every tool call passes through. Stdio callers are unaffected (no auth
53
+ * context; the host already owns the process).
49
54
  */
50
55
  export async function dispatchTool(name, db, args) {
51
56
  const executor = DISPATCH_MAP[name];
52
57
  if (!executor) {
53
58
  throw new Error(`Unknown tool: ${name}`);
54
59
  }
60
+ assertToolPermission(name);
55
61
  const result = await executor(db, (args || {}));
56
62
  const payload = FRESHNESS_TOOLS.has(name) ? attachFreshness(db, result) : result;
57
63
  return {
@@ -250,7 +250,11 @@ export function executeGetActionableSignals(db, params) {
250
250
  items = db.getRelevantContent(0.1, undefined, 200, 168); // Try 7 days
251
251
  }
252
252
  if (items.length === 0) {
253
- items = db.getRelevantContent(0.0, undefined, 200, 720); // Try 30 days, any score
253
+ // Deep 30-day/any-score fallback reaches the stale-epoch tail after a
254
+ // pipeline-version bump. This path trusts stored signal_type/signal_priority
255
+ // verbatim (confidence 0.90 below), so it MUST only see current-version
256
+ // rows — stale persisted signals are claims the live brain no longer makes.
257
+ items = db.getRelevantContent(0.0, undefined, 200, 720, true);
254
258
  }
255
259
  // Get user's detected tech for cross-referencing
256
260
  const context = db.getUserContext(true, false);
@@ -60,7 +60,10 @@ export function executeGetRelevantContent(db, params) {
60
60
  items = db.getRelevantContent(minScore, params.source_type, limit, 168);
61
61
  }
62
62
  if (items.length === 0) {
63
- items = db.getRelevantContent(0.0, params.source_type, limit, 720);
63
+ // Deep 30-day fallback at zero floor reaches the stale-epoch tail after a
64
+ // pipeline-version bump — require current-version scores so we never rank
65
+ // numbers the live scoring brain doesn't stand behind.
66
+ items = db.getRelevantContent(0.0, params.source_type, limit, 720, true);
64
67
  }
65
68
  return items;
66
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4da/mcp-server",
3
- "version": "4.6.6",
3
+ "version": "5.0.1",
4
4
  "mcpName": "io.github.4DA-Systems/4da-mcp-server",
5
5
  "description": "Dependency intelligence for AI coding agents. Live CVE scanning, dependency health, upgrade planning, ecosystem news, decision memory — zero config, privacy-first. 9 tools standalone, 14 total with desktop app.",
6
6
  "type": "module",
@@ -18,6 +18,17 @@
18
18
  "README.md",
19
19
  "LICENSE"
20
20
  ],
21
+ "scripts": {
22
+ "build": "tsc && pnpm run copy-schemas",
23
+ "copy-schemas": "node -e \"const fs=require('fs');const path=require('path');const src='src/schemas';const dst='dist/schemas';if(!fs.existsSync(dst))fs.mkdirSync(dst,{recursive:true});fs.readdirSync(src).filter(f=>f.endsWith('.json')).forEach(f=>fs.copyFileSync(path.join(src,f),path.join(dst,f)))\"",
24
+ "dev": "tsc --watch",
25
+ "start": "node dist/index.js",
26
+ "inspect": "npx @modelcontextprotocol/inspector node dist/index.js",
27
+ "setup": "node dist/setup.js",
28
+ "test": "vitest run",
29
+ "test:watch": "vitest",
30
+ "prepublishOnly": "pnpm run build"
31
+ },
21
32
  "keywords": [
22
33
  "mcp",
23
34
  "mcp-server",
@@ -69,8 +80,10 @@
69
80
  "url": "https://github.com/4DA-Systems/4DA/issues"
70
81
  },
71
82
  "dependencies": {
72
- "@modelcontextprotocol/sdk": "^1.29.0",
73
- "better-sqlite3": "^12.9.0"
83
+ "better-sqlite3": "^12.9.0",
84
+ "@modelcontextprotocol/node": "^2.0.0",
85
+ "@modelcontextprotocol/server": "^2.0.0",
86
+ "hono": "^4.12.34"
74
87
  },
75
88
  "devDependencies": {
76
89
  "@types/better-sqlite3": "^7.6.11",
@@ -80,16 +93,20 @@
80
93
  "vitest": "^4.1.5"
81
94
  },
82
95
  "engines": {
83
- "node": ">=18.0.0"
96
+ "node": ">=20.0.0"
84
97
  },
85
- "scripts": {
86
- "build": "tsc && pnpm run copy-schemas",
87
- "copy-schemas": "node -e \"const fs=require('fs');const path=require('path');const src='src/schemas';const dst='dist/schemas';if(!fs.existsSync(dst))fs.mkdirSync(dst,{recursive:true});fs.readdirSync(src).filter(f=>f.endsWith('.json')).forEach(f=>fs.copyFileSync(path.join(src,f),path.join(dst,f)))\"",
88
- "dev": "tsc --watch",
89
- "start": "node dist/index.js",
90
- "inspect": "npx @modelcontextprotocol/inspector node dist/index.js",
91
- "setup": "node dist/setup.js",
92
- "test": "vitest run",
93
- "test:watch": "vitest"
98
+ "pnpm": {
99
+ "onlyBuiltDependencies": [
100
+ "better-sqlite3",
101
+ "esbuild"
102
+ ],
103
+ "overrides": {
104
+ "fast-uri": ">=3.1.2",
105
+ "ip-address": "^10.1.1",
106
+ "vite": ">=7.3.5 <8",
107
+ "esbuild": ">=0.28.1",
108
+ "hono": ">=4.12.34 <5",
109
+ "@hono/node-server": ">=2.0.5 <3"
110
+ }
94
111
  }
95
- }
112
+ }