Problemy z timeoutem
Problem: Długotrwałe operacje przekraczają timeout Rozwiązanie: Implementuj raportowanie postępu lub podziel na mniejsze operacje
Serwery Model Context Protocol (MCP) to pomost między Cursor a twoimi zewnętrznymi narzędziami. Chociaż Cursor jest dostarczany z wieloma gotowymi serwerami MCP, prawdziwa moc leży w tworzeniu niestandardowych serwerów dostosowanych do unikalnych potrzeb twojej organizacji.
MCP stosuje model klient-serwer, gdzie:
Zbudujmy prosty serwer MCP, który integruje się z hipotetycznym wewnętrznym API.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";import { z } from "zod";
// Stwórz instancję serweraconst server = new McpServer({ name: "internal-api-server", version: "1.0.0", description: "Łączy Cursor z naszymi wewnętrznymi systemami"});
// Zdefiniuj narzędzieserver.tool( "get_user_info", "Pobierz informacje o użytkowniku z wewnętrznego API", { userId: z.string().describe("ID użytkownika") }, async ({ userId }) => { // Twoje wywołanie API tutaj const response = await fetch(`https://api.internal.com/users/${userId}`); const data = await response.json();
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; });
// Uruchom serwerconst transport = new StdioServerTransport();await server.connect(transport);
from mcp.server import Serverfrom mcp.server.stdio import StdioServerTransportfrom mcp.tools import Toolimport asyncioimport jsonimport aiohttp
# Stwórz instancję serweraserver = Server( name="internal-api-server", version="1.0.0")
# Zdefiniuj narzędzie@server.tool()async def get_user_info(userId: str) -> str: """Pobierz informacje o użytkowniku z wewnętrznego API""" async with aiohttp.ClientSession() as session: async with session.get(f"https://api.internal.com/users/{userId}") as response: data = await response.json() return json.dumps(data, indent=2)
# Uruchom serwerasync def main(): transport = StdioServerTransport() await server.connect(transport) await transport.serve()
if __name__ == "__main__": asyncio.run(main())
Dodaj do swojego ~/.cursor/mcp.json
:
{ "mcpServers": { "internal-api": { "command": "node", "args": ["/path/to/your/server.js"], "env": { "API_KEY": "your-api-key-here" } } }}
Twórz serwery, które udostępniają wiele powiązanych narzędzi:
// Serwer MCP analizatora bazy danychconst dbServer = new McpServer({ name: "database-analyzer", version: "1.0.0"});
// Narzędzie 1: inspektor schematudbServer.tool( "inspect_schema", "Pobierz informacje o schemacie bazy danych", { table: z.string() }, async ({ table }) => { const schema = await db.getTableSchema(table); return formatSchemaResponse(schema); });
// Narzędzie 2: analizator zapytańdbServer.tool( "analyze_query", "Przeanalizuj wydajność zapytania SQL", { query: z.string() }, async ({ query }) => { const plan = await db.explainQuery(query); return formatQueryPlan(plan); });
// Narzędzie 3: profiler danychdbServer.tool( "profile_data", "Profiluj rozkład danych w tabeli", { table: z.string(), column: z.string().optional() }, async ({ table, column }) => { const profile = await db.profileData(table, column); return formatDataProfile(profile); });
Buduj serwery, które utrzymują stan między żądaniami:
class StatefulMcpServer { private sessions: Map<string, SessionData> = new Map(); private server: McpServer;
constructor() { this.server = new McpServer({ name: "stateful-workflow", version: "1.0.0" });
this.setupTools(); }
private setupTools() { // Rozpocznij sesję this.server.tool( "start_session", "Zainicjuj nową sesję przepływu pracy", { workflowType: z.string() }, async ({ workflowType }) => { const sessionId = generateId(); this.sessions.set(sessionId, { type: workflowType, state: 'initialized', data: {} });
return { content: [{ type: "text", text: `Sesja rozpoczęta: ${sessionId}` }] }; } );
// Wykonaj kroki przepływu pracy this.server.tool( "execute_step", "Wykonaj następny krok w przepływie pracy", { sessionId: z.string(), input: z.any() }, async ({ sessionId, input }) => { const session = this.sessions.get(sessionId); if (!session) throw new Error("Sesja nie znaleziona");
// Przetwarzaj na podstawie obecnego stanu const result = await this.processWorkflowStep(session, input);
return { content: [{ type: "text", text: JSON.stringify(result) }] }; } ); }}
Udostępniaj dynamiczne zasoby, które Cursor może przeglądać:
// Serwer dokumentacji zapewniający przeglądalne zasobyserver.resource( "api_docs", "Przeglądaj dokumentację API", async (uri) => { // Parsuj URI, aby określić, którą dokumentację pokazać const path = uri.replace("api-docs://", "");
if (path === "/") { // Zwróć listę dostępnych API return { content: [{ type: "text", text: await getApiIndex() }] }; }
// Zwróć konkretną dokumentację API const doc = await getApiDoc(path); return { content: [{ type: "text", text: doc }] }; });
// Zasoby mogą być hierarchiczneserver.listResources = async () => { return [ { uri: "api-docs://users", name: "API użytkowników" }, { uri: "api-docs://payments", name: "API płatności" }, { uri: "api-docs://analytics", name: "API analityki" } ];};
Zespół bezpieczeństwa zbudował serwer MCP do automatycznej analizy bezpieczeństwa:
// Główna implementacja skanera bezpieczeństwaclass SecurityScannerMcp { private scanners = [ new DependencyScanner(), new SecretScanner(), new VulnerabilityScanner() ];
async setupTools() { // Kompleksowe skanowanie bezpieczeństwa this.server.tool( "security_scan", "Uruchom kompleksową analizę bezpieczeństwa", { directory: z.string(), scanTypes: z.array(z.enum(['deps', 'secrets', 'vulns'])).optional() }, async ({ directory, scanTypes }) => { const results = [];
for (const scanner of this.scanners) { if (!scanTypes || scanTypes.includes(scanner.type)) { const findings = await scanner.scan(directory); results.push(...findings); } }
// Wygeneruj raport const report = new SecurityReport(results); return { content: [{ type: "text", text: report.toMarkdown() }] }; } );
// Sprawdź konkretny plik this.server.tool( "check_file_security", "Kontrola bezpieczeństwa konkretnego pliku", { filePath: z.string() }, async ({ filePath }) => { const issues = await this.quickScan(filePath); return formatIssues(issues); } ); }}
// MCP monitorowania wydajności łączący z narzędziami APMclass PerformanceMcp { private apmClient: APMClient;
constructor() { this.apmClient = new APMClient({ endpoint: process.env.APM_ENDPOINT, apiKey: process.env.APM_API_KEY }); }
setupTools() { // Pobierz metryki wydajności this.server.tool( "get_performance_metrics", "Pobierz obecne metryki wydajności", { service: z.string(), timeRange: z.enum(['1h', '24h', '7d']).default('1h') }, async ({ service, timeRange }) => { const metrics = await this.apmClient.getMetrics({ service, from: getTimeFromRange(timeRange), to: new Date() });
return { content: [{ type: "text", text: this.formatMetrics(metrics) }] }; } );
// Analizuj wolne punkty końcowe this.server.tool( "analyze_slow_endpoints", "Znajdź i przeanalizuj wolne punkty końcowe API", { threshold: z.number().default(1000), limit: z.number().default(10) }, async ({ threshold, limit }) => { const slowEndpoints = await this.apmClient.getSlowTransactions({ threshold, limit });
const analysis = await this.analyzeEndpoints(slowEndpoints); return { content: [{ type: "text", text: analysis }] }; } ); }}
// W czacie Cursor:"Sprawdź metryki wydajności dla serwisu płatności"
// Serwer MCP odpowiada:/*Wydajność serwisu płatności (ostatnia 1h):- Czas odpowiedzi: p50=145ms, p95=623ms, p99=1823ms- Przepustowość: 1,234 żądań/min- Współczynnik błędów: 0.23%- Użycie CPU: 67%- Pamięć: 2.3GB / 4GB
Najwolniejsze punkty końcowe:1. POST /api/payment/process - 1823ms (p99)2. GET /api/payment/history - 967ms (p99)*/
"Przeanalizuj dlaczego punkt końcowy przetwarzania płatności jest wolny"
// Szczegółowa analiza ze śladami i sugestiami
// MCP automatyzacji wdrożeniaclass DeploymentMcp { setupTools() { // Wdróż do środowiska this.server.tool( "deploy", "Wdróż aplikację do określonego środowiska", { environment: z.enum(['dev', 'staging', 'prod']), version: z.string().optional(), dryRun: z.boolean().default(false) }, async ({ environment, version, dryRun }) => { // Kontrole przed wdrożeniem const checks = await this.runPreDeployChecks(environment); if (!checks.passed) { return { content: [{ type: "text", text: `Kontrole przed wdrożeniem nie powiodły się:\n${checks.failures.join('\n')}` }] }; }
// Wykonaj wdrożenie if (!dryRun) { const result = await this.deploy(environment, version); await this.notifyTeam(environment, result);
return { content: [{ type: "text", text: this.formatDeployResult(result) }] }; }
return { content: [{ type: "text", text: "Próba na sucho ukończona pomyślnie" }] }; } ); }}
// Implementuj uwierzytelnianie w swoim serwerze MCPclass SecureMcpServer { private async authenticate(headers: Headers): Promise<boolean> { const token = headers.get('Authorization')?.replace('Bearer ', ''); if (!token) return false;
// Zweryfikuj token z twoim serwisem uwierzytelniania return await this.authService.verifyToken(token); }
async handleRequest(request: Request) { if (!await this.authenticate(request.headers)) { throw new Error('Nieautoryzowany'); }
// Przetwórz uwierzytelnione żądanie }}
// Zawsze waliduj i oczyszczaj wejściaserver.tool( "execute_query", "Uruchom zapytanie do bazy danych", { query: z.string() .min(1) .max(1000) .refine( (q) => !q.match(/DROP|DELETE|TRUNCATE/i), "Operacje destruktywne nie są dozwolone" ) }, async ({ query }) => { // Dodatkowa walidacja const sanitized = sanitizeSQL(query); const result = await db.query(sanitized); return formatResult(result); });
// Używaj zmiennych środowiskowych dla sekretówconst config = { apiKey: process.env.API_KEY, dbPassword: process.env.DB_PASSWORD, // Nigdy nie hardkoduj sekretów!};
// Waliduj wymagane sekrety przy uruchomieniufunction validateEnvironment() { const required = ['API_KEY', 'DB_PASSWORD']; const missing = required.filter(key => !process.env[key]);
if (missing.length > 0) { throw new Error(`Brakujące wymagane zmienne środowiskowe: ${missing.join(', ')}`); }}
import { MockMcpClient } from '@mcp/testing';
describe('SecurityScannerMcp', () => { let server: SecurityScannerMcp; let client: MockMcpClient;
beforeEach(() => { server = new SecurityScannerMcp(); client = new MockMcpClient(server); });
test('security_scan znajduje luki', async () => { const result = await client.callTool('security_scan', { directory: './test-project', scanTypes: ['vulns'] });
expect(result.content[0].text).toContain('Znaleziono 3 luki'); });});
// Test z rzeczywistym połączeniem Cursorasync function testWithCursor() { // Uruchom swój serwer MCP const serverProcess = spawn('node', ['./dist/index.js']);
// Skonfiguruj instancję testową Cursor const testConfig = { mcpServers: { "test-server": { command: "node", args: ["./dist/index.js"] } } };
// Uruchom scenariusze testowe // ...}
# Konfiguracja rozwojunpm run build:watch
# Skonfiguruj swój lokalny serwer MCP w ustawieniach Cursor# Ustawienia > Funkcje > MCP > Dodaj serwer{ "mcpServers": { "my-server-dev": { "command": "node", "args": ["./dist/index.js"], "env": { "NODE_ENV": "development", "DEBUG": "mcp:*" } } }}
{ "name": "@company/mcp-internal-tools", "version": "1.0.0", "bin": { "mcp-internal": "./dist/index.js" }, "publishConfig": { "registry": "https://npm.company.com" }}
// Zespół instaluje globalnienpm install -g @company/mcp-internal-tools
// Konfiguracja w Cursor{ "mcpServers": { "internal-tools": { "command": "mcp-internal" } }}
// Wdróż jako serwer HTTPconst server = new McpHttpServer({ port: 3000, auth: new OAuthProvider()});
// Zespół konfiguruje zdalny URL{ "mcpServers": { "internal-tools": { "transport": "sse", "url": "https://mcp.company.com/sse" } }}
Dla dużych odpowiedzi używaj strumieniowania:
server.tool( "analyze_large_dataset", "Przeanalizuj duży zestaw danych", { datasetId: z.string() }, async function* ({ datasetId }) { const dataset = await loadDataset(datasetId);
// Strumieniuj wyniki podczas przetwarzania for await (const batch of processBatches(dataset)) { yield { content: [{ type: "text", text: `Przetworzono partię: ${JSON.stringify(batch.summary)}\n` }] }; } });
class CachedMcpServer { private cache = new LRUCache<string, any>({ max: 100, ttl: 1000 * 60 * 5 // 5 minut });
async handleExpensiveOperation(params: any) { const cacheKey = JSON.stringify(params);
// Sprawdź cache najpierw const cached = this.cache.get(cacheKey); if (cached) return cached;
// Wykonaj kosztowną operację const result = await this.expensiveOperation(params);
// Buforuj wynik this.cache.set(cacheKey, result); return result; }}
// Używaj pakietu debug do szczegółowego logowaniaimport debug from 'debug';const log = debug('mcp:my-server');
server.on('tool_call', (tool, params) => { log('Wywołane narzędzie:', tool, params);});
// Uruchom z DEBUG=mcp:* aby zobaczyć logi
# Zainstaluj inspektor MCPnpm install -g @mcp/inspector
# Przetestuj swój serwermcp-inspect ./my-server.js
# Interfejs testów interaktywnych> list_tools> call_tool get_user_info {"userId": "123"}
Problemy z timeoutem
Problem: Długotrwałe operacje przekraczają timeout Rozwiązanie: Implementuj raportowanie postępu lub podziel na mniejsze operacje
Wycieki pamięci
Problem: Użycie pamięci serwera rośnie z czasem Rozwiązanie: Właściwie czyść zasoby, używaj WeakMaps dla cache’y
Obsługa błędów
Problem: Błędy krasują serwer Rozwiązanie: Owijaj wszystkie handlery narzędzi w try-catch, zwracaj przyjazne błędy
Teraz, gdy potrafisz budować niestandardowe serwery MCP:
Pamiętaj: najlepsze serwery MCP rozwiązują prawdziwe problemy. Zacznij od swojego największego problemu i buduj od tego. Każda niestandardowa integracja czyni Cursor potężniejszym dla twojego konkretnego przepływu pracy.