Open4
MCP + Agent , Bun + React UI作成

概要
- MCP等の、Bun + React 構成 メモです。
[ 公開 2025/08/11 ]
環境
- bun:1.2.20
- React
- Vercel AI SDK
関連
- 前の node版と似ています。
- https://zenn.dev/link/comments/0b10e724b520c4
書いたコード
- https://github.com/kuc-arc-f/bun_41ex/tree/main/mcp_react4
- LLM= gemini 使用
- dev-start
bun run build
bun run dev
- front-UI
- mcp_react4/src/client/Home.tsx
- chat-start
const chatStart = async function(){
try{
setText("");
setIsLoading(false);
const elem = document.getElementById("input_text") as HTMLInputElement;
let inText = "";
if(elem){
inText = elem.value;
};
console.log("inText=", inText);
if(!inText){ return; }
setIsLoading(true);
const agentName = AgentUtil.validAgentName(inText.trim());
console.log("agentName=", AgentUtil.validAgentName(inText.trim()));
if(agentName){
let items = [];
if(agentName === "first-agent"){
items = firstAgent();
}
if(agentName === "price-list-up-agent"){
items = PriceListUpAgent(inText);
}
if(agentName === "price-list-updown-agent"){
items = PriceListUpdownAgent(inText);
}
console.log(items);
let htmAll = "";
const agentSendProc = async function(){
for(const row of items) {
htmAll += `<div class="label character-label w-full">${row.title}</div>`;
console.log(row);
let res = await ApiUtil.post({messages: row.text }, "/api/chat");
console.log(res);
htmAll += `<div class="chat-bubble character-bubble w-full">`;
htmAll += marked.parse(res.text);
htmAll += `</div>`
//console.log(htmAll);
setText(htmAll);
};
setIsLoading(false);
}
agentSendProc();
return;
}
const item = {messages: inText};
const body: any = JSON.stringify(item);
const res = await fetch("/api/chat", {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: body
});
setIsLoading(false);
const json = await res.json();
console.log(json);
let htm = marked.parse(json.text);
console.log(htm);
//@ts-ignore
setText(htm);
} catch(e){
console.error(e);
}
}
- LLM 送信、エンドポイント
- mcp_react4/src/index.ts
app.post('/api/chat', async (req: any, res: any) => {
try {
const body = req.body;
console.log(body)
const result = await generateText({
model: google(MODEL_NAME),
tools: {
firstGetRandom, firstGetDate, firstGetTime
},
maxSteps: 5,
messages: [{ role: "user", content: body.messages }],
});
console.log("artifact:");
console.log(result.text);
res.send({ret: 200, text: result.text});
} catch (error) {
res.sendStatus(500);
}
});

Agent Development Kit , Bun + React + UI
- Goocle ADK API連携で。Bun + React UI構成
環境
- bun:1.2.20
- React
- Agent Development Kit
- python
関連
- 前の node版と似ています。
- ADK実装は、前回と同じです。
- https://zenn.dev/link/comments/5fa6af3a6dc516
書いたコード
- dev-start
bun run build
bun run dev
- .env
ADK_API_URL="http://localhost:8000"
- front-UI
- mcp_react6/src/client/Home.tsx
- chat-start
const chatStart = async function(){
try{
setText("");
setIsLoading(false);
const elem = document.getElementById("input_text") as HTMLInputElement;
let inText = "";
let inToolText = "";
if(elem){
inText = elem.value;
};
const elemToolText = document.getElementById("tool_text") as HTMLInputElement;
if(elemToolText){
inToolText = elemToolText.value;
}
console.log("inText=", inText);
console.log("inToolText=", inToolText);
if(!inText){ return; }
setIsLoading(true);
const agentName = AgentUtil.validAgentName(inText.trim());
console.log("agentName=", agentName);
if(agentName){
let items = [];
if(agentName === "tool_agent_3"){
items = toolAgent3();
}
console.log(items);
let htmAll = "";
let itemCount = 1;
const agentSendProc = async function(){
for(const row of items) {
htmAll += `<div class="label character-label w-full">${row.title}</div>`;
console.log(row);
let res = null;
if(itemCount === 2){
res = await AgentUtil.postAgent(inToolText, "tool_agent_3");
}else{
res = await AgentUtil.postAgent(row.text, "tool_agent_3");
}
itemCount += 1;
console.log(res);
htmAll += `<div class="chat-bubble character-bubble w-full">`;
htmAll += marked.parse(res.text);
htmAll += `</div>`
//console.log(htmAll);
setText(htmAll);
};
setIsLoading(false);
}
agentSendProc();
return;
}
} catch(e){
console.error(e);
}
}
- chat-start エンドポイント
- mcp_react6/src/index.ts
app.post('/api/chat', async (req: any, res: any) => {
try {
const body = req.body;
console.log(body)
const item = {
appName: body.appName,
userId: body.userId,
sessionId: body.sessionId,
newMessage: {
role: "user",
parts: [{
text: body.messages
}]
}
};
console.log(item)
const sendBody = JSON.stringify(item);
const url = process.env.ADK_API_URL;
console.log("url=", url)
const response = await fetch( url + "/run", {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: sendBody
});
if(response.ok === false){
console.error("Error, res.ok = NG");
throw new Error("Error, res.ok = NG");
}
const json = await response.json();
const outIndex = json.length - 1;
console.log("#outIndex=", outIndex);
console.log("artifact:");
if(json[outIndex]){
const target = json[outIndex].content;
console.log(target.parts);
if(target.parts[0]){
console.log("text=", target.parts[0].text);
return res.send({ret: 200, text: target.parts[0].text});
}else{
console.log("text= NULL");
}
}
res.send({ret: 200, text: ""});
} catch (error) {
res.sendStatus(500);
}
});

Ollama + qwen3:8b + SQLite, Bun + MCP CLI版
- LLM= Ollama + qwen3:8b ローカルPC
- MCPで、SQLite書込、検索機能
環境
- bun:1.2.20
- React
- LLM Ollama + qwen3:8b
- SQLite
- drizzle-orm
書いたコード
-
drizzle ORM 設定、参考
- dev-start
bun run build
bun run dev
- .env
DB_FILE_NAME=file:local.db
- schema
- mcp-cli-16/src/db/schema.ts
import { int, sqliteTable, text, integer, primaryKey } from "drizzle-orm/sqlite-core";
import { sql } from 'drizzle-orm';
export const items = sqliteTable('items', {
id: integer('id').primaryKey({ autoIncrement: true }),
title: text('title').notNull(),
created_at: text('created_at').notNull().default(new Date().toISOString()),
updated_at: text('updated_at').notNull().default(new Date().toISOString())
});
export type Item = typeof items.$inferSelect;
export type NewItem = typeof items.$inferInsert;
- TODO 登録
- mcp-cli-16/src/tools/addTodo.ts
import { generateText, tool } from "ai";
import { z } from "zod";
import { drizzle } from 'drizzle-orm/libsql';
import { eq } from 'drizzle-orm';
import { items } from '../db/schema';
export const addTodo = tool({
description: "指定した 文字列をデータベースに登録する。",
// ツールを呼び出すパラメータ
parameters: z.object({
text: z.string().min(1, { message: 'タイトルは必須です' })
}),
execute: async ({ text }) => {
const db = drizzle(process.env.DB_FILE_NAME);
const newItem = {
title: text,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
};
const result = await db.insert(items).values(newItem).returning();
return "result : " + text;
},
});
- Log
mcp-cli-16>npm run dev
> dev
> tsx src
input:TODO一覧を、表形式で表示します。
input= TODO一覧を、表形式で表示します。
artifact:
<think>
</think>
| ID | タイトル | 作成日時 | 更新日時 |
|----|----------------|--------------------|--------------------|
| 2 | 株式の勉強する | 2025-08-14T05:32:55.366Z | 2025-08-14T05:32:55.366Z |
| 3 | 公園に散歩 | 2025-08-14T05:42:40.337Z | 2025-08-14T05:42:40.337Z |

Ollama + qwen3:8b + SQLite, Bun + MCP React UI
- LLM= Ollama + qwen3:8b ローカルPC
- MCPで、SQLite書込、検索機能
- Bun + React UI 構成
- 前のCLI版と、機能面は似ています。
環境
- bun:1.2.20
- React
- LLM Ollama + qwen3:8b
- SQLite
- drizzle-orm
書いたコード
-
drizzle ORM 設定、参考
- dev-start
bun run build
bun run dev
- .env
DB_FILE_NAME=file:local.db
- schema
- mcp_react8/src/db/schema.ts
import { int, sqliteTable, text, integer, primaryKey } from "drizzle-orm/sqlite-core";
import { sql } from 'drizzle-orm';
export const items = sqliteTable('items', {
id: integer('id').primaryKey({ autoIncrement: true }),
title: text('title').notNull(),
created_at: text('created_at').notNull().default(new Date().toISOString()),
updated_at: text('updated_at').notNull().default(new Date().toISOString())
});
export type Item = typeof items.$inferSelect;
export type NewItem = typeof items.$inferInsert;
- TODO 登録
- mcp_react8/src/tools/addTodo.ts
- chat-UI
- mcp_react8/src/client/Home.tsx
- https://github.com/kuc-arc-f/bun_41ex/blob/main/mcp_react8/src/client/Home.tsx
const chatStart = async function(){
try{
setText("");
setIsLoading(false);
const elem = document.getElementById("input_text") as HTMLInputElement;
let inText = "";
if(elem){
inText = elem.value;
};
const inputTodo = document.getElementById("input_todo") as HTMLInputElement;
let inTextTodo = "";
if(inputTodo){
inTextTodo = inputTodo.value;
};
console.log("inText=", inText);
if(!inText){ return; }
setIsLoading(true);
//const agentName = AgentUtil.validAgentName(inText.trim());
const agentName = inText.trim();
console.log("agentName=", agentName);
if(agentName){
let items = [];
if(agentName === "first-agent"){
items = firstAgent();
}
if(agentName === "todo-agent"){
items = todoAgent();
}
console.log(items);
let htmAll = "";
let countTool = 1;
const agentSendProc = async function(){
for(const row of items) {
htmAll += `<div class="label character-label w-full">${row.title}</div>`;
console.log(row);
let res = "";
if(agentName === "todo-agent" && countTool === 2)
{
res = await ApiUtil.post({messages: inTextTodo }, "/api/chat");
}else{
res = await ApiUtil.post({messages: row.text }, "/api/chat");
}
console.log(res);
htmAll += `<div class="chat-bubble character-bubble w-full">`;
htmAll += marked.parse(res.text);
htmAll += `</div>`
//console.log(htmAll);
setText(htmAll);
countTool += 1;
};
setIsLoading(false);
}
agentSendProc();
return;
}
const item = {messages: inText};
const body: any = JSON.stringify(item);
const res = await fetch("/api/chat", {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: body
});
setIsLoading(false);
const json = await res.json();
console.log(json);
let htm = marked.parse(json.text);
console.log(htm);
//@ts-ignore
setText(htm);
} catch(e){
console.error(e);
}
}