File size: 45,824 Bytes
a939442
 
0ed2558
a939442
0ed2558
 
 
 
 
 
 
 
 
 
 
 
a939442
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ed2558
 
 
 
 
 
 
 
 
 
 
a939442
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ed2558
 
 
 
 
 
 
 
 
 
 
a939442
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ed2558
 
 
 
 
 
 
 
 
 
 
a939442
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ed2558
 
 
 
 
 
 
 
 
 
 
a939442
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ed2558
 
 
 
 
 
 
 
 
 
 
a939442
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ed2558
 
 
 
 
 
 
 
 
 
 
a939442
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ed2558
 
 
 
 
 
 
 
 
 
 
a939442
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ed2558
 
 
 
 
a939442
0ed2558
 
 
 
 
a939442
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ed2558
a939442
 
0ed2558
 
a939442
 
 
0ed2558
 
 
a939442
 
 
 
 
 
 
 
 
0ed2558
 
 
a939442
0ed2558
 
a939442
0ed2558
 
 
 
 
a939442
0ed2558
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a939442
0ed2558
 
 
 
 
 
 
a939442
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { useInView } from 'react-intersection-observer'
import { FiBookOpen, FiClock, FiArrowRight, FiTrendingUp, FiX, FiArrowLeft, FiTag } from 'react-icons/fi'

const BLOGS = [
  {
    emoji: '🔌',
    tag: 'MCP Deep Dive',
    tagColor: '#6366f1',
    title: 'Understanding MCP: Model Context Protocol Explained',
    excerpt: 'MCP (Model Context Protocol) is Anthropic\'s open standard that bridges AI assistants to external data, tools, and APIs. This deep-dive covers how MCP\'s client-server architecture enables stateful context management, JSON-RPC transport, and how LLMs like Claude interact with real-world systems through structured tool calls.',
    readTime: '6 min read',
    date: 'Apr 2025',
    topics: ['MCP', 'Anthropic', 'LLM', 'Protocols'],
    featured: true,
    content: `
## What is MCP?

**Model Context Protocol (MCP)** is Anthropic's open standard that bridges AI assistants to external data, tools, and APIs. It defines how AI models can securely and reliably connect to the context they need — whether that's a database, a file system, a web API, or any other data source.

Think of MCP as the **USB standard for AI** — a universal connector that lets any AI model plug into any data source without custom integration code.

## The Problem MCP Solves

Before MCP, every AI integration was a one-off engineering effort:
- Custom code to connect Claude or GPT to your database
- No standard for how tools should expose capabilities
- Context was siloed — each AI app had to reinvent the wheel
- Security and trust boundaries were unclear

MCP standardizes all of this with a well-defined protocol.

## Core Architecture

MCP uses a **client-server architecture**:

\`\`\`
Claude (Client) ←→ MCP Protocol ←→ MCP Server ←→ Your Data/Tools
\`\`\`

**MCP Servers** expose:
- **Tools** — functions that AI can call (e.g., query_database, send_email)
- **Resources** — data that AI can read (e.g., files, database records)
- **Prompts** — reusable prompt templates for common workflows

**MCP Clients** (like Claude Desktop) connect to servers and make these capabilities available to the LLM.

## Transport: JSON-RPC

MCP uses **JSON-RPC 2.0** as its transport protocol. Messages are structured as:

\`\`\`json
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "query_database",
    "arguments": {
      "query": "SELECT * FROM users LIMIT 10"
    }
  },
  "id": 1
}
\`\`\`

This makes MCP language-agnostic — any language that can speak JSON-RPC can implement an MCP server.

## Stateful Context Management

One of MCP's key innovations is **stateful context**. Unlike simple one-shot API calls:
- Sessions persist across multiple tool calls
- Context accumulates as the conversation progresses
- The AI can make multi-step decisions using context from previous steps

## Security Model

MCP has a clear security boundary: **the user is always in control**. Servers declare what they can do, users grant permission, and clients enforce boundaries. No server can access resources it hasn't been explicitly granted permission to use.

## Key Takeaways

1. MCP is the **standard for AI-tool integration** — use it instead of custom integrations
2. The client-server model separates concerns cleanly
3. JSON-RPC transport makes it easy to implement in any language
4. Stateful sessions enable complex multi-step AI workflows
5. Built-in security model protects user data

MCP is rapidly becoming the de-facto standard for connecting AI to the real world. Understanding it positions you at the cutting edge of AI engineering.
    `
  },
  {
    emoji: '🛠️',
    tag: 'MCP Tools',
    tagColor: '#8b5cf6',
    title: 'Building Your First MCP Server: Tools, Resources & Prompts',
    excerpt: 'A hands-on guide to building production-grade MCP servers using the Python SDK. Learn how to define Tools, expose Resources, and create reusable Prompt templates — making your AI agent capable of reading files, querying APIs, and executing custom logic through a standardized protocol.',
    readTime: '9 min read',
    date: 'Apr 2025',
    topics: ['MCP Server', 'Python SDK', 'Tools', 'Resources'],
    featured: true,
    content: `
## Getting Started with MCP Python SDK

The fastest way to build an MCP server is with the **official Python SDK**. Let's build a complete server from scratch.

## Installation

\`\`\`bash
pip install mcp
\`\`\`

## Your First MCP Server

\`\`\`python
from mcp.server import Server
from mcp.server.models import InitializationOptions
import mcp.types as types

# Initialize the server
server = Server("my-first-mcp-server")

@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="get_weather",
            description="Get current weather for a city",
            inputSchema={
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "City name"}
                },
                "required": ["city"]
            }
        )
    ]

@server.call_tool()
async def handle_call_tool(name: str, arguments: dict):
    if name == "get_weather":
        city = arguments["city"]
        # Call your weather API here
        return [types.TextContent(type="text", text=f"Weather in {city}: Sunny, 25°C")]
\`\`\`

## Defining Resources

Resources let the AI read data from your system:

\`\`\`python
@server.list_resources()
async def handle_list_resources() -> list[types.Resource]:
    return [
        types.Resource(
            uri="file:///data/users.json",
            name="User Database",
            description="Current user records",
            mimeType="application/json"
        )
    ]

@server.read_resource()
async def handle_read_resource(uri: str) -> str:
    if uri == "file:///data/users.json":
        with open("data/users.json") as f:
            return f.read()
\`\`\`

## Creating Prompt Templates

Prompts are reusable templates for common AI workflows:

\`\`\`python
@server.list_prompts()
async def handle_list_prompts() -> list[types.Prompt]:
    return [
        types.Prompt(
            name="analyze-data",
            description="Analyze a dataset and provide insights",
            arguments=[
                types.PromptArgument(
                    name="dataset_name",
                    description="Name of the dataset to analyze",
                    required=True
                )
            ]
        )
    ]

@server.get_prompt()
async def handle_get_prompt(name: str, arguments: dict):
    if name == "analyze-data":
        return types.GetPromptResult(
            messages=[
                types.PromptMessage(
                    role="user",
                    content=types.TextContent(
                        type="text",
                        text=f"Please analyze the {arguments['dataset_name']} dataset..."
                    )
                )
            ]
        )
\`\`\`

## Running Your Server

\`\`\`python
import asyncio
from mcp.server.stdio import stdio_server

async def main():
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream, InitializationOptions(
            server_name="my-first-mcp-server",
            server_version="0.1.0"
        ))

asyncio.run(main())
\`\`\`

## Best Practices

1. **Descriptive tool names** — AI uses names to decide which tool to call
2. **Clear input schemas** — Be explicit about required vs optional parameters
3. **Error handling** — Always return meaningful error messages
4. **Idempotency** — Tools should be safe to call multiple times
5. **Rate limiting** — Protect your backend from AI-driven overload

You now have everything you need to build production-ready MCP servers. The possibilities are endless.
    `
  },
  {
    emoji: '🤖',
    tag: 'MCP Automation',
    tagColor: '#06b6d4',
    title: 'Claude + MCP: Orchestrating Multi-Step AI Automations',
    excerpt: 'Claude\'s native MCP integration unlocks a new paradigm in AI automation — where a single LLM can read emails, query databases, call APIs, and write files in a coordinated, multi-step flow. This post explores architectural patterns for building reliable MCP orchestration pipelines with tool chaining, error recovery, and state persistence.',
    readTime: '10 min read',
    date: 'Mar 2025',
    topics: ['Claude', 'MCP', 'Orchestration', 'Agents'],
    featured: true,
    content: `
## The Power of Claude + MCP

When Claude is connected to an MCP server, it transforms from a conversational AI into an **autonomous agent** capable of taking real-world actions. This opens up a new class of AI automations.

## A Real-World Example: Email Triage Agent

Imagine an agent that:
1. Reads your inbox via Gmail MCP server
2. Queries your CRM for sender context
3. Classifies email priority using Claude
4. Drafts responses for routine queries
5. Creates calendar events for meeting requests
6. Writes summaries to your Notion workspace

All of this runs automatically, orchestrated by a single Claude session.

## Tool Chaining Patterns

### Sequential Chaining
Each tool call's output feeds into the next:

\`\`\`
Read Email → Extract Info → Query CRM → Draft Response → Send
\`\`\`

### Parallel Execution
Multiple tools called simultaneously:

\`\`\`
Read Email → [Query CRM | Search Knowledge Base | Check Calendar] → Synthesize → Respond
\`\`\`

### Conditional Branching
Claude decides which path based on tool results:

\`\`\`
Read Email → Classify Intent → 
  if "meeting request": Check Calendar → Propose Slots
  if "support query": Search Docs → Draft Solution
  if "sales lead": Score Lead → Route to CRM
\`\`\`

## Error Recovery Architecture

Production MCP orchestrations must handle failures gracefully:

\`\`\`python
# In your MCP server, return structured errors
@server.call_tool()
async def handle_tool(name: str, arguments: dict):
    try:
        result = await execute_action(name, arguments)
        return [types.TextContent(type="text", text=result)]
    except RateLimitError:
        return [types.TextContent(
            type="text", 
            text="ERROR: Rate limited. Please retry in 60 seconds."
        )]
    except PermissionError:
        return [types.TextContent(
            type="text",
            text="ERROR: Insufficient permissions for this action."
        )]
\`\`\`

Claude is trained to handle these errors intelligently — it will retry, use fallbacks, or ask for clarification rather than silently failing.

## State Persistence

For long-running automations, persist state between sessions:

\`\`\`python
import json
from pathlib import Path

class StateManager:
    def __init__(self, session_id: str):
        self.path = Path(f"sessions/{session_id}.json")
    
    def save(self, state: dict):
        self.path.write_text(json.dumps(state))
    
    def load(self) -> dict:
        if self.path.exists():
            return json.loads(self.path.read_text())
        return {}

# Expose as MCP resource
@server.read_resource()
async def read_session_state(uri: str):
    session_id = extract_session_id(uri)
    manager = StateManager(session_id)
    return json.dumps(manager.load())
\`\`\`

## Monitoring & Observability

Always instrument your MCP orchestrations:

1. **Log every tool call** with inputs, outputs, and latency
2. **Track token usage** — multi-step pipelines can be expensive
3. **Alert on repeated errors** — usually indicates a broken tool
4. **Human-in-the-loop checkpoints** for high-stakes actions

## Key Architectural Principles

1. **Start simple** — build linear pipelines before adding branches
2. **Fail fast** — return clear errors rather than partial results
3. **Idempotency matters** — Claude may retry failed steps
4. **Budget tokens wisely** — complex orchestrations use many tokens
5. **Test with edge cases** — AI behavior can be surprising

MCP orchestration with Claude represents a fundamental shift in how we build software. Actions that required complex workflow engines now emerge naturally from a well-prompted AI with the right tools.
    `
  },
  {
    emoji: '⚙️',
    tag: 'AI Workflows',
    tagColor: '#f59e0b',
    title: 'n8n + LLM: Building Visual AI Automation Workflows',
    excerpt: 'n8n\'s node-based workflow engine pairs powerfully with LLM APIs and MCP to build production-ready AI automations without complex code. This guide covers integrating OpenAI, Claude, and MCP tool nodes in n8n to automate document processing, customer support triaging, and smart notification pipelines.',
    readTime: '8 min read',
    date: 'Mar 2025',
    topics: ['n8n', 'LLM', 'Automation', 'No-Code'],
    featured: false,
    content: `
## Why n8n + LLM is a Game Changer

n8n is an open-source workflow automation platform that lets you visually build complex pipelines. When combined with LLM APIs, it becomes a powerful tool for building AI automations without writing complex orchestration code.

## Setting Up Your First AI Workflow

### Step 1: Install n8n
\`\`\`bash
npm install -g n8n
n8n start
\`\`\`

### Step 2: Connect Your LLM
In n8n, add an **OpenAI** or **Anthropic** node and configure your API key. n8n handles authentication, retry logic, and rate limiting automatically.

## Document Processing Pipeline

A common use case: automatically process incoming documents:

\`\`\`
Webhook Trigger
    ↓
Extract Text (PDF/DOCX node)
    ↓
OpenAI Node (Summarize + Extract Key Points)
    ↓
Condition Node (Check document type)
    ↓
├── Contract: Notion Database (save summary)
├── Invoice: Google Sheets (extract amounts)
└── Report: Slack (send to team channel)
\`\`\`

## Customer Support Triage

Route support tickets intelligently:

\`\`\`
Email Trigger (Gmail node)
    ↓
Claude Node: "Classify this support ticket: [ticket]. 
Categories: billing, technical, account, general"
    ↓
Switch Node (based on classification)
    ↓
├── billing: Create Stripe customer record + assign billing team
├── technical: Search knowledge base + create Jira ticket
└── general: Draft response + send via Gmail
\`\`\`

## Smart Notification System

Use LLMs to decide what's worth notifying about:

\`\`\`javascript
// In n8n Code Node
const events = $input.all();
const summaryPrompt = \`
You have \${events.length} new events. 
Identify only the CRITICAL ones that need immediate attention.
Events: \${JSON.stringify(events)}
Return JSON: { critical: [], can_wait: [] }
\`;
// Pass to OpenAI node, then route based on criticality
\`\`\`

## Best Practices for n8n + LLM

1. **Use structured outputs** — Ask LLMs to return JSON for reliable parsing
2. **Add error workflows** — n8n supports error handler workflows
3. **Cache LLM responses** — Use Redis nodes to avoid repeated API calls
4. **Rate limit your webhooks** — Prevent accidental runaway costs
5. **Test with mock data** — n8n's built-in test mode is invaluable

## Cost Optimization Tips

- Use GPT-3.5 or Claude Haiku for classification tasks (cheap, fast)
- Reserve GPT-4 or Claude Sonnet for complex reasoning
- Set max_tokens limits to avoid unexpected large outputs
- Batch similar requests when possible

n8n democratizes AI automation — you can build in hours what would take weeks to code from scratch.
    `
  },
  {
    emoji: '🕸️',
    tag: 'AI Automation',
    tagColor: '#8b5cf6',
    title: 'Building AI Automation Workflows with LangGraph',
    excerpt: 'LangGraph brings graph-based state machines to LLM applications, enabling complex branching, loops, and multi-step automation. Learn how to design production-ready agentic workflows that handle real-world complexity — including conditional routing, human-in-the-loop nodes, and persistent memory across sessions.',
    readTime: '8 min read',
    date: 'Feb 2025',
    topics: ['LangGraph', 'Automation', 'Agents', 'Python'],
    featured: false,
    content: `
## What is LangGraph?

LangGraph is a library from LangChain that enables building **stateful, multi-step LLM applications** using graph-based state machines. It's designed for agentic workflows that require branching, looping, and complex decision-making.

## Core Concepts

### State
Every LangGraph workflow has a **state** — a typed dictionary that flows through the graph:

\`\`\`python
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph

class AgentState(TypedDict):
    messages: list[str]
    current_step: str
    data: dict
    error: str | None
\`\`\`

### Nodes
Nodes are Python functions that transform the state:

\`\`\`python
def call_llm(state: AgentState) -> AgentState:
    response = llm.invoke(state["messages"])
    return {"messages": state["messages"] + [response]}

def execute_tool(state: AgentState) -> AgentState:
    tool_result = run_tool(state["current_step"])
    return {"data": tool_result}
\`\`\`

### Edges
Edges connect nodes and can be conditional:

\`\`\`python
def route_decision(state: AgentState) -> str:
    if state.get("error"):
        return "handle_error"
    elif state["current_step"] == "done":
        return "end"
    else:
        return "continue"
\`\`\`

## Building a Research Agent

\`\`\`python
from langgraph.graph import StateGraph, END

# Build the graph
workflow = StateGraph(AgentState)

# Add nodes
workflow.add_node("plan", plan_research)
workflow.add_node("search", search_web)
workflow.add_node("synthesize", synthesize_results)
workflow.add_node("write_report", write_final_report)

# Add edges
workflow.set_entry_point("plan")
workflow.add_edge("plan", "search")
workflow.add_conditional_edges(
    "search",
    lambda s: "synthesize" if len(s["data"]) >= 3 else "search",
    {"synthesize": "synthesize", "search": "search"}
)
workflow.add_edge("synthesize", "write_report")
workflow.add_edge("write_report", END)

# Compile and run
app = workflow.compile()
result = app.invoke({"messages": ["Research quantum computing trends"]})
\`\`\`

## Human-in-the-Loop

LangGraph supports pausing for human approval:

\`\`\`python
from langgraph.checkpoint.sqlite import SqliteSaver

# Add checkpointer for persistence
memory = SqliteSaver.from_conn_string(":memory:")
app = workflow.compile(checkpointer=memory, interrupt_before=["execute_action"])

# Run until interruption
config = {"configurable": {"thread_id": "session-1"}}
result = app.invoke(initial_state, config=config)

# Human reviews and approves
human_approval = input("Approve action? (y/n): ")
if human_approval == "y":
    # Resume from checkpoint
    result = app.invoke(None, config=config)
\`\`\`

## Persistent Memory

\`\`\`python
# State persists across sessions with the same thread_id
config = {"configurable": {"thread_id": "user-123"}}

# First session
app.invoke({"messages": ["Remember: I prefer Python"]}, config=config)

# Days later, same thread_id
app.invoke({"messages": ["What language should I use?"]}, config=config)
# Agent remembers the preference!
\`\`\`

## When to Use LangGraph

✅ Complex multi-step workflows with branching logic
✅ Agents that need to loop until a condition is met
✅ Human-in-the-loop approval workflows
✅ Long-running tasks requiring state persistence

❌ Simple single-turn Q&A
❌ Straightforward sequential pipelines (use LangChain chains instead)
❌ When you need real-time streaming primarily

LangGraph is the right tool when your AI workflow needs to "think, act, observe, repeat" with production-grade reliability.
    `
  },
  {
    emoji: '🤝',
    tag: 'Multi-Agent',
    tagColor: '#06b6d4',
    title: 'AutoGen vs LangGraph: Which Multi-Agent Framework to Use?',
    excerpt: 'A practical comparison of Microsoft AutoGen and LangGraph for multi-agent development. We examine developer experience, orchestration flexibility, memory management, and when to choose each framework for production AI systems that require collaborative agent teams.',
    readTime: '7 min read',
    date: 'Jan 2025',
    topics: ['AutoGen', 'LangGraph', 'Agents', 'Architecture'],
    featured: false,
    content: `
## The Multi-Agent Framework Landscape

Both **Microsoft AutoGen** and **LangGraph** are popular frameworks for building multi-agent AI systems, but they take fundamentally different approaches. Choosing the wrong one can cost you weeks of refactoring.

## AutoGen at a Glance

AutoGen models agents as **conversational participants** that talk to each other:

\`\`\`python
import autogen

# Define agents
assistant = autogen.AssistantAgent(
    name="Assistant",
    llm_config={"model": "gpt-4"},
    system_message="You are a helpful AI assistant."
)

critic = autogen.AssistantAgent(
    name="Critic",
    llm_config={"model": "gpt-4"},
    system_message="You review and improve the assistant's work."
)

user_proxy = autogen.UserProxyAgent(
    name="User",
    human_input_mode="TERMINATE",
    code_execution_config={"work_dir": "coding"}
)

# Start the conversation
user_proxy.initiate_chat(
    assistant,
    message="Write a Python script to analyze sales data"
)
\`\`\`

**AutoGen strengths:**
- Natural conversation flow between agents
- Built-in code execution sandbox
- Easy to set up group chats
- Great for iterative refinement tasks

**AutoGen weaknesses:**
- Less control over execution flow
- Difficult to implement precise branching
- State management is conversation history (unstructured)
- Can be unpredictable in production

## LangGraph at a Glance

LangGraph models agents as **graph nodes** with explicit state transitions:

\`\`\`python
from langgraph.graph import StateGraph

# Explicit, controllable state
class State(TypedDict):
    task: str
    draft: str
    feedback: str
    approved: bool

workflow = StateGraph(State)
workflow.add_node("write", write_agent)
workflow.add_node("review", review_agent)
workflow.add_node("revise", revise_agent)

# Deterministic routing
workflow.add_conditional_edges(
    "review",
    lambda s: "approve" if s["approved"] else "revise"
)
\`\`\`

**LangGraph strengths:**
- Precise control over agent orchestration
- Typed state management
- Excellent for complex branching workflows
- Production-ready with checkpointing
- Human-in-the-loop support

**LangGraph weaknesses:**
- More boilerplate code
- Steeper learning curve
- Verbose for simple use cases

## Side-by-Side Comparison

| Feature | AutoGen | LangGraph |
|---------|---------|-----------|
| Learning Curve | Low | Medium |
| Control | Low | High |
| State Management | Conversation history | Typed graph state |
| Branching | Implicit | Explicit |
| Code Execution | Built-in | Manual |
| Production Readiness | Medium | High |
| Human-in-the-Loop | Basic | Advanced |
| Debugging | Hard | Easy |

## Decision Framework

**Choose AutoGen when:**
- You're prototyping or exploring
- The task is conversational by nature (debate, review, brainstorm)
- You want agents to execute code iteratively
- Speed of development > control

**Choose LangGraph when:**
- You're building production systems
- The workflow has clear, defined steps
- You need reliable branching and routing
- State management and observability matter
- You need human approval at specific steps

## The Hybrid Approach

Many teams use both: **AutoGen for rapid prototyping** to understand the problem, then **LangGraph for production implementation** with proper state management and reliability.

My recommendation: learn LangGraph first if you're building anything that needs to run reliably in production.
    `
  },
  {
    emoji: '⚡',
    tag: 'Agentic AI',
    tagColor: '#10b981',
    title: 'Agentic AI Design Patterns: ReAct, Plan-and-Execute & Beyond',
    excerpt: 'From simple ReAct loops to sophisticated Plan-and-Execute architectures, this post dissects the core design patterns powering today\'s agentic AI systems. Understand when to use each pattern, their failure modes, and how MCP tool use changes the agent reliability equation.',
    readTime: '11 min read',
    date: 'Dec 2024',
    topics: ['ReAct', 'Planning', 'Agents', 'Design Patterns'],
    featured: false,
    content: `
## The Agent Design Pattern Landscape

Agentic AI is maturing rapidly, and with it, a set of proven design patterns has emerged. Understanding these patterns is crucial for building reliable, efficient AI agents.

## Pattern 1: ReAct (Reason + Act)

The simplest and most widely used pattern. The agent alternates between reasoning about what to do and taking actions.

\`\`\`
Thought: I need to find the current price of NVIDIA stock
Action: search_web("NVIDIA stock price today")
Observation: NVIDIA (NVDA) is trading at $875.40

Thought: Now I can answer the user's question
Action: respond("NVIDIA is currently trading at $875.40")
\`\`\`

**When to use ReAct:**
- Simple, linear tasks
- When each step depends on the previous result
- Limited number of tool calls needed

**ReAct failure modes:**
- Gets stuck in loops when tools fail
- "Hallucinates" tool outputs
- Poor at multi-horizon planning

## Pattern 2: Plan-and-Execute

Separate planning from execution for better control:

\`\`\`python
# Phase 1: Planning
plan = llm.plan("""
Create a market analysis report for Tesla:
1. Get current stock price
2. Fetch last 3 earnings reports
3. Search for recent news (last 7 days)
4. Analyze sentiment from news
5. Synthesize into executive summary
""")

# Phase 2: Execution
for step in plan.steps:
    result = executor.run(step)
    plan.update_with_result(step, result)

# Phase 3: Synthesis
report = llm.synthesize(plan.all_results())
\`\`\`

**Advantages over ReAct:**
- Complete picture before starting → better decisions
- Can parallelize independent steps
- Easier to audit and debug
- Fail-fast on impossible plans

**When to use Plan-and-Execute:**
- Complex, multi-step research tasks
- When steps can be parallelized
- When you need auditability

## Pattern 3: Self-Reflection

The agent reviews and critiques its own outputs:

\`\`\`python
def reflexion_loop(task, max_iterations=3):
    response = agent.execute(task)
    
    for i in range(max_iterations):
        critique = agent.reflect(f"""
        Task: {task}
        Response: {response}
        
        What's wrong or incomplete? How can this be improved?
        """)
        
        if critique.score >= 0.9:
            break
            
        response = agent.revise(response, critique.feedback)
    
    return response
\`\`\`

This dramatically improves output quality for writing, code generation, and analysis tasks.

## Pattern 4: Multi-Agent Orchestration

Different specialized agents handle different domains:

\`\`\`
Orchestrator Agent
├── Research Agent (web search, document reading)
├── Analysis Agent (data processing, calculations)
├── Writing Agent (report generation)
└── Review Agent (quality check, fact verification)
\`\`\`

**Key principle:** Each agent has a **single responsibility** and communicates through structured interfaces.

## Pattern 5: Human-in-the-Loop (HITL)

Insert human checkpoints at critical decision points:

\`\`\`python
# Pause before irreversible actions
@checkpoint("before_send_email")
def send_email(recipient, content):
    # Human reviews here
    pass

@checkpoint("before_database_write")  
def update_customer_record(id, data):
    # Human approves changes
    pass
\`\`\`

HITL is **essential** for high-stakes agents in production.

## The MCP Effect on Agent Reliability

MCP significantly improves agent reliability because:

1. **Typed interfaces** — tools have schemas, reducing hallucinated arguments
2. **Clear error messages** — agents can recover from failures intelligently
3. **Resource isolation** — agents can't accidentally access unauthorized data
4. **Observability** — every tool call is logged for debugging

## Choosing Your Pattern

| Scenario | Recommended Pattern |
|----------|---------------------|
| Simple Q&A with tools | ReAct |
| Complex research report | Plan-and-Execute |
| Code generation | ReAct + Self-Reflection |
| Collaborative content creation | Multi-Agent |
| High-stakes business automation | Plan-and-Execute + HITL |

The best agents combine multiple patterns. Start with ReAct, add reflection when quality matters, add planning when complexity grows, and always add HITL when stakes are high.
    `
  },
  {
    emoji: '📚',
    tag: 'RAG',
    tagColor: '#ec4899',
    title: 'RAG vs Fine-Tuning: Making the Right Choice for Your LLM App',
    excerpt: 'Retrieval-Augmented Generation and fine-tuning both improve LLM performance, but solve different problems. This guide breaks down cost tradeoffs, latency implications, vector database selection, and when each approach delivers the best results for production AI applications.',
    readTime: '5 min read',
    date: 'Nov 2024',
    topics: ['RAG', 'Fine-Tuning', 'LLM', 'Architecture'],
    featured: false,
    content: `
## The Core Question

Every AI engineer eventually faces this decision: **should I use RAG or fine-tune my model?**

The answer depends entirely on what problem you're actually solving.

## What RAG Does

RAG (Retrieval-Augmented Generation) gives the LLM access to **external, dynamic knowledge** at inference time:

\`\`\`python
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.chains import RetrievalQA

# Build knowledge base
vectorstore = Chroma.from_documents(
    documents=your_docs,
    embedding=OpenAIEmbeddings()
)

# Query with retrieval
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    retriever=vectorstore.as_retriever(k=4)
)

answer = qa_chain.run("What is our refund policy?")
\`\`\`

**RAG solves:**
- Knowledge that changes frequently
- Proprietary knowledge the base model doesn't have
- Need to cite sources
- Knowledge that's too large to fit in context

## What Fine-Tuning Does

Fine-tuning adjusts the model's **weights** to change its behavior, style, or knowledge:

\`\`\`python
# Fine-tuning with OpenAI
from openai import OpenAI
client = OpenAI()

# Upload training data
training_file = client.files.create(
    file=open("training_data.jsonl", "rb"),
    purpose="fine-tune"
)

# Start fine-tuning job
job = client.fine_tuning.jobs.create(
    training_file=training_file.id,
    model="gpt-3.5-turbo"
)
\`\`\`

**Fine-tuning solves:**
- Consistent tone/style (e.g., always respond formally)
- Domain-specific formats (e.g., medical coding)
- Specialized reasoning patterns
- Reducing hallucinations in a specific domain

## Cost Comparison

| Approach | Upfront Cost | Per-Query Cost | Update Cost |
|----------|-------------|----------------|-------------|
| RAG | Vector DB setup | Embeddings + retrieval | Low (just add docs) |
| Fine-tuning | Training compute ($$$) | Inference only | High (retrain) |
| RAG + Fine-tune | Both | Both | Medium |

For most startups: **RAG is 10x cheaper** to iterate on.

## Latency Comparison

| Approach | Added Latency |
|----------|---------------|
| RAG | +100-500ms (retrieval) |
| Fine-tuning | None (or marginal) |

If sub-100ms response times matter, fine-tuning wins.

## Vector Database Selection

For RAG, choosing the right vector DB matters:

| Database | Best For | Hosted? |
|----------|----------|---------|
| Pinecone | Production, scale | Yes |
| Weaviate | Complex queries | Both |
| Chroma | Development, small scale | Self-hosted |
| pgvector | Already on Postgres | Self-hosted |
| Qdrant | Performance-critical | Both |

**My recommendation:** Start with Chroma locally, move to Pinecone or Qdrant in production.

## The Decision Framework

**Use RAG when:**
✅ Your knowledge changes (weekly/daily/hourly)
✅ You need to cite sources
✅ Knowledge base > 100K tokens
✅ You have limited budget for training

**Use Fine-tuning when:**
✅ You need a very specific output format or style
✅ The model repeatedly fails at a task even with good prompts
✅ Latency is critical
✅ You have high, consistent query volume (reduces per-query cost)

**Use Both when:**
✅ You need consistent style AND up-to-date knowledge
✅ Building a domain-specific assistant (e.g., legal, medical)

## The Emerging Approach: RAG + Long Context

With 1M+ token context windows (Gemini 1.5), you can often skip retrieval entirely and just stuff your entire knowledge base into the context. Simpler architecture, but expensive at scale.

**My final recommendation:** Start with RAG. It's faster to build, cheaper to iterate, and solves 80% of "my LLM doesn't know about X" problems.
    `
  },
]

export default function Blog() {
  const [ref, inView] = useInView({ triggerOnce: true, threshold: 0.05 })
  const [selectedBlog, setSelectedBlog] = useState(null)

  const featured = BLOGS.filter(b => b.featured)
  const rest = BLOGS.filter(b => !b.featured)

  return (
    <>
      <section id="blog" className="section">
        <div className="orb orb-purple" style={{ width: 350, height: 350, bottom: '5%', right: '-5%', position: 'absolute', opacity: 0.3 }} />
        <div className="container" ref={ref}>
          <motion.div className="section-header"
            initial={{ opacity: 0, y: 24 }} animate={inView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.55 }}>
            <div className="section-tag"><FiBookOpen size={12} /> Blog</div>
            <h2 className="section-title">Thoughts &amp; Insights</h2>
            <p className="section-subtitle">Deep research on MCP, AI automation workflows, agentic systems, and the future of intelligent AI engineering.</p>
          </motion.div>

          {/* Featured row */}
          <motion.div
            initial={{ opacity: 0 }} animate={inView ? { opacity: 1 } : {}} transition={{ duration: 0.4, delay: 0.1 }}
            style={{ marginBottom: '1.5rem' }}
          >
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: '0.9rem' }}>
              <FiTrendingUp size={13} style={{ color: '#6366f1' }} />
              <span style={{ fontSize: '0.72rem', fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'var(--text-accent)' }}>
                Featured Research
              </span>
            </div>
            <div className="blog-featured-grid">
              {featured.map((blog, i) => (
                <BlogCard key={blog.title} blog={blog} i={i} inView={inView} large onRead={() => setSelectedBlog(blog)} />
              ))}
            </div>
          </motion.div>

          {/* Divider */}
          <div style={{ height: 1, background: 'var(--border-subtle)', marginBottom: '1.5rem', opacity: 0.5 }} />

          {/* All articles grid */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: '0.9rem' }}>
            <span style={{ fontSize: '0.72rem', fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'var(--text-muted)' }}>
              More Articles
            </span>
          </div>
          <div className="blog-rest-grid">
            {rest.map((blog, i) => (
              <BlogCard key={blog.title} blog={blog} i={i + featured.length} inView={inView} onRead={() => setSelectedBlog(blog)} />
            ))}
          </div>
        </div>
      </section>

      {/* Read Modal */}
      <AnimatePresence>
        {selectedBlog && (
          <BlogReadModal blog={selectedBlog} onClose={() => setSelectedBlog(null)} />
        )}
      </AnimatePresence>
    </>
  )
}

function BlogCard({ blog, i, inView, large, onRead }) {
  return (
    <motion.article
      className="glass-card blog-card-item"
      style={{ padding: large ? '1.75rem' : '1.5rem', display: 'flex', flexDirection: 'column', gap: '0.85rem', cursor: 'pointer', position: 'relative', overflow: 'hidden' }}
      initial={{ opacity: 0, y: 36 }}
      animate={inView ? { opacity: 1, y: 0 } : {}}
      transition={{ duration: 0.55, delay: i * 0.08, ease: [0.22, 1, 0.36, 1] }}
      whileHover={{ y: -6, borderColor: blog.tagColor + '50' }}
      onClick={onRead}
    >
      {/* Subtle top accent line */}
      <div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 2, background: `linear-gradient(90deg, ${blog.tagColor}60, transparent)` }} />

      {/* Header */}
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <span style={{ fontSize: large ? '2rem' : '1.8rem' }}>{blog.emoji}</span>
        <span style={{
          padding: '3px 10px', borderRadius: 999, fontSize: '0.68rem', fontWeight: 700,
          background: `${blog.tagColor}15`, border: `1px solid ${blog.tagColor}30`,
          color: blog.tagColor, letterSpacing: '0.05em',
        }}>{blog.tag}</span>
      </div>

      {/* Title */}
      <h3 style={{ fontSize: large ? '1.05rem' : '0.97rem', fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1.45 }}>
        {blog.title}
      </h3>

      {/* Excerpt */}
      <p style={{ fontSize: '0.82rem', color: 'var(--text-secondary)', lineHeight: 1.75, flexGrow: 1 }}>
        {blog.excerpt}
      </p>

      {/* Topic chips */}
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
        {blog.topics.map(t => (
          <span key={t} className="tech-tag" style={{
            fontSize: '0.65rem', background: `${blog.tagColor}10`,
            borderColor: `${blog.tagColor}20`, color: blog.tagColor,
          }}>{t}</span>
        ))}
      </div>

      {/* Footer */}
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', paddingTop: '0.5rem', borderTop: '1px solid var(--border-subtle)' }}>
        <div style={{ display: 'flex', gap: 12 }}>
          <span style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>{blog.date}</span>
          <span style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: '0.75rem', color: 'var(--text-muted)' }}>
            <FiClock size={11} /> {blog.readTime}
          </span>
        </div>
        <motion.button
          style={{
            background: 'none', border: 'none', cursor: 'pointer',
            display: 'flex', alignItems: 'center', gap: 4,
            fontSize: '0.78rem', fontWeight: 600, color: blog.tagColor,
          }}
          whileHover={{ gap: 8 }}
          onClick={(e) => { e.stopPropagation(); onRead(); }}
        >
          Read <FiArrowRight size={12} />
        </motion.button>
      </div>
    </motion.article>
  )
}

function BlogReadModal({ blog, onClose }) {
  // Parse markdown-like content to simple HTML rendering
  const renderContent = (content) => {
    const lines = content.trim().split('\n')
    const elements = []
    let inCodeBlock = false
    let codeLines = []
    let codeLang = ''
    let inTable = false
    let tableRows = []

    const flushTable = () => {
      if (tableRows.length > 0) {
        const header = tableRows[0]
        const body = tableRows.slice(2)
        elements.push(
          <div key={`table-${elements.length}`} className="blog-table-wrap">
            <table className="blog-table">
              <thead>
                <tr>
                  {header.split('|').filter(c => c.trim()).map((cell, ci) => (
                    <th key={ci}>{cell.trim()}</th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {body.map((row, ri) => (
                  <tr key={ri}>
                    {row.split('|').filter(c => c.trim()).map((cell, ci) => (
                      <td key={ci}>{cell.trim()}</td>
                    ))}
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )
        tableRows = []
        inTable = false
      }
    }

    lines.forEach((line, idx) => {
      if (line.startsWith('```')) {
        if (inCodeBlock) {
          elements.push(
            <div key={`code-${idx}`} className="blog-code-block">
              <div className="blog-code-lang">{codeLang || 'code'}</div>
              <pre><code>{codeLines.join('\n')}</code></pre>
            </div>
          )
          codeLines = []
          codeLang = ''
          inCodeBlock = false
        } else {
          if (inTable) flushTable()
          codeLang = line.slice(3).trim()
          inCodeBlock = true
        }
        return
      }

      if (inCodeBlock) {
        codeLines.push(line)
        return
      }

      // Table detection
      if (line.includes('|') && line.trim().startsWith('|')) {
        inTable = true
        tableRows.push(line)
        return
      } else if (inTable) {
        flushTable()
      }

      if (!line.trim()) {
        elements.push(<div key={`br-${idx}`} style={{ height: '0.5rem' }} />)
        return
      }

      if (line.startsWith('## ')) {
        elements.push(<h2 key={idx} className="blog-modal-h2">{line.slice(3)}</h2>)
      } else if (line.startsWith('### ')) {
        elements.push(<h3 key={idx} className="blog-modal-h3">{line.slice(4)}</h3>)
      } else if (line.startsWith('**') && line.endsWith('**') && line.length > 4) {
        elements.push(<p key={idx} className="blog-modal-bold">{line.slice(2, -2)}</p>)
      } else if (line.startsWith('- ') || line.startsWith('✅') || line.startsWith('❌')) {
        const text = line.startsWith('- ') ? line.slice(2) : line
        // Parse inline bold
        const parts = text.split(/(\*\*[^*]+\*\*)/)
        elements.push(
          <div key={idx} className="blog-modal-li">
            <span className="blog-modal-bullet">›</span>
            <span>
              {parts.map((p, pi) =>
                p.startsWith('**') && p.endsWith('**')
                  ? <strong key={pi}>{p.slice(2, -2)}</strong>
                  : p
              )}
            </span>
          </div>
        )
      } else if (/^\d+\. /.test(line)) {
        const num = line.match(/^(\d+)\. /)[1]
        const text = line.replace(/^\d+\. /, '')
        const parts = text.split(/(\*\*[^*]+\*\*)/)
        elements.push(
          <div key={idx} className="blog-modal-li">
            <span className="blog-modal-num">{num}</span>
            <span>
              {parts.map((p, pi) =>
                p.startsWith('**') && p.endsWith('**')
                  ? <strong key={pi}>{p.slice(2, -2)}</strong>
                  : p
              )}
            </span>
          </div>
        )
      } else {
        // Normal paragraph with inline bold parsing
        const parts = line.split(/(\*\*[^*]+\*\*)/)
        elements.push(
          <p key={idx} className="blog-modal-p">
            {parts.map((p, pi) =>
              p.startsWith('**') && p.endsWith('**')
                ? <strong key={pi}>{p.slice(2, -2)}</strong>
                : p
            )}
          </p>
        )
      }
    })

    if (inTable) flushTable()

    return elements
  }

  return (
    <motion.div
      className="blog-modal-overlay"
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      transition={{ duration: 0.2 }}
      onClick={onClose}
    >
      <motion.div
        className="blog-modal-container"
        initial={{ opacity: 0, y: 40, scale: 0.97 }}
        animate={{ opacity: 1, y: 0, scale: 1 }}
        exit={{ opacity: 0, y: 40, scale: 0.97 }}
        transition={{ duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
        onClick={e => e.stopPropagation()}
      >
        {/* Modal Header */}
        <div className="blog-modal-header">
          <button className="blog-modal-back" onClick={onClose}>
            <FiArrowLeft size={16} />
            <span>Back</span>
          </button>
          <button className="blog-modal-close" onClick={onClose} aria-label="Close">
            <FiX size={18} />
          </button>
        </div>

        {/* Article Header */}
        <div className="blog-modal-article-header">
          <div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap', marginBottom: '1rem' }}>
            <span style={{ fontSize: '2.5rem' }}>{blog.emoji}</span>
            <span style={{
              padding: '4px 14px', borderRadius: 999, fontSize: '0.72rem', fontWeight: 700,
              background: `${blog.tagColor}15`, border: `1px solid ${blog.tagColor}30`,
              color: blog.tagColor, display: 'flex', alignItems: 'center', gap: 6,
            }}>
              <FiTag size={10} /> {blog.tag}
            </span>
          </div>
          <h1 className="blog-modal-title">{blog.title}</h1>
          <div className="blog-modal-meta">
            <span>{blog.date}</span>
            <span className="blog-modal-meta-dot">·</span>
            <FiClock size={12} />
            <span>{blog.readTime}</span>
          </div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: '1rem' }}>
            {blog.topics.map(t => (
              <span key={t} className="tech-tag" style={{
                fontSize: '0.68rem', background: `${blog.tagColor}10`,
                borderColor: `${blog.tagColor}20`, color: blog.tagColor,
              }}>{t}</span>
            ))}
          </div>
        </div>

        {/* Divider */}
        <div style={{ height: 1, background: 'var(--border-subtle)', margin: '0 2rem 1.5rem' }} />

        {/* Content */}
        <div className="blog-modal-content">
          <div className="blog-modal-body">
            {renderContent(blog.content)}
          </div>
        </div>

        {/* Footer */}
        <div className="blog-modal-footer">
          <button className="blog-modal-close-btn" onClick={onClose}>
            <FiArrowLeft size={14} /> Back to Articles
          </button>
        </div>
      </motion.div>
    </motion.div>
  )
}