Integration Guide
Advanced integration patterns and best practices for connecting your applications with the Fenceline MCP Server.
Integration Patterns
1. Streamable HTTP SDK integration
Use the current MCP SDK transport for stateful request/response clients and production integrations.
Best For:
- MCP-native applications
- Clients that maintain session state
- Request/response workflows
Example:
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
const client = new Client({ name: 'my-service', version: '1.0.0' });
const transport = new StreamableHTTPClientTransport(
new URL('https://mcp.fenceline.ai/mcp'),
{ requestInit: { headers: { 'X-API-Key': process.env.MCP_API_KEY } } }
);
await client.connect(transport);
const result = await client.callTool({ name: 'rag.search', arguments: { query: 'fence materials' } });
2. Compatibility JSON-RPC
Use POST /mcp/rpc for isolated JSON-RPC tool calls that don't need an MCP session.
3. Legacy Server-Sent Events (SSE)
The legacy SSE endpoint requires an MCP SDK client that can send an X-API-Key header.
Requirements:
- An MCP client library with legacy SSE transport support
- Custom request headers for authentication
- Session handling for the paired GET and POST requests
Native browser EventSource accepts credentials mode only. It can't attach the API key header required by this server.
Authentication Strategies
API Key Management
- Environment Variables: Store API keys in environment variables
- Secure Storage: Use secure key management systems
- Key Rotation: Implement regular key rotation policies
- Scope Limitation: Use keys with minimal required permissions
Example Configuration
// config.js
export const mcpConfig = {
apiKey: process.env.MCP_API_KEY,
baseUrl: 'https://mcp.fenceline.ai',
timeout: 30000,
retries: 3
};
Error Handling Patterns
Comprehensive Error Handling
class McpError extends Error {
constructor(message, code, data) {
super(message);
this.name = 'McpError';
this.code = code;
this.data = data;
}
}
async function callMcpTool(name, args) {
try {
const response = await fetch('https://mcp.fenceline.ai/mcp/rpc', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': mcpConfig.apiKey
},
body: JSON.stringify({
jsonrpc: '2.0',
id: Date.now(),
method: 'tools/call',
params: { name, arguments: args }
})
});
if (!response.ok) {
throw new McpError(
`HTTP ${response.status}: ${response.statusText}`,
response.status
);
}
const data = await response.json();
if (data.error) {
throw new McpError(
data.error.message,
data.error.code,
data.error.data
);
}
return data.result;
} catch (error) {
if (error instanceof McpError) {
throw error;
}
throw new McpError('Network or parsing error', -1, error.message);
}
}
Rate Limiting and Retry Logic
Exponential Backoff
async function callWithRetry(toolName, args, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await callMcpTool(toolName, args);
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
// Handle rate limiting specifically
if (error.code === 429) {
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
// Don't retry on authentication or validation errors
if (error.code === 401 || error.code === 400) {
throw error;
}
// Exponential backoff for other errors
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
Caching Strategies
Tool Results Caching
class McpCache {
constructor(ttl = 300000) { // 5 minutes default
this.cache = new Map();
this.ttl = ttl;
}
generateKey(toolName, args) {
return `${toolName}:${JSON.stringify(args)}`;
}
get(toolName, args) {
const key = this.generateKey(toolName, args);
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data;
}
this.cache.delete(key);
return null;
}
set(toolName, args, data) {
const key = this.generateKey(toolName, args);
this.cache.set(key, {
data,
timestamp: Date.now()
});
}
}
const cache = new McpCache();
async function cachedToolCall(toolName, args) {
const cached = cache.get(toolName, args);
if (cached) {
return cached;
}
const result = await callMcpTool(toolName, args);
cache.set(toolName, args, result);
return result;
}
Batch Processing
Parallel Tool Execution
async function batchToolCalls(calls, concurrency = 5) {
const results = [];
for (let i = 0; i < calls.length; i += concurrency) {
const batch = calls.slice(i, i + concurrency);
const batchPromises = batch.map(({ name, args }) =>
callWithRetry(name, args).catch(error => ({ error: error.message }))
);
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
return results;
}
Testing Integration
Unit Testing
// mcp.test.js
import { jest } from '@jest/globals';
import { callMcpTool } from './mcp-client.js';
// Mock the fetch function
global.fetch = jest.fn();
describe('MCP Integration', () => {
beforeEach(() => {
fetch.mockClear();
});
test('should handle successful tool call', async () => {
fetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
jsonrpc: '2.0',
id: 1,
result: { content: [{ type: 'text', text: 'Success' }] }
})
});
const result = await callMcpTool('rag.search', { query: 'test' });
expect(result.content[0].text).toBe('Success');
});
test('should handle API errors', async () => {
fetch.mockResolvedValueOnce({
ok: false,
status: 401,
statusText: 'Unauthorized'
});
await expect(callMcpTool('rag.search', { query: 'test' }))
.rejects.toThrow('HTTP 401: Unauthorized');
});
});
Production Deployment
Environment Configuration
# .env.production
MCP_API_KEY=your_production_api_key
MCP_BASE_URL=https://mcp.fenceline.ai
MCP_TIMEOUT=30000
MCP_MAX_RETRIES=3
MCP_CACHE_TTL=300000
Monitoring and Logging
class McpLogger {
static logToolCall(toolName, args, duration, success) {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
tool: toolName,
args: JSON.stringify(args),
duration,
success,
level: success ? 'info' : 'error'
}));
}
}
async function monitoredToolCall(toolName, args) {
const start = Date.now();
try {
const result = await callMcpTool(toolName, args);
McpLogger.logToolCall(toolName, args, Date.now() - start, true);
return result;
} catch (error) {
McpLogger.logToolCall(toolName, args, Date.now() - start, false);
throw error;
}
}
Next Steps
- Review the Code Examples for specific implementations
- Test your integration using the Interactive Console
- Check the OpenAPI specification for complete API details
- Monitor your integration with proper logging and error handling