@mindstone/mcp-server-salesforce 0.1.2

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.
@@ -0,0 +1,134 @@
1
+ import { z } from 'zod';
2
+ import { withErrorHandling, escapeSOQL, escapeSOQLLike, validateFields, validateAndMergeCustomFields, formatSOQLDate, checkSaveResult } from '../utils.js';
3
+ import { withConnection } from '../client.js';
4
+ import { ConnectorError } from '../types.js';
5
+ export function registerTaskTools(server) {
6
+ server.registerTool('salesforce_get_tasks', {
7
+ description: `Get tasks. Filters: subject_contains, status, priority, who_id, what_id, activity_date_from, activity_date_to. Max 200 (default: 50).`,
8
+ inputSchema: z.object({
9
+ limit: z.number().int().min(1).max(200).optional().describe('Max results 1-200 (default: 50)'),
10
+ subject_contains: z.string().optional().describe('Filter by subject'),
11
+ status: z.string().optional().describe('Filter by status'),
12
+ priority: z.string().optional().describe('Filter by priority'),
13
+ who_id: z.string().optional().describe('Filter by WhoId (Contact or Lead ID)'),
14
+ what_id: z.string().optional().describe('Filter by WhatId (Account or Opportunity ID)'),
15
+ activity_date_from: z.string().optional().describe('Filter tasks on/after date (YYYY-MM-DD)'),
16
+ activity_date_to: z.string().optional().describe('Filter tasks on/before date (YYYY-MM-DD)'),
17
+ fields: z.array(z.string()).optional().describe('Custom fields'),
18
+ }),
19
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
20
+ }, withErrorHandling(async (args) => {
21
+ return withConnection(undefined, async (conn) => {
22
+ const defaultFields = ['Id', 'Subject', 'Status', 'Priority', 'WhoId', 'WhatId', 'ActivityDate', 'Description', 'OwnerId', 'IsClosed'];
23
+ const fields = validateFields(args.fields || [], defaultFields);
24
+ let query = `SELECT ${fields.join(', ')} FROM Task`;
25
+ const conditions = [];
26
+ if (args.subject_contains)
27
+ conditions.push(`Subject LIKE '%${escapeSOQLLike(args.subject_contains)}%'`);
28
+ if (args.status)
29
+ conditions.push(`Status = '${escapeSOQL(args.status)}'`);
30
+ if (args.priority)
31
+ conditions.push(`Priority = '${escapeSOQL(args.priority)}'`);
32
+ if (args.who_id)
33
+ conditions.push(`WhoId = '${escapeSOQL(args.who_id)}'`);
34
+ if (args.what_id)
35
+ conditions.push(`WhatId = '${escapeSOQL(args.what_id)}'`);
36
+ if (args.activity_date_from)
37
+ conditions.push(`ActivityDate >= ${formatSOQLDate(args.activity_date_from, 'activity_date_from')}`);
38
+ if (args.activity_date_to)
39
+ conditions.push(`ActivityDate <= ${formatSOQLDate(args.activity_date_to, 'activity_date_to')}`);
40
+ if (conditions.length > 0)
41
+ query += ` WHERE ${conditions.join(' AND ')}`;
42
+ const limit = Math.min(Math.max(1, args.limit ?? 50), 200);
43
+ query += ` LIMIT ${limit}`;
44
+ const result = await conn.query(query);
45
+ return JSON.stringify({ ok: true, records: result.records, totalSize: result.totalSize });
46
+ });
47
+ }));
48
+ server.registerTool('salesforce_create_task', {
49
+ description: `Create a task. Required: subject. Optional: status, priority, who_id, what_id, activity_date, description, owner_id, fields.`,
50
+ inputSchema: z.object({
51
+ subject: z.string().min(1).describe('Task subject (required)'),
52
+ status: z.string().optional().describe('Task status (default: Not Started)'),
53
+ priority: z.string().optional().describe('Priority (High, Normal, Low)'),
54
+ who_id: z.string().optional().describe('Contact or Lead ID'),
55
+ what_id: z.string().optional().describe('Account or Opportunity ID'),
56
+ activity_date: z.string().optional().describe('Due date (YYYY-MM-DD)'),
57
+ description: z.string().optional().describe('Task description'),
58
+ owner_id: z.string().optional().describe('Owner User ID'),
59
+ fields: z.record(z.unknown()).optional().describe('Additional/custom fields'),
60
+ }),
61
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
62
+ }, withErrorHandling(async (args) => {
63
+ if (args.activity_date)
64
+ formatSOQLDate(args.activity_date, 'activity_date');
65
+ return withConnection(undefined, async (conn) => {
66
+ const data = { Subject: args.subject };
67
+ if (args.status)
68
+ data.Status = args.status;
69
+ if (args.priority)
70
+ data.Priority = args.priority;
71
+ if (args.who_id)
72
+ data.WhoId = args.who_id;
73
+ if (args.what_id)
74
+ data.WhatId = args.what_id;
75
+ if (args.activity_date)
76
+ data.ActivityDate = args.activity_date;
77
+ if (args.description)
78
+ data.Description = args.description;
79
+ if (args.owner_id)
80
+ data.OwnerId = args.owner_id;
81
+ if (args.fields)
82
+ validateAndMergeCustomFields(data, args.fields);
83
+ const result = await conn.sobject('Task').create(data);
84
+ if (!result.success)
85
+ throw new ConnectorError('Failed to create task', 'CREATE_ERROR', JSON.stringify(result.errors));
86
+ return JSON.stringify({ ok: true, status: 'success', object: 'Task', id: result.id, subject: args.subject });
87
+ });
88
+ }));
89
+ server.registerTool('salesforce_update_task', {
90
+ description: `Update a task. Required: id. Updatable: subject, status, priority, who_id, what_id, activity_date, description, owner_id, fields.`,
91
+ inputSchema: z.object({
92
+ id: z.string().min(1).describe('Salesforce Task ID (required)'),
93
+ subject: z.string().optional().describe('Task subject'),
94
+ status: z.string().optional().describe('Task status'),
95
+ priority: z.string().optional().describe('Priority'),
96
+ who_id: z.string().optional().describe('Contact or Lead ID'),
97
+ what_id: z.string().optional().describe('Account or Opportunity ID'),
98
+ activity_date: z.string().optional().describe('Due date (YYYY-MM-DD)'),
99
+ description: z.string().optional().describe('Description'),
100
+ owner_id: z.string().optional().describe('Owner User ID'),
101
+ fields: z.record(z.unknown()).optional().describe('Additional/custom fields'),
102
+ }),
103
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
104
+ }, withErrorHandling(async (args) => {
105
+ if (args.activity_date)
106
+ formatSOQLDate(args.activity_date, 'activity_date');
107
+ return withConnection(undefined, async (conn) => {
108
+ const updateData = { Id: args.id };
109
+ if (args.subject)
110
+ updateData.Subject = args.subject;
111
+ if (args.status)
112
+ updateData.Status = args.status;
113
+ if (args.priority)
114
+ updateData.Priority = args.priority;
115
+ if (args.who_id)
116
+ updateData.WhoId = args.who_id;
117
+ if (args.what_id)
118
+ updateData.WhatId = args.what_id;
119
+ if (args.activity_date)
120
+ updateData.ActivityDate = args.activity_date;
121
+ if (args.description)
122
+ updateData.Description = args.description;
123
+ if (args.owner_id)
124
+ updateData.OwnerId = args.owner_id;
125
+ if (args.fields)
126
+ validateAndMergeCustomFields(updateData, args.fields);
127
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
+ const result = await conn.sobject('Task').update(updateData);
129
+ checkSaveResult(result, 'Failed to update task');
130
+ return JSON.stringify({ ok: true, status: 'success', object: 'Task', id: args.id });
131
+ });
132
+ }));
133
+ }
134
+ //# sourceMappingURL=tasks.js.map
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerUserTools(server: McpServer): void;
3
+ //# sourceMappingURL=users.d.ts.map
@@ -0,0 +1,39 @@
1
+ import { z } from 'zod';
2
+ import { withErrorHandling, escapeSOQLLike, validateFields, isValidQueryFieldName } from '../utils.js';
3
+ import { withConnection } from '../client.js';
4
+ export function registerUserTools(server) {
5
+ server.registerTool('salesforce_get_users', {
6
+ description: `Get Salesforce users. Filters: name_contains, email_contains, role_contains, is_active. Max 200 (default: 50).`,
7
+ inputSchema: z.object({
8
+ limit: z.number().int().min(1).max(200).optional().describe('Max results 1-200 (default: 50)'),
9
+ name_contains: z.string().optional().describe('Filter by name'),
10
+ email_contains: z.string().optional().describe('Filter by email'),
11
+ role_contains: z.string().optional().describe('Filter by UserRole.Name'),
12
+ is_active: z.boolean().optional().describe('Filter by active status'),
13
+ fields: z.array(z.string()).optional().describe('Custom fields'),
14
+ }),
15
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
16
+ }, withErrorHandling(async (args) => {
17
+ return withConnection(undefined, async (conn) => {
18
+ const defaultFields = ['Id', 'Name', 'Email', 'Username', 'IsActive', 'UserRoleId', 'Profile.Name'];
19
+ const fields = validateFields(args.fields || [], defaultFields, isValidQueryFieldName);
20
+ let query = `SELECT ${fields.join(', ')} FROM User`;
21
+ const conditions = [];
22
+ if (args.name_contains)
23
+ conditions.push(`Name LIKE '%${escapeSOQLLike(args.name_contains)}%'`);
24
+ if (args.email_contains)
25
+ conditions.push(`Email LIKE '%${escapeSOQLLike(args.email_contains)}%'`);
26
+ if (args.role_contains)
27
+ conditions.push(`UserRole.Name LIKE '%${escapeSOQLLike(args.role_contains)}%'`);
28
+ if (args.is_active !== undefined)
29
+ conditions.push(`IsActive = ${args.is_active}`);
30
+ if (conditions.length > 0)
31
+ query += ` WHERE ${conditions.join(' AND ')}`;
32
+ const limit = Math.min(Math.max(1, args.limit ?? 50), 200);
33
+ query += ` LIMIT ${limit}`;
34
+ const result = await conn.query(query);
35
+ return JSON.stringify({ ok: true, records: result.records, totalSize: result.totalSize });
36
+ });
37
+ }));
38
+ }
39
+ //# sourceMappingURL=users.js.map
@@ -0,0 +1,44 @@
1
+ export declare const REQUEST_TIMEOUT_MS = 30000;
2
+ export declare const SERVER_NAME = "salesforce-mcp-server";
3
+ /** Server version reported on MCP `initialize`. Read from package.json so
4
+ * it cannot drift from the published npm version. */
5
+ export declare const SERVER_VERSION: string;
6
+ export interface BridgeState {
7
+ port: number;
8
+ token: string;
9
+ }
10
+ export declare class ConnectorError extends Error {
11
+ readonly code: string;
12
+ readonly resolution: string;
13
+ constructor(message: string, code: string, resolution: string);
14
+ }
15
+ export type AuthMode = 'bridge' | 'standalone_oauth' | 'manual_token' | 'unconfigured';
16
+ export interface SalesforceTokenData {
17
+ access_token: string;
18
+ refresh_token?: string;
19
+ instance_url: string;
20
+ token_type?: string;
21
+ issued_at?: string;
22
+ id?: string;
23
+ username?: string;
24
+ organization_id?: string;
25
+ expires_at?: number;
26
+ signature?: string;
27
+ login_url?: string;
28
+ }
29
+ export interface SalesforceAccount {
30
+ id: string;
31
+ username: string;
32
+ instance_url: string;
33
+ is_sandbox: boolean;
34
+ connected_at: string;
35
+ }
36
+ export interface SalesforceAccountsConfig {
37
+ accounts: SalesforceAccount[];
38
+ }
39
+ export interface SaveResult {
40
+ success: boolean;
41
+ id?: string;
42
+ errors?: unknown[];
43
+ }
44
+ //# sourceMappingURL=types.d.ts.map
package/dist/types.js ADDED
@@ -0,0 +1,19 @@
1
+ import { createRequire } from 'node:module';
2
+ const require = createRequire(import.meta.url);
3
+ const pkg = require('../package.json');
4
+ export const REQUEST_TIMEOUT_MS = 30_000;
5
+ export const SERVER_NAME = 'salesforce-mcp-server';
6
+ /** Server version reported on MCP `initialize`. Read from package.json so
7
+ * it cannot drift from the published npm version. */
8
+ export const SERVER_VERSION = pkg.version;
9
+ export class ConnectorError extends Error {
10
+ code;
11
+ resolution;
12
+ constructor(message, code, resolution) {
13
+ super(message);
14
+ this.code = code;
15
+ this.resolution = resolution;
16
+ this.name = 'ConnectorError';
17
+ }
18
+ }
19
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,62 @@
1
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ type ToolHandler<T> = (args: T, extra: unknown) => Promise<CallToolResult>;
3
+ /**
4
+ * Wraps a tool handler with standard error handling.
5
+ */
6
+ export declare function withErrorHandling<T>(fn: (args: T, extra: unknown) => Promise<string>): ToolHandler<T>;
7
+ /**
8
+ * Escape a string value for safe use in SOQL queries.
9
+ *
10
+ * Escapes the four characters that SOQL gives special meaning to:
11
+ * - `\\` (backslash — the SOQL escape character)
12
+ * - `'` (single quote — string delimiter; SOQL doubles the quote)
13
+ * - `%` (LIKE wildcard — matches zero-or-more characters)
14
+ * - `_` (LIKE wildcard — matches exactly one character)
15
+ *
16
+ * The wildcard characters MUST be escaped at every interpolation site so
17
+ * that user-supplied substrings cannot expand the pattern beyond the
18
+ * literal value the caller intended. For LIKE sites, prefer the
19
+ * dedicated `escapeSOQLLike` helper for an explicit signal of intent.
20
+ */
21
+ export declare function escapeSOQL(value: string): string;
22
+ /**
23
+ * Escape a user-supplied substring for interpolation inside a SOQL
24
+ * `LIKE '%...%'` clause. Identical to `escapeSOQL` semantically (both
25
+ * escape `\\`, `'`, `%`, `_`) but exists as a separate helper so every
26
+ * `LIKE` site is unambiguously marked as wildcard-aware.
27
+ */
28
+ export declare function escapeSOQLLike(value: string): string;
29
+ /**
30
+ * Validate that a field name is safe (alphanumeric + underscore only).
31
+ */
32
+ export declare function isValidFieldName(field: string): boolean;
33
+ /**
34
+ * Validate query field names (supports dot-notation for relationship fields).
35
+ */
36
+ export declare function isValidQueryFieldName(field: string): boolean;
37
+ /**
38
+ * Validate that an sObject API name is safe.
39
+ */
40
+ export declare function validateObjectName(name: string): void;
41
+ /**
42
+ * Format a date string for SOQL (YYYY-MM-DD format required).
43
+ */
44
+ export declare function formatSOQLDate(dateStr: string, paramName: string): string;
45
+ /**
46
+ * Validate field names and return validated or default fields.
47
+ */
48
+ export declare function validateFields(fields: string[], defaultFields: string[], validator?: (field: string) => boolean): string[];
49
+ /**
50
+ * Validate and merge custom fields into update data.
51
+ */
52
+ export declare function validateAndMergeCustomFields(updateData: Record<string, unknown>, fields: Record<string, unknown>): void;
53
+ export declare const ALLOWED_FILTER_OPERATORS: Set<string>;
54
+ export declare function checkSaveResult(result: {
55
+ success: boolean;
56
+ errors?: unknown[];
57
+ } | Array<{
58
+ success: boolean;
59
+ errors?: unknown[];
60
+ }>, errorMessage: string): void;
61
+ export {};
62
+ //# sourceMappingURL=utils.d.ts.map
package/dist/utils.js ADDED
@@ -0,0 +1,134 @@
1
+ import { ConnectorError } from './types.js';
2
+ /**
3
+ * Wraps a tool handler with standard error handling.
4
+ */
5
+ export function withErrorHandling(fn) {
6
+ return async (args, extra) => {
7
+ try {
8
+ const result = await fn(args, extra);
9
+ return { content: [{ type: 'text', text: result }] };
10
+ }
11
+ catch (error) {
12
+ if (error instanceof ConnectorError) {
13
+ return {
14
+ content: [
15
+ {
16
+ type: 'text',
17
+ text: JSON.stringify({
18
+ ok: false,
19
+ error: error.message,
20
+ code: error.code,
21
+ resolution: error.resolution,
22
+ }),
23
+ },
24
+ ],
25
+ isError: true,
26
+ };
27
+ }
28
+ const errorMessage = error instanceof Error ? error.message : String(error);
29
+ return {
30
+ content: [{ type: 'text', text: JSON.stringify({ ok: false, error: errorMessage }) }],
31
+ isError: true,
32
+ };
33
+ }
34
+ };
35
+ }
36
+ /**
37
+ * Escape a string value for safe use in SOQL queries.
38
+ *
39
+ * Escapes the four characters that SOQL gives special meaning to:
40
+ * - `\\` (backslash — the SOQL escape character)
41
+ * - `'` (single quote — string delimiter; SOQL doubles the quote)
42
+ * - `%` (LIKE wildcard — matches zero-or-more characters)
43
+ * - `_` (LIKE wildcard — matches exactly one character)
44
+ *
45
+ * The wildcard characters MUST be escaped at every interpolation site so
46
+ * that user-supplied substrings cannot expand the pattern beyond the
47
+ * literal value the caller intended. For LIKE sites, prefer the
48
+ * dedicated `escapeSOQLLike` helper for an explicit signal of intent.
49
+ */
50
+ export function escapeSOQL(value) {
51
+ if (!value)
52
+ return '';
53
+ return value
54
+ // Backslash MUST be escaped first to avoid double-escaping.
55
+ .replace(/\\/g, '\\\\')
56
+ .replace(/'/g, "''")
57
+ .replace(/%/g, '\\%')
58
+ .replace(/_/g, '\\_');
59
+ }
60
+ /**
61
+ * Escape a user-supplied substring for interpolation inside a SOQL
62
+ * `LIKE '%...%'` clause. Identical to `escapeSOQL` semantically (both
63
+ * escape `\\`, `'`, `%`, `_`) but exists as a separate helper so every
64
+ * `LIKE` site is unambiguously marked as wildcard-aware.
65
+ */
66
+ export function escapeSOQLLike(value) {
67
+ return escapeSOQL(value);
68
+ }
69
+ /**
70
+ * Validate that a field name is safe (alphanumeric + underscore only).
71
+ */
72
+ export function isValidFieldName(field) {
73
+ return /^[a-zA-Z][a-zA-Z0-9_]*$/.test(field);
74
+ }
75
+ /**
76
+ * Validate query field names (supports dot-notation for relationship fields).
77
+ */
78
+ export function isValidQueryFieldName(field) {
79
+ return /^[a-zA-Z][a-zA-Z0-9_.]*$/.test(field);
80
+ }
81
+ /**
82
+ * Validate that an sObject API name is safe.
83
+ */
84
+ export function validateObjectName(name) {
85
+ if (!name || !/^[A-Za-z][A-Za-z0-9_]*$/.test(name)) {
86
+ throw new ConnectorError(`Invalid object name: "${name}"`, 'INVALID_OBJECT_NAME', 'Object names must start with a letter and contain only letters, numbers, and underscores (e.g., Account, Invoice__c)');
87
+ }
88
+ }
89
+ /**
90
+ * Format a date string for SOQL (YYYY-MM-DD format required).
91
+ */
92
+ export function formatSOQLDate(dateStr, paramName) {
93
+ const match = dateStr.match(/^(\d{4}-\d{2}-\d{2})/);
94
+ if (!match) {
95
+ throw new ConnectorError(`Invalid ${paramName}: "${dateStr}"`, 'INVALID_DATE_FORMAT', 'Use YYYY-MM-DD format (e.g., "2026-01-09")');
96
+ }
97
+ return match[1];
98
+ }
99
+ /**
100
+ * Validate field names and return validated or default fields.
101
+ */
102
+ export function validateFields(fields, defaultFields, validator = isValidFieldName) {
103
+ if (!fields || fields.length === 0)
104
+ return defaultFields;
105
+ const invalidFields = fields.filter((f) => !validator(f));
106
+ if (invalidFields.length > 0) {
107
+ throw new ConnectorError(`Invalid field names: ${invalidFields.join(', ')}`, 'INVALID_FIELD_NAMES', 'Field names must start with a letter and contain only letters, numbers, and underscores (e.g., Id, Name, Custom_Field__c)');
108
+ }
109
+ return fields;
110
+ }
111
+ /**
112
+ * Validate and merge custom fields into update data.
113
+ */
114
+ export function validateAndMergeCustomFields(updateData, fields) {
115
+ const reservedFields = Object.keys(fields).filter((f) => f === 'Id' || f === 'id');
116
+ if (reservedFields.length > 0) {
117
+ throw new ConnectorError('Cannot override record ID via fields parameter', 'RESERVED_FIELD_OVERRIDE', 'Remove "Id" from the fields object — Salesforce manages record IDs automatically');
118
+ }
119
+ const invalidFields = Object.keys(fields).filter((f) => !isValidFieldName(f));
120
+ if (invalidFields.length > 0) {
121
+ throw new ConnectorError(`Invalid custom field names: ${invalidFields.join(', ')}`, 'INVALID_FIELD_NAMES', 'Field names must start with a letter and contain only letters, numbers, and underscores (e.g., Situation__c, Pain__c)');
122
+ }
123
+ for (const [key, value] of Object.entries(fields)) {
124
+ updateData[key] = value;
125
+ }
126
+ }
127
+ export const ALLOWED_FILTER_OPERATORS = new Set(['=', '!=', '<', '>', '<=', '>=', 'LIKE']);
128
+ export function checkSaveResult(result, errorMessage) {
129
+ const res = Array.isArray(result) ? result[0] : result;
130
+ if (!res.success) {
131
+ throw new ConnectorError(errorMessage, 'UPDATE_ERROR', JSON.stringify(res.errors));
132
+ }
133
+ }
134
+ //# sourceMappingURL=utils.js.map
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@mindstone/mcp-server-salesforce",
3
+ "version": "0.1.2",
4
+ "mcpName": "io.github.mindstone/mcp-server-salesforce",
5
+ "description": "Salesforce CRM MCP server \u2014 accounts, contacts, opportunities, leads, tasks, and custom objects via Salesforce API",
6
+ "license": "FSL-1.1-MIT",
7
+ "type": "module",
8
+ "bin": {
9
+ "mcp-server-salesforce": "dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "!dist/**/*.map"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/mindstone/mcp-servers.git",
18
+ "directory": "connectors/salesforce"
19
+ },
20
+ "homepage": "https://github.com/mindstone/mcp-servers/tree/main/connectors/salesforce",
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "scripts": {
25
+ "build": "tsc && shx chmod +x dist/index.js",
26
+ "prepare": "npm run build",
27
+ "watch": "tsc --watch",
28
+ "start": "node dist/index.js",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest",
31
+ "test:coverage": "vitest run --coverage"
32
+ },
33
+ "dependencies": {
34
+ "@modelcontextprotocol/sdk": "^1.26.0",
35
+ "jsforce": "^3.6.2",
36
+ "zod": "^3.23.0"
37
+ },
38
+ "devDependencies": {
39
+ "@mindstone/mcp-test-harness": "file:../../test-harness",
40
+ "@types/node": "^22",
41
+ "@vitest/coverage-v8": "^4.1.3",
42
+ "msw": "^2.13.2",
43
+ "shx": "^0.3.4",
44
+ "typescript": "^5.8.2",
45
+ "vitest": "^4.1.3"
46
+ },
47
+ "engines": {
48
+ "node": ">=20"
49
+ },
50
+ "overrides": {
51
+ "fast-uri": "^3.1.2",
52
+ "hono": "^4.12.18",
53
+ "ip-address": "^10.2.0"
54
+ }
55
+ }