Custom Client Entwicklung
Diese Anleitung zeigt, wie du eigene MCP-Clients fĂŒr die Codefluss API entwickelst.
Grundlagen
MCP basiert auf JSON-RPC 2.0 ĂŒber HTTP. Jeder HTTP-Client kann als MCP-Client dienen.
Minimaler Client
fetch-basierter Client
class CodeflussMCPClient {
private baseUrl = 'https://api.codefluss.com/api/mcp';
private apiKey: string;
private requestId = 0;
constructor(apiKey: string) {
this.apiKey = apiKey;
}
private async request<T>(method: string, params?: Record<string, unknown>): Promise<T> {
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.apiKey}`,
},
body: JSON.stringify({
jsonrpc: '2.0',
id: ++this.requestId,
method,
params,
}),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.error) {
throw new Error(`MCP Error ${data.error.code}: ${data.error.message}`);
}
return data.result as T;
}
async initialize() {
return this.request<{
protocolVersion: string;
capabilities: Record<string, unknown>;
serverInfo: { name: string; version: string };
}>('initialize', {
protocolVersion: '2024-11-05',
capabilities: {},
clientInfo: {
name: 'custom-client',
version: '1.0.0',
},
});
}
async listTools() {
return this.request<{
tools: Array<{
name: string;
description: string;
inputSchema: Record<string, unknown>;
}>;
}>('tools/list');
}
async callTool(name: string, args: Record<string, unknown>) {
return this.request<{
content: Array<{
type: string;
text?: string;
resource?: Record<string, unknown>;
}>;
}>('tools/call', { name, arguments: args });
}
async listResources() {
return this.request<{
resources: Array<{
uri: string;
name: string;
mimeType: string;
}>;
}>('resources/list');
}
async readResource(uri: string) {
return this.request<{
contents: Array<{
uri: string;
mimeType: string;
text: string;
}>;
}>('resources/read', { uri });
}
}Request/Response Flow
1. Initialize
Jede Session beginnt mit der Initialisierung:
const client = new CodeflussMCPClient('cf_live_xxx');
const serverInfo = await client.initialize();
console.log('Connected to:', serverInfo.serverInfo.name);
console.log('Version:', serverInfo.serverInfo.version);
console.log('Capabilities:', serverInfo.capabilities);Response:
{
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": {},
"resources": { "subscribe": false }
},
"serverInfo": {
"name": "codefluss-mcp",
"version": "0.0.1-alpha"
}
}2. Tools auflisten
const { tools } = await client.listTools();
tools.forEach(tool => {
console.log(`${tool.name}: ${tool.description}`);
});3. Tool aufrufen
const result = await client.callTool('list_projects', {});
const projects = JSON.parse(result.content[0].text);
console.log('Projects:', projects);Error Handling
JSON-RPC Fehler
interface MCPError {
code: number;
message: string;
data?: unknown;
}
// Standard JSON-RPC Fehlercodes
const ErrorCodes = {
PARSE_ERROR: -32700,
INVALID_REQUEST: -32600,
METHOD_NOT_FOUND: -32601,
INVALID_PARAMS: -32602,
INTERNAL_ERROR: -32603,
// MCP-spezifische Codes
UNAUTHORIZED: -32001,
RATE_LIMITED: -32002,
RESOURCE_NOT_FOUND: -32003,
} as const;Robuster Client
class RobustMCPClient extends CodeflussMCPClient {
private maxRetries = 3;
private retryDelay = 1000;
protected async request<T>(method: string, params?: Record<string, unknown>): Promise<T> {
let lastError: Error | null = null;
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
try {
return await super.request<T>(method, params);
} catch (error) {
lastError = error as Error;
// Nicht bei Client-Fehlern wiederholen
if (error instanceof MCPError) {
if (error.code === ErrorCodes.INVALID_PARAMS) {
throw error;
}
if (error.code === ErrorCodes.UNAUTHORIZED) {
throw error;
}
}
// Rate Limiting: Exponential Backoff
if (error instanceof MCPError && error.code === ErrorCodes.RATE_LIMITED) {
await this.sleep(this.retryDelay * Math.pow(2, attempt));
continue;
}
// Netzwerkfehler: Kurz warten und wiederholen
await this.sleep(this.retryDelay);
}
}
throw lastError;
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}VollstÀndiges Beispiel
import { CodeflussMCPClient } from './mcp-client';
async function main() {
const client = new CodeflussMCPClient(process.env.CODEFLUSS_API_KEY!);
try {
// 1. Initialisieren
console.log('Connecting to Codefluss MCP...');
const info = await client.initialize();
console.log(`Connected: ${info.serverInfo.name} v${info.serverInfo.version}`);
// 2. VerfĂŒgbare Tools anzeigen
console.log('\nAvailable tools:');
const { tools } = await client.listTools();
tools.forEach(t => console.log(` - ${t.name}`));
// 3. Projekte abrufen
console.log('\nFetching projects...');
const result = await client.callTool('list_projects', {});
const projects = JSON.parse(result.content[0].text);
console.log(`Found ${projects.length} projects:`);
projects.forEach((p: { name: string; id: string }) => {
console.log(` - ${p.name} (${p.id})`);
});
// 4. Design Tokens eines Projekts lesen
if (projects.length > 0) {
console.log(`\nReading design tokens for ${projects[0].name}...`);
const tokens = await client.callTool('get_design_tokens', {
projectId: projects[0].id,
});
console.log('Tokens:', JSON.parse(tokens.content[0].text));
}
} catch (error) {
console.error('Error:', error);
process.exit(1);
}
}
main();SDK Empfehlung
FĂŒr produktive Anwendungen empfehlen wir das offizielle MCP SDK:
npm install @modelcontextprotocol/sdkVerwendung mit SDK
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { HttpClientTransport } from '@modelcontextprotocol/sdk/client/http.js';
const transport = new HttpClientTransport({
url: 'https://api.codefluss.com/api/mcp',
headers: {
Authorization: `Bearer ${process.env.CODEFLUSS_API_KEY}`,
},
});
const client = new Client({
name: 'my-app',
version: '1.0.0',
});
await client.connect(transport);
// Tools auflisten
const { tools } = await client.listTools();
// Tool aufrufen
const result = await client.callTool('list_projects', {});Best Practices
- Connection Pooling: FĂŒr hĂ€ufige Requests HTTP Keep-Alive nutzen
- Caching: Tool-Liste und Resources können gecacht werden
- Timeouts: Angemessene Timeouts setzen (empfohlen: 30s)
- Logging: Requests/Responses fĂŒr Debugging loggen
- Type Safety: TypeScript mit generierten Types verwenden