-
-
Notifications
You must be signed in to change notification settings - Fork 12
Open
Description
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
Labels
No labels