Overview
Guardway Gateway provides comprehensive Model Context Protocol (MCP) integration, enabling LLMs to use external tools and services through a unified interface. MCP servers can be run locally (via stdio) or accessed remotely (via HTTP), with full access control and monitoring.
Local & Remote Servers Support for both stdio and HTTP transports
Automatic Discovery Tool and resource discovery
Access Control Per-API-key tool filtering (allowlist/denylist)
OAuth Support Authentication for remote servers
Session Management Persistent MCP sessions
Tool Filtering Fine-grained access control
Metrics & Monitoring Comprehensive observability
Multiple Transports stdio (Python/Node.js) and HTTP
What is MCP (Model Context Protocol)
Model Context Protocol is a standard protocol that allows LLMs to interact with external services, tools, and data sources in a consistent way.
Core Concepts
Tools Functions that LLMs can call. Defined with JSON Schema for parameters. Examples: file operations, web searches, API calls.
Resources Data sources that LLMs can query. URI-based access. Examples: documents, databases, files.
Prompts Pre-defined prompt templates. Reusable conversation starters. Examples: code review, summarization.
Transports stdio: Bidirectional JSON-RPC over stdin/stdout. HTTP: RESTful JSON-RPC over HTTP.
Protocol Flow
Client (Guardway Gateway) MCP Server
| |
|--- initialize ------------->|
|<-- initialized -------------|
| |
|--- tools/list ------------->|
|<-- tools list --------------|
| |
|--- tools/call ------------->|
| {name, arguments} |
|<-- result ------------------|
MCP Architecture in Guardway Gateway
Components
Location: /apps/gateway/src/mcp/
mcp/
├── manager.ts # MCP server lifecycle management
└── filter-tools.ts # Tool access control
MCP Manager
The McpManager class handles:
Server process spawning (local servers)
Transport detection (stdio vs HTTP)
JSON-RPC message proxying
Session lifecycle
Health monitoring
export class McpManager {
async ensureLocalStarted ( name : string , cfg : McpServerConfig ) : Promise < RuntimeEntry >
async sendStdio ( name : string , cfg : McpServerConfig , body : string ) : Promise < Response >
async stop ( name : string ) : Promise < void >
async upstreamUrl ( name : string , cfg : McpServerConfig ) : Promise < string >
async status ( name : string , cfg ?: McpServerConfig ) : Promise < StatusInfo >
async applyConfig ( name : string , cfg : McpServerConfig ) : Promise < void >
}
Data Flow
User Request
↓
[Chat Completion API]
├─ Parse tools parameter
├─ Discover available tools
└─ Filter tools by API key policy
↓
LLM (with tools)
├─ Decides to use tool
└─ Returns tool_calls
↓
[MCP Proxy]
├─ Get server runtime
├─ Proxy tools/call to MCP server
└─ Return result
↓
[Tool Response]
↓
LLM (with tool result)
↓
Final Response
Transport Detection
Guardway Gateway automatically detects the appropriate transport:
stdio Detection
HTTP Detection
Python MCP servers (mcp-server-*)
JavaScript MCP servers (@modelcontextprotocol/server-*)
Commands containing uvx, python, pipx
@playwright/mcp package
Servers that bind to HTTP port
Logs “Listening on http://“
MCP Services
Guardway Gateway includes built-in MCP servers and supports adding custom servers.
Built-in MCP Servers
1. Filesystem Server
Access local filesystem with safety constraints:
{
"name" : "filesystem" ,
"mode" : "local" ,
"enabled" : true ,
"local" : {
"command" : "npx" ,
"args" : [ "@modelcontextprotocol/server-filesystem" , "/app/data" ]
},
"allowedTools" : [ "read_file" , "list_directory" ]
}
Tools:
read_file - Read file contents
write_file - Write to file
list_directory - List directory contents
search_files - Search for files
2. Git Server
Git operations and repository access:
{
"name" : "git" ,
"mode" : "local" ,
"enabled" : true ,
"local" : {
"command" : "npx" ,
"args" : [ "@modelcontextprotocol/server-git" ]
}
}
Tools:
git_status - Get repository status
git_diff - Show differences
git_log - View commit history
git_show - Show commit details
3. Time Server
Date and time operations:
{
"name" : "time" ,
"mode" : "local" ,
"enabled" : true ,
"local" : {
"command" : "npx" ,
"args" : [ "@modelcontextprotocol/server-time" ]
}
}
Tools:
get_current_time - Get current time
get_timezone - Get timezone info
convert_time - Convert between timezones
4. Fetch Server
HTTP requests and web scraping:
{
"name" : "fetch" ,
"mode" : "local" ,
"enabled" : true ,
"local" : {
"command" : "npx" ,
"args" : [ "@modelcontextprotocol/server-fetch" ]
}
}
Tools:
fetch - Make HTTP requests
fetch_html - Fetch and parse HTML
fetch_json - Fetch JSON data
Python MCP Servers
Brave Search:
{
"name" : "brave-search" ,
"mode" : "local" ,
"enabled" : true ,
"local" : {
"command" : "uvx" ,
"args" : [ "mcp-server-brave-search" ],
"env" : {
"BRAVE_API_KEY" : "your-api-key"
}
}
}
PostgreSQL:
{
"name" : "postgres" ,
"mode" : "local" ,
"enabled" : true ,
"local" : {
"command" : "uvx" ,
"args" : [ "mcp-server-postgres" ],
"env" : {
"POSTGRES_CONNECTION" : "postgresql://user:pass@localhost/db"
}
}
}
Playwright (Browser Automation):
{
"name" : "playwright" ,
"mode" : "local" ,
"enabled" : true ,
"local" : {
"command" : "npx" ,
"args" : [ "@playwright/mcp" ]
}
}
Adding MCP Servers
Via Admin UI
Fill in configuration
Name: Unique identifier
Mode: Local or Remote
Display Name: Human-readable name
Description: What the server does
Command/URL: Execution details
Allowed Tools: Tool filtering (optional)
Monitor status in server list
Via API
Add Local Server
Add Remote Server
Update Server
Delete Server
Get Status
POST /v1/mcp/servers
Content-Type: application/json
Authorization: Bearer YOUR_ADMIN_KEY
{
"name" : "my-server",
"mode" : "local",
"enabled" : true ,
"displayName" : "My Custom Server",
"description" : "Custom MCP server for XYZ",
"local" : {
"command" : "python",
"args" : [ "-m" , "my_mcp_server"],
"env" : {
"API_KEY" : "secret-key"
}
},
"allowedTools" : [ "tool1" , "tool2"]
}
POST /v1/mcp/servers
Content-Type: application/json
Authorization: Bearer YOUR_ADMIN_KEY
{
"name" : "remote-server",
"mode" : "remote",
"enabled" : true ,
"displayName" : "Remote MCP Service",
"remote" : {
"baseUrl" : "https://mcp.example.com",
"headers" : {
"X-API-Key" : "your-key"
}
}
}
PATCH /v1/mcp/servers/my-server
Content-Type: application/json
Authorization: Bearer YOUR_ADMIN_KEY
{
"enabled" : false
}
DELETE /v1/mcp/servers/my-server
Authorization: Bearer YOUR_ADMIN_KEY
GET /v1/mcp/servers/my-server/status
Authorization: Bearer YOUR_ADMIN_KEY
Response: {
"status" : "running" ,
"detail" : "Server is healthy"
}
Local Server Configuration
Command Options:
{
"local" : {
"command" : "npx" , // Executable command
"args" : [ // Command arguments
"@modelcontextprotocol/server-filesystem" ,
"/app/data"
],
"env" : { // Environment variables
"LOG_LEVEL" : "info" ,
"DATA_PATH" : "/app/data"
}
}
}
Common Commands:
Node.js packages (npx)
Python packages (uvx)
Direct execution
{
"command" : "npx" ,
"args" : [ "@modelcontextprotocol/server-name" ]
}
Remote Server Configuration
HTTP Servers:
{
"mode" : "remote" ,
"remote" : {
"baseUrl" : "https://mcp.example.com/api/v1" ,
"headers" : {
"Authorization" : "Bearer token" ,
"X-Custom-Header" : "value"
}
}
}
With OAuth:
{
"mode" : "remote" ,
"remote" : {
"baseUrl" : "https://mcp.example.com/api/v1" ,
"oauth" : {
"clientId" : "client-id" ,
"clientSecret" : "client-secret" ,
"tokenUrl" : "https://auth.example.com/oauth/token" ,
"scopes" : "mcp.read mcp.write" ,
"grantType" : "client_credentials"
}
}
}
OAuth Configuration for Remote Servers
Store OAuth client secrets securely. Never embed them directly in configuration files checked into version control. Use environment variables or a secrets manager to inject credentials at runtime.
Grant Types
Client Credentials
Password Grant
The most common grant type for server-to-server communication: {
"oauth" : {
"clientId" : "your-client-id" ,
"clientSecret" : "your-client-secret" ,
"tokenUrl" : "https://auth.provider.com/oauth/token" ,
"grantType" : "client_credentials" ,
"scopes" : "mcp.read mcp.write"
}
}
Token Request: POST https://auth.provider.com/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type = client_credentials
& client_id = your-client-id
& client_secret = your-client-secret
& scope = mcp.read+mcp.write
{
"oauth" : {
"clientId" : "your-client-id" ,
"clientSecret" : "your-client-secret" ,
"tokenUrl" : "https://auth.provider.com/oauth/token" ,
"grantType" : "password" ,
"username" : "user@example.com" ,
"password" : "password"
}
}
Token Management
Automatic Refresh:
Guardway Gateway automatically requests tokens before expiration
5-minute buffer for token refresh
Cached tokens reused until expiration
Storage:
Tokens stored in memory (not persisted)
Cleared on server restart
Automatically refreshed on first use
Manual Refresh:
DELETE /v1/mcp/servers/my-server/token
Authorization: Bearer YOUR_ADMIN_KEY
Restrict which tools a server can expose:
{
"name" : "filesystem" ,
"allowedTools" : [
"read_file" ,
"list_directory"
]
}
Only read_file and list_directory are available when using a global allowlist, even if the server offers more tools. This is useful for restricting a server to a safe subset of its capabilities.
Fine-grained access control per API key:
Location: API Key configuration
{
"id" : "key-123" ,
"name" : "Production API Key" ,
"mcpPolicy" : {
"access" : "restricted" ,
"servers" : {
"allow" : [ "filesystem" , "time" ],
"deny" : [ "git" , "fetch" ]
},
"tools" : {
"filesystem" : {
"allow" : [ "read_file" , "list_directory" ],
"deny" : [ "write_file" , "delete_file" ]
},
"time" : {
"allow" : [ "get_current_time" ]
},
"*" : {
"deny" : [ "dangerous_operation" ]
}
}
}
}
Policy Structure:
type KeyMcpPolicy = {
access ?: 'all' | 'restricted' ; // Default: 'all'
servers ?: {
allow ?: string []; // Allowlist of server names
deny ?: string []; // Denylist of server names
};
tools ?: {
[ serverName : string ] : {
allow ?: string []; // Allowlist of tool names
deny ?: string []; // Denylist of tool names
};
'*' ?: { // Applies to all servers
allow ?: string [];
deny ?: string [];
};
};
};
Deny rules always take precedence over allow rules. If a tool appears in both an allow and a deny list, it will be denied.
Evaluation Order:
Check access mode (if restricted, apply filters)
Check server allowlist/denylist
Check wildcard (*) tool filters
Check server-specific tool filters
Combine results (deny takes precedence)
Examples:
Allow only safe operations
Block dangerous tools globally
Mix allow and deny
{
"access" : "restricted" ,
"servers" : {
"allow" : [ "filesystem" , "time" ]
},
"tools" : {
"filesystem" : {
"allow" : [ "read_file" , "list_directory" ]
}
}
}
Via Admin UI
Expand MCP Access Control
Configure policy
Access Mode: All or Restricted
Allowed Servers: Select servers
Blocked Servers: Select servers to block
Tool Filters: Configure per-server
Via API
Update Key MCP Policy:
PATCH /v1/keys/key-123
Content-Type: application/json
Authorization: Bearer YOUR_ADMIN_KEY
{
"mcpPolicy" : {
"access" : "restricted",
"servers" : {
"allow" : [ "filesystem" , "time"]
},
"tools" : {
"filesystem" : {
"allow" : [ "read_file" , "list_directory"]
}
}
}
}
Session Management
Session Lifecycle
Session Creation:
Client Request → Initialize MCP → Create Session → Store Session ID
Session Reuse:
Client Request → Check Session → Reuse Existing → Update Last Used
Session Expiration:
Idle Timeout (30min) → Mark Expired → Cleanup on Next Request
Session Storage
Sessions stored in Redis:
type McpSession = {
id : string ;
serverName : string ;
apiKeyId : string ;
createdAt : number ;
lastUsedAt : number ;
expiresAt : number ;
state : 'active' | 'expired' ;
};
Key: gwai:mcp:session:{apiKeyId}:{serverName}
Per-User Sessions
Each API key gets isolated sessions per server:
API Key A + Server filesystem → Session A1
API Key A + Server git → Session A2
API Key B + Server filesystem → Session B1
Per-user sessions provide isolation between users, per-user state management, and security and privacy guarantees.
JSON-RPC Proxy
Guardway Gateway proxies JSON-RPC 2.0 messages between clients and MCP servers.
Protocol
Request Format
Response Format
Error Format
{
"jsonrpc" : "2.0" ,
"id" : "req-123" ,
"method" : "tools/call" ,
"params" : {
"name" : "read_file" ,
"arguments" : {
"path" : "/app/data/file.txt"
}
}
}
Proxy Endpoint
POST /v1/mcp/{serverName}/message
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY
{
"jsonrpc" : "2.0",
"id" : "test-1",
"method" : "tools/list",
"params" : {}
}
Response:
{
"jsonrpc" : "2.0" ,
"id" : "test-1" ,
"result" : {
"tools" : [
{
"name" : "read_file" ,
"description" : "Read file contents" ,
"inputSchema" : {
"type" : "object" ,
"properties" : {
"path" : { "type" : "string" }
},
"required" : [ "path" ]
}
}
]
}
}
Supported Methods
Initialization:
initialize - Initialize client connection
notifications/initialized - Notify initialization complete
Tools:
tools/list - List available tools
tools/call - Call a tool with arguments
Resources:
resources/list - List available resources
resources/read - Read resource contents
Prompts:
prompts/list - List available prompts
prompts/get - Get prompt template
MCP in Chat Completions
Request:
POST /v1/chat/completions
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY
{
"model" : "gpt-4",
"messages" : [
{
"role" : "user",
"content" : "Read the file /app/data/config.json"
}
],
"tools" : [
{
"type" : "function",
"function" : {
"name" : "read_file",
"description" : "Read file contents",
"parameters" : {
"type" : "object",
"properties" : {
"path" : { "type": "string" }
},
"required" : [ "path" ]
}
}
}
]
}
LLM Response (tool call):
{
"id" : "chatcmpl-123" ,
"choices" : [{
"message" : {
"role" : "assistant" ,
"content" : null ,
"tool_calls" : [{
"id" : "call_abc" ,
"type" : "function" ,
"function" : {
"name" : "read_file" ,
"arguments" : "{ \" path \" : \" /app/data/config.json \" }"
}
}]
},
"finish_reason" : "tool_calls"
}]
}
Execute Tool:
Client or Guardway Gateway executes tool call via MCP proxy.
Submit Result:
POST /v1/chat/completions
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY
{
"model" : "gpt-4",
"messages" : [
{
"role" : "user",
"content" : "Read the file /app/data/config.json"
},
{
"role" : "assistant",
"content" : null,
"tool_calls" : [{
"id" : "call_abc",
"type" : "function",
"function" : {
"name" : "read_file",
"arguments" : "{ \" path \" : \" /app/data/config.json \" }"
}
}]
},
{
"role" : "tool",
"tool_call_id" : "call_abc",
"content" : "{ \" status \" : \" success \" , \" content \" : \" ...file contents... \" }"
}
]
}
Final Response:
{
"id" : "chatcmpl-456" ,
"choices" : [{
"message" : {
"role" : "assistant" ,
"content" : "The configuration file contains the following settings: ..."
},
"finish_reason" : "stop"
}]
}
Guardway Gateway can automatically discover and inject tools:
Request with mcp_auto_tools: true:
POST /v1/chat/completions
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY
{
"model" : "gpt-4",
"messages" : [
{
"role" : "user",
"content" : "What's the current time?"
}
],
"mcp_auto_tools" : true # Guardway Gateway discovers and injects tools
}
When mcp_auto_tools is enabled, Guardway Gateway automatically discovers tools from all enabled MCP servers, filters tools based on API key policy, injects tool definitions into the request, and proxies tool calls to the appropriate servers.
Example 1: File Operations - Setup
Example 1: File Operations - Chat
Example 2: Web Search - Setup
Example 2: Web Search - Chat
Example 3: Database Query - Setup
Example 3: Database Query - Chat
{
"name" : "filesystem" ,
"mode" : "local" ,
"enabled" : true ,
"local" : {
"command" : "npx" ,
"args" : [ "@modelcontextprotocol/server-filesystem" , "/app/data" ]
}
}
Troubleshooting MCP
Common Issues
1. Server Won’t Start
Symptom: Server status shows “stopped” or “error”
Diagnosis:
# Check logs
docker logs agsec-gateway
# Check server status
curl http://localhost:8080/v1/mcp/servers/my-server/status \
-H "Authorization: Bearer ADMIN_KEY"
Verify that the command is correct and executable, check environment variables, ensure dependencies are installed (npx packages), check file permissions, and review stderr output in logs.
2. stdio Transport Issues
Symptom: Server starts but tool calls fail
Diagnosis:
Check if server logs “running on stdio”
Verify JSON-RPC messages are valid
Check for npx execution errors
If you encounter npx execution errors, try using node directly instead of npx as a workaround. You can also pre-download packages with npx --yes package-name --help, check npm cache permissions, and verify the package is compatible with stdio transport.
3. HTTP Transport Issues
Symptom: Server fails to bind port
Diagnosis:
Check if port is already in use
Verify server logs “Listening on http://”
Check firewall rules
Use a different port with --port 8081, kill any existing process on the port, and ensure --host 127.0.0.1 is set.
4. Tool Not Available
Symptom: Tool not shown in tools/list
Diagnosis:
# List tools
POST /v1/mcp/my-server/message
{
"jsonrpc" : "2.0",
"id" : "1",
"method" : "tools/list",
"params" : {}
}
If a tool is missing, check the allowedTools server configuration, the API key MCP policy, that the server is running, and the tool filtering rules.
5. OAuth Token Errors
Symptom: “401 Unauthorized” from remote server
Diagnosis:
Check token expiration
Verify OAuth credentials
Check token URL
Refresh the token manually with DELETE /v1/mcp/servers/name/token, verify client ID and secret, check scopes configuration, and test the OAuth flow independently.
6. Session Expiration
Symptom: “Session expired” errors
Sessions expire after 30 minutes of inactivity. Create a new session by making a new request. Increasing session timeout is not recommended for security reasons.
Debug Mode
Enable verbose logging:
# Environment variable
LOG_LEVEL = debug
# In code
logger.debug( { name, event: 'mcp_event' }, 'MCP event details' );
Metrics Monitoring
Check MCP metrics:
GET /v1/metrics
Authorization: Bearer YOUR_API_KEY
Key Metrics:
mcp.total.requests - Total MCP requests
mcp.total.errors - Total errors
mcp.total.toolsCall - Tool invocations
mcp.byServer.{name}.requests - Per-server metrics
mcp.total.stdioTimeouts - stdio timeout count
Health Checks
Verify server health:
# Status endpoint
GET /v1/mcp/servers/my-server/status
# Initialize test
POST /v1/mcp/my-server/message
{
"jsonrpc" : "2.0",
"id" : "health-check",
"method" : "initialize",
"params" : {
"protocolVersion" : "2024-11-05",
"capabilities" : {},
"clientInfo" : { "name": "test", "version": "1.0" }
}
}