Skip to content

Failed to send request to MCP server: cURL error 28: Operation timed out after 30008 #28

@bloodykheeng

Description

@bloodykheeng

am getting this error after setting up everything well
{
"success": false,
"error": "Failed to fetch tools from MCP server 'documents': Failed to send request to MCP server: cURL error 28: Operation timed out after 30008 milliseconds with 0 bytes received (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for http://127.0.0.1:8000/mcp/documents"
}

here is my routes in ai

// Local server (for AI desktop clients)
Mcp::local('documents', DocumentStatsServer::class);

// Web server (for HTTP API)
Mcp::web('/mcp/documents', DocumentStatsServer::class);

here is my mcp server and tool

<?php

namespace App\Mcp\Servers;

use App\Mcp\Tools\GetDocumentStatsTool;
use Laravel\Mcp\Server;

class DocumentStatsServer extends Server
{
    /**
     * The MCP server's name.
     */
    protected string $name = 'Document Stats Server';

    /**
     * The MCP server's version.
     */
    protected string $version = '0.0.1';

    /**
     * The MCP server's instructions for the LLM.
     */
    protected string $instructions = <<<'MARKDOWN'
        This server provides statistics and information about documents in the system.
        You can query specific documents by ID or filename, or get overall statistics.
    MARKDOWN;

    /**
     * The tools registered with this MCP server.
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
     */
    protected array $tools = [
        GetDocumentStatsTool::class,
    ];

    /**
     * The resources registered with this MCP server.
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Resource>>
     */
    protected array $resources = [
        //
    ];

    /**
     * The prompts registered with this MCP server.
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Prompt>>
     */
    protected array $prompts = [
        //
    ];
}

here is the tool

<?php

namespace App\Mcp\Tools;

use App\Models\Document;
use Illuminate\JsonSchema\JsonSchema;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;

class GetDocumentStatsTool extends Tool
{
    /**
     * The tool's description.
     */
    protected string $description = <<<'MARKDOWN'
        Get document statistics. Provide document_id or filename for specific document, or leave empty for overall stats.
    MARKDOWN;

    /**
     * Handle the tool request.
     */
    public function handle(Request $request): Response
    {
        $docId = $request->get('document_id');
        $filename = $request->get('filename');

        // Specific document
        if ($docId || $filename) {
            $query = Document::with('chunks');

            if ($docId) {
                $doc = $query->find($docId);
            } else {
                $doc = $query->where('original_filename', 'like', "%{$filename}%")->first();
            }

            if (!$doc) {
                return Response::error('Document not found');
            }

            $stats = [
                'id' => $doc->id,
                'filename' => $doc->original_filename,
                'mime_type' => $doc->mime_type,
                'file_size' => $doc->file_size_human,
                'total_pages' => $doc->total_pages,
                'total_chunks' => $doc->total_chunks,
                'extraction_method' => $doc->extraction_method,
                'chunking_strategy' => $doc->chunking_strategy,
                'created_at' => $doc->created_at,
            ];

            return Response::text(json_encode($stats, JSON_PRETTY_PRINT));
        }

        // Overall stats
        $totalDocs = Document::count();
        $byMethod = Document::selectRaw('extraction_method, count(*) as count')
            ->groupBy('extraction_method')
            ->get()
            ->pluck('count', 'extraction_method');

        $byStrategy = Document::selectRaw('chunking_strategy, count(*) as count')
            ->groupBy('chunking_strategy')
            ->get()
            ->pluck('count', 'chunking_strategy');

        $byMimeType = Document::selectRaw('mime_type, count(*) as count')
            ->groupBy('mime_type')
            ->get()
            ->pluck('count', 'mime_type');

        $stats = [
            'total_documents' => $totalDocs,
            'by_extraction_method' => $byMethod,
            'by_chunking_strategy' => $byStrategy,
            'by_mime_type' => $byMimeType,
        ];

        return Response::text(json_encode($stats, JSON_PRETTY_PRINT));
    }

    /**
     * Get the tool's input schema.
     *
     * @return array<string, \Illuminate\JsonSchema\JsonSchema>
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'document_id' => $schema->integer()
                ->description('Optional: The document ID'),
            'filename' => $schema->string()
                ->description('Optional: Search by filename'),
        ];
    }
}


here is my controller wher am calling the tool

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Mcp\Tools\GetDocumentStatsTool;
use App\Models\Document;
use App\Models\DocumentChunk;
use Exception;
use Illuminate\Http\Request;
use Pgvector\Laravel\Distance;
use Prism\Prism\Enums\Provider;
use Prism\Prism\Facades\Prism;
use Prism\Relay\Facades\Relay;

class ChatController extends Controller
{
    private string $provider = 'ollama';
    private string $embeddingModel = 'embeddinggemma';
    // private string $model = 'gpt-oss:120b-cloud';
    private string $model = 'qwen3-coder:480b-cloud';


    public function askAboutDocs(Request $request)
    {
        $request->validate([
            'question' => 'required|string',
            'document_name' => 'sometimes|string',
            'chunking_strategy' => 'sometimes|string|in:Character,Recursive Character,Document Specific,Semantic Splitting,Agentic Splitting'
        ]);

        try {
            $question = $request->input('question');
            $documentName = $request->input('document_name');
            $chunkingStrategy = $request->input('chunking_strategy');

            // Get the MCP tool
            $docStatsTool = Relay::tools('documents');

            // Generate embedding for the question
            $questionEmbedding = $this->generateEmbedding($question);

            // Build query for similar chunks
            $query = DocumentChunk::query()
                ->nearestNeighbors('embedding', $questionEmbedding,  Distance::Cosine)
                ->with('document:id,original_filename');

            // Filter by document if specified
            if ($documentName) {
                $query->whereHas('document', function ($q) use ($documentName) {
                    $q->where('original_filename', 'like', "%{$documentName}%");
                });
            }

            // Filter by chunking strategy if specified
            if ($chunkingStrategy) {
                $query->where('chunking_strategy', $chunkingStrategy);
            }

            $relevantChunks = $query->limit(5)->get();

            if ($relevantChunks->isEmpty()) {
                return response()->json([
                    'success' => false,
                    'error' => 'No relevant information found in documents'
                ], 404);
            }

            // Build context from chunks
            $context = $relevantChunks->map(function ($chunk) {
                return "Document: {$chunk->document->original_filename}\nPage: {$chunk->page_number}\n{$chunk->page_content}";
            })->join("\n\n---\n\n");

            // Ask AI using Prism
            $prompt = "Based on the following context, answer the question.\n\nContext:\n{$context}\n\nQuestion: {$question}\n\nAnswer:";

            // $aiResponse = $this->generateText($prompt);

            $aiResponse = $this->generateText($prompt, [...$docStatsTool]);

            return response()->json([
                'success' => true,
                'question' => $question,
                'answer' => $aiResponse,
                'sources' => $relevantChunks->map(function ($chunk) {
                    return [
                        'document' => $chunk->document->original_filename,
                        'page' => $chunk->page_number,
                        'chunking_strategy' => $chunk->chunking_strategy,
                        'preview' => substr($chunk->page_content, 0, 150) . '...'
                    ];
                })
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'error' => $e->getMessage()
            ], 500);
        }
    }

    private function generateEmbedding(string $text): array
    {
        $response = Prism::embeddings()
            ->using(Provider::from($this->provider), $this->embeddingModel)
            ->fromArray([$text])
            ->asEmbeddings();

        return $response->embeddings[0]->embedding;
    }

    // private function generateText(string $prompt): string
    // {
    //     $response = Prism::text()
    //         ->using(Provider::from($this->provider), $this->model)
    //         ->withPrompt($prompt)
    //         ->asText();

    //     return $response->text;
    // }

    private function generateText(string $prompt, array $tools = []): string
    {
        $prism = Prism::text()
            ->using(Provider::from($this->provider), $this->model)
            ->withPrompt($prompt);

        if (!empty($tools)) {
            $prism->withMaxSteps(3)->withTools($tools);
        }

        $response = $prism->asText();

        return $response->text;
    }
}


am not understang why i get that error yet in claude it works

when i did this in claude code , claude coder was able to see the mcp and the tool well

claude mcp add -s user -t http 'docs-injest-documents-mcp' 'http://localhost:8000/mcp/documents'

so an help on this error will be appreciated

{
    "success": false,
    "error": "Failed to fetch tools from MCP server 'documents': Failed to send request to MCP server: cURL error 28: Operation timed out after 30008 milliseconds with 0 bytes received (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for http://127.0.0.1:8000/mcp/documents"
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions