@mcp-bastion/core 1.0.0 → 1.0.5

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 (2) hide show
  1. package/dist/index.js.map +1 -1
  2. package/package.json +51 -51
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/rate-limit.ts","../src/logger.ts","../src/guard.ts"],"sourcesContent":["/**\r\n * Token bucket rate limiter for MCP tool calls.\r\n * Iteration cap (15), timeout (60s).\r\n */\r\n\r\nconst DEFAULT_MAX_ITERATIONS = 15;\r\nconst DEFAULT_TIMEOUT_MS = 60_000;\r\n\r\ninterface SessionState {\r\n iterations: number;\r\n startedAt: number;\r\n}\r\n\r\nexport class TokenBucketRateLimiter {\r\n private readonly maxIterations: number;\r\n private readonly timeoutMs: number;\r\n private readonly sessions = new Map<string, SessionState>();\r\n\r\n constructor(\r\n maxIterations = DEFAULT_MAX_ITERATIONS,\r\n timeoutMs = DEFAULT_TIMEOUT_MS\r\n ) {\r\n this.maxIterations = maxIterations;\r\n this.timeoutMs = timeoutMs;\r\n }\r\n\r\n private getSessionKey(requestId?: string | null, sessionId?: string | null): string {\r\n return sessionId ?? requestId ?? \"default\";\r\n }\r\n\r\n private cleanupExpired(key: string): void {\r\n const state = this.sessions.get(key);\r\n if (!state) return;\r\n const elapsed = Date.now() - state.startedAt;\r\n if (elapsed > this.timeoutMs) {\r\n this.sessions.delete(key);\r\n }\r\n }\r\n\r\n checkIteration(\r\n requestId?: string | null,\r\n sessionId?: string | null\r\n ): { allowed: boolean; error?: string } {\r\n const key = this.getSessionKey(requestId, sessionId);\r\n this.cleanupExpired(key);\r\n\r\n const state = this.sessions.get(key);\r\n if (!state) {\r\n return { allowed: true };\r\n }\r\n\r\n const elapsed = Date.now() - state.startedAt;\r\n if (elapsed > this.timeoutMs) {\r\n this.sessions.delete(key);\r\n return { allowed: false, error: \"Session timeout exceeded (60s limit)\" };\r\n }\r\n\r\n if (state.iterations >= this.maxIterations) {\r\n return {\r\n allowed: false,\r\n error: `Maximum iterations exceeded (${this.maxIterations} limit)`,\r\n };\r\n }\r\n\r\n return { allowed: true };\r\n }\r\n\r\n consumeIteration(\r\n requestId?: string | null,\r\n sessionId?: string | null\r\n ): void {\r\n const key = this.getSessionKey(requestId, sessionId);\r\n let state = this.sessions.get(key);\r\n if (!state) {\r\n state = { iterations: 0, startedAt: Date.now() };\r\n this.sessions.set(key, state);\r\n }\r\n state.iterations += 1;\r\n }\r\n\r\n resetSession(requestId?: string | null, sessionId?: string | null): void {\r\n const key = this.getSessionKey(requestId, sessionId);\r\n this.sessions.delete(key);\r\n }\r\n}\r\n","/**\r\n * Simple logger for MCP-Bastion. Uses console with level filtering.\r\n */\r\n\r\nconst PREFIX = \"[mcp-bastion]\";\r\n\r\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\r\n\r\nconst LEVEL_ORDER: Record<LogLevel, number> = {\r\n debug: 0,\r\n info: 1,\r\n warn: 2,\r\n error: 3,\r\n};\r\n\r\nlet currentLevel: LogLevel = \"info\";\r\n\r\nexport function setLogLevel(level: LogLevel): void {\r\n currentLevel = level;\r\n}\r\n\r\nfunction shouldLog(level: LogLevel): boolean {\r\n return LEVEL_ORDER[level] >= LEVEL_ORDER[currentLevel];\r\n}\r\n\r\nexport const logger = {\r\n debug(msg: string, ...args: unknown[]): void {\r\n if (shouldLog(\"debug\")) console.debug(PREFIX, msg, ...args);\r\n },\r\n info(msg: string, ...args: unknown[]): void {\r\n if (shouldLog(\"info\")) console.info(PREFIX, msg, ...args);\r\n },\r\n warn(msg: string, ...args: unknown[]): void {\r\n if (shouldLog(\"warn\")) console.warn(PREFIX, msg, ...args);\r\n },\r\n error(msg: string, ...args: unknown[]): void {\r\n if (shouldLog(\"error\")) console.error(PREFIX, msg, ...args);\r\n },\r\n};\r\n","/**\r\n * MCP-Bastion proxy wrapper for MCP server handlers.\r\n * Wraps CallTool and ReadResource; rate limit in-process, ML via sidecar.\r\n */\r\n\r\nimport type {\r\n CallToolRequest,\r\n CallToolResult,\r\n ReadResourceRequest,\r\n ReadResourceResult,\r\n} from \"@modelcontextprotocol/sdk/types.js\";\r\nimport { TokenBucketRateLimiter } from \"./rate-limit.js\";\r\nimport { logger } from \"./logger.js\";\r\n\r\nexport interface McpBastionOptions {\r\n maxIterations?: number;\r\n timeoutMs?: number;\r\n enableRateLimit?: boolean;\r\n sidecarUrl?: string;\r\n enablePromptGuard?: boolean;\r\n enablePiiRedaction?: boolean;\r\n}\r\n\r\nconst DEFAULT_OPTIONS: Required<McpBastionOptions> = {\r\n maxIterations: 15,\r\n timeoutMs: 60_000,\r\n enableRateLimit: true,\r\n sidecarUrl: \"\",\r\n enablePromptGuard: false,\r\n enablePiiRedaction: false,\r\n};\r\n\r\nfunction createMcpError(code: number, message: string): CallToolResult {\r\n return {\r\n content: [{ type: \"text\", text: `[MCP-Bastion] ${message}` }],\r\n isError: true,\r\n };\r\n}\r\n\r\nasync function callSidecar(\r\n url: string,\r\n endpoint: \"prompt-guard\" | \"pii-redact\",\r\n payload: unknown\r\n): Promise<unknown> {\r\n const res = await fetch(`${url}/${endpoint}`, {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify(payload),\r\n });\r\n if (!res.ok) {\r\n throw new Error(`Sidecar ${endpoint} failed: ${res.status}`);\r\n }\r\n return res.json();\r\n}\r\n\r\ntype CallToolHandler = (request: CallToolRequest) => Promise<CallToolResult>;\r\ntype ReadResourceHandler = (\r\n request: ReadResourceRequest\r\n) => Promise<ReadResourceResult>;\r\n\r\n/** Wraps CallTool handler. Rate limit in-process; prompt guard via sidecar. */\r\nexport function wrapCallToolHandler(\r\n handler: CallToolHandler,\r\n options: McpBastionOptions = {}\r\n): CallToolHandler {\r\n const opts = { ...DEFAULT_OPTIONS, ...options };\r\n const rateLimiter = new TokenBucketRateLimiter(\r\n opts.maxIterations,\r\n opts.timeoutMs\r\n );\r\n\r\n return async (request: CallToolRequest): Promise<CallToolResult> => {\r\n const requestId = String((request as { id?: string | number }).id ?? \"\");\r\n const sessionId = (request.params?._meta as Record<string, string>)?.[\"session_id\"];\r\n\r\n if (opts.enableRateLimit) {\r\n const { allowed, error } = rateLimiter.checkIteration(\r\n requestId,\r\n sessionId\r\n );\r\n if (!allowed) {\r\n logger.warn(\"rate_limit_blocked\", requestId, sessionId, error);\r\n return createMcpError(-32002, error ?? \"Rate limit exceeded\");\r\n }\r\n }\r\n\r\n if (opts.enablePromptGuard && opts.sidecarUrl) {\r\n try {\r\n const args = request.params?.arguments ?? {};\r\n const text = JSON.stringify(args);\r\n const result = (await callSidecar(\r\n opts.sidecarUrl,\r\n \"prompt-guard\",\r\n { text }\r\n )) as { malicious?: boolean };\r\n if (result?.malicious) {\r\n logger.warn(\"prompt_injection_blocked\", requestId);\r\n return createMcpError(\r\n -32001,\r\n \"Request blocked: potential prompt injection detected\"\r\n );\r\n }\r\n } catch (err) {\r\n logger.warn(\"prompt_guard_sidecar_unavailable\", err);\r\n return createMcpError(-32001, \"Prompt guard sidecar unavailable\");\r\n }\r\n }\r\n\r\n rateLimiter.consumeIteration(requestId, sessionId);\r\n\r\n let result = await handler(request);\r\n\r\n if (opts.enablePiiRedaction && opts.sidecarUrl && result?.content) {\r\n try {\r\n const redacted = (await callSidecar(\r\n opts.sidecarUrl,\r\n \"pii-redact\",\r\n { content: result.content }\r\n )) as { content?: CallToolResult[\"content\"] };\r\n if (redacted?.content) {\r\n result = { ...result, content: redacted.content };\r\n }\r\n } catch {\r\n return result;\r\n }\r\n }\r\n\r\n return result;\r\n };\r\n}\r\n\r\n/** Wraps ReadResource handler. PII redaction via sidecar when enabled. */\r\nexport function wrapReadResourceHandler(\r\n handler: ReadResourceHandler,\r\n options: McpBastionOptions = {}\r\n): ReadResourceHandler {\r\n const opts = { ...DEFAULT_OPTIONS, ...options };\r\n\r\n return async (request: ReadResourceRequest): Promise<ReadResourceResult> => {\r\n const result = await handler(request);\r\n\r\n if (opts.enablePiiRedaction && opts.sidecarUrl && result?.contents) {\r\n try {\r\n const redacted = (await callSidecar(\r\n opts.sidecarUrl,\r\n \"pii-redact\",\r\n { content: result.contents }\r\n )) as { content?: ReadResourceResult[\"contents\"] };\r\n if (redacted?.content) {\r\n return { ...result, contents: redacted.content };\r\n }\r\n } catch {\r\n return result;\r\n }\r\n }\r\n\r\n return result;\r\n };\r\n}\r\n\r\n/** Patches setRequestHandler to wrap CallTool and ReadResource handlers. */\r\nexport function wrapWithMcpBastion<T extends { setRequestHandler: (schema: unknown, handler: unknown) => void }>(\r\n server: T,\r\n options: McpBastionOptions = {}\r\n): T {\r\n const opts = { ...DEFAULT_OPTIONS, ...options };\r\n const original = server.setRequestHandler.bind(server);\r\n\r\n server.setRequestHandler = function (\r\n schema: unknown,\r\n handler: unknown\r\n ): void {\r\n const schemaStr = String((schema as { name?: string })?.name ?? schema);\r\n if (schemaStr.includes(\"CallTool\") || schemaStr.includes(\"tools/call\")) {\r\n return original(\r\n schema,\r\n wrapCallToolHandler(handler as CallToolHandler, opts)\r\n );\r\n }\r\n if (schemaStr.includes(\"ReadResource\") || schemaStr.includes(\"resources/read\")) {\r\n return original(\r\n schema,\r\n wrapReadResourceHandler(handler as ReadResourceHandler, opts)\r\n );\r\n }\r\n return original(schema, handler);\r\n } as T[\"setRequestHandler\"];\r\n\r\n return server;\r\n}\r\n"],"names":["result"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/rate-limit.ts","../src/logger.ts","../src/guard.ts"],"sourcesContent":["/**\n * Token bucket rate limiter for MCP tool calls.\n * Iteration cap (15), timeout (60s).\n */\n\nconst DEFAULT_MAX_ITERATIONS = 15;\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\ninterface SessionState {\n iterations: number;\n startedAt: number;\n}\n\nexport class TokenBucketRateLimiter {\n private readonly maxIterations: number;\n private readonly timeoutMs: number;\n private readonly sessions = new Map<string, SessionState>();\n\n constructor(\n maxIterations = DEFAULT_MAX_ITERATIONS,\n timeoutMs = DEFAULT_TIMEOUT_MS\n ) {\n this.maxIterations = maxIterations;\n this.timeoutMs = timeoutMs;\n }\n\n private getSessionKey(requestId?: string | null, sessionId?: string | null): string {\n return sessionId ?? requestId ?? \"default\";\n }\n\n private cleanupExpired(key: string): void {\n const state = this.sessions.get(key);\n if (!state) return;\n const elapsed = Date.now() - state.startedAt;\n if (elapsed > this.timeoutMs) {\n this.sessions.delete(key);\n }\n }\n\n checkIteration(\n requestId?: string | null,\n sessionId?: string | null\n ): { allowed: boolean; error?: string } {\n const key = this.getSessionKey(requestId, sessionId);\n this.cleanupExpired(key);\n\n const state = this.sessions.get(key);\n if (!state) {\n return { allowed: true };\n }\n\n const elapsed = Date.now() - state.startedAt;\n if (elapsed > this.timeoutMs) {\n this.sessions.delete(key);\n return { allowed: false, error: \"Session timeout exceeded (60s limit)\" };\n }\n\n if (state.iterations >= this.maxIterations) {\n return {\n allowed: false,\n error: `Maximum iterations exceeded (${this.maxIterations} limit)`,\n };\n }\n\n return { allowed: true };\n }\n\n consumeIteration(\n requestId?: string | null,\n sessionId?: string | null\n ): void {\n const key = this.getSessionKey(requestId, sessionId);\n let state = this.sessions.get(key);\n if (!state) {\n state = { iterations: 0, startedAt: Date.now() };\n this.sessions.set(key, state);\n }\n state.iterations += 1;\n }\n\n resetSession(requestId?: string | null, sessionId?: string | null): void {\n const key = this.getSessionKey(requestId, sessionId);\n this.sessions.delete(key);\n }\n}\n","/**\n * Simple logger for MCP-Bastion. Uses console with level filtering.\n */\n\nconst PREFIX = \"[mcp-bastion]\";\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nlet currentLevel: LogLevel = \"info\";\n\nexport function setLogLevel(level: LogLevel): void {\n currentLevel = level;\n}\n\nfunction shouldLog(level: LogLevel): boolean {\n return LEVEL_ORDER[level] >= LEVEL_ORDER[currentLevel];\n}\n\nexport const logger = {\n debug(msg: string, ...args: unknown[]): void {\n if (shouldLog(\"debug\")) console.debug(PREFIX, msg, ...args);\n },\n info(msg: string, ...args: unknown[]): void {\n if (shouldLog(\"info\")) console.info(PREFIX, msg, ...args);\n },\n warn(msg: string, ...args: unknown[]): void {\n if (shouldLog(\"warn\")) console.warn(PREFIX, msg, ...args);\n },\n error(msg: string, ...args: unknown[]): void {\n if (shouldLog(\"error\")) console.error(PREFIX, msg, ...args);\n },\n};\n","/**\n * MCP-Bastion proxy wrapper for MCP server handlers.\n * Wraps CallTool and ReadResource; rate limit in-process, ML via sidecar.\n */\n\nimport type {\n CallToolRequest,\n CallToolResult,\n ReadResourceRequest,\n ReadResourceResult,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { TokenBucketRateLimiter } from \"./rate-limit.js\";\nimport { logger } from \"./logger.js\";\n\nexport interface McpBastionOptions {\n maxIterations?: number;\n timeoutMs?: number;\n enableRateLimit?: boolean;\n sidecarUrl?: string;\n enablePromptGuard?: boolean;\n enablePiiRedaction?: boolean;\n}\n\nconst DEFAULT_OPTIONS: Required<McpBastionOptions> = {\n maxIterations: 15,\n timeoutMs: 60_000,\n enableRateLimit: true,\n sidecarUrl: \"\",\n enablePromptGuard: false,\n enablePiiRedaction: false,\n};\n\nfunction createMcpError(code: number, message: string): CallToolResult {\n return {\n content: [{ type: \"text\", text: `[MCP-Bastion] ${message}` }],\n isError: true,\n };\n}\n\nasync function callSidecar(\n url: string,\n endpoint: \"prompt-guard\" | \"pii-redact\",\n payload: unknown\n): Promise<unknown> {\n const res = await fetch(`${url}/${endpoint}`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n });\n if (!res.ok) {\n throw new Error(`Sidecar ${endpoint} failed: ${res.status}`);\n }\n return res.json();\n}\n\ntype CallToolHandler = (request: CallToolRequest) => Promise<CallToolResult>;\ntype ReadResourceHandler = (\n request: ReadResourceRequest\n) => Promise<ReadResourceResult>;\n\n/** Wraps CallTool handler. Rate limit in-process; prompt guard via sidecar. */\nexport function wrapCallToolHandler(\n handler: CallToolHandler,\n options: McpBastionOptions = {}\n): CallToolHandler {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n const rateLimiter = new TokenBucketRateLimiter(\n opts.maxIterations,\n opts.timeoutMs\n );\n\n return async (request: CallToolRequest): Promise<CallToolResult> => {\n const requestId = String((request as { id?: string | number }).id ?? \"\");\n const sessionId = (request.params?._meta as Record<string, string>)?.[\"session_id\"];\n\n if (opts.enableRateLimit) {\n const { allowed, error } = rateLimiter.checkIteration(\n requestId,\n sessionId\n );\n if (!allowed) {\n logger.warn(\"rate_limit_blocked\", requestId, sessionId, error);\n return createMcpError(-32002, error ?? \"Rate limit exceeded\");\n }\n }\n\n if (opts.enablePromptGuard && opts.sidecarUrl) {\n try {\n const args = request.params?.arguments ?? {};\n const text = JSON.stringify(args);\n const result = (await callSidecar(\n opts.sidecarUrl,\n \"prompt-guard\",\n { text }\n )) as { malicious?: boolean };\n if (result?.malicious) {\n logger.warn(\"prompt_injection_blocked\", requestId);\n return createMcpError(\n -32001,\n \"Request blocked: potential prompt injection detected\"\n );\n }\n } catch (err) {\n logger.warn(\"prompt_guard_sidecar_unavailable\", err);\n return createMcpError(-32001, \"Prompt guard sidecar unavailable\");\n }\n }\n\n rateLimiter.consumeIteration(requestId, sessionId);\n\n let result = await handler(request);\n\n if (opts.enablePiiRedaction && opts.sidecarUrl && result?.content) {\n try {\n const redacted = (await callSidecar(\n opts.sidecarUrl,\n \"pii-redact\",\n { content: result.content }\n )) as { content?: CallToolResult[\"content\"] };\n if (redacted?.content) {\n result = { ...result, content: redacted.content };\n }\n } catch {\n return result;\n }\n }\n\n return result;\n };\n}\n\n/** Wraps ReadResource handler. PII redaction via sidecar when enabled. */\nexport function wrapReadResourceHandler(\n handler: ReadResourceHandler,\n options: McpBastionOptions = {}\n): ReadResourceHandler {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n\n return async (request: ReadResourceRequest): Promise<ReadResourceResult> => {\n const result = await handler(request);\n\n if (opts.enablePiiRedaction && opts.sidecarUrl && result?.contents) {\n try {\n const redacted = (await callSidecar(\n opts.sidecarUrl,\n \"pii-redact\",\n { content: result.contents }\n )) as { content?: ReadResourceResult[\"contents\"] };\n if (redacted?.content) {\n return { ...result, contents: redacted.content };\n }\n } catch {\n return result;\n }\n }\n\n return result;\n };\n}\n\n/** Patches setRequestHandler to wrap CallTool and ReadResource handlers. */\nexport function wrapWithMcpBastion<T extends { setRequestHandler: (schema: unknown, handler: unknown) => void }>(\n server: T,\n options: McpBastionOptions = {}\n): T {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n const original = server.setRequestHandler.bind(server);\n\n server.setRequestHandler = function (\n schema: unknown,\n handler: unknown\n ): void {\n const schemaStr = String((schema as { name?: string })?.name ?? schema);\n if (schemaStr.includes(\"CallTool\") || schemaStr.includes(\"tools/call\")) {\n return original(\n schema,\n wrapCallToolHandler(handler as CallToolHandler, opts)\n );\n }\n if (schemaStr.includes(\"ReadResource\") || schemaStr.includes(\"resources/read\")) {\n return original(\n schema,\n wrapReadResourceHandler(handler as ReadResourceHandler, opts)\n );\n }\n return original(schema, handler);\n } as T[\"setRequestHandler\"];\n\n return server;\n}\n"],"names":["result"],"mappings":"AAKA,MAAM,yBAAyB;AAC/B,MAAM,qBAAqB;AAOpB,MAAM,uBAAuB;AAAA,EACjB;AAAA,EACA;AAAA,EACA,+BAAe,IAAA;AAAA,EAEhC,YACE,gBAAgB,wBAChB,YAAY,oBACZ;AACA,SAAK,gBAAgB;AACrB,SAAK,YAAY;AAAA,EACnB;AAAA,EAEQ,cAAc,WAA2B,WAAmC;AAClF,WAAO,aAAa,aAAa;AAAA,EACnC;AAAA,EAEQ,eAAe,KAAmB;AACxC,UAAM,QAAQ,KAAK,SAAS,IAAI,GAAG;AACnC,QAAI,CAAC,MAAO;AACZ,UAAM,UAAU,KAAK,IAAA,IAAQ,MAAM;AACnC,QAAI,UAAU,KAAK,WAAW;AAC5B,WAAK,SAAS,OAAO,GAAG;AAAA,IAC1B;AAAA,EACF;AAAA,EAEA,eACE,WACA,WACsC;AACtC,UAAM,MAAM,KAAK,cAAc,WAAW,SAAS;AACnD,SAAK,eAAe,GAAG;AAEvB,UAAM,QAAQ,KAAK,SAAS,IAAI,GAAG;AACnC,QAAI,CAAC,OAAO;AACV,aAAO,EAAE,SAAS,KAAA;AAAA,IACpB;AAEA,UAAM,UAAU,KAAK,IAAA,IAAQ,MAAM;AACnC,QAAI,UAAU,KAAK,WAAW;AAC5B,WAAK,SAAS,OAAO,GAAG;AACxB,aAAO,EAAE,SAAS,OAAO,OAAO,uCAAA;AAAA,IAClC;AAEA,QAAI,MAAM,cAAc,KAAK,eAAe;AAC1C,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,gCAAgC,KAAK,aAAa;AAAA,MAAA;AAAA,IAE7D;AAEA,WAAO,EAAE,SAAS,KAAA;AAAA,EACpB;AAAA,EAEA,iBACE,WACA,WACM;AACN,UAAM,MAAM,KAAK,cAAc,WAAW,SAAS;AACnD,QAAI,QAAQ,KAAK,SAAS,IAAI,GAAG;AACjC,QAAI,CAAC,OAAO;AACV,cAAQ,EAAE,YAAY,GAAG,WAAW,KAAK,MAAI;AAC7C,WAAK,SAAS,IAAI,KAAK,KAAK;AAAA,IAC9B;AACA,UAAM,cAAc;AAAA,EACtB;AAAA,EAEA,aAAa,WAA2B,WAAiC;AACvE,UAAM,MAAM,KAAK,cAAc,WAAW,SAAS;AACnD,SAAK,SAAS,OAAO,GAAG;AAAA,EAC1B;AACF;AChFA,MAAM,SAAS;AAIf,MAAM,cAAwC;AAAA,EAC5C,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAEA,IAAI,eAAyB;AAEtB,SAAS,YAAY,OAAuB;AACjD,iBAAe;AACjB;AAEA,SAAS,UAAU,OAA0B;AAC3C,SAAO,YAAY,KAAK,KAAK,YAAY,YAAY;AACvD;AAEO,MAAM,SAAS;AAAA,EACpB,MAAM,QAAgB,MAAuB;AAC3C,QAAI,UAAU,OAAO,EAAG,SAAQ,MAAM,QAAQ,KAAK,GAAG,IAAI;AAAA,EAC5D;AAAA,EACA,KAAK,QAAgB,MAAuB;AAC1C,QAAI,UAAU,MAAM,EAAG,SAAQ,KAAK,QAAQ,KAAK,GAAG,IAAI;AAAA,EAC1D;AAAA,EACA,KAAK,QAAgB,MAAuB;AAC1C,QAAI,UAAU,MAAM,EAAG,SAAQ,KAAK,QAAQ,KAAK,GAAG,IAAI;AAAA,EAC1D;AAAA,EACA,MAAM,QAAgB,MAAuB;AAC3C,QAAI,UAAU,OAAO,EAAG,SAAQ,MAAM,QAAQ,KAAK,GAAG,IAAI;AAAA,EAC5D;AACF;ACfA,MAAM,kBAA+C;AAAA,EACnD,eAAe;AAAA,EACf,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,YAAY;AAAA,EACZ,mBAAmB;AAAA,EACnB,oBAAoB;AACtB;AAEA,SAAS,eAAe,MAAc,SAAiC;AACrE,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,iBAAiB,OAAO,IAAI;AAAA,IAC5D,SAAS;AAAA,EAAA;AAEb;AAEA,eAAe,YACb,KACA,UACA,SACkB;AAClB,QAAM,MAAM,MAAM,MAAM,GAAG,GAAG,IAAI,QAAQ,IAAI;AAAA,IAC5C,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAA;AAAA,IAC3B,MAAM,KAAK,UAAU,OAAO;AAAA,EAAA,CAC7B;AACD,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,WAAW,QAAQ,YAAY,IAAI,MAAM,EAAE;AAAA,EAC7D;AACA,SAAO,IAAI,KAAA;AACb;AAQO,SAAS,oBACd,SACA,UAA6B,IACZ;AACjB,QAAM,OAAO,EAAE,GAAG,iBAAiB,GAAG,QAAA;AACtC,QAAM,cAAc,IAAI;AAAA,IACtB,KAAK;AAAA,IACL,KAAK;AAAA,EAAA;AAGP,SAAO,OAAO,YAAsD;AAClE,UAAM,YAAY,OAAQ,QAAqC,MAAM,EAAE;AACvE,UAAM,YAAa,QAAQ,QAAQ,QAAmC,YAAY;AAElF,QAAI,KAAK,iBAAiB;AACxB,YAAM,EAAE,SAAS,MAAA,IAAU,YAAY;AAAA,QACrC;AAAA,QACA;AAAA,MAAA;AAEF,UAAI,CAAC,SAAS;AACZ,eAAO,KAAK,sBAAsB,WAAW,WAAW,KAAK;AAC7D,eAAO,eAAe,QAAQ,SAAS,qBAAqB;AAAA,MAC9D;AAAA,IACF;AAEA,QAAI,KAAK,qBAAqB,KAAK,YAAY;AAC7C,UAAI;AACF,cAAM,OAAO,QAAQ,QAAQ,aAAa,CAAA;AAC1C,cAAM,OAAO,KAAK,UAAU,IAAI;AAChC,cAAMA,UAAU,MAAM;AAAA,UACpB,KAAK;AAAA,UACL;AAAA,UACA,EAAE,KAAA;AAAA,QAAK;AAET,YAAIA,SAAQ,WAAW;AACrB,iBAAO,KAAK,4BAA4B,SAAS;AACjD,iBAAO;AAAA,YACL;AAAA,YACA;AAAA,UAAA;AAAA,QAEJ;AAAA,MACF,SAAS,KAAK;AACZ,eAAO,KAAK,oCAAoC,GAAG;AACnD,eAAO,eAAe,QAAQ,kCAAkC;AAAA,MAClE;AAAA,IACF;AAEA,gBAAY,iBAAiB,WAAW,SAAS;AAEjD,QAAI,SAAS,MAAM,QAAQ,OAAO;AAElC,QAAI,KAAK,sBAAsB,KAAK,cAAc,QAAQ,SAAS;AACjE,UAAI;AACF,cAAM,WAAY,MAAM;AAAA,UACtB,KAAK;AAAA,UACL;AAAA,UACA,EAAE,SAAS,OAAO,QAAA;AAAA,QAAQ;AAE5B,YAAI,UAAU,SAAS;AACrB,mBAAS,EAAE,GAAG,QAAQ,SAAS,SAAS,QAAA;AAAA,QAC1C;AAAA,MACF,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAGO,SAAS,wBACd,SACA,UAA6B,IACR;AACrB,QAAM,OAAO,EAAE,GAAG,iBAAiB,GAAG,QAAA;AAEtC,SAAO,OAAO,YAA8D;AAC1E,UAAM,SAAS,MAAM,QAAQ,OAAO;AAEpC,QAAI,KAAK,sBAAsB,KAAK,cAAc,QAAQ,UAAU;AAClE,UAAI;AACF,cAAM,WAAY,MAAM;AAAA,UACtB,KAAK;AAAA,UACL;AAAA,UACA,EAAE,SAAS,OAAO,SAAA;AAAA,QAAS;AAE7B,YAAI,UAAU,SAAS;AACrB,iBAAO,EAAE,GAAG,QAAQ,UAAU,SAAS,QAAA;AAAA,QACzC;AAAA,MACF,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAGO,SAAS,mBACd,QACA,UAA6B,IAC1B;AACH,QAAM,OAAO,EAAE,GAAG,iBAAiB,GAAG,QAAA;AACtC,QAAM,WAAW,OAAO,kBAAkB,KAAK,MAAM;AAErD,SAAO,oBAAoB,SACzB,QACA,SACM;AACN,UAAM,YAAY,OAAQ,QAA8B,QAAQ,MAAM;AACtE,QAAI,UAAU,SAAS,UAAU,KAAK,UAAU,SAAS,YAAY,GAAG;AACtE,aAAO;AAAA,QACL;AAAA,QACA,oBAAoB,SAA4B,IAAI;AAAA,MAAA;AAAA,IAExD;AACA,QAAI,UAAU,SAAS,cAAc,KAAK,UAAU,SAAS,gBAAgB,GAAG;AAC9E,aAAO;AAAA,QACL;AAAA,QACA,wBAAwB,SAAgC,IAAI;AAAA,MAAA;AAAA,IAEhE;AACA,WAAO,SAAS,QAAQ,OAAO;AAAA,EACjC;AAEA,SAAO;AACT;"}
package/package.json CHANGED
@@ -1,51 +1,51 @@
1
- {
2
- "name": "@mcp-bastion/core",
3
- "version": "1.0.0",
4
- "description": "Security middleware for MCP servers protecting LLM agents from prompt injection, resource exhaustion, and PII leakage",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "module": "dist/index.js",
8
- "types": "dist/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "import": "./dist/index.js",
12
- "types": "./dist/index.d.ts"
13
- }
14
- },
15
- "files": [
16
- "dist",
17
- "README.md"
18
- ],
19
- "scripts": {
20
- "build": "vite build && tsc --emitDeclarationOnly --outDir dist",
21
- "dev": "vite build --watch",
22
- "test": "vitest run",
23
- "test:watch": "vitest",
24
- "prepublishOnly": "npm run build"
25
- },
26
- "keywords": [
27
- "mcp",
28
- "security",
29
- "middleware",
30
- "llm",
31
- "prompt-injection",
32
- "pii"
33
- ],
34
- "author": "Viquar Khan",
35
- "license": "MIT",
36
- "repository": {
37
- "type": "git",
38
- "url": "https://github.com/vaquarkhan/MCP-Bastion"
39
- },
40
- "dependencies": {
41
- "@modelcontextprotocol/sdk": "^1.0.0"
42
- },
43
- "devDependencies": {
44
- "typescript": "^5.3.0",
45
- "vite": "^5.0.0",
46
- "vitest": "^1.0.0"
47
- },
48
- "engines": {
49
- "node": ">=18.0.0"
50
- }
51
- }
1
+ {
2
+ "name": "@mcp-bastion/core",
3
+ "version": "1.0.5",
4
+ "description": "Security middleware for MCP servers protecting LLM agents from prompt injection, resource exhaustion, and PII leakage",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "vite build && tsc --emitDeclarationOnly --outDir dist",
21
+ "dev": "vite build --watch",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "keywords": [
27
+ "mcp",
28
+ "security",
29
+ "middleware",
30
+ "llm",
31
+ "prompt-injection",
32
+ "pii"
33
+ ],
34
+ "author": "Viquar Khan",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/vaquarkhan/MCP-Bastion.git"
39
+ },
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "typescript": "^5.3.0",
45
+ "vite": "^5.0.0",
46
+ "vitest": "^1.0.0"
47
+ },
48
+ "engines": {
49
+ "node": ">=18.0.0"
50
+ }
51
+ }