# Project: Mini MCP WebSocket Server (B4J)
# Files included below. Copy each file into a new B4J project with the same filename.
---
File: Main.bas
' Main WebSocket MCP server
Sub Process_Globals
Private ws As WebSocketServer
Private ServerPort As Int = 8888
Private store As ProductStore
End Sub
Sub AppStart (Args() As String)
Log("Starting Mini MCP WebSocket Server...")
store.Initialize
ws.Initialize("ws")
ws.Start(ServerPort, "0.0.0.0")
Log($"🚀 MCP WebSocket server started on ws://0.0.0.0:${ServerPort}/")
End Sub
' New client connected
Sub ws_Connected (wsClient As WebSocket)
Log("Client connected: " & wsClient.GetRemoteAddress)
' Send an MCP-style "initialized" capabilities message (not strictly JSON-RPC)
Dim gen As JSONGenerator
Dim cap As Map = CreateMap("protocolVersion": "1.0", "capabilities": CreateMap("tools": Array As String("echo","get_products","search_items","create_invoice","check_stock","get_sales_summary","get_time","get_server_info")))
gen.Initialize(cap)
wsClient.SendText(gen.ToString)
End Sub
Sub ws_TextMessage (wsClient As WebSocket, msg As String)
Log("Received: " & msg)
Dim parser As JSONParser
parser.Initialize(msg)
Try
Dim req As Map = parser.NextObject
Dim method As String = req.GetDefault("method", "")
Dim id = req.GetDefault("id", Null)
Select method
Case "echo"
Handle_Echo(wsClient, id, req)
Case "get_products"
Handle_GetProducts(wsClient, id, req)
Case "search_items"
Handle_SearchItems(wsClient, id, req)
Case "create_invoice"
Handle_CreateInvoice(wsClient, id, req)
Case "check_stock"
Handle_CheckStock(wsClient, id, req)
Case "get_sales_summary"
Handle_GetSalesSummary(wsClient, id, req)
Case "get_time"
Handle_GetTime(wsClient, id, req)
Case "get_server_info"
Handle_GetServerInfo(wsClient, id, req)
Case Else
ReplyError(wsClient, id, -32601, "Method not found: " & method)
End Select
Catch
ReplyError(wsClient, Null, -32700, "Parse error or invalid JSON")
End Try
End Sub
Sub ws_Disconnected (wsClient As WebSocket)
Log("Client disconnected: " & wsClient.GetRemoteAddress)
End Sub
' ----------------------
' RPC helper / replies
Private Sub ReplyResult(wsClient As WebSocket, id, result As Object)
Dim resp As Map = CreateMap("jsonrpc": "2.0", "id": id, "result": result)
Dim gen As JSONGenerator
gen.Initialize(resp)
wsClient.SendText(gen.ToString)
End Sub
Private Sub ReplyError(wsClient As WebSocket, id, code As Int, message As String)
Dim resp As Map = CreateMap("jsonrpc": "2.0")
If id <> Null Then resp.Put("id", id)
resp.Put("error", CreateMap("code": code, "message": message))
Dim gen As JSONGenerator
gen.Initialize(resp)
wsClient.SendText(gen.ToString)
End Sub
' ----------------------
' Handlers (business / database tools)
Private Sub Handle_Echo(wsClient As WebSocket, id, req As Map)
Dim params As Map = req.GetDefault("params", CreateMap())
Dim text As String = params.GetDefault("text", "")
Dim result As Map = CreateMap("echo": text)
ReplyResult(wsClient, id, result)
End Sub
Private Sub Handle_GetProducts(wsClient As WebSocket, id, req As Map)
Dim list As List = store.GetAllProducts
ReplyResult(wsClient, id, list)
End Sub
Private Sub Handle_SearchItems(wsClient As WebSocket, id, req As Map)
Dim params As Map = req.GetDefault("params", CreateMap())
Dim q As String = params.GetDefault("query", "")
Dim list As List = store.Search(q)
ReplyResult(wsClient, id, list)
End Sub
Private Sub Handle_CreateInvoice(wsClient As WebSocket, id, req As Map)
Dim params As Map = req.GetDefault("params", CreateMap())
' params expected: items = Array of {product_id, qty}
Dim items As List = params.GetDefault("items", CreateList())
If items.Size = 0 Then
ReplyError(wsClient, id, -32602, "Missing or empty 'items' parameter")
Return
End If
Dim invoice As Map
invoice.Initialize
invoice.Put("id", DateTime.Now)
invoice.Put("created_at", DateTime.Now)
Dim lines As List
lines.Initialize
Dim total As Double = 0
For Each it As Map In items
Dim pid = it.Get("product_id")
Dim qty As Int = it.GetDefault("qty", 1)
Dim p As Map = store.GetById(pid)
If p = Null Then Continue
Dim line As Map = CreateMap("product_id": pid, "name": p.Get("name"), "unit_price": p.Get("price"), "qty": qty, "line_total": p.Get("price") * qty)
lines.Add(line)
total = total + line.Get("line_total")
' reduce stock
store.AdjustStock(pid, -qty)
Next
invoice.Put("lines", lines)
invoice.Put("total", total)
' For demo: just return invoice (no persistent DB)
ReplyResult(wsClient, id, invoice)
End Sub
Private Sub Handle_CheckStock(wsClient As WebSocket, id, req As Map)
Dim params As Map = req.GetDefault("params", CreateMap())
Dim pid = params.GetDefault("product_id", Null)
If pid = Null Then
ReplyError(wsClient, id, -32602, "Missing 'product_id' parameter")
Return
End If
Dim p As Map = store.GetById(pid)
If p = Null Then
ReplyError(wsClient, id, -32602, "Product not found: " & pid)
Return
End If
ReplyResult(wsClient, id, CreateMap("product_id": pid, "stock": p.Get("stock")))
End Sub
Private Sub Handle_GetSalesSummary(wsClient As WebSocket, id, req As Map)
' Demo summary computed from store's sales history (if enabled). For now return simple inventory summary.
Dim summary As Map
summary.Initialize
summary.Put("product_count", store.Count)
summary.Put("total_stock", store.TotalStock)
ReplyResult(wsClient, id, summary)
End Sub
Private Sub Handle_GetTime(wsClient As WebSocket, id, req As Map)
ReplyResult(wsClient, id, DateTime.Now)
End Sub
Private Sub Handle_GetServerInfo(wsClient As WebSocket, id, req As Map)
Dim info As Map = CreateMap("name": "Mini MCP B4J Server", "version": "0.1", "transport": "websocket")
ReplyResult(wsClient, id, info)
End Sub
' ----------------------
' Utilities for logging (optional)
Sub Log(s As String)
Dim t As String = DateTime.Now
Console.WriteLine(t & " | " & s)
End Sub
---
File: ProductStore.bas
' Lightweight in-memory product store for demo purposes.
Sub Class_Globals
Private products As List ' List of Map
End Sub
Public Sub Initialize
products.Initialize
SeedSampleData
End Sub
Private Sub SeedSampleData
products.Clear
AddProduct(1, "Apple iPhone 14", 2999.00, 10)
AddProduct(2, "Samsung Galaxy S23", 2499.00, 15)
AddProduct(3, "Wireless Mouse", 79.90, 50)
AddProduct(4, "Mechanical Keyboard", 199.90, 30)
End Sub
Public Sub AddProduct(id As Object, name As String, price As Double, stock As Int)
Dim m As Map
m.Initialize
m.Put("id", id)
m.Put("name", name)
m.Put("price", price)
m.Put("stock", stock)
products.Add(m)
End Sub
Public Sub GetAllProducts As List
' Return a shallow copy
Dim out As List
out.Initialize
For Each p As Map In products
out.Add(CloneMap(p))
Next
Return out
End Sub
Public Sub Search(q As String) As List
Dim out As List
out.Initialize
If q = Null Or q.Trim = "" Then Return out
q = q.ToLowerCase
For Each p As Map In products
If p.Get("name").ToString.ToLowerCase.Contains(q) Then
out.Add(CloneMap(p))
End If
Next
Return out
End Sub
Public Sub GetById(id As Object) As Map
For Each p As Map In products
If p.Get("id") = id Then
Return CloneMap(p)
End If
Next
Return Null
End Sub
Public Sub AdjustStock(id As Object, delta As Int) As Boolean
For i = 0 To products.Size - 1
Dim p As Map = products.Get(i)
If p.Get("id") = id Then
Dim newStock As Int = p.Get("stock") + delta
If newStock < 0 Then newStock = 0
p.Put("stock", newStock)
Return True
End If
Next
Return False
End Sub
Public Sub Count As Int
Return products.Size
End Sub
Public Sub TotalStock As Int
Dim s As Int = 0
For Each p As Map In products
s = s + p.Get("stock")
Next
Return s
End Sub
' Helper to clone a Map so we don't leak internal references
Private Sub CloneMap(original As Map) As Map
Dim m As Map
m.Initialize
For Each k As String In original.Keys
m.Put(k, original.Get(k))
Next
Return m
End Sub
---
File: README.txt
Quick start
1) Create a new B4J project.
2) Add three code modules and paste the contents above as files:
- Main.bas
- ProductStore.bas
- README.txt (optional)
3) Ensure you have the WebSocketServer library available (B4J core). The code uses the WebSocketServer type and WebSocket events (Connected, TextMessage, Disconnected).
4) Run the project. The console will show the server address (default ws://0.0.0.0:8888).
Example request (JSON-RPC over WebSocket):
{
"jsonrpc": "2.0",
"id": 1,
"method": "get_products",
"params": {}
}
Example response:
{
"jsonrpc": "2.0",
"id": 1,
"result": [ {"id":1,"name":"Apple iPhone 14","price":2999.0,"stock":10}, ... ]
}
Notes & next steps
- This demo uses an in-memory store. Replace ProductStore with a SQLite or JDBC-backed implementation for persistence.
- Add authentication, CORS or TLS if exposing to networks.
- Optionally implement MCP "initialize" messages strictly following the MCP spec if needed.
---
---
File: CustomerStore.bas
' Lightweight in-memory customer store
Sub Class_Globals
Private customers As List ' List of Map
End Sub
Public Sub Initialize
customers.Initialize
SeedSampleData
End Sub
Private Sub SeedSampleData
customers.Clear
AddCustomer(1, "John Doe", "[email protected]", "012-3456789")
AddCustomer(2, "Alice Tan", "[email protected]", "013-9876543")
AddCustomer(3, "Michael Lee", "[email protected]", "017-2223344")
End Sub
Public Sub AddCustomer(id As Object, name As String, email As String, phone As String)
Dim c As Map
c.Initialize
c.Put("id", id)
c.Put("name", name)
c.Put("email", email)
c.Put("phone", phone)
customers.Add(c)
End Sub
Public Sub GetAllCustomers As List
Dim out As List
out.Initialize
For Each c As Map In customers
out.Add(CloneMap(c))
Next
Return out
End Sub
Public Sub FindById(id As Object) As Map
For Each c As Map In customers
If c.Get("id") = id Then Return CloneMap(c)
Next
Return Null
End Sub
Public Sub SearchByName(q As String) As List
Dim out As List
out.Initialize
If q = Null Or q.Trim = "" Then Return out
q = q.ToLowerCase
For Each c As Map In customers
If c.Get("name").ToString.ToLowerCase.Contains(q) Then
out.Add(CloneMap(c))
End If
Next
Return out
End Sub
Private Sub CloneMap(original As Map) As Map
Dim m As Map
m.Initialize
For Each k As String In original.Keys
m.Put(k, original.Get(k))
Next
Return m
End Sub
---
[UPDATE] Main.bas additions below
Please paste these into Main.bas inside appropriate sections.
1) Add to Process_Globals:
Private cust As CustomerStore
2) In AppStart:
cust.Initialize
3) Add new handlers:
Private Sub Handle_GetCustomers(wsClient As WebSocket, id, req As Map)
ReplyResult(wsClient, id, cust.GetAllCustomers)
End Sub
Private Sub Handle_FindCustomer(wsClient As WebSocket, id, req As Map)
Dim params As Map = req.GetDefault("params", CreateMap())
Dim cid = params.GetDefault("customer_id", Null)
If cid = Null Then
ReplyError(wsClient, id, -32602, "Missing 'customer_id'")
Return
End If
Dim c As Map = cust.FindById(cid)
If c = Null Then
ReplyError(wsClient, id, -32602, "Customer not found: " & cid)
Return
End If
ReplyResult(wsClient, id, c)
End Sub
Private Sub Handle_SearchCustomer(wsClient As WebSocket, id, req As Map)
Dim params As Map = req.GetDefault("params", CreateMap())
Dim q As String = params.GetDefault("query", "")
ReplyResult(wsClient, id, cust.SearchByName(q))
End Sub
4) Add tool routing inside ws_TextMessage SELECT CASE:
Case "get_customers"
Handle_GetCustomers(wsClient, id, req)
Case "find_customer"
Handle_FindCustomer(wsClient, id, req)
Case "search_customer"
Handle_SearchCustomer(wsClient, id, req)
---
End of project files.