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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions