valentin_vranic started a new conversation+100 XP
2mos ago
Probably it's supposed to be a blog post, but I find it well to place it here too.
So my first, real "wtf" moment after so many "wow"s regarding of capabilities of AI agents, and the development with them.
So long story short. Together with my teammate , we started working on a dashboard for our customers, with many things in plan from our PM, and superiors, but nothing was clearly defined in the beginning, rather as we moved forward new tasks arose. We used AI for development clearly, but in kind of Developer Driven mode, putting together our stuffs, using agents to help our process.
After 3 weeks, we came to present our first phase achievement. After presentation one of our colleagues, who is not particularly a tech-dev guy, mostly a project manager came up with his dashboard, which he put together totally with claude code, also in 3 weeks. Nobody knows about that. And we were amazed too, how much things he put in, with totally user friendly approach. Kind of like almost production ready dashboard, even added couple of special complex functionalities which were amazing. So the CEO was amazed too, that a non dev guy was able to put this together.
Yes, it's a bit buggy, and have couple of security issues. It combines couple of different languagess/frameworks (python, nextjs, go...), for me not so familiar ones. But the boss was amazed. And I had some really mixed feelings about it, mostly like my work is not good enough, not fast enough, although I did everything as I wanted. We spoke to him about our approach using AI for development, and for later issues, when real problems will arise, with scalability and fixing issues in general. But probably soon, that will be managed by ai too. But summa summarum we switched to this project, and will continue to develop it. IDK what will be the end of this project, and will it break in the future, but currently here we are.
I'm not waiting for help, or any guidance, only wanted to share my thoughts. Please feel free to share yours too.
Thanks all,
Val
valentin_vranic wrote a reply+100 XP
2mos ago
So, finally, I was able to come up with a working solution. Hopefully someone facing some same kind of issues will find it helpful.
private function checkSSEServer(McpServer $server, array $headers): array
{
try {
$response = Http::timeout(10)
->withHeaders($headers)
->get($server->url);
$body = $response->body();
$endpoint = $this->extractSseEndpoint($body, $server->url);
$sessionId = $this->extractSessionId($endpoint);
$requestHeaders = $sessionId ? array_merge($headers, ['Mcp-Session-Id' => $sessionId]) : $headers;
return $this->requestToolsList($endpoint, $requestHeaders);
}
private function extractSseEndpoint(string $body, string $baseUrl): ?string
{
foreach (preg_split('/\r\n|\r|\n/', $body) as $line) {
$line = trim($line);
if (! str_starts_with($line, 'data:')) {
continue;
}
$payload = trim(substr($line, 5));
if ($payload === '') {
continue;
}
if (Str::startsWith($payload, 'http://') || Str::startsWith($payload, 'https://')) {
return $payload;
}
$jsonPayload = json_decode($payload, true);
if (is_array($jsonPayload)) {
foreach (['endpoint', 'url', 'messageUrl'] as $key) {
if (isset($jsonPayload[$key]) && is_string($jsonPayload[$key]) && $jsonPayload[$key] !== '') {
return $this->resolveUrl($baseUrl, $jsonPayload[$key]);
}
}
}
if (Str::startsWith($payload, '/') || ! Str::contains($payload, ' ')) {
return $this->resolveUrl($baseUrl, $payload);
}
}
return null;
}
private function requestToolsList(string $url, array $headers): ?array
{
$initializeId = 1;
$initializeResponse = Http::timeout(10)
->connectTimeout(5)
->withHeaders($headers)
->post($url, [
'jsonrpc' => '2.0',
'id' => $initializeId,
'method' => 'initialize',
'params' => [
'protocolVersion' => self::MCP_PROTOCOL_VERSION,
'capabilities' => (object) [],
'clientInfo' => [
'name' => 'ManiAgents',
'version' => '1.0.0',
],
],
]);
$sessionId = $initializeResponse->header('Mcp-Session-Id');
$requestHeaders = $sessionId ? array_merge($headers, ['Mcp-Session-Id' => $sessionId]) : $headers;
$initializedResponse = Http::timeout(10)
->connectTimeout(5)
->withHeaders($requestHeaders)
->post($url, [
'jsonrpc' => '2.0',
'method' => 'notifications/initialized',
]);
do {
$params = $cursor ? ['cursor' => $cursor] : (object) [];
$toolsListResponse = Http::timeout(10)
->connectTimeout(5)
->withHeaders($requestHeaders)
->post($url, [
'jsonrpc' => '2.0',
'id' => $toolsListId++,
'method' => 'tools/list',
'params' => $params,
]);
$json = $toolsListResponse->json();
if (is_array($json)) {
if (isset($json['error'])) {
$this->lastErrorMessage = 'MCP error on tools/list: '.($json['error']['message'] ?? 'Unknown error');
Log::warning('MCP tools/list returned error', [
'url' => $url,
'error' => $json['error'],
]);
return null;
}
$result = $json['result'] ?? null;
if (is_array($result) && isset($result['tools']) && is_array($result['tools'])) {
$allTools = [...$allTools, ...$result['tools']];
$cursor = isset($result['nextCursor']) && is_string($result['nextCursor']) ? $result['nextCursor'] : null;
continue;
}
if (isset($json['tools']) && is_array($json['tools'])) {
$allTools = [...$allTools, ...$json['tools']];
$cursor = null;
continue;
}
}
} while ($cursor !== null);
}
So, it's not handling when it's unsuccessful, only to show what is my workflow for it.
McpServer is only my model consisting some props like name, url, etc...
valentin_vranic started a new conversation+100 XP
2mos ago
Hi all!
I'm facing an issue for a while. Firstly it was the lack of understanding of MCP-s, secondly the understanding of capabilities of Laravel and MCP-s.
So as I know, the MCP-s are not kind of REST endpoint(s) as I thought in the beginning, but an SSE (server-sent-events). Which is a new for me.
So my starting approach was to just send a tools-list method to the url, but later realised that it does need an open session connection to the MCP itself and then can only happen the query.
So my question is, is there any solutions to this in laravel? A lightweight approach for a quick MCP client which only lists the tools of a certain MCP server? I'm building a workflow app, where one of the features would be that users can add/pick MCP servers and list their tools to visually see them.
Thanks in advance, Val
valentin_vranic liked a comment+100 XP
2mos ago
Hi @silveira, I've read about a similar issue in another post when upgrading to Livewire 4 and Filament. They fixed it by deleting the node_modules directory and installed the nodes packages again. I suggest giving it a try.
valentin_vranic wrote a reply+100 XP
2mos ago
valentin_vranic liked a comment+100 XP
3mos ago
Providing some essential code would help a lot.
@valentin_vranic This.
valentin_vranic wrote a reply+100 XP
3mos ago
Firstly the issue title and your real question I think is a bit misleading, thus I don't see the real relation between them (at first look).
IMO I would pass the object if it't not so heavy, and a simple model, and use #[Locked] on it in child, to prevent any malicious data manipulation between queries.
And as a mandatory step, when looping the over parent property of child components, passing the :key on it to keep track of them.
IDK what the pivot datas consists and what losing them actually means. That's correct that the action is isolated for a component. But I'm not sure that refreshing the parent makes it efficient.
Providing some essential code would help a lot.
valentin_vranic wrote a reply+100 XP
4mos ago
For that purpose you can change the
public string const HOME = '/'; in app/Providers/RouteServiceProvider.php
EDIT:
My bad, the code above is for laravel 11x.
Here's the docs for 12, which could help: https://laravel.com/docs/12.x/authentication#redirecting-authenticated-users
valentin_vranic liked a comment+100 XP
4mos ago
valentin_vranic wrote a reply+100 XP
4mos ago
valentin_vranic started a new conversation+100 XP
4mos ago
I have an application, where I connect to multiple db-s. I have In 8 connections 3 where read & write credentials are separately defined, and the rest where only the read were defined and write was left empty.
'mysql' => [
'driver' => 'mysql',
'port' => env('DB_PORT', 3306),
'database' => env('DB_MYSQL_DATABASE'),
'read' => [
'host' => [
env('DB_MYSQL_HOST'),
],
'username' => env('DB_MYSQL_READ_USERNAME'),
'password' => env('DB_MYSQL_READ_PASSWORD'),
],
'write' => [
'host' => [
env('DB_MYSQL_HOST'),
],
'username' => env('DB_MYSQL_WRITE_USERNAME'),
'password' => env('DB_MYSQL_WRITE_PASSWORD'),
],
],
'mysql_second' => [
'driver' => 'mysql',
'port' => env('DB_PORT', 3306),
'database' => env('DB_MYSQL_SECOND_DATABASE'),
'read' => [
'host' => [
env('DB_MYSQL_SECOND_HOST'),
],
'username' => env('DB_MYSQL_SECOND_READ_USERNAME'),
'password' => env('DB_MYSQL_SECOND_READ_PASSWORD'),
],
'write' => [
'host' => [
''
],
],
]
This worked under laravel 11. After I've upgraded to version 12 I'm getting
SQLSTATE[HY000] [2002] No such file or directory for those connections where the write is left empty like above.
My question is what happened in the upgrade? Does anyone had some similar issues?
And yeah, why don't I write it into single dimension without read, write in this second scenario, even if it works like that?
Because I wanted to keep the structure.
Probably I will if there's no other solution.
NOTE: maybe it's not in the upgrade, but on system I use it. On live test server (debian 12) everything seems okay yet. But in my local env, macos under Herd, it got broken.
Thanks in advance,
Val
valentin_vranic wrote a reply+100 XP
5mos ago
valentin_vranic wrote a reply+100 XP
5mos ago
Hi. First thing I noticed, and probably will fix the issue too.
You don't need the wire:key="questionnaire-step-{{ $step }}" in the parent div.
wire:key is mostly used in loops to track the elements inside.
The second is for the livewire components call. Use :key instead of wire:key for them, thus that is the recommended approach. Like <livewire:questionnaires.step1 :key="'q-step1'" />
And the third would be, the $refresh on your nextStep method. Because you modify the step property, it will automatically re-render the view, and if there's no any other use case to force the whole component to $refresh then don't do it.
Hope this helps, Val
valentin_vranic wrote a reply+100 XP
5mos ago
Hey. I came up with an idea/solution. You can like it or hate it, but this is what I can give you :D
Using, livewire 3 and alpineJs/vanillaJs solutions.
class InfiniteScroll extends Component
{
use WithPagination, WithoutUrlPagination;
public const int PER_PAGE = 9;
public int $pageNumber = 0;
public array $colors = [
'#FF6B6B',
'#FFD93D',
'#6BCB77',
'#4D96FF',
'#B28DFF',
'#FF8E72',
'#00C2A8',
'#FFB3C1',
'#A7C7E7',
];
public function loadMore(): void
{
$this->nextPage();
$html = '';
foreach ($this->users as $i => $user) {
$html .= view('components.user-tile', [
'user' => $user,
'bg' => $this->colors[($i + 1 * self::PER_PAGE) % count($this->colors)],
])->render();
}
$this->dispatch('loadMore', ['html' => $html]);
}
#[Computed]
public function users()
{
return User::query()
->orderBy('id')
->paginate(self::PER_PAGE);
}
}
The view:
<div x-data="infiniteScroll">
<div style="min-height: 100vh; display: flex; align-items: center; justify-content: center;">
<div style="display: grid; grid-template-columns: repeat(3, 120px); grid-auto-rows: 120px; gap: 16px;"
wire:ignore
id="infiniteScroll">
@foreach ($this->users as $user)
@php
$bg = $colors[$loop->index % count($colors)];
@endphp
<x-user-tile :user="$user" :bg="$bg"/>
@endforeach
</div>
</div>
<div x-intersect="$wire.loadMore()"></div>
</div>
@script
<script>
Alpine.data('infiniteScroll', () => ({
init() {
Livewire.on('loadMore', (detail) => {
const container = document.querySelector('#infiniteScroll');
let block = detail[0]
container.insertAdjacentHTML('beforeend', block.html);
})
}
}))
</script>
@endscript
x-user-tile (in views/components)
@props(['user', 'bg'])
<div wire:key="{{ $user->id }}"
style="background: {{ $bg }};
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
color: #1f2937;">
{{ $user->name }}
</div>
I'm using the laravel pagination, combined with livewire use WithPagination, WithoutUrlPagination;
With this approach, I only append to the parent, preventing the growth of property, and querying the whole dataset using only the limit.
p.s. with livewire 4, there is/will be a solution for this: https://livewire.laravel.com/docs/4.x/islands#append-and-prepend-modes
valentin_vranic liked a comment+100 XP
5mos ago
After Episode 7 everything was confused. It would be better to continue how you started by creating Payooner, Wire and Wise providers and it would be great you demonstrate how would you add a new functionality to the providers. Also I was expecting that you will provide how to divide PaymentInterface by multiple parts (RefundableInterface, CapturedPaymentInterface)
Say that Payooner has an option to capture a payment and then confirm it, but Wire transfer there is only refundable option available
class Payooner implements RefundableInterface, CapturedPaymentInterface class Wire implements RefundableInterface
valentin_vranic liked a comment+100 XP
5mos ago
@automica I second that. I do not get the benefit from this yet and a concrete example in the test (without the interface and with the interface) would help.
valentin_vranic wrote a reply+100 XP
5mos ago
Well, I'm using PHPStorm, with barryvdh/laravel-ide-helper package. But the experience is somewhere similar you described.
My experience is that, when the view's name is rendered from the convention of class name, it is more reactive on referencing the properties, methods used in view.
The other way, sometimes when it's a bit different named view, it simply doesn't recognize anything.
It would a be a pleasure to have some other insights from others too.
valentin_vranic wrote a reply+100 XP
5mos ago
valentin_vranic wrote a reply+100 XP
5mos ago
So couple of things.
Firstly provide the class content too, especially where saveSelectedCustomer happens.
It's not a good practice to combine name and wire:model attributes, especially with lw stick with the latter.
Where forms are present, like here, I like to set wire:submit="saveSelectedCustomer" and set the submit button type to submit, and remove the wire:click from it too, thus this will lock, disable the form while it submits it.
There's no attribute like wire:blur rather a modifier like wire:model.blur which updates the property when clicked away from the input itself:
https://livewire.laravel.com/docs/3.x/wire-model#updating-on-blur-event
Lastly, don't make unnecessary mess in the forum posting the same question more than once.
valentin_vranic wrote a reply+100 XP
5mos ago
valentin_vranic was awarded Best Answer+1000 XP
5mos ago
valentin_vranic wrote a reply+100 XP
5mos ago
valentin_vranic wrote a reply+100 XP
5mos ago
valentin_vranic wrote a reply+100 XP
5mos ago
Hi. Well it's supposed to work as you described. I'm only not aware of this part
<livewire:currlm-select-block::currlm-select-block-setting it's not a valid syntax with ::, rather if it's nested file in file structure, then concatenate it with .
Like:
<livewire:currlm-select-block.currlm-select-block-setting
valentin_vranic wrote a reply+100 XP
5mos ago
valentin_vranic wrote a reply+100 XP
5mos ago
Hey. I'm not so familiar with flux behaviors and components. However I don't recommend to render unnecessary view for each row, rather define one on bottom of parent page, something like let's say:
<x-dialog wire:show="clickedPerson">
<x-dialog.panel panelWidth="w-50">
@if($clickedPerson)
@include('livewire.person', ['person' => $clickedPerson])
@endif
</x-dialog.panel>
</x-dialog>
This is my custom livewire panel case. So in each row to have a button, with wire:click="loadPerson($id)" which will trigger a method, and that will load the person by id.
Something like this, so in this case, it will be dependent on the provided $id rather than the loaded view.
valentin_vranic wrote a reply+100 XP
5mos ago
I don't see where you listen for the global event itself, on for confirm method.
What if:
adding #[On('open-modal')] above public function confirm($eventToEmit = null, $payload = [])?
And it could be used like you mentioned in the beginning with wire:click="$dispatchTo('ConfirmationModal', 'confirm', {{$payload}})"
Am I missing something?