@ackuity/inline-proxy
v0.7.7
Published
A transparent MCP proxy that intercepts tool calls and evaluates them against the [Ackuity Inline Decision Engine](https://ackuity.ai) before they reach the MCP server. Destructive operations (DELETE, DROP, TRUNCATE) are blocked in real time.
Keywords
Readme
Ackuity Inline MCP Proxy
A transparent MCP proxy that intercepts tool calls and evaluates them against the Ackuity Inline Decision Engine before they reach the MCP server. Destructive operations (DELETE, DROP, TRUNCATE) are blocked in real time.
How It Works
MCP Client (LLM Agent)
│
▼
┌──────────────────────┐
│ Ackuity Inline │
│ MCP Proxy │──── tools/call ──▶ Inline Decision Engine
│ │◀─── allow/block ──┘
└──────────────────────┘
│ (if allowed)
▼
MCP Server (e.g. DAB)The proxy sits between any MCP client and MCP server. When it sees a tools/call request, it sends the tool name and arguments to the Ackuity protect_tools API. If the decision is block, an error is returned to the client and the call never reaches the MCP server.
Installation
npx @ackuity/inline-proxyOr install globally:
npm install -g @ackuity/inline-proxyFor local development:
git clone https://github.com/Ackuity/ackuity-mcp-proxy.git
cd ackuity-mcp-proxy
npm install
npm run buildConfiguration
Set these environment variables, or add them to a .env file in the project root:
| Variable | Required | Description | Default |
|--------------------|----------|--------------------------------------------------|------------------------------------------------------|
| INLINE_API_TOKEN | Yes | Bearer token for the Inline Decision Engine | — |
| INLINE_URL | Yes | Protect tools endpoint URL | — |
| INLINE_AGENT_ID | Yes | Agent ID registered with the Inline Engine | — |
Usage
The proxy supports three modes depending on how your MCP client and server communicate.
Mode 1: stdio
Client connects via stdin/stdout. Proxy spawns the MCP server as a child process.
node dist/main.js --mode stdio --cmd "dab start --config dab-config.json"Use this when your MCP client (e.g. Claude Desktop, Cursor) expects to launch a command.
Mode 2: stdio-to-server
Client connects via stdin/stdout. Proxy forwards to an already-running HTTP MCP server.
node dist/main.js --mode stdio-to-server --target http://localhost:5000/mcpUse this when your MCP server is already running on HTTP and your client expects stdio.
Mode 3: server
Proxy runs as an HTTP server. Both client and MCP server communicate over HTTP.
node dist/main.js --mode server --target http://localhost:5000/mcp --port 7500Use this when your MCP client connects over HTTP (e.g. a web app or remote agent).
How to Use with Your Agent
Option 1: Spawn the proxy from your agent (stdio / stdio-to-server)
If your agent uses the MCP SDK, you can launch the proxy as a subprocess using StdioClientTransport. The proxy reads Ackuity credentials from environment variables, so pass them via env.
Replace <ACKUITY-DIST-REF> in the examples below depending on how you installed the proxy:
| Install method | command | <ACKUITY-DIST-REF> |
|---|---|---|
| Cloned and built locally | node | path/to/ackuity-inline-proxy/dist/main.js |
| npx from GitHub | npx | github:Ackuity/ackuity-mcp-proxy |
| Published to npm | npx | @ackuity/inline-proxy |
TypeScript / JavaScript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const transport = new StdioClientTransport({
command: "npx", // or "node" if running locally
args: [
"<ACKUITY-DIST-REF>",
"--mode", "stdio-to-server",
"--target", "http://localhost:5000/mcp",
],
env: {
...process.env,
INLINE_API_TOKEN: "your_token_here",
INLINE_URL: "https://inline.ackuity.ai/api/v1/inline/protect_tools",
INLINE_AGENT_ID: "your_agent_id_here",
},
});
const client = new Client({ name: "my-agent", version: "1.0.0" });
await client.connect(transport);
// All tool calls now go through the Ackuity Inline Decision Engine
const tools = await client.listTools();
const result = await client.callTool({ name: "read_records", arguments: { entity: "Employees" } });Tip: Use
--mode stdiowith--cmdinstead if you want the proxy to also spawn the MCP server:args: [ "<ACKUITY-DIST-REF>", "--mode", "stdio", "--cmd", "npx @azure/data-api-builder start --config dab-config.json", ],
Python
from mcp.client.stdio import stdio_client, StdioServerParameters
server = StdioServerParameters(
command="npx", # or "node" if running locally
args=[
"<ACKUITY-DIST-REF>",
"--mode", "stdio-to-server",
"--target", "http://localhost:5000/mcp",
],
env={
**os.environ,
"INLINE_API_TOKEN": "your_token_here",
"INLINE_URL": "https://inline.ackuity.ai/api/v1/inline/protect_tools",
"INLINE_AGENT_ID": "your_agent_id_here",
},
)
async with stdio_client(server) as (read_stream, write_stream):
async with ClientSession(read_stream, write_stream) as session:
await session.initialize()
result = await session.call_tool("read_records", {"entity": "Employees"})Option 2: Run the proxy as a Docker container (server mode)
When running in server mode, the proxy exposes an HTTP endpoint at /mcp. This is ideal for containerized deployments where your agent and MCP server are separate services.
docker-compose.yml
services:
mcp-server:
image: your-mcp-server-image
ports:
- "5000:5000"
ackuity-proxy:
image: node:22-slim
working_dir: /app
command: npx @ackuity/inline-proxy --mode server --target http://mcp-server:5000/mcp --port 7500
ports:
- "7500:7500"
environment:
INLINE_API_TOKEN: ${INLINE_API_TOKEN}
INLINE_URL: ${INLINE_URL}
INLINE_AGENT_ID: ${INLINE_AGENT_ID}
depends_on:
- mcp-server
agent:
image: your-agent-image
environment:
MCP_ENDPOINT: http://ackuity-proxy:7500/mcp
depends_on:
- ackuity-proxyYour agent connects to http://ackuity-proxy:7500/mcp instead of directly to the MCP server. All tool calls are evaluated by the Ackuity Inline Decision Engine before reaching the MCP server.
With a .env file
INLINE_API_TOKEN=your_token_here
INLINE_URL=https://inline.ackuity.ai/api/v1/inline/protect_tools
INLINE_AGENT_ID=your_agent_id_hereThen run:
docker compose upUsing Server Mode with OAuth / Authenticated MCP Servers
If your MCP server requires authentication (e.g. OAuth, API keys, or session tokens), server mode is the recommended approach. The proxy runs as a standalone HTTP service, so authentication is handled entirely between your agent and the MCP server — the proxy transparently forwards headers and credentials while still intercepting tool calls for evaluation.
Why server mode for authenticated setups
In stdio mode, the proxy spawns the MCP server as a child process, which makes it difficult to pass authentication context. In server mode, the proxy sits between your agent and an already-authenticated MCP server over HTTP, so:
- Your agent authenticates with the MCP server as usual (OAuth flows, bearer tokens, etc.)
- The proxy intercepts only
tools/callrequests for security evaluation - All other traffic (including auth handshakes) passes through unchanged
Example: OAuth-protected MCP server
# 1. Start your MCP server (handles its own OAuth)
your-mcp-server --port 5000
# 2. Start the Ackuity proxy in front of it
npx @ackuity/inline-proxy --mode server --target http://localhost:5000/mcp --port 7500
# 3. Point your agent to the proxy instead of the MCP server
# Agent connects to http://localhost:7500/mcpYour agent's existing authentication flow (OAuth redirects, token refresh, etc.) works as before — just change the MCP endpoint URL to point to the proxy.
Token expiry
If the MCP server returns a 401 Unauthorized or 403 Forbidden, the token has expired. Your agent is responsible for refreshing the token with the OAuth provider and retrying the request — the proxy passes these status codes through unchanged.
Example: Agent passing a bearer token
// Your agent sends requests to the proxy, which forwards to the MCP server
const response = await fetch("http://localhost:7500/mcp", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer <your-oauth-token>", // Passed through to MCP server
},
body: JSON.stringify({
jsonrpc: "2.0",
method: "tools/call",
params: { name: "delete_record", arguments: { id: 123 } },
}),
});
// The proxy evaluates the tool call with Ackuity before forwardingFail-Closed Behavior
If the Inline Decision Engine is unreachable or returns an error (e.g. invalid token), the proxy blocks the tool call rather than forwarding it. This prevents destructive operations from slipping through during outages.
Project Structure
src/
├── main.ts Entry point, arg parsing, mode selection
├── common.ts Shared utilities (logging, arg parser, constants)
├── inline_engine.ts Protect Tools API types, response manager, API call
└── mode.ts ProxyMode interface and implementations (Stdio, StdioToServer, Server)Logging
All MCP traffic is logged to proxy_logs/proxy_traffic.txt in the working directory. Status messages are written to stderr with a [PROXY] prefix.
